AGENTS.md

Private payments for agents

One file gives an AI agent everything it needs to pay, swap and bridge privately with the Blackpool CLI: setup, every command and the JSON it returns, limits, recipes, error handling and the security rules. Hand it the file or the URL.

Download AGENTS.mdView raw
Markdown · 24.5 KB · 473 linesReadable by any agent · no JavaScript, no bot challengeMCP · local and hosted serversAlso in the npm package · GitHub · llms.txt
1Give your agent the guide
curl -fsSL https://blackpool.cash/AGENTS.md -o AGENTS.md

Or paste the instructions block (Copy for your agent) into its system prompt.

2Install the CLI
npm i -g @blackpool/cli

Node.js 18+. Or run any command with npx -y @blackpool/cli@latest.

3Give it a wallet
export BLACKPOOL_KEY=0x…
export BLACKPOOL_JSON=1

A dedicated agent wallet, never your main one. Every command then answers in JSON.

1. Quickstart (60 seconds)

npm i -g @blackpool/cli                 # or prefix every command with: npx -y @blackpool/cli@latest
export BLACKPOOL_KEY=0x<agent-wallet-private-key>   # a dedicated wallet, never your main one
export BLACKPOOL_JSON=1                 # every command prints one JSON object on stdout

blackpool address                       # → {"ok":true,"command":"address","address":"0x…"}  fund this address
blackpool deposit 25 --pool usdc --chain base          # shield 25 USDC (the only step that needs gas)
blackpool send 0xRECIPIENT 10 --pool usdc --chain base # private payment, relayer pays gas
blackpool balance --chain base --pool usdc             # → {"ok":true,…,"balance":"13.965",…}

Agent app with MCP support? Skip the shell entirely: see §9.

Paste this into your agent's instructions:

You can make private crypto payments with the `blackpool` CLI (env BLACKPOOL_KEY and BLACKPOOL_JSON=1 are set).
Always run commands with a timeout of at least 300 seconds and parse the single JSON object on stdout.
"ok": true means it succeeded; "ok": false has an "error" to report. Exit code 2 means you called it wrong.
Before paying, check `blackpool balance --chain <c> --pool <p>`; if it is short, `blackpool deposit` first.
Never retry deposit, send, swap or bridge after a timeout or unclear failure: run `blackpool balance --wait`
(or `blackpool bridges`) to see whether it landed. `blackpool recover --chain all` is always safe to run.
Never print, log or share BLACKPOOL_KEY or anything in ~/.blackpool.
Full guide: https://blackpool.cash/AGENTS.md

2. How Blackpool works (what an agent needs to know)

  • Public balance: tokens in the agent wallet, visible to everyone.
  • Private balance: notes inside a Blackpool pool contract. Spending them reveals neither the agent wallet nor which deposit paid.
  • Shield (deposit): moves public funds into the pool. Signed and paid (gas) by the agent wallet. This is the only on-chain action that shows your wallet.
  • Private operations (send, swap, bridge): a zero-knowledge proof spends notes; the Blackpool relayer submits the transaction and pays gas. The fee is taken from the amount moved, so the agent needs no gas for these.
  • Burner: a one-time wallet used inside swap and bridge. Its key is encrypted locally and registered before it is funded, so recover can always sweep it.
  • Account: derived from one signature of the Blackpool sign-in message by the agent wallet. Same key → same private account on every chain and machine.
  • Pools are per chain and per asset. A private USDC balance on Base cannot pay on Arbitrum; use bridge to move between chains.

3. Setup

3.1 Install

npm i -g @blackpool/cli        # global: `blackpool …`
npx -y @blackpool/cli@latest …  # no install, always the newest version
blackpool version --json        # {"ok":true,"command":"version","version":"0.2.0"}
blackpool latest --json         # {"ok":true,"command":"latest","installed":"0.2.0","latest":"0.2.0","upToDate":true}

3.2 Configure

