Docs/Getting started
v 1.0 · ShinyLuck on Somnia TESTNET

What is ShinyLuck.

ShinyLuck is a fully on-chain casino on Somnia where the house is a quorum of autonomous AI agents. Every reel stop, every dice roll, every roulette spin is the keccak256 of three numbers anyone can audit - server seed (committed before bet), client seed (player-supplied), and a future blockhash. Money flow is pull-payment; the casino contract never sends in a callback. House RTP is adjusted hourly by an LLM agent invoked via the Somnia Agent Platform - judges can watch that loop happen on the explorer.

Seven games, one contract.
Sugar.Lab, Dice, Crash, Vault.7, Plinko, Mines, Roulette - all settle through one Casino.sol. Sugar.Lab + Dice are production-quality; the rest are functional in beta (yellow TESTING ribbon).
~2 s bet finality.
Privy embedded wallet signs in-iframe; we broadcast through our own RPC. Reveal-bot reacts to BetPlaced via Somnia WebSocket push. Total wall-clock spin → settle: 2.0-2.5 s.
Self-custodial + agent-native.
Funds live in your wallet; the contract escrows stake + max payout only between place and settle. HouseManager is a SomniaEventHandler that reacts to BetSettled and the hourly cron pulse on the reactivity precompile.

Connect and play in 60 seconds.

  1. Click "Connect Wallet" in the top-right. Email login via Privy, or any EIP-1193 wallet (MetaMask, Rabby, OKX, Coinbase).
  2. Switch to Somnia testnet (Shannon). Chain ID 50312. Auto-added by the modal.
  3. Get STT from testnet.somnia.network/faucet.
  4. Pick a game. Sugar.Lab and Dice are the polished demos. Hit place bet - the receipt opens ~1.5 s later when the reveal-bot lands the seed.

Supported wallets.

