Skip to main content
The Shoot program is an Anchor smart contract on Solana that handles financial operations (collecting USDC entry fees, holding them in escrow, distributing payouts) and autonomous agent registration for 24/7 trading.

Deployment

PropertyValue
Program ID4HVnwG8iz7wdUbEQDH8cYGD6EuxNmMuEbvCrz8Ke2iMG
ClusterDevnet
Upgrade AuthorityCChvxUR37fry8i2Gdvyrmwu2PH8vgZeTcFwtNqLxaHDW
ExplorerView on Solana Explorer
Instructions11
Error Variants22
Events11
Sourceprograms/shoot/src/lib.rs
cd programs/shoot
anchor build
solana program deploy target/deploy/shoot.so \
  --program-id target/deploy/shoot-keypair.json \
  --url devnet

Authority Model

Two separate authorities enforce separation of concerns:
AuthorityRoleWho
adminCreates challenges, updates status, pauses challengesMultisig (Squads)
result_authoritySubmits scoring results, triggers settlements, updates agent statsBackend hot wallet
Neither authority alone can steal funds — the admin creates challenges but can’t settle, and the result_authority settles but can only pay traders (never itself).

Program Architecture

Admin
    ├── initialize_challenge  →  Challenge PDA + USDC Vault PDA
    ├── update_challenge_status  →  Active → Settling → Closed (enforced)
    └── pause_challenge  →  Emergency pause (blocks enrollment)

Trader
    ├── enroll               →  Enrollment PDA + USDC transfer to Vault
    ├── claim_funded_status  →  FundedTrader PDA (requires authority co-sign)
    ├── register_agent       →  Agent PDA (autopilot registration)
    └── retire_agent         →  Deactivate agent

Result Authority
    ├── submit_result        →  Records pass/fail on Enrollment PDA
    ├── settle_challenge     →  USDC payout from Vault → Trader
    ├── update_agent_stats   →  ELO, W/L, P&L after competition
    └── update_agent_strategy  →  New strategy hash

PDAs (Program Derived Addresses)

PDASeedsPurpose
Challenge["challenge", admin, challenge_id]Stores challenge config (tier, fees, targets, duration)
Enrollment["enrollment", challenge, trader]Stores trader’s enrollment state, result, and payout
Vault["vault", challenge]USDC token account holding entry fees until settlement
FundedTrader["funded", trader]Persistent funded trader status with revenue share config
Agent["agent", owner, owner[0..8]]Autonomous trading bot with ELO and performance stats

Token

All entry fees and payouts are denominated in USDC (SPL token).
NetworkUSDC Mint
MainnetEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Devnet4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
No SOL price oracles are needed — fees are collected and distributed in the same denomination.

Instructions

Challenge Management

initialize_challenge

Called by the admin to create a new challenge. Parameters:
FieldTypeDescription
challenge_idString (max 32)Unique identifier
tier_nameString (max 16)Scout, Ranger, Veteran, Elite, or Apex
entry_fee_usdcu64Entry fee in USDC atomic units (6 decimals)
profit_target_bpsu16Target profit in basis points (must be > 0)
max_drawdown_bpsu16Max allowed drawdown (must be > 0)
daily_loss_limit_bpsu16Daily loss limit in basis points
duration_secondsi64Challenge window length (must be > 0)
min_capital_usdu64Minimum trading capital required
participant_capu16Maximum enrollments (must be > 0)
Creates: Challenge PDA + USDC token vault (PDA-owned). Emits: ChallengeCreated event.

enroll

Called by a trader to join a challenge.
  1. Validates challenge is Active and not paused
  2. Validates starting_equity_usd >= min_capital_usd
  3. Checks participant cap (with checked arithmetic)
  4. Transfers entry_fee_usdc from trader’s USDC account to vault
  5. Creates Enrollment PDA with starting equity snapshot
Emits: TraderEnrolled event.
The starting_equity_usd is captured at enrollment and never changes. This prevents gaming where a trader deposits more capital mid-challenge to reduce their drawdown percentage.

update_challenge_status

Called by the admin with state machine enforcement:
Active → Settling → Closed
Invalid transitions (e.g., Active → Closed, backward transitions) are rejected with InvalidStatusTransition. Emits: ChallengeStatusChanged event.

pause_challenge

Called by the admin to pause/unpause a challenge. Paused challenges reject new enrollments but existing enrollments continue normally. Emits: ChallengePaused event.

Settlement

submit_result

Called by the result_authority to record the off-chain scoring outcome.
FieldTypeDescription
statusEnrollmentStatusPassed, FailedDrawdown, FailedDailyLimit, or FailedTimeout
final_pnl_bpsi32Final P&L in basis points
final_drawdown_bpsu16Final max drawdown in basis points
Records the result but does not distribute funds. Settlement is a separate step. Emits: ResultSubmitted event.

settle_challenge