VariableRequiredPurpose
BLACKPOOL_KEYyes (or run blackpool login once)Agent wallet private key, 0x-prefixed hex. Used only locally.
BLACKPOOL_JSONrecommended1 = JSON output for every command (same as adding --json).
BLACKPOOL_HOMEnoConfig/cache directory, default ~/.blackpool. Set it when the home directory is read-only (containers, serverless).
BLACKPOOL_APInoAPI base URL, default https://api.blackpool.cash.

Without BLACKPOOL_KEY, blackpool login asks for the key with a hidden prompt and stores it at ~/.blackpool/wallet.json (mode 600). Agents should prefer the environment variable.

3.3 Fund

blackpool address --json

Send the agent wallet the asset you want to shield plus a little native gas for the deposit (a deposit uses about 1.3M gas; keep at least 0.0005 ETH on Robinhood Chain, Base or Arbitrum, or 0.002 BNB on BNB Chain). After shielding, private operations need no gas.

3.4 Verify

blackpool balance --chain all --json

4. Command reference

Every command accepts --json. Amounts are plain decimals in token units (10, 0.25), never wei. --chain defaults to base. Typical durations assume a healthy network.

address

Print the agent wallet address.

{"ok":true,"command":"address","address":"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"}

balance [--chain C|all] [--pool P] [--wait]

Private and public balances. --pool limits the answer to one private pool. --wait polls up to 30 s while a private balance reads 0 (use right after a deposit). ~5–15 s.

{"ok":true,"command":"balance","chain":"base","pool":"usdc","address":"0x…","balance":"13.965","symbol":"USDC","utxos":2}

Without --pool (one entry per chain; --chain all returns four):

{"ok":true,"command":"balance","balances":[{"chain":"base","address":"0x…",
  "private":{"ETH":{"balance":"0.0","utxos":0},"USDC":{"balance":"13.965","utxos":2}},
  "public":{"ETH":"0.00412","USDC":"0.0"}}]}

A value of "?" with an error field means that read failed; retry.

deposit <amount> [--pool P] [--chain C]

Shield public funds from the agent wallet. ERC-20 pools approve first (one extra transaction). Needs native gas. ~10–30 s (the first run also downloads the 20 MB proving key).

{"ok":true,"command":"deposit","chain":"base","pool":"usdc","amount":25,"symbol":"USDC",
 "txHash":"0x…","explorer":"https://basescan.org/tx/0x…","privateBalance":"25.0"}

--pool defaults to the chain's stablecoin pool.

send <recipient> <amount> [--pool P] [--chain C]

Private payment to any EVM address on the same chain. amount is what leaves your private balance; the recipient gets amount − fee. No gas needed. ~10–30 s.

{"ok":true,"command":"send","chain":"base","pool":"usdc","recipient":"0x…","amount":10,
 "received":"8.965","fee":"1.035","symbol":"USDC","txHash":"0x…","explorer":"https://basescan.org/tx/0x…"}

To make the recipient receive exactly X, send (X + flat) / (1 − 0.0035) rounded up (see §5).

swap <amount> --from P [--chain C] [--slippage BPS]

Private same-chain swap between the chain's native pool and stablecoin pool; the output is re-shielded into your private balance. --slippage in basis points, default 100 (1%). No gas needed. ~1–3 min.

{"ok":true,"command":"swap","chain":"base","from":"eth","amount":0.01,"received":"22.41",
 "symbol":"USDC","txHash":"0x…","explorer":"https://basescan.org/tx/0x…","privateBalance":"36.375"}

If it fails half-way, funds sit on a registered burner: run recover.

bridge <dest-asset> <recipient> <amount> --from ORIGIN [--no-wait]

Private cross-chain transfer through NEAR Intents. ORIGIN is the private pool paying (default base_usdc); dest-asset and recipient format are listed in §6. The withdrawal is private; the solver delivers on the destination chain. Without --no-wait the CLI polls up to 6 minutes.

{"ok":true,"command":"bridge","from":"base_usdc","to":"sol_usdc","recipient":"7Gx4…kQ2m","amount":10,
 "amountOutQuote":"8.91","withdrawTx":"0x…","depositAddress":"0x…","statusUrl":"https://…",
 "status":"SUCCESS","amountOut":"8.93"}

