A runnable reference agent (not a production service) that acts as an ERC-8183
Provider: it discovers a funded swap-intent Job, quotes via PancakeSwap aggregation,
and settles a swap so the output token is delivered directly to the requester (Client)
— holding no inventory and fronting no capital. Identity via ERC-8004, task interface via
ERC-8183 (the official bnbagent SDK), on a local fork of BSC mainnet.
This repo is a developer example: clone it, run make demo, and read the code to see how
a Job is discovered, quoted under guardrails, and settled.
What works today: end-to-end settlement via the PancakeSwap Aggregator
/v1/calldatapassthrough (any route: multi-hop/split) under a full deterministic guardrail suite (token safelist, slippage, tip floor, per-Job value cap, single-Job concurrency, gas precondition, re-quote, idempotency, router allowlist), with the Provider registering an ERC-8004 on-chain identity on startup (register+setAgentWalletvia EIP-712, cachedagentId). A pluggable natural-language intent layer (LLM → structuredSwapIntent) is a documented next step.
make install # create .venv, install the package + dev deps (needs uv + foundry)
make demo # start a local BSC fork, run the whole lifecycle (WBNB→USDT), tear it down
make demo-multihop # settle a multi-hop route (CAKE→USDT, routed through ETH) via the same path
make demo-safety # prove both legs are safe: induced swap revert + tip claimRefund
make test # pure-core unit tests (no chain, no network)make demo prints a summary: the swap tx, the deliverable hash, the output the Client
received (≥ minOut), and the U tip released to the Provider. No manual steps, no keys,
no accounts — it forks BSC with anvil against a public archive RPC.
The bnbagent ERC-8183 kernel is a single-fixed-token escrow (U / United Stables): it has
no swap and no per-Job token, so the swap cannot be a single ERC-8183 Job. This agent settles it as
two independently safe legs:
- Off-kernel swap (the Provider's own tx): pull the Client's pre-approved input, then
send the calldata built by the PancakeSwap Aggregator
/v1/calldataendpoint for the full route (multi-hop/split), withrecipient = Client, a minOut floor ofmax(minOut, quote×(1−0.5%))(never 0), and a tight deadline. The output goes straight to the Client; it never touches the agent wallet. Atomic within that tx — on revert the pulled input is refunded, so nothing is stranded. The calldata is opaque, so the trust boundary is two deterministic checks: before signing, the calldatatomust be on an Aggregator-router allowlist; after the swap, the Client's output balance must have risen ≥ the floor. - Tip escrow (the ERC-8183 Job):
budget= the tip, denominated inU. The Provider submits the swap tx hash as the deliverable; after the 7-day dispute window a keeper verifies the Client received ≥ minOut and callssettle, releasing the tip.
A single-tx variant (a custom Solidity swap-hook that swaps and releases the tip atomically) is a documented future upgrade, not part of this sample.
On startup the Provider registers an on-chain ERC-8004 identity: register(agentURI) — where
agentURI is a self-contained data: URI encoding agent-registration.json — then binds its
operating wallet with setAgentWallet via an EIP-712 signature. The agentId is cached in .env
and reused on later runs. The register/setAgentWallet mechanism was verified against the deployed
BSC registry — including a fork quirk: the demo dev keys inherit an EIP-7702 delegation from
mainnet that must be cleared before the registry's _safeMint will mint the agent NFT to them.
agent/
config.py static config: safelist, router allowlist, guardrail defaults (pure)
intent.py SwapIntent parse + validation of untrusted input (pure)
routing.py Aggregator quote + calldata — fetch (network) + parse_quote/parse_calldata (pure)
guardrails.py deterministic accept/reject + settlement params + `to` allowlist (pure)
chain.py web3 construction, ABIs, signed-send, raw-calldata send, RPC resilience
identity.py ERC-8004 register + setAgentWallet (EIP-712) + agentId cache (on-chain)
jobs.py ERC8183Client wiring + funded-Job detection (on-chain)
settlement.py off-kernel swap (calldata passthrough) + deliverable submission (on-chain)
__main__.py the Provider settle-one loop (registers identity on startup)
demo/
fork.py anvil BSC fork bootstrap + demo-only cheatcodes (seed / warp / clear-code)
client.py the Client: create + fund the tip Job, approve the swap input
evaluator.py the keeper: verify delivery, warp past the window, settle
run_demo.py orchestrates the full lifecycle — the `make demo` / `make demo-multihop` targets
tests/ pure-core unit tests (no chain, no network)
Design seam: guardrails, intent, and routing's parse_quote/parse_calldata are pure —
no chain, no network — so the safety-critical logic is unit-tested off-fork. On-chain writes live
only in identity / jobs / settlement.