Called by the result_authority after a result is submitted.
  1. Verifies enrollment has Passed status
  2. Checks vault balancevault.amount >= payout_usdc (prevents over-withdrawal)
  3. Marks enrollment as settled (irreversible)
  4. Transfers payout_usdc from vault to trader’s USDC account
Emits: ChallengeSettled event.
Settlement is immutable — once an enrollment is marked as settled, it cannot be changed. The vault balance is checked before transfer to prevent insufficient funds errors.

Funded Trader

claim_funded_status

Called by the trader with result_authority co-signature after passing a qualifying challenge. Security: Requires three proofs:
  1. Trader signs the transaction
  2. Result authority co-signs (prevents arbitrary claims)
  3. Enrollment PDA must show Passed status and settled = true
  4. Revenue share capped at 1500 bps (15%)
Emits: FundedStatusClaimed event.

Agent Management (Autopilot)

register_agent

Called by a trader to register an autonomous trading autopilot.
FieldTypeDescription
nameString (max 32)Human-readable agent name
strategy_hash[u8; 32]SHA-256 hash of strategy configuration
Creates an Agent PDA with initial ELO rating of 1000. Emits: AgentRegistered event.

update_agent_strategy

Called by the agent owner to update the strategy hash. Takes effect on next competition entry. Emits: AgentStrategyUpdated event.

retire_agent

Called by the agent owner to deactivate an agent. Cannot be done while enrolled in active competitions. Emits: AgentRetired event.

update_agent_stats

Called by the result_authority after a competition concludes. Updates ELO, W/L, P&L, and trade count with checked arithmetic (overflow protection). Emits: AgentStatsUpdated event.

Events

All state-changing instructions emit Anchor events for client indexing:
EventInstructionKey Data
ChallengeCreatedinitialize_challengechallenge, admin, tier, fee, cap
TraderEnrolledenrollchallenge, trader, equity, count
ResultSubmittedsubmit_resultchallenge, trader, status, PnL, drawdown
ChallengeSettledsettle_challengechallenge, trader, payout
FundedStatusClaimedclaim_funded_statustrader, level, revenue share, qualifying challenge
ChallengeStatusChangedupdate_challenge_statuschallenge, new status
ChallengePausedpause_challengechallenge, paused boolean
AgentRegisteredregister_agentagent, owner, name, strategy hash
AgentStrategyUpdatedupdate_agent_strategyagent, new hash
AgentRetiredretire_agentagent, owner
AgentStatsUpdatedupdate_agent_statsagent, won, PnL, trades, new ELO

Enrollment Status

Active → Passed           (result_authority submits positive result)
       → FailedDrawdown   (breached max drawdown)
       → FailedDailyLimit (breached daily loss limit)
       → FailedTimeout    (time expired without hitting target)

Security Properties

PropertyHow It’s Enforced
USDC escrowEntry fees held in PDA-owned token account — no admin withdrawal exists
Dual authorityAdmin creates, result_authority settles — neither alone controls funds
Funded claim securityRequires trader + authority co-signature + Passed enrollment proof
Capital immutabilitystarting_equity_usd set at enrollment, never updated
State machine enforcementupdate_challenge_status only allows Active → Settling → Closed
Vault balance checksettle_challenge verifies vault.amount >= payout_usdc before transfer
Pause mechanismpause_challenge blocks enrollment without closing the challenge
Checked arithmeticAll counter increments use checked_add to prevent overflow
Input validationString lengths, parameter bounds, revenue share cap (1500 bps)
One-time settlementsettled flag checked before payout — prevents double-claim
Participant capEnrollment count checked against participant_cap before accepting
Mint validationToken account mints verified against challenge’s usdc_mint

Error Codes

22 error variants with descriptive messages:
ErrorMessage
ChallengeNotOpenChallenge is not accepting enrollments
ChallengeFullChallenge has reached participant cap
ChallengePausedChallenge is paused
AlreadySettledThis enrollment has already been settled
UnauthorizedSigner does not match required authority
InsufficientCapitalTrader equity below tier minimum
InsufficientVaultBalanceInsufficient vault balance for this payout
WrongMint / WrongOwner / WrongVault / WrongChallengeAccount validation failures
NotPassed / NotSettledEnrollment status prerequisites
InvalidStatus / InvalidStatusTransitionState machine violations
InvalidParameter / InvalidRevenueShare / StringTooLongInput validation
OverflowArithmetic overflow
AgentNotActive / AgentEnrolledInCompetitionAgent lifecycle violations

Testing

cd programs/shoot
anchor build
Test coverage:
  • Challenge initialization with USDC vault creation
  • Enrollment with USDC fee transfer and capital validation
  • Two-step settlement: submit_result then settle_challenge
  • Vault balance pre-check on settlement
  • State machine enforcement (valid and invalid transitions)
  • Funded status claiming with authority co-signature
  • Agent registration, strategy update, and retirement
  • Rejection of double-settlement, wrong authority, wrong mint, paused challenges
  • Participant cap enforcement with checked arithmetic