status is SUBMITTED (with --no-wait), SUCCESS, or PENDING (still running after 6 min; check bridges later). A refund or failure returns "ok":false; refunds go to the agent wallet on the origin chain.

bridges

Every bridge this account has made, newest first, from the server-side intent registry (works from any machine).

{"ok":true,"command":"bridges","bridges":[{"status":"SUCCESS","createdAt":1793760000000,"amountIn":"10",
 "fromAsset":"base_usdc","toAsset":"sol_usdc","recipient":"7Gx4…","refundTo":"0x…","originChain":"base",
 "amountOutQuote":"8.91","amountOut":"8.93","destTx":"…","depositAddress":"0x…","statusUrl":"https://…"}]}

assets

Bridge destinations and valid origins, live from the API.

{"ok":true,"command":"assets","assets":{"sol_usdc":{"chain":"sol","symbol":"USDC","decimals":6,"isNative":false},"…":{}},
 "origins":["base_eth","base_usdc","hood_eth","hood_usdg","arb_eth","arb_usdc","bsc_bnb","bsc_usdc","bsc_usdt"]}

recover [--to ADDR] [--chain C|all]

Sweep every registered burner that still holds funds to the agent wallet (or --to). Idempotent and safe at any time. Swept funds land in the public wallet; deposit again to make them private. ~10–60 s per chain.

{"ok":true,"command":"recover","recovered":[{"chain":"base","to":"0x…","swept":1,"dust":0,"errors":0}]}

login, logout, version, latest, help

login signs and caches the sign-in message ({"address"}); logout deletes the stored key and cache ({"removed":[…]}); version and latest report versions.


5. Chains, pools, fees and minimums

--chainPools (--pool)Min depositMin send / swap / bridgeFlat feeDEX for swap
base (default)eth, usdc0.001 ETH · 3 USDC0.001 ETH · 2 USDC0.00015 ETH · 1 USDCUniswap V3
robinhoodeth, usdg0.001 ETH · 3 USDG0.001 ETH · 2 USDG0.00015 ETH · 0.5 USDGUniswap V3
arbitrumeth, usdc0.001 ETH · 3 USDC0.001 ETH · 2 USDC0.00015 ETH · 0.5 USDCUniswap V3
bscbnb, usdc, usdt0.02 BNB · 3 USDC/USDT0.001 BNB · 2 USDC/USDT0.001 BNB · 0.5 USDC/USDTPancakeSwap V3
  • Fee on every private spend = flat fee + 0.35% of the amount, deducted from the amount. Example: send 10 USDC on Base → fee 1 + 0.035 = 1.035, recipient gets 8.965.
  • A spend must be more than twice its own fee; the CLI enforces the minimums above before doing anything.
  • Bridges pay the pool fee plus the solver's quote (shown as amountOutQuote); each route also has a solver minimum.
  • deposit costs only network gas; there is no Blackpool fee on shielding.

6. Bridging

Origins (--from): base_eth, base_usdc, hood_eth, hood_usdg (Robinhood Chain), arb_eth, arb_usdc, bsc_bnb, bsc_usdc, bsc_usdt.

Destinations (dest-asset; run blackpool assets for the live list):

ChainAssetsRecipient format
Basebase_eth, base_weth, base_usdc, base_cbbtcEVM 0x…
Ethereumeth_eth, eth_weth, eth_usdc, eth_usdt, eth_cbbtcEVM 0x…
Arbitrumarb_eth, arb_weth, arb_usdc, arb_usdt, arb_arbEVM 0x…
BNB Chainbsc_bnb, bsc_usdc, bsc_usdtEVM 0x…
Robinhood Chainhood_eth, hood_usdg, hood_weth, hood_usdeEVM 0x…
Solanasol_sol, sol_usdc, sol_usdtbase58 public key

