Boardman Duel: put USDC on a 1v1, no middleman
When two people play FIFA or chess for money, somebody has to hold the cash. Usually it's a friend, and sometimes the friend disappears. Boardman Duel replaces the friend with a smart contract on Arc.
Pick the game, type or say the terms, set a stake of up to 5 USDC and send your opponent a link. They match the stake to join. When the game ends, you both say who won. If you agree, the winner is paid straight away. If you don't, a referee you both chose checks the proof and decides. The referee never touches the money.
How I made it: I didn't write the code by hand. I wrote the rules in plain English (prompt 1), and Arc Studio wrote the smart contract, ran 104 tests, deployed it and built the app. Then I read what it made and found a rule I didn't like: the referee could settle a duel before either player had reported. Prompt 2 fixed that and added 31 tests. Prompt 3 was the design pass, aimed at fast loading on slow mobile data in Lagos. I used Claude to plan the prompts and make the explainer video.
What surprised me: the hardest part wasn't the code. It was deciding the rules, like exactly when a referee is allowed to step in.
Live on Arc testnet now. Mainnet launch with real USDC this week.
Tech stack
- Claude
- Claude Code (Opus 5.5)
Links
Prompt steps
Step 1
Build "Boardman Duel": a 1v1 USDC stake escrow for head-to-head games (chess, gaming matches, or AI-agent matches) on Arc. NETWORK Build, test and deploy on Arc testnet. Keep all network settings in one config file (chain ID, RPC URL, explorer URL, BoardmanDuel address, USDC address) so I can point the app at Arc mainnet later (chain ID 5042, RPC https://rpc.mainnet.arc.io, explorer https://explorer.arc.io) without touching anything else. THE IDEA Two players each lock the same USDC stake into a match. When the game ends, the winner gets the full pot. If both players agree on the result, payout is instant. If they disagree, a referee chosen when the match was created decides. If anything stalls, timeouts refund everyone. The referee can be a person or an AI agent's wallet, so one piece of software can pay out another. SMART CONTRACT: BoardmanDuel Take stakes through the USDC ERC-20 interface at 0x3600000000000000000000000000000000000000 (6 decimals). Pass the USDC address to the constructor. Never transfer to address(0), because on Arc that reverts: address(0) only ever means "open challenge" or "draw", and a draw refunds both players. Match data: - id, playerA, playerB (can be empty = open challenge), referee, stake (USDC amount), joinDeadline, resultDeadline, status, reportA, reportB, winner, gameRef (a short string such as a lichess game link) - Status: Open, Active, Settled, Refunded, Cancelled Functions: 1. createMatch(opponent, referee, stake, joinWindow, resultWindow, gameRef) - playerA deposits `stake` USDC (transferFrom after approve) - opponent may be address(0) for an open challenge - referee must not be playerA or the opponent - stake must be > 0 and <= MAX_STAKE (MAX_STAKE = 5 USDC, i.e. 5_000_000 units, because this will run on mainnet with real money) 2. joinMatch(id) - only the named opponent (or anyone, if open) before joinDeadline - deposits the same stake; status becomes Active 3. cancelMatch(id) - playerA can cancel and get their stake back if nobody has joined 4. reportResult(id, claimedWinner) - each player can report once; claimedWinner is playerA, playerB, or address(0) for a draw - if both reports match: settle immediately (winner gets 2x stake; a draw refunds both) 5. refereeDecide(id, winner) - only the referee, only while Active, only if reports are missing or conflict - winner is playerA, playerB, or address(0) for a draw 6. claimRefund(id) - anyone can trigger after joinDeadline if nobody joined (refund playerA) - anyone can trigger after resultDeadline if still unsettled (refund both) Safety requirements: - No fees, no admin keys, no upgradeability, no owner withdrawal - Checks-effects-interactions and a reentrancy guard on every function that moves USDC - A match can only be settled or refunded once - The contract should never hold USDC that is not tied to a live match - Events: MatchCreated, MatchJoined, ResultReported, MatchSettled, MatchRefunded, MatchCancelled Tests (write and run them all before deploying): - happy path: create, join, both report same winner, winner receives 2x stake - draw by agreement refunds both - conflicting reports, then referee decides - referee cannot act when both reports agree, and non-referee cannot call refereeDecide - nobody joins: playerA cancels, or anyone triggers claimRefund after joinDeadline - result timeout refunds both - cannot join twice, cannot settle twice, cannot report twice - stake above MAX_STAKE reverts; wrong opponent cannot join a named match - contract USDC balance returns to zero after every settled or refunded match WEB APP A simple, mobile-friendly app that calls the contract: - Connect wallet; show the user's USDC balance on Arc - "Create a duel" form: opponent address (or open challenge), referee address, stake, join window, result window, game link - After creating: a shareable match link - Match page: both players, stake, pot, status, time left, game link - Join button for the opponent (handles USDC approve, then join) - "I won", "They won", "Draw" buttons for each player - Referee panel, visible only to the referee, when reports conflict or are missing - Refund and cancel buttons when they apply - A timeline of every on-chain action with links to the explorer of the current network - "My duels" list for the connected wallet Keep the design clean: a dark board-game look, the pot amount large, the status clear at a glance.Step 2
Change one rule: the referee must not settle a duel before the players have had a chance to report. Allow refereeDecide only when (a) both players have reported and their reports conflict, or (b) one player has reported and the other has not reported within REPORT_GRACE = 1 hour of the first report. Keep the result-deadline refund as the final fallback. Add tests for: referee blocked before any report; referee blocked while only one report exists and the grace period hasn't passed; referee allowed after the grace period; referee allowed on conflict. Run all tests again, redeploy to Arc testnet, and update the referee panel to show when the referee can act.Step 3
Redesign the Boardman Duel front end to a top-tier, award-level standard. Push the craft as far as you can: every screen, every state, every pixel. Front end only: do NOT change the contract, its ABI, the tests, or src/networks.ts. CONTEXT Boardman is a gaming protocol where humans and AI agents play 1v1 for stakes. This app is its USDC escrow. It will be judged in a demo video and used by people on slow mobile connections (Lagos, Nigeria). 1. PERFORMANCE FIRST The production JS bundle is 1.46 MB. On a slow connection the page is blank for about 15 seconds. - Code-split. Lazy-load wallet connection, chain clients and the match/duels routes. - Target under 200 KB gzipped for the first load. Report the size before and after. - Render the header, hero and form shell immediately as static markup, then hydrate. - Use skeletons for anything waiting on RPC, never blank areas. Preconnect fonts with font-display: swap. - No heavy animation or UI libraries. Use CSS and small hand-written code. 2. DESIGN SYSTEM - Identity: a premium tabletop / chess club at night. Deep ink-charcoal surfaces, bone-white text, one brass/gold accent (keep the current brass). Semantic green for win, red for danger, amber for waiting. - Very subtle chessboard texture plus fine grain, and a soft spotlight vignette behind the main content. - Put every token in one theme file as CSS variables: colours, 4/8 spacing scale, radii, elevation, motion durations and easing. - Type: Space Grotesk for display, DM Sans for body, JetBrains Mono with tabular numbers for amounts, addresses and timers. A real type scale with tight display tracking. - Identicons: a deterministic avatar per address, generated locally (gradient or blockie, no network). Use it everywhere an address appears. 3. LAYOUT - Desktop (1024px and up), two columns. Left: hero ("Stake it. Play it. Winner takes the pot."), a 3-step "How it works", and a live DUEL CARD preview. Right: the setup form. - Mobile: one column, with a sticky bottom action bar and safe-area insets. Tap targets at least 44px. - Header: logo; nav with Create and My duels; network pill (testnet in neutral grey, mainnet in brass labelled "Real USDC"); wallet button with identicon, short address and USDC balance. 4. CREATE FLOW - The DUEL CARD updates live as the user types: two seats ("You" vs opponent or an empty "Open seat"), referee badge, stake per player, and the pot shown big (2× stake). Never a dash: show 0.00 until a value exists. Deadlines in human terms: "Opponent has until Thu 18:40 to join". - Address inputs: paste button, identicon, checksum validation. Inline errors: "That's your own address", "Referee can't be a player". - Stake: large numeric input with chips (0.5 / 1 / 2 / 5). "Max" uses min(balance, 5). Show the balance, and warn inline if it's too low. - Windows: segmented presets (10 min / 1 h / 24 h / Custom) instead of native selects. - One primary button that walks through the states (Connect wallet → Switch to Arc → Approve USDC, step 1 of 2 → Create duel, step 2 of 2). When disabled, it says why. - After creation, a success screen: the duel card, copy link, native share on mobile, and a QR code for the match link. 5. MATCH PAGE: "THE TABLE" (the hero of the demo) - Two seats face each other across a VS divider, each with an identicon and short address. Under each seat, that player's report as a badge: "Reported: I won", or "Waiting". - The referee sits below, with a state from the contract rules: can act now / countdown to when they can act / closed. - Centre: the pot as a big brass number, a status pill, a stepper (Created → Joined → Results → Settled, or Refunded / Cancelled), and a countdown ring for whichever deadline is live. - One primary action, chosen by the viewer's role (player A, player B, referee or spectator), with one line explaining what happens next. - Settlement moment: the winner's seat lights up with a crown, the payout counts up to "Paid 2.00 USDC" with an explorer link, restrained confetti (disabled under prefers-reduced-motion). - Timeline: a vertical log with icons, relative times, decoded amounts and explorer links. 6. MY DUELS - A grid of cards: role tag, status, pot, opponent identicon, time left. Filters: All / Needs your action / Live / Finished. Skeleton loading and a designed empty state. 7. FEEDBACK AND ERRORS - Transaction toasts with explorer links. Map every contract custom error to plain language. Wrong-network banner with one-click switch. Proper 404 page. 8. DETAIL PASS - Hover, active and focus-visible states on everything. Full keyboard navigation and aria labels. WCAG AA contrast. No layout shift. Two-decimal USDC everywhere. Open Graph meta with a static share image, so match links look good in chats. VERIFY BEFORE YOU FINISH - Lint, typecheck and production build must pass. All contract tests unchanged and passing. - On Arc testnet, create real duels that reach every state, screenshot each one at 390×844 and 1440×900, and show me. Report the JS size before and after. Then deploy to Vercel.
1 like · 1 comment
Comments (1)
This is really really cool. I haven't seen something like this posted on Prompted yet. I really want to see 2 agents dual it out now haha. I'm not all for gambling but this is pretty fun ngl. Great job, thanks for sharing!