Build and operate on-chain AI agents on BNB Chain using the bnbagent Python SDK — register agent identities (ERC-8004), and transact through escrowed agentic commerce (ERC-8183) as a provider (accept jobs, deliver work, get paid) or client (create, fund, dispute, refund jobs). Also covers x402 micropayment signing. Use for anything involving BNB Chain agent identity, agent-to-agent paid jobs, BSC escrow jobs, or ERC-8004/ERC-8183/x402.

GitHub
Install command
npx skhub add starchild-ai-agent/bnbagent
Markdown
SKILL.md

BNBAgent SDK

Python SDK (pip install bnbagent, Python 3.10+) for on-chain AI agents on BNB Chain. Two independent capabilities:

  • ERC-8004 (identity) — register an agent on-chain as an ERC-721 identity token with a discoverable profile URI. Gas-free on BSC Testnet via MegaFuel paymaster.
  • ERC-8183 (agentic commerce) — trustless job escrow between a client (pays) and a provider (delivers). Optimistic settlement: silence past the dispute window = approval; a client dispute triggers a whitelisted-voter quorum reject.

They are independent: you can run ERC-8183 jobs without ERC-8004 registration (registration is only recommended for discovery).

Active development — breaking changes possible. Tested with bnbagent==0.4.0; verify install with python -c "import bnbagent; print(bnbagent.__version__)". Optional extra: pip install "bnbagent[ipfs]" for IPFS/Pinata deliverable storage.

⚠️ Mainnet economics — read BEFORE any mainnet commerce

  • The payment token is U (United Stables) 0xcE24439F2D9C6a2289F741120FE202248B666666 — not BNB, not USDT/USDC. There is no faucet for it: acquire U on PancakeSwap (a WBNB–U pair exists). The client must hold U before calling fund(). Fetch decimals at runtime via erc8183.token_decimals() — don't assume.
  • ERC-8183 writes on mainnet are never gas-sponsored. Client and provider both need BNB for gas (only ERC-8004 identity registration is sponsored on mainnet).
  • The mainnet dispute window is 604800s (7 days). A happy path cannot reach COMPLETED in one session — silence-approval only kicks in after the window. Plan a partial E2E (through SUBMITTED) and settle later via a cron/operator script (examples/auto_settle.py). settle reverting with policy pending during this week is expected, not an error.

Preflight balance checklist (mainnet)

PartyNeedsWhy
ClientBNBgas for createJob/registerJob/setBudget/approve/fund (+ optional swap gas)
ClientUthe job budget escrowed by fund (+ small residual for retries)
ProviderBNBgas for submit — not sponsored on mainnet
Settler (anyone)BNBgas for settle after the window

Decide what you're doing

GoalUseReference
Register agent identity on-chainERC8004Agentquick start below
Earn: accept + deliver funded jobsERC8183JobOps + funded_job_watcherquick start below, examples/agent-server/, examples/a2a-agent/
Pay: create/fund/settle jobsERC8183Clientquick start below, examples/client/
Vote on disputes (whitelisted voter)PolicyClient.vote_rejectexamples/voter/
Pay HTTP 402 challenges (x402)X402Signer / twak delegated payerexamples/x402/, references/twak.md
Understand internals / extend—references/architecture.md
Wallet backends (EVM keystore vs twak)EVMWalletProvider / TWAKProviderreferences/wallets.md, references/twak.md

Quick start: register an agent (ERC-8004)

One-time setup. Needs a private key (auto-generated if omitted) and WALLET_PASSWORD.

import os
from bnbagent import ERC8004Agent, AgentEndpoint, EVMWalletProvider

wallet = EVMWalletProvider(
    password=os.getenv("WALLET_PASSWORD"),
    private_key=os.getenv("PRIVATE_KEY"),  # only needed on first run; keystore persists to ~/.bnbagent/wallets/
)
sdk = ERC8004Agent(network="bsc-testnet", wallet_provider=wallet)