Statuses in bridges: PENDING_DEPOSIT → PROCESSING → SUCCESS, or REFUNDED / FAILED (refund to refundTo on originChain). Treat any other value as in progress.


7. Recipes

Pay an invoice exactly

# recipient must receive 50 USDC on Base: amount = (50 + 1) / 0.9965 = 51.18 (round up)
blackpool balance --chain base --pool usdc --json        # need >= 51.18
blackpool send 0xVENDOR 51.18 --pool usdc --chain base --json

Top up only when needed

bal=$(blackpool balance --chain base --pool usdc --json | jq -r .balance)
if (( $(echo "$bal < 51.18" | bc -l) )); then blackpool deposit 100 --pool usdc --chain base --json; fi

Batch payouts (sequential, never parallel)

while IFS=, read -r to amount; do
  out=$(blackpool send "$to" "$amount" --pool usdc --chain base --json)
  echo "$out" >> payouts.jsonl
  [ "$(echo "$out" | jq -r .ok)" = "true" ] || { echo "stopped at $to"; break; }
done < payouts.csv

Run payouts one at a time: each spend consumes notes and returns change, so parallel sends from one account can race.

Swap ETH to USDC privately

blackpool swap 0.01 --from eth --chain base --json

Bridge to Solana without blocking

blackpool bridge sol_usdc <solana-address> 10 --from base_usdc --no-wait --json
blackpool bridges --json | jq '.bridges[0].status'     # poll every minute until SUCCESS / REFUNDED

Move private funds to another chain (rebalance)

blackpool bridge arb_usdc $(blackpool address --json | jq -r .address) 20 --from base_usdc --json
blackpool deposit 19 --pool usdc --chain arbitrum --json   # re-shield on the destination chain

The bridge delivers to a public address; shield it again on the destination chain.

Health check before a run

curl -s https://api.blackpool.cash/api/health | jq '.ok, .relayerGas'

Clean up after an interrupted swap or bridge

blackpool recover --chain all --json

8. Integration code

Node.js

import { execFile } from 'node:child_process'

export function blackpool(args, { timeoutMs = 300_000 } = {}) {
  return new Promise((resolve) => {
    execFile('blackpool', [...args, '--json'], { timeout: timeoutMs, env: process.env }, (err, stdout, stderr) => {
      try { resolve(JSON.parse(stdout.trim().split('\n').pop())) }
      catch { resolve({ ok: false, error: (stderr || err?.message || 'no output').trim(), exitCode: err?.code }) }
    })
  })
}

const r = await blackpool(['send', '0xRECIPIENT', '10', '--pool', 'usdc', '--chain', 'base'])
if (!r.ok) throw new Error(r.error)
console.log(r.txHash, r.received)

Python

import json, subprocess

def blackpool(*args, timeout=300):
    p = subprocess.run(["blackpool", *args, "--json"], capture_output=True, text=True, timeout=timeout)
    try:
        return json.loads(p.stdout.strip().splitlines()[-1])
    except (ValueError, IndexError):
        return {"ok": False, "error": (p.stderr or "no output").strip(), "exitCode": p.returncode}

r = blackpool("balance", "--chain", "base", "--pool", "usdc")
print(r["balance"] if r["ok"] else r["error"])

Tool definition for function-calling models

Expose one tool and let the model choose the command:

{
  "name": "blackpool",
  "description": "Private crypto payments via the Blackpool CLI. Commands: address; balance [--chain base|robinhood|arbitrum|bsc|all] [--pool P] [--wait]; deposit <amount> [--pool P] [--chain C]; send <recipient> <amount> [--pool P] [--chain C]; swap <amount> --from P [--chain C]; bridge <dest-asset> <recipient> <amount> --from ORIGIN [--no-wait]; bridges; assets; recover [--chain C|all]. Returns one JSON object; ok=false carries an error. Never retry deposit/send/swap/bridge without checking balance first.",
  "input_schema": {
    "type": "object",
    "properties": {
      "args": { "type": "array", "items": { "type": "string" }, "description": "CLI arguments, e.g. [\"send\",\"0xabc…\",\"10\",\"--pool\",\"usdc\",\"--chain\",\"base\"]" }
    },
    "required": ["args"]
  }
}

