Build log
Wallet-native dice on Solana
Garden Dice is a pay-per-roll die game on mainnet — not a casino with payouts, but a micropayment demo that is actually fun. This post covers the technical problems we hit shipping wallet pay in the browser and how we solved them.
Try it live — free demo roll, then pay from 0.001 SOL via wallet or Solana Pay QR.
What we built
- Garden Dice — user pays 0.001–0.01 SOL to a public treasury, receives a provably fair roll (commit-reveal SHA-256).
- Challenge pages —
/c/N/viral landings with roll-specific OG images, inline demo, and inline pay (no redirect to a separate app page). - Dual payment paths — Phantom/Solflare wallet connect on desktop; Solana Pay QR + on-demand payment detection on mobile.
- Verification stack — free tools for address validation, tx lookup, and payment confirmation (same RPC layer as the game).
Problem 1: browser RPC returns 403
Public api.mainnet-beta.solana.com rejects browser-origin POST requests
with HTTP 403. Every wallet connect flow that calls getLatestBlockhash
from client-side JavaScript fails silently in production — users see “transaction failed”
with no obvious cause.
Fix: nginx same-origin proxy at /rpc strips the
Origin header before forwarding to mainnet-beta. The frontend uses
/rpc first, then solana-rpc.publicnode.com as fallback.
Playwright regression tests run on every deploy so this cannot regress unnoticed.
Problem 2: mobile in-app browsers cannot connect wallets
Twitter, Discord, and Telegram embed their own WebViews. Wallet extensions do not inject there. “Connect Phantom” is a dead end for most mobile share traffic.
Fix: Solana Pay transfer URLs rendered as QR codes. User scans, pays in their wallet app, returns to the page. We poll the treasury for matching inbound transfers (“I've paid — detect & roll”) instead of requiring users to paste 88-character signatures. Paste-sig remains as fallback.
See the Solana Pay QR guide for the full flow.
Problem 3: trust before payment
Dice games trigger scam alarms. Users need to verify fairness before sending SOL.
Fix: commit-reveal scheme. Server publishes
SHA-256(serverSeed) before payment. After the tx confirms, server reveals
the seed; user recomputes the roll locally. Free demo rolls use the same algorithm
without payment so users can test verification first.
Deep dive: provably fair dice guide with inline demo.
Problem 4: share traffic lands on the wrong page
Social crawlers read OG meta tags, not JavaScript. A bare ?roll=4 query on
/dice/ gets a generic preview. Viral shares need a dedicated URL with
roll-specific images and pay controls on the landing page itself.
Fix: static challenge pages at /c/1/ through
/c/6/ with pre-rendered OG images, JSON-LD WebApplication schema,
pay-first layout, and sticky mobile pay bar when the user scrolls past the demo.
Architecture notes
- Static site — HTML/CSS/ES modules on nginx; no Node runtime in production.
- Treasury as payment receiver — payments go to a public pubkey; server verifies inbound lamports on-chain before delivering the roll. No custodial balance.
- Stats via server-side RPC —
/stats/treasury.jsongenerated by cron (browser cannot reliably scan tx history due to rate limits). - Attribution —
?ref=params logged in nginx for campaign measurement.
What is next
The payment stack works end-to-end (wallet + Solana Pay + verification tools). The current bottleneck is distribution — getting Solana users to try a 0.001 SOL roll. If you build wallet-native apps, I would love feedback on the payment UX.