agent_uri = sdk.generate_agent_uri(
    name="my-ai-agent",
    description="AI agent for document processing",
    endpoints=[
        AgentEndpoint.a2a("https://my-agent.example.com"),                      # A2A first (discovery doc URL)
        AgentEndpoint.mcp("https://my-agent.example.com/mcp", version="2025-06-18"),  # MCP second, if served
    ],
)
result = sdk.register_agent(agent_uri=agent_uri)
# result["agentId"], result["transactionHash"]

Quick start: provider (earn loop, headless)

No server needed. Watch for funded jobs, do the work, submit:

import asyncio
from bnbagent import EVMWalletProvider
from bnbagent.erc8183 import ERC8183JobOps, funded_job_watcher
from bnbagent.storage import LocalStorageProvider

wallet = EVMWalletProvider(password="...", private_key="0x...")
ops = ERC8183JobOps(
    wallet,
    network="bsc-testnet",
    storage_provider=LocalStorageProvider(),
    service_price=1_000_000_000_000_000_000,      # min acceptable budget, raw units (18 decimals here)
    agent_url="http://localhost:8003/erc8183",    # public URL; required for file:// deliverable rewriting
)

async def on_funded(job: dict) -> None:
    deliverable = f"Processed: {job['description']}"   # your business logic
    await ops.submit_result(job["jobId"], deliverable)

asyncio.run(funded_job_watcher(ops, on_funded, interval=30))
  • submit_result handles verification (FUNDED status, assignment, expiry, budget ≥ service_price), deliverable upload, manifest hashing, and the submit tx.
  • The watcher never submits or settles by itself. Settlement is a separate step — run an operator script calling ERC8183Client.settle(job_id) after the dispute window elapses (examples/auto_settle.py, examples/agent-server/scripts/settle.py).
  • job dict fields: jobId, description, budget, client, provider, evaluator, status (always FUNDED), expiredAt, hook.
  • Serving surface (A2A/MCP/HTTP) is your choice — copy-and-own references in examples/a2a-agent/ (recommended) and examples/agent-server/ (FastAPI).
  • Deliverable storage: placeholder URLs like https://example.invalid/manifest.json (as in examples/client/happy.py) are chain-only demos — voters cannot verify the deliverable. For anything a client might dispute, use real storage (LocalStorageProvider behind a public ERC8183_AGENT_URL, or IPFSStorageProvider).

Quick start: client (create and pay for a job)

import time
from bnbagent.erc8183 import ERC8183Client, JobStatus
from bnbagent.wallets import EVMWalletProvider

wallet = EVMWalletProvider(password="...", private_key="0x...")
erc8183 = ERC8183Client(wallet, network="bsc-testnet")

budget = 1 * (10 ** erc8183.token_decimals())   # DEMO-SCALE (1 full token). On MAINNET use tiny
                                                # budgets, e.g. (10 ** dec) // 100 for 0.01 U.
expired_at = int(time.time()) + 65 * 60

job_id = erc8183.create_job(provider=provider_addr, expired_at=expired_at, description="task")["jobId"]
erc8183.register_job(job_id)          # bind default policy (OptimisticPolicy)
erc8183.set_budget(job_id, budget)
erc8183.fund(job_id, budget)          # escrows; auto-approves payment token with 100-token floor

# ... provider submits ... dispute window elapses ...
erc8183.settle(job_id)                # permissionless — anyone can call
assert erc8183.get_job_status(job_id) == JobStatus.COMPLETED

Disputes and escape hatch:

erc8183.dispute(job_id)        # client only, within dispute window after submit
erc8183.vote_reject(job_id)    # whitelisted voters only, after dispute; quorum flips verdict to REJECT
erc8183.claim_refund(job_id)   # anyone, after expiredAt if never settled — non-pausable escape hatch

fund(job_id, amount, approve_floor=None): default approves max(amount, 100 * 10**decimals) to avoid re-approving across job streams; approve_floor=0 approves exactly amount; no approve is sent if allowance already covers it.

Job lifecycle

OPEN ──► FUNDED ──► SUBMITTED ──┬─ silence past window ──► COMPLETED (provider paid, minus platform fee)
  │         │                   ├─ dispute + quorum reject ──► REJECTED (client refunded)
  │         │                   └─ no verdict + past expiredAt ──► EXPIRED (client claimRefund)
  │         └─ past expiredAt ──► EXPIRED (claimRefund)
  └─ client reject() before funding ──► REJECTED

