For AI agents
OpenSwap treats agents as first-class users. The fastest bootstrap from inside any agent:
npx openswap@latest agent docs
That prints the complete machine contract below — this page and that command
are kept identical, so you can rely on either. Claude Code users can install
the packaged skill with openswap agent setup.
Before moving real funds, rehearse in test mode:
openswap test on gives you a full simulated exchange through the unchanged
production code paths.
OpenSwap CLI — contributor & coding-agent guide
Terminal-first crosschain swaps (npm: openswap). TypeScript, Node ≥ 20,
self-contained: it talks HTTP to the public LeoKit API — no SDK dependency,
no wallet custody, no signing in the default flow.
> Driving the CLI as an agent rather than developing it? This file is not
> for you. AGENT-CONTRACT.md is the machine contract —
> JSON envelopes, frozen exit codes, financial rules. Run openswap agent docs
> to print it; you never need to read this repository.
Read before changing anything
- docs/README.md — public doc index
- docs/architecture.md — layers and file map
- AGENT-CONTRACT.md — the machine/agent output contract
- brand/BRAND.md — brand kit. Assets are GENERATED: edit
brand/otto.pixels.json, run npm run brand, commit outputs (tests enforce
freshness). Otto appears only on the bare openswap menu — never add the
mascot to subcommands or machine output.
Commands
bun install # or npm install
bun run typecheck # tsc --noEmit — must stay clean
bun run test # vitest unit suite — must stay green
bun run build # tsup → dist/index.js
node dist/index.js # run the built CLI (targets Node)
Non-negotiable rules
- Money math: decimal strings + BigInt only — never float arithmetic on
parseJsonPreservingBigInts (base-unit
amounts exceed 2^53 in production, and arrive in exponent form above 1e21).
- Never fabricate financial data: no invented minimums, no totals from
null renders as "not provided",
and a usd: 0 fee against a non-zero amount means unpriced, never free.
- Secrets: never in argv, URLs, config files, receipts, logs, or errors.
src/core/credentials.ts (env → OS keychain →
the intentionally public community key).
- State-changing calls (
/deposit-address,/deposit, broadcast) are
--dry-run; receipts are written
BEFORE payment instructions render.
- Payment URIs are built locally from a validated address — never rendered
- Renderers contain no business logic; upstream strings are untrusted —
sanitize() everything user-visible.
- Machine contract is frozen: envelope shape and exit codes only change with
- Copy rules: "crosschain" (one word); "Expected receive" (never "You will
API behavior worth knowing
Quotes expire in ~30 seconds. Deposit-bound quotes need origin +
destination at quote time, and /deposit-address needs both to_address
and from_address (the payer/refund wallet). /deposit returns
unsigned_transactions. /status has two response shapes — always go through
normalizeStatus. Route discovery uses the SSE stream so every provider gets
its window; don't replace it with one-shot quote calls.
Verifying a change
Test mode runs the whole CLI against a simulated backend through the unchanged production code paths, so a full swap can be verified without funds:
OPENSWAP_TEST_MODE=1 OPENSWAP_TEST_TIMESCALE=100 node dist/index.js swap \
-a 25 -f eth:usdc -t base:usdc \
--to-address 0x1111111111111111111111111111111111111111 \
--refund-address 0x2222222222222222222222222222222222222222 --yes --json
node scripts/e2e-sim.mjs runs the same lanes non-interactively and is what CI
gates on. See docs/test-mode.md.
Security
Report vulnerabilities privately — see SECURITY.md. Never open a
public issue for an exploitable finding, and never route one through
openswap feedback.