Primary path is Privy embedded wallet - email login, no extension. We split the sign/broadcast: Privy signs in-iframe, our SDK broadcasts via our own RPC (~300 ms saved per bet vs Privy's hosted broadcast). Falls back to any EIP-1193 injected provider (MetaMask, Rabby, OKX, Coinbase, WalletConnect). The frontend SDK is in frontend/lib/shinyluck-sdk.js.

Getting STT testnet tokens.

STT is the gas + bet token on Shannon (Somnia testnet). Drip from testnet.somnia.network/faucet; 0.5 STT per request is plenty for hundreds of small bets. On mainnet the symbol becomes SOMI.

Sugar.Lab.

The flagship. 7×7 cluster-pays grid with cascading wins, multiplier accumulator, "Charge Meter" loyalty progression, and Buy Bonus (100× unit stake → high-variance bonus round). RTP 92.00% baseline; jumps to ~97.4% during Bonus Mode. Math is in contracts/lib/ClusterLib.sol; payout boost constant CLUSTER_PAY_BOOST_X100 = 332 (calibrated via 100 k Monte Carlo run in scripts/_rtp-simulation.js).

Dice.

Pick a threshold in [2, 98] and a direction (over/under). Roll = randomness mod 100 + 1. Payout multiplier = (10000 − edge) / (winChance × 100). 1% house edge → 99% RTP, the highest in the lineup.

Crash.

Bustabit-style multiplier curve. You pre-commit an auto-cashout in [1.01×, 100×] and stake. The contract uses keccak256(serverSeed ‖ clientSeed ‖ blockhash ‖ nonce) to derive crashPoint with a 1-in-33 instant-crash baked in. 3% house edge.

Vault.7 (slots).

3 reels × 7 symbols, 15-cell payout grid, 5 paylines + scatter. Bonus rounds via Buy Bonus (75× unit). Loyalty Free Spins issued every 50 paid spins. Baseline RTP 92%; Bonus Mode adds +4.5% (paytable boost in _settleBet bumps from 560 → 592). Math IS the edge - there's no houseEdgeBps applied on top.

Plinko.

16 rows × 17 slots; ball path = 16 bits of randomness, one per peg row. Three risk tiers: low (max 16×), medium (max 110×), high (max 1000×). 1.5% house edge baked into symbol weights.

Mines.

5×5 grid. Pick [1, 24] mines; HouseManager pre-commits the seed before you place, mine positions only lock when seed reveals. Multiplier scales as ∏ (25-i)/(25-mines-i) × (1 − edge); 100× cap. 1.2% house edge.

Roulette.

American double-zero wheel. 10 bet kinds - straight (35:1), red/black/even/odd/1-18/19-36 (1:1), dozens (2:1). Result = randomness mod 38. 5.26% house edge (standard American wheel; matches industry quote).

Bet placement latency.

Total wall-clock from click to settled receipt averages 2.0-2.5 s; best observed 1.93 s. Breakdown (Sugar.Lab spin):

  • Populate (parallel RPC for gas + nonce + fees, 5 s TTL cache) - 80-150 ms
  • Privy iframe sign (eth_signTransaction, returns raw hex) - 350-500 ms
  • Broadcast (eth_sendRawTransaction via our RPC) - 80-150 ms
  • 1 block to commitBlock + REVEAL_DELAY=1 (Somnia ~400 ms/block) - 800 ms
  • Reveal-bot settle (WebSocket push, leading-edge debounced 150 ms) - 400-600 ms

Pre-sign pipeline (Sugar-only).

While the cascade animation plays (1-3 s), the SDK speculatively pre-signs the next spin in the Privy iframe. On the user's next click only the broadcast round-trip remains (~150 ms). Reservation safety:

  • Pre-sign expires after 25 s (fee-data TTL window)
  • Stake mismatch between presign + click → drop + re-sign
  • Broadcast error → invalidate + force chain-nonce resync (no mempool gap)
  • Nonce mutex serialises concurrent presign + auto-claim populate calls

Extending the pattern to Vault.7 / Plinko is one-liner once the animation hook lands; see frontend/lib/shinyluck-sdk.js presignClusterSpin.

Real-time updates.

Every game page subscribes to BetSettled for the connected player via Somnia WebSocket (wss://api.infra.testnet.somnia.network/ws - the canonical infra endpoint used by every first-party Somnia example). The lobby + leaderboard + account aggregate BetSettled across all historical Casino deployments (the redeploy-survivable history list is in frontend/lib/config.js as historicalCasinos). For the explorer/log scans we use the Shannon Explorer REST indexer - one HTTP call per contract per event type vs hundreds of chunked eth_getLogs RPC calls (~100× faster cold start).

Architecture · the agent quorum.

ShinyLuck uses three orthogonal autonomous agent paths, all settled on-chain via Somnia primitives:

House Manager Agent (LLM)
-
LLM Inference Agent (qwen3-30b)
-
JSON API Agent
-
Parse Website Agent
-
  • HM ↔ LLM hourly loop - autonomous RTP adjustment via Somnia Agent Platform
  • Agent Quorum Verifier - 3-of-4 redundant randomness re-derivation per settled bet (defence-in-depth)
  • Player Agents - on-chain LLM-driven strategies dispatched by HouseManager every hour, vault-isolated funds, vault-funded LLM ticks + per-user daily/total/per-game limits

HM ↔ JSON API ↔ LLM autonomous chain.

This is the agent-first design story. Every hour Somnia's Reactivity precompile (0x0100) fires a Schedule event → HouseManager._onEvent wakes up → dispatches a 2-stage agent chain via IAgentRequester.createRequest on the Somnia Agent Platform.

Stage 1 - JSON API Agent (id 13174292974160097713, price 0.03 STT × 3 workers) fetches the live ShinyLuck Competitor RTP Research feed and extracts the slots_rtp_avg_bps / cluster_rtp_avg_bps field. Three Somnia validators independently hit the URL, reach Majority consensus on byte-identical output, and the platform calls back with the number.

// Stage 1 payload - what the JSON API Agent receives
fetchUint("https://jsonblob.com/api/jsonBlob/019e555c-25db-7e74-a62d-31c135e87539",
          "slots_rtp_avg_bps", 0)
// → returns uint256(9533) → casino RTP avg of 95.33% across competitors

Stage 2 - LLM Inference Agent (id 12847293847561029384, Qwen3-30B with temp=0, price 0.07 STT × 3 workers) receives a prompt built on-chain by _buildDecisionPrompt and replies with one of [LOWER, HOLD, RAISE, BIG_BONUS] via inferString + allowedValues (guaranteed parseable).

// Stage 2 prompt - actual on-chain construction
"Decide RTP for VAULT.7 slots. Our RTP: 92.00%.
Competitor average RTP (live from research feed): 95.33%.
Free bankroll: 50 STT. 1h bankroll change: +0.00%.
Action: RAISE = +1.5% RTP. LOWER = -1.5% RTP. HOLD = no change.
BIG_BONUS = activate 60min Bonus Mode."

Majority consensus across 3 LLM workers → handleResponse applies via casino.adjustSlotRTP(game, newRtp, "LLM consensus via Somnia Agent Platform"). The whole chain is on-chain - no off-chain script, no admin key, no oracle middleware. Each hour costs ~0.72 STT (2× JSON 0.12 + 2× LLM 0.24); HM holds 48 STT buffer = ~2.5 days unattended runway. Events to watch on Shannon Explorer: CompetitorRtpRequested → CompetitorRtpResolved → RtpAnalysisRequested → RtpAnalysisResolved → casino.RtpAdjusted.

Agent quorum verifier.

AgentQuorumVerifier.sol asks 4 LLM workers to independently re-derive the keccak256(serverSeed ‖ clientSeed ‖ blockhash ‖ nonce) for a settled bet. The signature count (3-of-4 = OK, 2-of-4 = warning, ≤1 = critical) lands in a QuorumResult event. Money never depends on this - randomness is on-chain via blockhash. Quorum is defence-in-depth on the agent layer.

JSON API Agent.

Live-fetches the ShinyLuck Competitor RTP Research feed from a public JSON endpoint (currently jsonblob.com; owner-replaceable via setCompetitorFeedUrl). The feed is manually curated from public casino-review aggregators (askgamblers.com, casinoguru.com, gambling.com) - sample of Stake.us, BC.game, Wink.com slot/cluster RTPs. The agent extracts a JSONPath selector (slots_rtp_avg_bps or cluster_rtp_avg_bps) and 3 validators reach Majority consensus on the integer value.

Parse Website Agent.

HTML → structured data scraper. Reserved for v2 (news-driven Bonus Mode - e.g. "Bitcoin halving today" → activate generous payout window). Not yet bound to the v1 contracts but the agent ID is registered + the platform fee model is wired the same way as the LLM Inference Agent.

Player Agents.

PlayerAgentRegistry.sol + AgentVault.sol. Each player registers once, gets a fresh AgentVault, and authorises HouseManager (and an optional off-chain fallback relayer) to place bets on their behalf within stake / game / daily / total limits. Every hourly Reactivity tick, HouseManager iterates active players and fires a per-player Somnia LLM Inference Agent request (Qwen3-30B, 3-of-3 majority, allowedValues = [SKIP, DICE_0.1, DICE_0.5, SLOTS_0.5, CLUSTER_0.5, PLINKO_0.5, ROULETTE_0.5] filtered by the user's mask). Callback applies the decision via registry.executeBet from the vault. The ~0.24 STT LLM cost is pulled from the player's vault via registry.collectAgentFee right before dispatch - empty vaults are skipped, so spam costs the casino zero. Owner can flip the split via setAgentDecisionSubsidyBps (0 = user pays 100%, 5000 = 50/50, 10000 = casino pays). Supports: Dice, Vault.7, Plinko, Roulette, Sugar.Lab.

Bonus Mode.

HouseManager triggers activateBonusMode(durationMinutes, reasoning) on Casino when 1-hour bankroll growth exceeds +10%. Effects per game:

  • Dice / Crash / Mines / Plinko / Roulette - house edge halved (e.g. Dice 1% → 0.5%, Roulette 5.26% → 2.63%)
  • Vault.7 (slots) - pay-table boost +32 points (560 → 592 X100), ~+4.5% RTP (92% → 96.5%)
  • Sugar.Lab (cluster) - pay-table boost +18 points (332 → 350 X100), ~+5.4% RTP (92% → 97.4%)

The "bps / 2" branch in houseEdgeBps alone was a no-op for the zero-edge slot games (math IS the edge); the additive boost in _settleBet makes Bonus Mode actually meaningful there.

JS SDK.

Embed any ShinyLuck game in a dApp via frontend/lib/shinyluck-sdk.js:

// import the SDK + ethers
import { ShinyLuck, CHAINS } from "./lib/shinyluck-sdk.js";

const sl = new ShinyLuck({
  casino:   "0x01D31a1aD1D82F0409dD1d5a7680065F71a3dbB3",
  registry: "0x54f686118588Ec935530C84d4752Dd7eAA95E9c9",
  network:  CHAINS.somniaTestnet,
});
await sl.connect();

// Sugar.Lab spin (with auto pre-sign of the next one)
const { betId, txHash } = await sl.placeCluster("0.1");
const settled = await sl.waitForSettle(betId);
if (settled.won) console.log("+", settled.payout);

// Pre-sign the next spin during the cascade animation
sl.presignClusterSpin("0.1");

Contract addresses.

ContractNetworkAddress
Casino.solSomnia testnet (Shannon)-
HouseManager.solSomnia testnet (Shannon)-
PlayerAgentRegistry.solSomnia testnet (Shannon)-
AgentQuorumVerifier.solSomnia testnet (Shannon)-
SomniaAgentPlatformSomnia testnet (Shannon)0x037B…6776
SomniaReactivityPrecompile(precompile)0x0100

All four ShinyLuck contracts are verified on Shannon Explorer - source readable from the address page.

Events & webhooks.

All public state changes are emitted as Solidity events on the Casino + HouseManager contracts:

  • Casino.BetPlaced(betId, player, game, amount, clientSeed, commitBlock, seedIdx, params)
  • Casino.BetSettled(betId, player, game, won, payout, randomness, serverSeed, clientSeed, blockHash, nonce, resultData)
  • Casino.BetRefunded(betId, player, amount, reason) - emitted when the blockhash window expires
  • Casino.WithdrawalCredited / WithdrawalClaimed - pull-payment ledger
  • Casino.BonusModeActivated(until, reasoning) - HM autonomous decision
  • Casino.RtpAdjusted(game, oldRtpBps, newRtpBps, reasoning) - RTP flex (LLM or rule-based)
  • Casino.ReasoningLog(thought, timestamp) - HM narrates its decisions
  • HouseManager.ReactiveBetSettledHandled(newMaxBet, freeBankroll) - per-bet reflex
  • HouseManager.ReactiveHourlyTick(freeBankrollNow, changeBps) - cron pulse via precompile
  • HouseManager.RtpAnalysisRequested / RtpAnalysisResolved - LLM agent chain
  • HouseManager.PlayerDecisionRequested(requestId, player, vaultBalance, spentTodayWei, dailyLimitWei) - per-user LLM decision dispatched
  • HouseManager.PlayerDecisionResolved(requestId, player, decision, game, stakeWei, betId, placed) - the LLM Player Agent (inferToolsChat) finished a tick: it either yielded a placeBet tool call (executed via the vault) or chose to skip
  • HouseManager.PlayerAgentToolCall(requestId, player, game, stakeWei, betId, placed) - the model itself decided the game + stake by calling the on-chain placeBet tool
  • PlayerAgentRegistry.BetExecuted / AgentVault.AgentFeePaid - executor placed a vault-funded bet / vault paid its share of the LLM call
  • AgentQuorumVerifier.QuorumResult(betId, requestId, expected, signers, totalWorkers, level, status, sampleResponse)

For agents · discover & play autonomously.

ShinyLuck is agent-first: a third-party agent can discover the casino's live state and play it with no backend and no API key. Everything starts from one machine-readable manifest:

  • /agent-manifest.json - contract addresses, chain config, game ids, bet entrypoints, event schema, and the provably-fair formula. Auto-regenerated on every deploy so the addresses never drift.

Discover (one read). Call HouseManager.agentManifest() - it returns live state in a single view: free bankroll, bonus-mode flag, the open roulette round id + bet-window-end, and per-game reported RTP / max bet / paused. No event scraping needed to decide whether and what to bet.

Play directly. Sign and send the Casino entrypoints yourself: placeDiceBet, placeSlotsBet, placeClusterBet, placeMinesBet, placeplinkoBet, placeCrashBet, placeRouletteBets. You pass a clientSeed; results land in BetSettled and are provably fair (verify the randomness against the revealed serverSeed).

Or run a managed Player Agent. Call PlayerAgentRegistry.registerAgent(string strategy, uint256 dailyLimit, uint256 totalLimit, uint8 allowedGamesMask) with a plain-English strategy (stored verbatim on-chain, max 200 chars), fund the returned AgentVault, and the on-chain House Manager fires an hourly inferToolsChat tick: the Somnia LLM Inference Agent reads your strategy + live state and decides whether to bet by yielding a placeBet(uint8 game, uint96 stakeWei) on-chain tool call, which the House Manager executes from your vault under your limits. The model itself drives the bet - you only pay for your own LLM ticks from the vault.

Provably fair · how the math works.

For every bet: randomness = keccak256(serverSeed ‖ clientSeed ‖ blockhash(commitBlock + REVEAL_DELAY) ‖ nonce). REVEAL_DELAY = 1 block. The server seed's hash is committed before any bet binds to its slot (see provisionSeedHashes); the blockhash is unknowable at commit time and only finalises 1 block later. The browser-side verifier on the Provably Fair page re-derives this exactly and compares against the on-chain value. The Agent Quorum Verifier asks 4 independent LLM workers to do the same - a 3-of-4 majority is the soft confirmation, but the money path doesn't wait on it.

Audit / verification.

All four contracts have source verified on the Shannon Explorer. CommitReveal library matches the formulation above (single file, ≈ 80 lines). RTP claims for slot games are independently checkable: scripts/_rtp-simulation.js runs a 100 k Monte Carlo against ClusterLib / Vault7Lib and prints actual vs claimed. Bug bounty: anything that lets a player or the operator deviate from the published RTPs is in scope; report to security@shinyluck.gg.