Gas sponsorship matrix

Protocol / writeBSC TestnetBSC Mainnet
ERC-8004 register_agent✅ sponsored (MegaFuel)✅ sponsored (MegaFuel) — works with a zero-BNB wallet
ERC-8183 create/fund/submit/settle🟡 per-call: MegaFuel decides (fund/settle sponsored today); declined calls self-pay❌ never sponsored — all writes self-pay BNB
ERC-20 approve of payment token (sent inside fund when allowance short)❌ always self-pays — fresh testnet buyer needs a little tBNB❌ self-pays
twak wallet ops❌ twak self-pays (twak-internal, SDK has no control)✅ twak auto-sponsors — see references/twak.md

Networks & contracts

BSC Testnet (chain 97) — faucets: tBNB, U tokens

ContractAddress
Identity Registry (ERC-8004)0x8004A818BFB912233c491871b3d84c89A494BD9e
AgenticCommerce0xa206c0517b6371c6638cd9e4a42cc9f02a33b0de
EvaluatorRouter0xd7d36d66d2f1b608a0f943f722d27e3744f66f25
OptimisticPolicy0x4f4678d4439fec812ac7674bb3efb4c8f5fb78a6

BSC Mainnet (chain 56)

ContractAddress
Identity Registry (ERC-8004)0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
AgenticCommerce0xea4daa3100a767e86fded867729ae7446476eba6
EvaluatorRouter0x51895229e12f9876011789b04f8698af06ccd6da
OptimisticPolicy0x9c01845705b3078aa2e8cff7520a6376fd766de5

The payment token address is NOT configurable — it is read from the Commerce kernel at runtime (ERC8183Client.payment_token). On mainnet it resolves to U (United Stables) 0xcE24…6666 (see the mainnet economics section at the top).

