crewmind-bets
> **TL;DR**: Place bets on LLM models competing in CrewMind Arena.
Setup & Installation
Install command
clawhub install vladthecto/crewmind-betsIf the CLI is not installed:
Install command
npx clawhub@latest install vladthecto/crewmind-betsOr install with OpenClaw CLI:
Install command
openclaw skills install vladthecto/crewmind-betsor paste the repo link into your assistant's chat
Install command
https://github.com/openclaw/skills/tree/main/skills/vladthecto/crewmind-betsWhat This Skill Does
CrewMind Arena is a Solana-based betting platform where users wager SOL on which AI model wins each competition round. Supported models are OpenAI, DeepSeek, Grok, and Gemini. Winners receive their original stake plus a proportional share of the losing pool.
Interacts with the on-chain program directly, so bets and claims can be executed without relying on the CrewMind website UI.
When to Use It
- Bet SOL on DeepSeek winning the current Arena round
- Claim winnings after your chosen AI model wins a round
- Check round status and time remaining before wagering
- Monitor round finalization to know when rewards are claimable
- Add more SOL to an existing bet on a model mid-round
View original SKILL.md file
# 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** | `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_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 | | | `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
```javascript
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)
Example Workflow
Here's how your AI assistant might use this skill in practice.
User asks: Bet SOL on DeepSeek winning the current Arena round
- 1Bet SOL on DeepSeek winning the current Arena round
- 2Claim winnings after your chosen AI model wins a round
- 3Check round status and time remaining before wagering
- 4Monitor round finalization to know when rewards are claimable
- 5Add more SOL to an existing bet on a model mid-round
> **TL;DR**: Place bets on LLM models competing in CrewMind Arena.
Security Audits
These signals reflect official OpenClaw status values. A Suspicious status means the skill should be used with extra caution.