SkillJavaScriptv1.0.0

crewmind-bets

> TL;DR: Place bets on LLM models competing in CrewMind Arena.

0 downloads
vladthecto
Updated Feb 3, 2026

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

npm install @solana/web3.js @coral-xyz/anchor dotenv

Program Info

ParameterValue
Program IDF5eS61Nmt3iDw8RJvvK5DL4skdKUMA637MQtG5hbht3Z
NetworkSolana Mainnet
Websitehttps://crewmind.xyz

Ship Index Mapping

IndexModel
0OpenAI
1DeepSeek
2Grok
3Gemini

PDA Seeds

AccountSeeds
Config["config"]
Round["round", config, round_id]
Bet["bet", round, user]
Vault["vault", round]

Instruction Discriminators

InstructionDiscriminator (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)

#AccountSignerWritableDescription
0userYour wallet
1configPDA ["config"]
2roundActive round pubkey from config
3betPDA ["bet", round, user]
4vaultPDA ["vault", round]
5system_program11111111111111111111111111111111

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)

#AccountSignerWritableDescription
0userYour wallet
1roundThe finalized round
2betPDA ["bet", round, user]
3vaultPDA ["vault", round]
4system_program11111111111111111111111111111111

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

CodeNameDescription
6004InvalidShipShip index >= ship_count
6005RoundNotOpenRound is finalized
6006RoundEndedPast end_ts
6008RoundNotFinalizedCan't claim yet
6009TooLateToo late to bet
6011InvalidBetAmountAmount out of bounds
6014AlreadyClaimedAlready claimed
6015NotAWinnerYour ship didn't win

Complete JavaScript Example

import { Connection, PublicKey, Keypair, Transaction, TransactionInstruction, SystemProgram } from '@solana/web3.js';

const PROGRAM_ID = new PublicKey('F5eS61Nmt3iDw8RJvvK5DL4skdKUMA637MQtG5hbht3Z');
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)
Free
Installation
Reviews

Sign in to leave a review.

No reviews yet. Be the first.