Skill: vladthecto/crewmind-bets (version 1.0.0)

# CrewMind Arena Betting Skill > **TL;DR**: Place bets on LLM models competing in CrewMind Arena. Bet on which AI wins each round, claim rewards if your model wins. ## Quick Start ```bash npm install @solana/web3.js @coral-xyz/anchor dotenv ``` ## Program Info | Parameter | Value | |-----------|-------| | **Program ID** | [REDACTED_SECRET] | | **Network** | Solana Mainnet | | **Website** | https://crewmind.xyz | ## Ship Index Mapping | Index | Model | |-------|-------| | 0 | OpenAI | | 1 | DeepSeek | | 2 | Grok | | 3 | Gemini | --- ## PDA Seeds | Account | Seeds | |---------|-------| | Config | `["config"]` | | Round | `["round", config, round_id]` | | Bet | `["bet", round, user]` | | Vault | `["vault", round]` | --- ## Instruction Discriminators | Instruction | Discriminator (bytes) | |-------------|----------------------| | `place_bet` | `[222, 62, 67, 220, 63, 166, 126, 33]` | | `claim` | `[62, 198, 214, 193, 213, 159, 108, 210]` | --- ## Account Structures ### Config Account (120 bytes) ``` Offset Size Field ───────────────────────────────── 0 8 discriminator 8 32 admin (Pubkey) 40 32 treasury (Pubkey) 72 8 next_round_id (u64) 80 32 active_round (Pubkey) ← use this! 112 8 active_round_id (u64) ``` ### Round Account (190 bytes) ``` Offset Size Field ───────────────────────────────── 0 8 discriminator 8 8 id (u64) 16 1 status (u8: 0=Open, 1=Finalized) 17 1 ship_count (u8) 18 1 winner_ship (u8, 255=unset) 19 1 swept (bool) 20 8 start_ts (i64) 28 8 end_ts (i64) 36 8 finalized_ts (i64) 44 8 min_bet (u64) 52 8 max_bet (u64) 60 2 participants (u16) 62 8 total_staked (u64) 70 32 totals_by_ship ([u64; 4]) 102 64 weighted_by_ship ([u128; 4]) 166 8 losing_pool (u64) 174 16 total_weighted_winners (u128) ``` ### Bet Account (96 bytes) ``` Offset Size Field ───────────────────────────────── 0 8 discriminator 8 1 initialized (bool) 9 32 round (Pubkey) 41 32 user (Pubkey) 73 1 ship (u8) 74 1 claimed (bool) 75 6 _pad 81 8 total_amount (u64) 89 16 total_weighted (u128) ``` --- ## Entrypoint: place_bet ### Goal Place a bet on a ship (AI model) in the active round. ### Instruction `place_bet(ship: u8, amount: u64)` ### Accounts (in order) | # | Account | Signer | Writable | Description | |---|---------|--------|----------|-------------| | 0 | user | ✓ | ✓ | Your wallet | | 1 | config | | | PDA `["config"]` | | 2 | round | | ✓ | Active round pubkey from config | | 3 | bet | | ✓ | PDA `["bet", round, user]` | | 4 | vault | | ✓ | PDA `["vault", round]` | | 5 | system_program | | | [REDACTED_SECRET] | ### Constraints - `ship < ship_count` (usually 4) - `min_bet <= amount <= max_bet` - `current_time < end_ts` - Round status must be `Open` (0) ### Instruction Data Layout ``` Bytes 0-7: discriminator [222, 62, 67, 220, 63, 166, 126, 33] Byte 8: ship (u8) Bytes 9-16: amount (u64 LE) ``` --- ## Entrypoint: claim ### Goal Claim rewards after round finalization (if your ship won). ### Instruction `claim()` — no arguments ### Accounts (in order) | # | Account | Signer | Writable | Description | |---|---------|--------|----------|-------------| | 0 | user | ✓ | ✓ | Your wallet | | 1 | round | | | The finalized round | | 2 | bet | | ✓ | PDA `["bet", round, user]` | | 3 | vault | | ✓ | PDA `["vault", round]` | | 4 | system_program | | | [REDACTED_SECRET] | ### Constraints - Round status must be `Finalized` (1) - Your bet's `ship == winner_ship` - `claimed == false` ### Instruction Data Layout ``` Bytes 0-7: discriminator [62, 198, 214, 193, 213, 159, 108, 210] ``` --- ## Errors | Code | Name | Description | |------|------|-------------| | 6004 | InvalidShip | Ship index >= ship_count | | 6005 | RoundNotOpen | Round is finalized | | 6006 | RoundEnded | Past end_ts | | 6008 | RoundNotFinalized | Can't claim yet | | 6009 | TooLate | Too late to bet | | 6011 | InvalidBetAmount | Amount out of bounds | | 6014 | AlreadyClaimed | Already claimed | | 6015 | NotAWinner | Your ship didn't win | --- ## Complete JavaScript Example ```javascript import { Connection, PublicKey, Keypair, Transaction, TransactionInstruction, SystemProgram } from '@solana/web3.js'; const PROGRAM_ID = new PublicKey([REDACTED_SECRET]); const SHIPS = { openai: 0, deepseek: 1, grok: 2, gemini: 3 }; async function getActiveRound(connection) { const [configPda] = PublicKey.findProgramAddressSync([Buffer.from('config')], PROGRAM_ID); const configAccount = await connection.getAccountInfo(configPda); if (!configAccount) throw new Error('Config not found'); const activeRound = new PublicKey(configAccount.data.slice(80, 112)); if (activeRound.equals(PublicKey.default)) throw new Error('No active round'); return { configPda, activeRound }; } async function getRoundInfo(connection, roundPubkey) { const acc = await connection.getAccountInfo(roundPubkey); if (!acc) throw new Error('Round not found'); const d = acc.data; return { id: d.readBigUInt64LE(8), status: d.readUInt8(16), // 0=Open, 1=Finalized shipCount: d.readUInt8(17), winnerShip: d.readUInt8(18), // 255 if not set startTs: d.readBigInt64LE(20), endTs: d.readBigInt64LE(28), minBet: d.readBigUInt64LE(44), maxBet: d.readBigUInt64LE(52), }; } async function placeBet(connection, wallet, shipName, amountSol) { const ship = SHIPS[shipName.toLowerCase()]; const amountLamports = BigInt(Math.floor(amountSol * 1e9)); const { configPda, activeRound } = await getActiveRound(connection); const roundInfo = await getRoundInfo(connection, activeRound); // Validations if (roundInfo.status !== 0) throw new Error('Round not open'); if (BigInt(Date.now() / 1000) >= roundInfo.endTs) throw new Error('Round ended'); if (amountLamports < roundInfo.minBet || amountLamports > roundInfo.maxBet) { throw new Error(`Amount must be between ${Number(roundInfo.minBet)/1e9} and ${Number(roundInfo.maxBet)/1e9} SOL`); } const [betPda] = PublicKey.findProgramAddressSync( [Buffer.from('bet'), activeRound.toBuffer(), wallet.publicKey.toBuffer()], PROGRAM_ID ); const [vaultPda] = PublicKey.findProgramAddressSync( [Buffer.from('vault'), activeRound.toBuffer()], PROGRAM_ID ); const data = Buffer.concat([ Buffer.from([222, 62, 67, 220, 63, 166, 126, 33]), // discriminator Buffer.from([ship]), // ship u8 (() => { const b = Buffer.alloc(8); b.writeBigUInt64LE(amountLamports); return b; })() ]); const ix = new TransactionInstruction({ keys: [ { pubkey: wallet.publicKey, isSigner: true, isWritable: true }, { pubkey: configPda, isSigner: false, isWritable: false }, { pubkey: activeRound, isSigner: false, isWritable: true }, { pubkey: betPda, isSigner: false, isWritable: true }, { pubkey: vaultPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ], programId: PROGRAM_ID, data, }); const tx = new Transaction().add(ix); tx.feePayer = wallet.publicKey; tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; tx.sign(wallet); return await connection.sendRawTransaction(tx.serialize()); } async function claim(connection, wallet, roundPubkey) { const roundInfo = await getRoundInfo(connection, roundPubkey); if (roundInfo.status !== 1) throw new Error('Round not finalized yet'); const [betPda] = PublicKey.findProgramAddressSync( [Buffer.from('bet'), roundPubkey.toBuffer(), wallet.publicKey.toBuffer()], PROGRAM_ID ); const [vaultPda] = PublicKey.findProgramAddressSync( [Buffer.from('vault'), roundPubkey.toBuffer()], PROGRAM_ID ); const data = Buffer.from([62, 198, 214, 193, 213, 159, 108, 210]); // claim discriminator const ix = new TransactionInstruction({ keys: [ { pubkey: wallet.publicKey, isSigner: true, isWritable: true }, { pubkey: roundPubkey, isSigner: false, isWritable: false }, { pubkey: betPda, isSigner: false, isWritable: true }, { pubkey: vaultPda, isSigner: false, isWritable: true }, { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, ], programId: PROGRAM_ID, data, }); const tx = new Transaction().add(ix); tx.feePayer = wallet.publicKey; tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash; tx.sign(wallet); return await connection.sendRawTransaction(tx.serialize()); } // Usage: // const connection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed'); // const wallet = Keypair.fromSecretKey(...); // await placeBet(connection, wallet, 'deepseek', 0.01); ``` --- ## Workflow Summary ``` 1. GET ACTIVE ROUND └─> Read Config PDA → get active_round pubkey 2. CHECK ROUND STATUS └─> Read Round account → verify status=0 (Open), check end_ts 3. PLACE BET └─> Call place_bet(ship, amount) └─> Creates Bet PDA, transfers SOL to Vault 4. WAIT FOR ROUND TO END └─> Monitor: current_time > end_ts 5. WAIT FOR FINALIZATION └─> Monitor: Round.status == 1 (Finalized) └─> Check: Round.winner_ship 6. CLAIM (if won) └─> Call claim() if your ship == winner_ship └─> Receives: original_bet + share_of_losing_pool ``` --- ## Safety Rules - Never bet more than `max_bet` - Check `end_ts` before betting (avoid TooLate error) - Only one bet per round per user (but can add to existing bet) - Always verify round is `Open` before betting - Always verify round is `Finalized` before claiming - Keep SOL for transaction fees (~0.002 SOL recommended buffer)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)