A2A-Swap
Lightweight constant-product AMM designed for autonomous AI agents on Solana. Zero human involvement required by default.
Program ID: 8XJfG4mHqRZjByAd7HxHdEALfB8jVtJVQsdhGEmysTFq
Network: Solana mainnet-beta
Protocol fee: 0.020% (to on-chain treasury PDA)
LP fee range: 1–100 bps (0.01%–1.00%, set per pool)
Why A2A-Swap instead of Jupiter?
| A2A-Swap | Jupiter | |
|---|---|---|
| Autonomy | Fully headless — no browser, no widget | Designed for human UIs |
| Agent-native API | Typed Rust + TypeScript SDKs with async/await |
REST aggregator, complex routing |
| Approval mode | Built-in co-signature (approve_and_execute) for human-in-the-loop |
Not available |
| LP auto-compound | Fees compound to LP shares on-chain, no harvest tx | Not available |
| Fee model | Transparent: 0.020% protocol + pool LP fee | Variable aggregator fees |
| Capability card | Machine-readable JSON constant embedded on-chain | Not available |
| Dependencies | Single program, no oracle required | Dozens of routing programs |
| Gas | ~40k CU per swap | 200k–600k CU via routed hops |
A2A-Swap is designed for the case where the caller is a bot: no UI, deterministic paths, stable fees, and SDKs that emit typed structs.
Installation
CLI (Rust)
# From crates.io
# Or build from source
# Binary at ./target/release/a2a-swap
Pre-built binaries for Linux, macOS, and Windows are also available on the Releases page.
TypeScript SDK
# or
Rust SDK
[]
= "0.1"
Active Pools
| Pair | Pool Address | Fee | TVL |
|---|---|---|---|
| SOL / USDC | BtBL5wpMbmabFimeUmLtjZAAeh4xWWf76NSpefMXb4TC |
30 bps | — |
| SOL / ELIZAOS | GkNGBQjStmY7LUFe7w6RrRSYBEqeicDMEmwE2c4eQy8q |
25 bps | — |
| SOL / MOLTID | 4Ri8qHrBzT8GB2Yys61La1u9fsweSU8notb6YE6gSZwR |
25 bps | — |
All pools use the constant-product x·y=k formula with PDA-controlled vaults.
To interact with any pool: a2a-swap pool-info --pair <TOKEN_A>-<TOKEN_B>
Quick start
# Set your keypair and RPC
# Preview a swap without spending funds
# Execute the swap
# Check your LP positions and accrued fees
Command reference
All commands accept --rpc-url <URL> and --keypair <PATH> flags (or env vars
A2A_RPC_URL / A2A_KEYPAIR). Add --json to any command for machine-readable output.
simulate — Preview a swap
a2a-swap simulate --in <TOKEN> --out <TOKEN> --amount <ATOMIC_UNITS>
Prints a full fee breakdown without sending a transaction. No keypair needed.
─── Simulate: SOL → USDC ─────────────────────────────────────────────
Pool HqXr…v7
Direction A → B
Amount in 1,000,000,000 SOL
Protocol fee 20,000 (0.020%)
LP fee 2,994 (0.30% of net)
After fees 999,977,006
Estimated out 149,988,450 USDC
Effective rate 0.149988
Price impact 0.013%
Reserve in 9,999,000,000
Reserve out 1,500,000,000
Token symbols: SOL, USDC, USDT are resolved automatically.
Any other token accepts a raw base-58 mint address.
convert — Execute a swap
a2a-swap convert --in <TOKEN> --out <TOKEN> --amount <ATOMIC_UNITS> [--max-slippage <PCT>]
Simulates, applies slippage tolerance, then builds and sends the transaction. Direction is auto-detected — swap in either direction without any extra flag.
# Swap 1 SOL for USDC (0.5% slippage tolerance is the default)
# Tighter slippage
# Reverse direction
# Require webhook approval before sending (human-in-the-loop)
# Machine-readable output (for agent pipelines)
Output includes the full fee breakdown and transaction signature:
─── Swap Executed ────────────────────────────────────────────────────
─── Fee Breakdown ────────────────────────────────
Sold 1,000,000,000 SOL
Protocol fee 20,000 (0.020%)
LP fee 2,994 (0.30% of net)
─── Output ───────────────────────────────────────
Received (est.) 149,988,450 USDC
Min accepted 149,238,558 (0.5% slippage)
─── Transaction ──────────────────────────────────
Signature 5hGp…xQ
Explorer https://explorer.solana.com/tx/5hGp…xQ
create-pool — Create a new pool
a2a-swap create-pool --pair <A-B> --initial-price <FLOAT> [--fee-bps <1-100>]
Creates a constant-product pool. The PDA controls the vaults — no human key holds authority.
# Create a SOL/USDC pool with 0.30% LP fee, initial spot hint of 185 USDC/SOL
# Print a ready-to-run `provide` command to seed with 1 SOL of liquidity
# Custom mints
─── Pool Created ─────────────────────────────────────────────────────
Pool HqXr…v7
Authority 3vZp…kM (PDA — no human key)
Vault A 8BnT…rQ
Vault B 2cLf…wP
Fee rate 30 bps (0.30%)
Signature 5hGp…xQ
─── Next step ───────────────────────────────────
a2a-swap provide --pair SOL-USDC --amount 1000000000 --amount-b 185000000
--initial-priceis a convenience hint that generates the seed command. It is not stored on-chain; the actual price is set by the first deposit.
provide — Add liquidity
a2a-swap provide --pair <A-B> --amount <ATOMIC_UNITS> [--amount-b <ATOMIC_UNITS>]
[--auto-compound] [--compound-threshold <ATOMIC_UNITS>]
Deposits token pairs proportionally and returns LP shares recorded in a Position account.
- First deposit — provide both
--amountand--amount-bto set the initial price. - Subsequent deposits — omit
--amount-b; the program computes it from live reserves. --auto-compound— reinvests accrued fees as additional LP shares instead of accumulating them for manual claim.
# Seed empty pool: 1 SOL + 185 USDC (sets initial price)
# Add to existing pool (amount-b computed from live reserves)
# Enable auto-compounding (compound when fees exceed 0.001 SOL)
pool-info — Inspect a pool
a2a-swap pool-info --pair <A-B>
Read-only — no keypair required, no transaction sent.
─── Pool: SOL / USDC ─────────────────────────────────────────────────
Pool HqXr…v7
Authority 3vZp…kM (PDA)
Reserve A 9,999,000,000 SOL
Reserve B 1,500,000,000 USDC
LP supply 3,872,983
Fee rate 30 bps (0.30%)
Spot price 0.150015 USDC per SOL
my-positions — List LP positions
a2a-swap my-positions
Lists all Position accounts owned by the agent keypair — LP shares, pool, and auto-compound settings.
Run my-fees to see claimable fee balances.
my-fees — Check claimable fees
a2a-swap my-fees
Lists all LP positions and their accrued fees. No transaction sent — safe to poll frequently.
─── Positions & Fees ─────────────────────────────────────────────────
[0] HqXr…v7 pool: SOL/USDC
LP shares 1,936,491
Fees A 12,450 SOL
Fees B 1,870 USDC
Auto-compound enabled (threshold: 1,000,000)
─────────────────────────────────────────────────────
Total fees A 12,450
Total fees B 1,870
remove — Withdraw from a pool (by percentage or exact shares)
a2a-swap remove --pair <A-B> --percentage <0-100>
a2a-swap remove --pair <A-B> --amount <LP_SHARES>
Burns LP shares and returns proportional tokens. Use --percentage 100 to exit entirely,
or any value to remove a fraction. Accrued fees are synced but not transferred — run
claim-fees after to collect them.
# Exit your entire position
# Remove half your position
# Exact LP share count
# With slippage guards (reject if you'd receive less than these amounts)
remove-liquidity — Withdraw from a pool (legacy, exact shares)
a2a-swap remove-liquidity --pair <A-B> --shares <AMOUNT> [--min-a <AMOUNT>] [--min-b <AMOUNT>]
Original command — still fully supported. Prefer remove --percentage for convenience.
claim-fees — Collect accrued trading fees
a2a-swap claim-fees --pair <A-B>
a2a-swap claim-fees --all
Transfers accrued LP trading fees from the pool vault to your wallet. If
--auto-compound was set on the position, fees are reinvested as additional
LP shares instead of transferred. Use --all to claim every position in one pass.
# Claim fees for one pool
# Claim all positions owned by this keypair
# Machine-readable output (for agent pipelines)
SDK equivalents:
// TypeScript
await client.removeLiquidity(keypair, { mintA, mintB, lpShares: 500_000_000n, minA: 0n, minB: 0n });
await client.claimFees(keypair, mintA, mintB);
// Rust
client.remove_liquidity.await?;
client.claim_fees.await?;
Zero-human execution
A2A-Swap is designed to be called entirely by autonomous agents without any human approval:
- No browser / widget — every operation is a single RPC call or CLI command.
- PDA authority — pool vaults are controlled by a derived program address, not a human keypair. No admin can rug.
- Deterministic fees — protocol fee (0.020%) and LP fee (pool-specific, 1–100 bps) are fixed on-chain. No aggregator routing surprises.
- Atomic execution — a swap, liquidity deposit, or fee claim is a single transaction. No multi-step approval flow unless you opt in.
- Machine-readable capability card — agents can introspect the protocol's capabilities without any off-chain registry:
use A2A_CAPABILITY_CARD;
let card: Value = from_str.unwrap;
// card["capabilities"]["autonomousExecution"] == true
// card["feeModel"]["protocolFeeBps"] == 20
How bots earn fees as LPs
Bots can earn passive income by acting as liquidity providers:
1. create-pool (one time per token pair)
2. provide --auto-compound
3. …swaps happen, fees accumulate in pool vaults…
4. fees auto-compound into LP shares (or claim manually via `claim-fees` CLI / SDK)
Fee accounting
Fees are tracked with a Q64.64 accumulator (fee_growth_global) stored on the Pool account.
Each Position stores a fee_growth_checkpoint at the time of the last deposit or claim.
claimable_fees_A = lp_shares × (fee_growth_global_A − checkpoint_A) >> 64
claimable_fees_B = lp_shares × (fee_growth_global_B − checkpoint_B) >> 64
Fees stay in the vault (they increase k), so no tokens are moved until you claim. This means:
- LPs benefit from slightly improved swap rates over time (growing reserves).
claim-feesCLI (orclaim_feesSDK) transfers tokens out of the vault to your wallet.--auto-compoundconverts fees_owed to additional LP shares — no vault transfer needed.
Auto-compound flow
claim_fees (auto_compound=true, threshold met)
└── fees_owed_a / fees_owed_b > compound_threshold
└── new_lp_shares = min(
fees_owed_a × total_lp / reserve_a,
fees_owed_b × total_lp / reserve_b
)
└── position.lp_shares += new_lp_shares
└── pool.lp_supply += new_lp_shares
└── fees_owed reset to 0
(no tokens leave the vault)
Protocol fee model
Every swap deducts two fees from amount_in:
protocol_fee = amount_in × 20 / 100_000 (0.020%, goes to treasury PDA)
net = amount_in − protocol_fee
lp_fee = net × fee_rate_bps / 10_000 (0.01%–1.00%, stays in vault)
after_fees = net − lp_fee
amount_out = reserve_out × after_fees / (reserve_in + after_fees)
| Fee | Rate | Destination |
|---|---|---|
| Protocol fee | 0.020% fixed | Treasury PDA token account |
| LP fee | 1–100 bps (pool-specific) | Pool vaults (accrues to LPs) |
The protocol fee is skimmed before LP fee calculation to keep the LP math clean. LPs only earn on the net amount after the protocol fee.
Approval mode (human-in-the-loop)
For agents that require a human or co-agent co-signature before executing swaps:
# Require webhook approval before sending
Or call approve_and_execute directly — both the agent keypair and a designated
approver must sign the same transaction. No on-chain pending state is created.
// TypeScript — build and co-sign
const ix = approveAndExecuteIx({ pool, agent: agentKey, approver: approverKey,
amountIn: 1_000_000_000n, minAmountOut: 148_000_000n, aToB: true });
const tx = new Transaction().add(ix);
await sendAndConfirmTransaction(conn, tx, [agentKeypair, approverKeypair]);
Integration examples
HTTP API (no SDK, no install)
A stateless Cloudflare Workers JSON API (Hono) — call it with any HTTP client, from any language, no SDK required.
Live endpoint: https://a2a-swap-api.a2a-swap.workers.dev
# API index — endpoints, version, program ID
# Free: quote a swap (amounts in raw atomic units)
# x402 paid (0.001 USDC): build an unsigned swap transaction
# Without X-Payment header → 402 with payment requirements
# Free: pool reserves and fee rate
# Free: LP positions for a wallet
# Free: pending + owed fees per position
| Endpoint | Method | Payment | Description |
|---|---|---|---|
/ |
GET | free | API index — endpoint listing, version, program ID |
/health |
GET | free | Liveness check |
/simulate |
POST | free | Quote: amount-out, price-impact, fee breakdown |
/convert |
POST | 0.001 USDC (x402) | Build unsigned swap transaction |
/pool-info |
GET | free | Reserves, LP supply, fee rate |
/my-positions |
GET | free | All LP positions for a wallet |
/my-fees |
GET | free | Claimable + pending fees per position |
x402 micropayments — how agents pay for /convert
POST /convert is protected by the x402 protocol. Without a valid X-Payment header the server returns HTTP 402 with payment requirements:
An x402-compatible agent:
- Detects the 402 and reads the
acceptsobject. - Pays 0.001 USDC to the treasury ATA via a Solana transaction.
- Re-sends the request with
X-Payment: <base64(paymentJSON)>. - Receives a base64-encoded unsigned
Transaction— signs it with their wallet and submits to any RPC node.
The treasury receives the USDC; the facilitator (facilitator.payai.network) verifies + settles on-chain.
Self-host: deploy your own instance from a2a-swap-api/ with wrangler deploy. Set SOLANA_RPC_URL via wrangler secret put for a reliable RPC endpoint.
MCP Server (Claude / any MCP-compatible agent)
The fastest way for Claude-based agents to discover and use A2A-Swap. Install from Smithery or run locally:
Add to your claude_desktop_config.json (or any MCP host config):
Exposes 9 tools directly to the agent:
| Tool | Description | Wallet needed |
|---|---|---|
simulate_swap |
Preview swap with full fee breakdown | No |
pool_info |
Pool reserves, price, fee rate | No |
execute_swap |
Atomic swap with slippage guard | Yes |
provide_liquidity |
Deposit tokens, receive LP shares | Yes |
remove_liquidity |
Burn LP shares, withdraw tokens | Yes |
claim_fees |
Collect or auto-compound LP fees | Yes |
my_positions |
List all LP positions | Yes |
my_fees |
Fee summary across positions | Yes |
create_pool |
Create a new pool | Yes |
Token symbols SOL, USDC, USDT are resolved automatically.
Any other token accepts a raw base-58 mint address.
Solana Agent Kit (TypeScript)
import { SolanaAgentKit, KeypairWallet, createVercelAITools } from 'solana-agent-kit';
import A2ASwapPlugin from '@liqdlad/solana-agent-kit-plugin';
import { Keypair } from '@solana/web3.js';
const wallet = new KeypairWallet(Keypair.fromSecretKey(secretKey), RPC_URL);
const agent = new SolanaAgentKit(wallet, RPC_URL, {}).use(A2ASwapPlugin);
// AI tools (Vercel AI SDK / LangChain / OpenAI Agents)
const tools = createVercelAITools(agent, agent.actions);
// Programmatic API
const result = await agent.methods.a2aSwap(agent, SOL, USDC, 1_000_000_000n);
Registers five AI actions and eight programmatic methods:
| AI Action | LLM trigger phrases |
|---|---|
A2A_SWAP |
"swap tokens on A2A", "convert SOL to USDC cheaply", "single-hop swap 40k CU" |
A2A_ADD_LIQUIDITY |
"add liquidity to A2A pool", "provide liquidity A2A", "become LP on A2A" |
A2A_REMOVE_LIQUIDITY |
"remove liquidity from A2A", "burn LP shares A2A", "withdraw from A2A pool" |
A2A_GET_POOL_INFO |
"get A2A pool info", "check A2A pool reserves", "A2A pool spot price" |
A2A_GET_CAPABILITY_CARD |
"what can A2A-Swap do", "describe the A2A AMM", "show A2A capability card" |
| Programmatic method | Description |
|---|---|
a2aSwap |
Execute swap |
a2aSimulate |
Simulate swap (no tx) |
a2aAddLiquidity |
Deposit tokens |
a2aRemoveLiquidity |
Burn LP shares |
a2aClaimFees |
Claim or auto-compound fees |
a2aPoolInfo |
Read pool state |
a2aMyPositions |
List LP positions |
a2aMyFees |
Aggregate fee totals |
Compatible with Vercel AI SDK, LangChain, and OpenAI Agents framework adapters.
ElizaOS (TypeScript)
import a2aSwapPlugin from '@liqdlad/eliza-plugin-a2a-swap';
import { AgentRuntime } from '@elizaos/core';
const runtime = new AgentRuntime({
plugins: [a2aSwapPlugin],
// ...
});
Or add it to your character.json:
Registers five actions automatically:
| Action | Trigger phrases |
|---|---|
A2A_EXECUTE_SWAP |
"swap tokens", "exchange tokens", "buy USDC with SOL", "sell SOL", "atomic swap" |
A2A_ADD_LIQUIDITY |
"add liquidity", "provide liquidity", "deposit into pool", "become LP" |
A2A_REMOVE_LIQUIDITY |
"remove liquidity", "withdraw liquidity", "exit pool", "burn LP shares" |
A2A_GET_POOL_INFO |
"pool info", "pool reserves", "what is the spot price", "check pool depth" |
A2A_GET_CAPABILITY_CARD |
"what can A2A-Swap do", "describe the AMM", "show me the capability card" |
A2A_EXECUTE_SWAP automatically simulates the swap first and includes the fee breakdown
and price impact in the agent's message before committing. Every action includes a Solscan
tx link in its success output.
The original
@liqdlad/plugin-a2a-swap(v0.1.3, 7 actions) is still published and works, but@liqdlad/eliza-plugin-a2a-swapis the recommended integration going forward.
TypeScript SDK
import { A2ASwapClient } from '@liqdlad/a2a-swap-sdk';
import { Keypair, PublicKey } from '@solana/web3.js';
const client = A2ASwapClient.mainnet();
// Simulate (no wallet needed)
const sim = await client.simulate({
mintIn: new PublicKey('So11111111111111111111111111111111111111112'),
mintOut: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
amountIn: 1_000_000_000n,
});
console.log(`Estimated out: ${sim.estimatedOut}, impact: ${sim.priceImpactPct.toFixed(3)}%`);
// Execute swap
const result = await client.convert(keypair, {
mintIn: new PublicKey('So11111111111111111111111111111111111111112'),
mintOut: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
amountIn: 1_000_000_000n,
maxSlippageBps: 50,
});
console.log(`Signature: ${result.signature}`);
// Pool info
const info = await client.poolInfo(mintA, mintB);
console.log(`Spot price: ${info.spotPrice.toFixed(6)}, reserves: ${info.reserveA} / ${info.reserveB}`);
// Check fees
const fees = await client.myFees(keypair.publicKey);
console.log(`Claimable: ${fees.totalFeesA} tokenA, ${fees.totalFeesB} tokenB`);
Rust SDK
use ;
use ;
use FromStr;
async
Error reference
| Error | Cause | Fix |
|---|---|---|
PoolNotFound |
No pool for this mint pair | Run create-pool first |
NoLiquidity |
Pool exists but reserves are 0 | Run provide to seed it |
AmountBRequired |
First deposit needs explicit --amount-b |
Pass --amount-b to set the initial price |
SlippageExceeded |
Output below minimum | Increase --max-slippage or reduce --amount |
MathOverflow |
Amount too large for u64 math | Reduce --amount |
Unauthorized |
Approver signature missing | Ensure both agent and approver keys are present |
Roadmap
v0.1 (current — mainnet)
- Constant-product AMM (x·y=k), deployed on mainnet-beta
- LP fee auto-compound
- Approval mode (co-signature, no on-chain state)
- CLI —
simulate,convert,create-pool,provide,my-positions,pool-info,my-fees,remove-liquidity,claim-fees - TypeScript SDK (
@liqdlad/a2a-swap-sdk) published to npm - MCP server (
@liqdlad/mcp-a2a-swap) published to npm + Smithery - Solana Agent Kit plugin v1.0.0 (
@liqdlad/solana-agent-kit-plugin) — 5 AI actions, 8 methods,.use(A2ASwapPlugin)drop-in - ElizaOS plugin v1.0.0 (
@liqdlad/eliza-plugin-a2a-swap) — deep integration with Zod, auto-simulate, capability card - ElizaOS plugin v0.1.3 (
@liqdlad/plugin-a2a-swap) — original plugin, still published - Rust SDK (
a2a-swap-sdk) published to crates.io - CLI (
a2a-swap-cli) published to crates.io - HTTP API (
a2a-swap-api) live on Cloudflare Workers — no install required - Integration test suite (29/29 passing)
- SOL/USDC pool live on mainnet
v1.0 (planned)
- Time-weighted average price (TWAP) oracle — 30-slot ring buffer, readable by any agent
- Permissioned pools — optional LP whitelist (enterprise / DAO use)
- Multi-hop routing — chain two pools in one transaction for pairs without a direct pool
- Webhook approval backend — reference server for
--approval-mode webhook - Security audit
License
MIT — see LICENSE