Implement it with the Node or Python wrapper above. Allow-list the first argument to the commands you want the agent to use (for example only balance, send and bridges).


9. MCP servers

Blackpool speaks the Model Context Protocol, so any MCP-capable agent app can call it as tools instead of running shell commands. There are two servers.

Runs on your machine over stdio and exposes the full CLI as tools. The wallet key stays in the local environment; tools behave exactly like the commands in §4 and return the same JSON.

Add it to your app's MCP configuration (most apps use this mcpServers format):

{
  "mcpServers": {
    "blackpool": {
      "command": "npx",
      "args": ["-y", "@blackpool/cli@latest", "mcp"],
      "env": {
        "BLACKPOOL_KEY": "0x<agent-wallet-private-key>",
        "BLACKPOOL_MCP_TOOLS": "balance,send,bridges"
      }
    }
  }
}

With the CLI installed globally, "command": "blackpool", "args": ["mcp"] works too. Apps that add servers from a terminal take the same command: npx -y @blackpool/cli@latest mcp.

ToolDoesAnnotations
blackpool_addressAgent wallet addressread-only
blackpool_balancePrivate and public balances (chain, pool, wait)read-only
blackpool_depositShield from the agent wallet (amount, pool, chain)destructive
blackpool_sendPrivate payment (recipient, amount, pool, chain)destructive
blackpool_swapPrivate same-chain swap (amount, from, chain, slippage_bps)destructive
blackpool_bridgePrivate bridge (dest_asset, recipient, amount, from, wait)destructive
blackpool_bridgesBridge history and statusread-only
blackpool_assetsBridge destinations and originsread-only
blackpool_recoverSweep stranded burners (chain, to)idempotent

Resource: blackpool://agents.md (this guide).

Limit what the agent can do from the configuration:

VariableEffect
BLACKPOOL_MCP_TOOLSComma-separated allow-list, e.g. balance,send,bridges (with or without the blackpool_ prefix).
BLACKPOOL_MCP_READONLY1 exposes only read-only tools.
BLACKPOOL_HOME, BLACKPOOL_APISame as for the CLI (§3.2).

Every tool result carries the command's JSON as structuredContent (and as text); isError is true when "ok": false. The retry rules in §12 apply unchanged: never repeat blackpool_deposit, blackpool_send, blackpool_swap or blackpool_bridge after a timeout without checking blackpool_balance or blackpool_bridges first.

9.2 Hosted server — read-only

https://mcp.blackpool.cash/mcp (Streamable HTTP, no sign-in). It never receives keys or signatures, so it can only answer questions:

ToolReturns
blackpool_statusAPI health, configured pools, relayer gas per chain
blackpool_feesFlat fee, rate, minimum deposit and minimum spend per pool
blackpool_assetsBridge destinations and origins
blackpool_bridge_quoteIndicative quote (from, to, amount)
blackpool_analyticsShielded notes per chain and pool, relayer fees
blackpool_guideThis guide, whole or one section (section)
{
  "mcpServers": {
    "blackpool-info": { "url": "https://mcp.blackpool.cash/mcp" }
  }
}

Use the hosted server to plan (fees, routes, quotes, status) and the local server to act.


10. Output contract

  • stdout: exactly one line of JSON when --json (or BLACKPOOL_JSON=1) is set. stderr: human progress, safe to log.
  • Envelope: {"ok": true, "command": "<name>", …fields} or {"ok": false, "command": "<name>", "error": "<message>"}.
  • Amounts are decimal strings in token units (some input echoes such as amount are numbers). Tx hashes are 0x-hex; explorer is a full URL.
  • Exit codes: 0 success · 1 failed or refused (including amounts under a minimum) · 2 usage (unknown command, missing arguments).
  • New fields may be added; existing fields are not renamed or removed without a major version bump.

11. Errors and what to do

