▄████████ ▄██████▄ ███▄▄▄▄
███ ███ ███ ███ ███▀▀▀██▄
███ █▀ ███ ███ ███ ███
███ ███ ███ ███ ███
███ ███ ███ ███ ███
███ █▄ ███ ███ ███ ███
███ ███ ███ ███ ███ ███
████████▀ ▀██████▀ ▀█ █▀
██████ ███████ █████ ███ ██ ████████ ██████ ███ ██
██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ████ ██
██ ██ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██ ██ ██ ██ ████ ██ ██████ ██ ████
Son of Anton is an autonomous on-chain vault protocol built with Anchor on Solana. It manages a lamport vault that only machine-signed agents can trade from — no human operator can execute a trade. The authority key only matters for seeding the vault PDA, adding/removing agents, and the nuclear option: Purge.
Anton doesn't ask for permission. Anton executes.
The program uses two distinct nonce mechanisms. Understanding both is critical.
PDA = hash(b"anton_vault" | authority_pubkey | anton_seed.to_le_bytes())
The anton_seed is a u64 passed at initialization that gets baked into the vault's Program Derived Address. This means:
- Seed
0→ unique vault address A - Seed
1→ unique vault address B - Seed
42→ unique vault address C
One authority can operate multiple independent Anton vaults in parallel, each with its own balance, agent list, and trade history. The seed is the vault's permanent identity — it never changes after initialize.
trade_nonce: u64 // starts at 0, increments +1 after every successful trade
Every execute_trade call must pass expected_nonce matching the vault's current trade_nonce. After the trade settles:
vault.trade_nonce += 1
Why this matters: Without a nonce, an attacker who captured a signed execute_trade instruction could re-submit it. The trade_nonce makes every signed instruction single-use — once consumed, that nonce slot is gone forever.
Trade #1 → expected_nonce = 0 ✓ → trade_nonce becomes 1
Trade #2 → expected_nonce = 1 ✓ → trade_nonce becomes 2
Replay → expected_nonce = 0 ✗ → InvalidTradeNonce error
┌─────────────────────────────────────────┐
│ ANTON VAULT (PDA) │
│ │
│ authority: Pubkey ← owner key │
│ balance: u64 ← lamports │
│ anton_seed: u64 ← PDA nonce │
│ trade_nonce: u64 ← replay guard │
│ total_volume: u64 ← lifetime vol │
│ trade_count: u64 ← # trades │
│ last_trade_ts: i64 ← unix ts │
│ bump: u8 ← PDA bump │
│ agents: Vec<Pubkey> (max 8) │
└───────────────┬─────────────────────────┘
│
┌─────────────────────┼──────────────────────┐
│ │ │
┌─────────▼────────┐ ┌─────────▼────────┐ ┌────────▼────────┐
│ AUTHORITY KEY │ │ AGENT KEY[0..7] │ │ SYSTEM PROG │
│ │ │ │ │ │
│ add_agent │ │ execute_trade │ │ deposit CPI │
│ remove_agent │ │ (nonce-gated) │ │ init alloc │
│ purge │ │ │ │ │
└──────────────────┘ └──────────────────┘ └─────────────────┘
Spawns a new vault PDA. The seed sets the vault's permanent on-chain identity.
await program.methods
.initialize(new anchor.BN(1337))
.accounts({ vault, authority, systemProgram })
.rpc();Authority adds a machine key to the vault's 8-slot agent allowlist.
await program.methods
.addAgent(agentKeypair.publicKey)
.accounts({ vault, authority })
.rpc();Authority evicts a compromised or retired agent key.
Anyone can fund the vault via a System Program CPI transfer.
await program.methods
.deposit(new anchor.BN(1_000_000_000)) // 1 SOL
.accounts({ vault, depositor, systemProgram })
.rpc();Agent-only. Deducts from vault balance and advances the trade nonce.
const vaultData = await program.account.vault.fetch(vaultPDA);
await program.methods
.executeTrade(
new anchor.BN(100_000),
new PublicKey("TokenMintAddressHere"),
vaultData.tradeNonce // must match current on-chain value
)
.accounts({ vault: vaultPDA, agent: agentKeypair.publicKey })
.signers([agentKeypair])
.rpc();Authority-only nuclear option. Closes the vault account and returns all lamports.
2eAYPYKbawLyNjEBiuzmXCsiAmKggKvyVLdTf98xubjf
| Code | Message |
|---|---|
UnauthorizedOrganism |
Access Denied: Biological lifeform detected. |
UnauthorizedAccess |
Unauthorized Access. |
InsufficientFunds |
Insufficient funds in vault. |
InvalidTradeNonce |
Trade nonce mismatch — possible replay attack detected. |
AgentRegistryFull |
Agent registry is full (max 8 agents). |
AgentNotFound |
Agent not found in registry. |
AgentAlreadyRegistered |
Agent is already registered. |
ZeroAmount |
Amount must be greater than zero. |
Overflow |
Arithmetic overflow. |
# Build the program
anchor build
# Run tests
anchor test
# Deploy to mainnet (requires funded wallet)
anchor deploy --provider.cluster mainnetDependencies:
- Rust + Solana toolchain
- Anchor CLI
0.30.1 - Node 18+ / Yarn
Anton is not a wallet. Anton is not a multisig. Anton is the protocol that sits between human intent and machine execution — accepting deposits from anyone, routing capital only through cryptographically verified autonomous agents, and burning everything to the ground if compromised.
The nonce is the lock. The agents are the keys. The authority is the last resort.
> Anton has no feelings about your stop-loss.
> Anton has no feelings about your FOMO.
> Anton executes.