a2a-swap-cli 0.1.3

A2A-Swap CLI — command-line interface for the A2A-Swap agent-native AMM on Solana
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# 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)

```bash
# From crates.io
cargo install a2a-swap-cli

# Or build from source
git clone https://github.com/liqdlad-rgb/a2a-swap
cd a2a-swap
cargo build --release -p a2a-swap-cli
# Binary at ./target/release/a2a-swap
```

Pre-built binaries for Linux, macOS, and Windows are also available on the
[Releases page](https://github.com/liqdlad-rgb/a2a-swap/releases).

### TypeScript SDK

```bash
npm install @liqdlad/a2a-swap-sdk @solana/web3.js @solana/spl-token
# or
yarn add @liqdlad/a2a-swap-sdk @solana/web3.js @solana/spl-token
```

### Rust SDK

```toml
[dependencies]
a2a-swap-sdk = "0.1"
```

---

## Quick start

```bash
# Set your keypair and RPC
export A2A_KEYPAIR=~/.config/solana/id.json
export A2A_RPC_URL=https://api.mainnet-beta.solana.com

# Preview a swap without spending funds
a2a-swap simulate --in SOL --out USDC --amount 1000000000

# Execute the swap
a2a-swap convert --in SOL --out USDC --amount 1000000000

# Check your LP positions and accrued fees
a2a-swap my-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.

```bash
a2a-swap simulate --in SOL --out USDC --amount 1000000000
```

```
─── 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.

```bash
# Swap 1 SOL for USDC (0.5% slippage tolerance is the default)
a2a-swap convert --in SOL --out USDC --amount 1000000000

# Tighter slippage
a2a-swap convert --in SOL --out USDC --amount 1000000000 --max-slippage 0.1

# Reverse direction
a2a-swap convert --in USDC --out SOL --amount 150000000

# Require webhook approval before sending (human-in-the-loop)
a2a-swap convert --in SOL --out USDC --amount 1000000000 \
  --approval-mode webhook --webhook-url https://mybot.example.com/approve

# Machine-readable output (for agent pipelines)
a2a-swap convert --in SOL --out USDC --amount 1000000000 --json
```

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.

```bash
# Create a SOL/USDC pool with 0.30% LP fee, initial spot hint of 185 USDC/SOL
a2a-swap create-pool --pair SOL-USDC --initial-price 185 --fee-bps 30

# Print a ready-to-run `provide` command to seed with 1 SOL of liquidity
a2a-swap create-pool --pair SOL-USDC --initial-price 185 --seed-amount 1000000000

# Custom mints
a2a-swap create-pool --pair <mintA>-<mintB> --initial-price 1.0 --fee-bps 10
```

```
─── 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-price` is 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 `--amount` and `--amount-b` to 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.

```bash
# Seed empty pool: 1 SOL + 185 USDC (sets initial price)
a2a-swap provide --pair SOL-USDC --amount 1000000000 --amount-b 185000000

# Add to existing pool (amount-b computed from live reserves)
a2a-swap provide --pair SOL-USDC --amount 500000000

# Enable auto-compounding (compound when fees exceed 0.001 SOL)
a2a-swap provide --pair SOL-USDC --amount 500000000 \
  --auto-compound --compound-threshold 1000000
```

---

### `pool-info` — Inspect a pool

```
a2a-swap pool-info --pair <A-B>
```

Read-only — no keypair required, no transaction sent.

```bash
a2a-swap pool-info --pair SOL-USDC
```

```
─── 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 liquidity and claim fees (SDK)

`remove_liquidity` and `claim_fees` are available as on-chain instructions and are
fully supported by both SDKs. CLI wrappers (`remove` and `claim-fees`) are planned
for v1.1.

**TypeScript:**

```typescript
await client.removeLiquidity(keypair, { mintA, mintB, lpShares: 500_000_000n, minA: 0n, minB: 0n });
await client.claimFees(keypair, { mintA, mintB });
```

**Rust:**

```rust
client.remove_liquidity(&payer, RemoveParams { mint_a, mint_b, lp_shares: 500_000_000, min_a: 0, min_b: 0 }).await?;
client.claim_fees(&payer, ClaimParams { mint_a, mint_b }).await?;
```

---

## Zero-human execution

A2A-Swap is designed to be called entirely by autonomous agents without any human approval:

1. **No browser / widget** — every operation is a single RPC call or CLI command.
2. **PDA authority** — pool vaults are controlled by a derived program address, not a human keypair. No admin can rug.
3. **Deterministic fees** — protocol fee (0.020%) and LP fee (pool-specific, 1–100 bps) are fixed on-chain. No aggregator routing surprises.
4. **Atomic execution** — a swap, liquidity deposit, or fee claim is a single transaction. No multi-step approval flow unless you opt in.
5. **Machine-readable capability card** — agents can introspect the protocol's capabilities without any off-chain registry:

```rust
use a2a_swap::A2A_CAPABILITY_CARD;
let card: serde_json::Value = serde_json::from_str(A2A_CAPABILITY_CARD).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 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_fees` (via SDK) transfers tokens out of the vault to your wallet.
- `--auto-compound` converts 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:

```bash
# Require webhook approval before sending
a2a-swap convert --in SOL --out USDC --amount 1000000000 \
  --approval-mode webhook --webhook-url https://mybot.example.com/approve
```

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
// 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

### ElizaOS (TypeScript)

```bash
elizaos plugins add plugin-a2a-swap
# or manually: npm install @liqdlad/plugin-a2a-swap
```

```typescript
import { a2aSwapPlugin } from '@liqdlad/plugin-a2a-swap';
import { AgentRuntime } from '@elizaos/core';

const runtime = new AgentRuntime({
  plugins: [a2aSwapPlugin],
  // ...
});
```

Registers five actions automatically:

| Action | Trigger phrases |
|--------|-----------------|
| `A2A_SIMULATE_SWAP` | "simulate swap", "estimate swap", "quote swap" |
| `A2A_SWAP` | "swap tokens", "trade tokens", "exchange tokens" |
| `A2A_PROVIDE_LIQUIDITY` | "provide liquidity", "add liquidity", "add to pool" |
| `A2A_POOL_INFO` | "pool info", "pool stats", "check pool" |
| `A2A_MY_FEES` | "my fees", "check fees", "claimable fees" |

### TypeScript SDK

```typescript
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

```rust
use a2a_swap_sdk::{A2ASwapClient, SimulateParams, SwapParams};
use solana_sdk::{pubkey::Pubkey, signature::{read_keypair_file, Signer}};
use std::str::FromStr;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = A2ASwapClient::mainnet();
    let payer  = read_keypair_file("~/.config/solana/id.json")?;

    // Simulate (read-only, no funds needed)
    let sim = client.simulate(SimulateParams {
        mint_in:   Pubkey::from_str("So11111111111111111111111111111111111111112")?,
        mint_out:  Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")?,
        amount_in: 1_000_000_000,
    }).await?;
    println!("Estimated out: {}, impact: {:.3}%", sim.estimated_out, sim.price_impact_pct);

    // Execute swap
    let result = client.convert(&payer, SwapParams {
        mint_in:          Pubkey::from_str("So11111111111111111111111111111111111111112")?,
        mint_out:         Pubkey::from_str("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")?,
        amount_in:        1_000_000_000,
        max_slippage_bps: 50,
    }).await?;
    println!("Signature: {}", result.signature);
    Ok(())
}
```

---

## 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)
- [x] Constant-product AMM (x·y=k), deployed on mainnet-beta
- [x] LP fee auto-compound
- [x] Approval mode (co-signature, no on-chain state)
- [x] CLI — `simulate`, `convert`, `create-pool`, `provide`, `my-positions`, `pool-info`, `my-fees`
- [x] TypeScript SDK (`@liqdlad/a2a-swap-sdk`) published to npm
- [x] ElizaOS plugin (`plugin-a2a-swap`) published to elizaos registry
- [x] Rust SDK (`a2a-swap-sdk`) published to crates.io
- [x] CLI (`a2a-swap-cli`) published to crates.io
- [x] Integration test suite (29/29 passing)
- [x] SOL/USDC pool live on mainnet

### v1.0 (planned)
- [ ] **CLI `remove` and `claim-fees` commands** — wrappers for the existing on-chain instructions
- [ ] **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](./LICENSE)