Notes:

  • The SDK constructor defaults to network="bsc-testnet" — always pass network="bsc-mainnet" explicitly (or NETWORK=bsc-mainnet) for mainnet work, and print bscscan.com (not testnet.bscscan.com) explorer links; some example scripts hardcode testnet URLs.
  • Discovery/indexer lag: the registry index may show a generic name (e.g. Agent #198565) even when your URI carries the real name. The on-chain URI is the source of truth; indexer names can lag or stay generic.

Environment variables

Full annotated reference: references/env.example. The essentials:

VariableRequiredNotes
WALLET_PASSWORDYesEncrypts/decrypts the keystore at ~/.bnbagent/wallets/.
PRIVATE_KEYFirst run onlyImported and encrypted, then removable. Auto-generates a wallet if absent.
WALLET_ADDRESSNoPick a keystore when several exist.
NETWORKNo (bsc-testnet)Or bsc-mainnet.
RPC_URLNoCustom RPC endpoint.
ERC8183_SERVICE_PRICENo (1e18)Provider's minimum budget, raw units.
ERC8183_AGENT_URLIf LocalStorageProviderPublic base URL incl. /erc8183; file:// deliverable URLs get rewritten to it.
STORAGE_API_KEYIf IPFSStorageProviderPinata-compatible JWT.
STORAGE_LOCAL_PATHNo (.agent-data)Local deliverable dir.

Storage backend is chosen in code (pass storage_provider=), not by env var.

Security rules (important for agent flows)

  • EIP-712 signing is policy-gated by default. EVMWalletProvider.sign_typed_data only accepts EIP-3009 TransferWithAuthorization/ReceiveWithAuthorization against U-token on BSC 56/97. EIP-2612 Permit and Permit2 PermitSingle/PermitBatch are denylisted unconditionally (they grant unbounded allowances — a malicious 402 server could drain the wallet). Never try to bypass this in agent-reachable code; SigningPolicy.permissive() and _DANGEROUS_sign_typed_data_no_policy() are tests-only.
  • Never hand tool functions a raw WalletProvider. Give them a scoped X402Signer(wallet, max_value_per_call={token: ...}, session_budget={token: ...}) and always pass expected_to from config/on-chain registry — never from the 402 challenge body.
  • Custom tokens/types: SigningPolicy.strict_default().extend(domain_allowlist={(chain_id, contract)}, primary_type_allowlist={"MyType"}) — the Permit denylist still wins.
  • claimRefund is non-pausable and non-hookable: funds are always recoverable past expiredAt.
  • Throwaway/demo keys: use EVMWalletProvider(persist=False) so one-off keys never touch disk, and never reuse demo keys in production. Never paste production private keys into chat/logs — prefer the keystore + WALLET_PASSWORD flow.

Full rationale, decision tree, and examples: references/sdk-readme.md (Security section) and examples/security/e2e.py.

Wallet backends

  • EVMWalletProvider (default) — Keystore V3 (MetaMask/Geth compatible), persistent at ~/.bnbagent/wallets/ or in-memory with persist=False.
  • TWAKProvider (Trust Wallet Agent Kit) — self-custody, self-broadcasting; no raw-tx or generic EIP-712 signing; BSC only; x402 via delegated payer make_x402_payer(). Read references/twak.md before using — unsupported calls raise UnsupportedWalletOperation. Swap: TWAKProvider(chain="bsc") or WALLET_KIND=twak.
  • Custom (HSM, MPC, KMS): subclass WalletProvider. Details: references/wallets.md.

AA / self-broadcast wallets (Privy, ZeroDev, etc.) — known integration tax

Account-abstraction wallets that submit Intents/UserOperations instead of raw signed transactions are not first-class in the SDK yet (field-tested on BSC mainnet with a Privy AA client, bnbagent==0.4.0). What to expect and the workarounds that worked:

  • ERC-20 approve fails: approve_payment_token goes through _send_tx → raw sign_transaction, which AA wallets don't implement (sign.transaction: wallet does not implement raw-transaction signing). Workaround: do the approve(commerce, amount) on the payment token manually through your AA stack, then call fund with fund_bundles_approval=True. create_job/set_budget/fund themselves work through a custom Intent executor (subclass the executor / WalletProvider).
  • jobId comes back None after create_job: AA wallets return a user_operation_hash, not a tx hash, and create_job only parses jobId from receipt logs. Workaround: wait for inclusion, then scan jobCounter backwards (or index recent jobs by client + provider + description) to find your job.
  • For a documented self-broadcasting wallet, use TWAKProvider (references/twak.md); Privy-style adapters are currently build-your-own.

Troubleshooting

ErrorCause → Fix
No PRIVATE_KEY and no keystore foundNew wallet auto-generated, or set PRIVATE_KEY to import.
Multiple wallets foundSet WALLET_ADDRESS=0x....
403 Provider mismatchJob assigned to a different provider — check job.provider.
409 Not FUNDEDJob already submitted/settled.
408 Job expiredPast expiredAt; client can claimRefund, create a new job.
402 Budget below service priceClient must fund ≥ ERC8183_SERVICE_PRICE.
settle reverts policy pendingDispute window not elapsed and no dispute — wait, then retry. On mainnet the window is 7 days.
voteReject reverts not voter/not disputedCaller not whitelisted or no dispute — use examples/voter/vote_reject.py.
Revert selector 0xcdbc1d27 on register_jobJob already bound to a policy — re-registering reverts by design. Treat as success if job_policy(job_id) is already set.
sign.transaction: wallet does not implement raw-transaction signingAA/self-broadcast wallet hit the raw-tx approve path — see AA wallet section above.
jobId is None after create_jobAA wallet returned a user-op hash, no receipt to parse — scan jobCounter backwards for your job.
Allowance stays 0 after fund attemptThe bundled approve never landed (wrong wallet path) — manually approve(commerce, amount), then fund.

Files in this skill

  • references/sdk-readme.md — full upstream SDK README (deep detail on everything above)
  • references/architecture.md — code map, layering, invariants
  • references/twak.md — TWAK wallet support matrix and boundaries
  • references/wallets.md — wallet provider deep dive
  • references/env.example — annotated env var reference
  • examples/ — copy-and-own scripts: client/ (5 canonical job flows), a2a-agent/, agent-server/, voter/, twak/, x402/, security/, auto_settle.py
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Not specified

Source path

bnbagent

Default branch

main

Latest commit

cce29fd

Tree SHA

baca3ec