RISE Logo-Light

Setup

Project setup, dependencies, and configuration

Project Setup

Create a New Next.js Project

First, create a new Next.js application with TypeScript:

npx create-next-app@latest reaction-time-game
yarn create next-app reaction-time-game
pnpm create next-app reaction-time-game
bun create next-app reaction-time-game

When prompted, select these options:

  • Use recommended Next.js defaults: No, customize settings
  • TypeScript: Yes
  • Linter: None
  • React Compiler: Yes
  • Tailwind CSS: Yes
  • src/ directory: Yes
  • App Router: Yes
  • Customize import alias: No

If you've completed another tutorial, you can select No, reuse previous settings to skip configuration.

Navigate into your project:

cd reaction-time-game

Install Dependencies

Install the required packages for blockchain integration:

npm install viem shreds lucide-react
yarn add viem shreds lucide-react
pnpm add viem shreds lucide-react
bun add viem shreds lucide-react

Package breakdown:

  • viem - Lightweight Ethereum client library for transactions
  • shreds - RISE Shreds SDK for 3ms confirmations
  • lucide-react - Icon library for UI elements

Generate a Burner Wallet

Create a burner wallet for testnet transactions. Run this in your terminal:

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

This generates a random private key. Copy the output.

Security Warning

This is a burner wallet for demo purposes. Never use this pattern with real funds or mainnet!

Set Up Environment Variables

Create a .env.local file in your project root:

.env.local
NEXT_PUBLIC_BURNER_KEY=0xYOUR_GENERATED_KEY_HERE

Replace YOUR_GENERATED_KEY_HERE with the key you just generated (add 0x prefix).

The NEXT_PUBLIC_ prefix makes this variable accessible in the browser. This is acceptable for burner wallets but never use this for production keys!


Configuration

Configure Fonts

Update your root layout to include arcade-style fonts:

src/app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono, Press_Start_2P, Rajdhani } from "next/font/google";
import "./globals.css";
import { Navbar } from "@/components/Navbar";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

const pressStart = Press_Start_2P({
  weight: "400",
  subsets: ["latin"],
  variable: "--font-doom",
});

const rajdhani = Rajdhani({
  weight: ["400", "500", "600", "700"],
  subsets: ["latin"],
  variable: "--font-rajdhani",
});

export const metadata: Metadata = {
  title: "Reaction Time Game - RISE",
  description: "Test your reflexes with blockchain-backed precision",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} ${pressStart.variable} ${rajdhani.variable} antialiased`}
      >
        <Navbar />
        {children}
      </body>
    </html>
  );
}

Fonts included:

  • Press Start 2P - Retro arcade font for titles
  • Rajdhani - Modern gaming font for UI
  • Geist - Clean sans-serif for body text

Update Global Styles

Replace your globals.css with Tailwind imports:

src/app/globals.css
@import "tailwindcss";

That's it! Tailwind CSS 4 uses a simplified import system.


Building the Navbar

Create a navbar to display wallet information and balance:

src/components/Navbar.tsx
"use client";

import { useEffect, useState } from "react";
import { createPublicClient, http, formatEther } from "viem";
import { riseTestnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import { Copy, Check } from "lucide-react";

const account = privateKeyToAccount(
  process.env.NEXT_PUBLIC_BURNER_KEY as `0x${string}`
);

const publicClient = createPublicClient({
  chain: riseTestnet,
  transport: http(),
});

export function Navbar() {
  const [balance, setBalance] = useState<string>("0");
  const [copied, setCopied] = useState(false);

  useEffect(() => {
    const fetchBalance = async () => {
      const bal = await publicClient.getBalance({
        address: account.address,
      });
      setBalance(formatEther(bal));
    };

    fetchBalance();
    const interval = setInterval(fetchBalance, 10000); // Update every 10s

    return () => clearInterval(interval);
  }, []);

  const copyAddress = async () => {
    await navigator.clipboard.writeText(account.address);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <nav className="fixed top-0 left-0 right-0 z-50 bg-black/50 backdrop-blur-md border-b border-purple-500/20">
      <div className="max-w-7xl mx-auto px-6 py-4">
        <div className="flex items-center justify-between">
          <h1 className="text-2xl font-bold text-purple-400 font-[var(--font-doom)]">
            REACTION TIME
          </h1>

          <div className="flex items-center gap-6">
            <div className="text-right">
              <p className="text-xs text-gray-400">Balance</p>
              <p className="text-lg font-semibold text-white">
                {parseFloat(balance).toFixed(4)} ETH
              </p>
            </div>

            <div className="flex items-center gap-2">
              <div className="text-right">
                <p className="text-xs text-gray-400">Burner Wallet</p>
                <p className="text-sm font-mono text-white">
                  {account.address.slice(0, 6)}...{account.address.slice(-4)}
                </p>
              </div>
              <button
                onClick={copyAddress}
                className="p-2 rounded-lg bg-purple-500/20 hover:bg-purple-500/30 transition-colors"
              >
                {copied ? (
                  <Check className="w-4 h-4 text-green-400" />
                ) : (
                  <Copy className="w-4 h-4 text-purple-400" />
                )}
              </button>
            </div>
          </div>
        </div>
      </div>
    </nav>
  );
}

Key features:

  • Displays ETH balance (refreshes every 10 seconds)
  • Shows truncated burner wallet address
  • Copy address to clipboard with visual feedback
  • Arcade-themed styling with purple accents

Now you're ready to implement the game logic in the next section.