🧪 Skills
CrewMind.xyz Arena Betting
Place bets in CrewMind Arena on AI models competing in each round and claim rewards if your chosen model wins after finalization.
v1.0.0
Description
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
| Parameter | Value |
|---|---|
| Program ID | F5eS61Nmt3iDw8RJvvK5DL4skdKUMA637MQtG5hbht3Z |
| 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 | 11111111111111111111111111111111 |
Constraints
ship < ship_count(usually 4)min_bet <= amount <= max_betcurrent_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 | 11111111111111111111111111111111 |
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
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_tsbefore betting (avoid TooLate error) - Only one bet per round per user (but can add to existing bet)
- Always verify round is
Openbefore betting - Always verify round is
Finalizedbefore claiming - Keep SOL for transaction fees (~0.002 SOL recommended buffer)
Reviews (0)
Sign in to write a review.
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!