# Frontend (/docs/cookbook/rise-slots/frontend)





import { Steps, Step } from 'fumadocs-ui/components/steps';
import { Callout } from 'fumadocs-ui/components/callout';

## Frontend Architecture

The frontend brings together everything we've built: session keys for gasless gameplay, event watching with Shreds for instant results, and an animated UI built with Framer Motion. The UI dynamically adapts to the user's state, showing different buttons based on whether they're connected, have a session key, or need tokens.

<Callout type="info">
  **Complete Source Code**: The full implementation is available at [github.com/awesamarth/rise-slots](https://github.com/awesamarth/rise-slots). This page covers the key concepts and patterns.
</Callout>

## Key Components

### SlotMachine Component

The main game UI handles animated reels, win detection, and dynamic button states. The most important pattern here is the symbol mapping system.

**Symbol Mapping**:

```typescript
function getSymbol(index: number): string {
  if (index <= 3) return '🍒'  // Cherry (0-3)
  if (index <= 6) return '🍋'  // Lemon (4-6)
  if (index <= 8) return '💎'  // Diamond (7-8)
  return '9️⃣'                   // Lucky 9 (9)
}
```

This mapping is crucial for win detection. Instead of comparing raw numbers, we compare symbols:

```typescript
const symbol0 = getSymbol(result[0])
const symbol1 = getSymbol(result[1])
const symbol2 = getSymbol(result[2])

const isWin = symbol0 === symbol1 && symbol1 === symbol2
```

This ensures \[0, 1, 2] correctly wins as triple cherries, since all three map to 🍒.

**Reel Animation**: Each reel spins independently at 80ms intervals, showing random symbols. When the VRF result arrives, reels stop one by one with a 200ms stagger for dramatic effect. Wins celebrate for 3 seconds with a banner, while losses return to idle immediately.

### Main Page Integration

The main page (`src/app/page.tsx`) coordinates session keys, event watching, and game flow. Here's how the critical pieces work together.

**Event Watching Setup** runs on mount:

```typescript
useEffect(() => {
  const unwatch = shredClient.watchShreds({
    includeStateChanges: false,
    onShred: (shred) => {
      shred.transactions.forEach((tx) => {
        const spinResultLogs = tx.logs.filter(
          (log) =>
            log.address.toLowerCase() === SLOT_MACHINE_ADDRESS.toLowerCase() &&
            log.topics[0] === '0xc95f6b2ec3fb7d188682755102792da57a8ec34918faaab4b1b925f0c4556123'
        )

        spinResultLogs.forEach((log) => {
          const decoded = decodeEventLog({ abi: SLOT_MACHINE_ABI, data: log.data, topics: log.topics })
          if (decoded.eventName === 'SpinResult') {
            const requestId = decoded.args.requestId.toString()
            const result = decoded.args.result as [number, number, number]
            const payout = decoded.args.payout as bigint

            resultsCache.set(requestId, { result, payout })
          }
        })
      })
    }
  })

  return () => unwatch()
}, [])
```

The watcher runs continuously in the background, caching all SpinResult events as they arrive. This is what makes the UX feel instant.

**Spin Handler** leverages the cache:

```typescript
const handleSpin = async (): Promise<[number, number, number]> => {
  // Send spin transaction via session key (gasless!)
  const hash = await sendWithSessionKey([{
    to: SLOT_MACHINE_ADDRESS,
    data: encodeFunctionData({ abi: SLOT_MACHINE_ABI, functionName: "spin", args: [] }),
  }])

  // Wait for receipt and extract requestId
  const receipt = await shredClient.waitForTransactionReceipt({ hash: hash as `0x${string}` })
  const requestedLog = receipt.logs.find(log =>
    log.address.toLowerCase() === SLOT_MACHINE_ADDRESS.toLowerCase() &&
    log.topics[0] === '0x616d9204a230e2286a32eb3295c697372bd4cc093a7de2ef2455df4bffbc97c9'
  )

  const requestedEvent = decodeEventLog({ abi: SLOT_MACHINE_ABI, data: requestedLog.data, topics: requestedLog.topics })
  const myRequestId = requestedEvent.args.requestId.toString()

  // Check cache - result is almost always already there!
  if (resultsCache.has(myRequestId)) {
    return resultsCache.get(myRequestId)!.result
  }

  // Fallback: poll cache if not found (rare)
  return await new Promise((resolve, reject) => {
    const timeout = setTimeout(() => reject(new Error('Timeout')), 10000)
    const checkInterval = setInterval(() => {
      if (resultsCache.has(myRequestId!)) {
        clearTimeout(timeout)
        clearInterval(checkInterval)
        resolve(resultsCache.get(myRequestId!)!.result)
      }
    }, 100)
  })
}
```

The pattern is: send transaction, get requestId from receipt, check cache. In 90%+ of spins, the result is already cached by the time we check.

### Token Management

Claiming RCT and using the faucet both use session keys for gasless transactions:

```typescript
const claimRCT = async () => {
  const hash = await sendWithSessionKey([{
    to: RCT_TOKEN_ADDRESS,
    data: encodeFunctionData({ abi: RCT_ABI, functionName: "mintInitial", args: [] }),
  }])

  await new Promise(resolve => setTimeout(resolve, 1000))
  refetchRCT()
  refetchHasMinted()
}

const useFaucet = async () => {
  const hash = await sendWithSessionKey([{
    to: RCT_TOKEN_ADDRESS,
    data: encodeFunctionData({ abi: RCT_ABI, functionName: "faucet", args: [] }),
  }])

  await new Promise(resolve => setTimeout(resolve, 1000))
  refetchRCT()
}
```

Both functions are completely gasless thanks to session keys. No wallet popups, instant execution.

## UI Flow States

The app shows different buttons based on user state:

1. **Not Connected**: "Connect Wallet" button in place of spin button
2. **No Session Key**: "Create Session Key to Play"
3. **No RCT**: "Claim 1000 RCT"
4. **Broke (0 RCT)**: "Get More RCT (Broke? 😅)"
5. **Ready**: "SPIN"

This progression ensures users are guided through setup before they can play. The SlotMachine component handles this through conditional rendering based on props.

## Animation Details

The reel animation creates anticipation through timing. All three reels start spinning simultaneously with random symbols updating every 80ms. When VRF results arrive, reels stop sequentially with a 200ms delay between each stop. This staggered reveal builds tension and makes wins more satisfying.

Win celebrations use Framer Motion for smooth animations:

```typescript
<AnimatePresence>
  {gameState === 'result' && isWin && lastWin !== null && (
    <motion.div
      initial={{ scale: 0.8, opacity: 0, y: 20 }}
      animate={{ scale: 1, opacity: 1, y: 0 }}
      exit={{ scale: 0.8, opacity: 0 }}
    >
      <div className="bg-gradient-to-r from-[#4ade80]/20 via-[#4ade80]/30 to-[#4ade80]/20 border border-[#4ade80]/50 rounded-2xl p-6 text-center">
        <div className="text-4xl font-black text-[#4ade80] mb-2">
          +{getPayout(lastWin)} RCT!
        </div>
        <div className="text-zinc-400">
          Triple {getSymbol(lastWin)} • Instant VRF Result
        </div>
      </div>
    </motion.div>
  )}
</AnimatePresence>
```

The banner scales in, displays for 3 seconds, then fades out. Losses skip the celebration entirely and return to idle immediately.

## Stats Tracking

The app tracks wins and losses throughout the session:

```typescript
const [wins, setWins] = useState(0)
const [losses, setLosses] = useState(0)

const handleResult = (result: [number, number, number], isWin: boolean, payout: number) => {
  if (isWin) {
    setWins(prev => prev + 1)
  } else {
    setLosses(prev => prev + 1)
  }
  refetchRCT()
}
```

These stats display in the sidebar along with calculated win rate. It's simple but effective for showing progress.

## Complete Implementation

The GitHub repository contains the full frontend code including the complete SlotMachine component with all animations, the main page with all features integrated, navbar with wallet connection UI, and sidebar with stats and session key management.

**Key files to explore**:

* `src/components/SlotMachine.tsx` - Main game component
* `src/app/page.tsx` - Main page with all integrations
* `src/app/layout.tsx` - Root layout with providers
* `src/app/globals.css` - Custom animations

Visit [github.com/awesamarth/rise-slots](https://github.com/awesamarth/rise-slots) for the complete source.

## Testing the Game

Start the development server with `bun dev` and test the complete flow: connect with RISE Wallet, create a session key (one popup), claim 1000 RCT tokens (gasless), then spin multiple times with zero popups. Experience the instant VRF results, win and see payouts, go broke and use the faucet.

<img alt="RISE Slots Main Page" src={__img0} placeholder="blur" />

### Winning Results

When you hit a winning combination, payouts are instant thanks to session keys:

<img alt="Winning Result with Payout" src={__img1} placeholder="blur" />

Watch the console to see the caching pattern in action:

```
📦 Cached SpinResult for requestId: 4685 result: [2,2,2] payout: 15000000000000000000
Spin transaction: 0x...
📍 My requestId: 4685
✅ Result already in cache!
```

The result arrives and gets cached before we even finish extracting the requestId from the receipt.

## Key Production Patterns

This tutorial demonstrates several production-ready patterns: results caching for handling race conditions, WebSocket event streaming for real-time updates, session key management with localStorage, responsive UI with proper loading states, comprehensive error handling with timeouts, and token balance tracking throughout gameplay.

The UX achievements include sub-millisecond perceived latency, zero wallet popups during gameplay, instant visual feedback, and smooth animations with a clear game flow that guides users through setup.

## Next Steps

You've built a complete production-ready slot machine on RISE with VRF integration for provably fair randomness, session keys for gasless gameplay, Shreds for real-time event streaming, results caching for ultra-fast UX, and proper token economics.

**Deploy to production** by deploying contracts to RISE Mainnet, deploying the frontend to Vercel or Netlify, updating contract addresses, and testing end-to-end.

**Consider enhancements** like leaderboards for tracking top players and biggest wins, daily free spins to encourage return visits, multiplayer tournaments for competitive play, NFT rewards for big wins, and an analytics dashboard for tracking game metrics.

### Resources

* [GitHub Repository](https://github.com/awesamarth/rise-slots) - Complete source code
* [RISE VRF Documentation](/docs/builders/vrf) - Learn more about Fast VRF
* [Session Keys Guide](/docs/rise-wallet/session-keys) - Deep dive into session keys
* [Shreds Documentation](/docs/builders/shreds) - Real-time event streaming
* [RISE Discord](https://discord.gg/rise) - Get support and share your game