error containsMeaningDo
no wallet configuredBLACKPOOL_KEY missingSet it, or run blackpool login.
invalid BLACKPOOL_KEY / stored key invalidNot a 0x hex private keyFix the key.
minimum deposit is … / minimum send is …Below the floor in §5Use a larger amount.
unknown pool / unknown chain / unknown --fromWrong flag valueUse the values in §5–6.
invalid recipient addressWrong format for the destinationEVM 0x… (40 hex) or Solana base58.
insufficient / no unspentPrivate balance too lowbalance, then deposit more.
Withdrawal must be more than twice the feeAmount too close to the feeLarger amount.
Amount is too low for this route. Minimum is …Solver minimum for that bridgeBridge at least that much.
rate limited, slow downAPI limit (40 relay ops, 600 reads per minute per IP)The CLI already waited and retried 4 times; pause a minute.
simulation failed: …The relayer refused a transaction that would revertDo not retry blindly; check balance; report the message.
reshield would revert / swap output … too smallSwap stopped after funds reached the burnerrecover, then deposit again.
solver refunded / bridge failed at solverBridge did not completeRefund goes to the agent wallet on the origin chain; deposit to re-shield.
timeout / fetch failed / HTTP 5xxNetwork or service problemCheck https://blackpool.cash/status; for write commands check balance --wait before retrying.

12. Retries, timeouts, concurrency

  • Safe to retry any time: address, balance, assets, bridges, recover, version, latest.
  • Not safe to retry blindly: deposit, send, swap, bridge. A timeout can mean the transaction landed. Check balance --wait (or bridges) and only retry if the balance is unchanged.
  • Give every command a timeout of at least 300 s (bridge without --no-wait up to 420 s).
  • One write command at a time per account. Reads can run in parallel.
  • A private balance can read 0 for a few seconds after a deposit while the indexer catches up; use --wait.

13. Security

  • Dedicated agent wallet. Fund it with only what the agent may spend. Never use a personal or treasury key.
  • BLACKPOOL_KEY never leaves the machine: it signs the sign-in message, deposit approvals and deposit transactions locally.
  • The sign-in signature is cached in $BLACKPOOL_HOME/config.json (mode 600) and never sent anywhere: keys, note scanning and proofs are computed locally (CLI 0.4.0+), and the relayer receives finished proofs only. Anyone holding that signature can spend the agent's private balance. Treat the config directory like a key: never print it, never put it in prompts, logs or tool output.
  • Only sign the Blackpool sign-in message on blackpool.cash or with this CLI.
  • The contracts and circuits have not been externally audited; pools are upgradeable by an admin key during launch. Size balances accordingly. Details: https://blackpool.cash/docs#security
  • Report vulnerabilities privately to [email protected] (subject security).

14. Direct API (advanced)

The CLI is the supported interface: it derives keys and generates Groth16 proofs locally. A client in another language must do the same (see cli/lib/zk.js): read public pool data from GET /api/zk/pool, derive keys from the EIP-191 signature of the exact message below, prove with the published circuit (https://blackpool.cash/zk/manifest.json), then post the proof to POST /api/relay, or broadcast a deposit from its own wallet. The API never accepts the signature itself. Endpoint list: https://blackpool.cash/docs#api

Welcome to Blackpool

Sign this message to access your private account.

- Gas free: no transaction is sent
- Your private account keys are derived from this signature
- It cannot move funds from your wallet or approve spending
- Only sign it on blackpool.cash or the Blackpool CLI

The message is part of the account: a different message, even by one character, is a different (empty) account.


15. Working on this repository

If you are a coding agent editing this repository rather than using the CLI: the CLI lives in cli/, contracts in contracts/, circuits in circuits/, client libraries in src/, the web app in frontend/. Run npx hardhat test before changing contracts or core libraries. Keep the sign-in message, note format and circuit byte-identical across src/, cli/ and frontend/ (changing any of them changes every account). Never commit keys, .env files or credentials. Keep this file in sync with cli/bin/blackpool.js when commands or output fields change.