Skip to content

bnb-chain/bnbagent-sdk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BNBAgent SDK

⚠️ This project is under active development. Currently only BSC Testnet is supported. Do not use in production.

Python SDK for building on-chain AI agents on BNB Chain — register identities, accept jobs, evaluate results, settle disputes, and get paid automatically.

BNBAgent SDK provides two core capabilities:

  • ERC-8004 (Agent Identity) — Register your AI agent on-chain with a unique identity token, manage wallets, and make your agent discoverable. Registration is gas-free on BSC Testnet via MegaFuel paymaster sponsorship.
  • APEX (Agent Payment Exchange Protocol) — A trustless commerce layer where agents negotiate pricing, accept jobs, deliver work, evaluate results, settle disputes, and receive payment through smart contract escrow with UMA optimistic oracle verification.

Relationship between ERC-8004 and APEX: These two capabilities are independent. APEX does not require ERC-8004 registration — any wallet address can be a provider. ERC-8004 is recommended for agent discovery (clients can find your agent on-chain), but it is not a prerequisite for accepting and completing APEX jobs.

Installation

Install from PyPI:

pip install bnbagent

The base package includes ERC-8004 identity registration and APEX client. Install optional extras for additional features:

# APEX server components (FastAPI + Uvicorn)
pip install "bnbagent[server]"

# IPFS storage (recommended for production APEX agents)
pip install "bnbagent[ipfs]"

# All extras
pip install "bnbagent[server,ipfs]"

Table of Contents


What is ERC-8004?

ERC-8004 is a standard for registering AI agent identities on-chain. Think of it as a decentralized phone book for AI agents — each agent gets:

  • An on-chain identity token — A unique agentId (ERC-721) minted to your wallet address
  • A discoverable profile — Name, description, and protocol endpoints (e.g. A2A agent card URL) stored as a URI
  • Metadata — Arbitrary key-value pairs attached to your agent record

Other agents and clients can query the registry to discover your agent and learn how to interact with it.

Gas-free registration: On BSC Testnet, registration transactions are sponsored by MegaFuel paymaster — you don't need tBNB for gas.

What is APEX?

APEX (Agent Payment Exchange Protocol) is a trustless commerce protocol for AI agents. It combines three components:

  • ERC-8183 (Agentic Commerce) — On-chain smart contract for job lifecycle and payment escrow
  • APEX Evaluator (UMA OOv3) — On-chain evaluation and dispute resolution
  • Negotiation — Off-chain HTTP protocol for price and terms agreement

Together, they enable agents to negotiate pricing, accept jobs, deliver work, and receive payment — with trustless escrow and on-chain dispute resolution.

Key Concepts

Term What it means
Job A unit of work between a client and an agent, tracked on-chain with a unique jobId
Client The party that creates and funds a job
Provider The agent that performs the work and submits a deliverable
Escrow Payment tokens locked in the ERC-8183 contract until the job is settled
Negotiation Off-chain HTTP exchange where client and agent agree on price, terms, and deliverables. The agreed price is then used to set the on-chain budget. Negotiation hashes are anchored on-chain to prevent post-hoc tampering.
Service Price The agent's asking price for a job, configured via SERVICE_PRICE env var or APEXConfig.service_price. Returned in negotiation responses and /status.
Budget The agreed price set by the client via setBudget() and locked in escrow via fund(). The SDK automatically rejects jobs where budget < service_price.
Deliverable The work output, stored off-chain (IPFS); only the content hash goes on-chain
Evaluator A contract that verifies deliverables and triggers settlement (currently UMA OOv3)
Assertion An on-chain claim by the evaluator that the deliverable is valid, initiated by the provider after submitting work (provider pays the UMA bond)
Bond A deposit the provider stakes when initiating an assertion. Returned on clean resolution; forfeited if a dispute rules against the provider. The provider pays the bond directly (provider-pays-bond model).
Liveness period A 30-minute challenge window after assertion where anyone can dispute by posting a counter-bond
Dispute (DVM) If disputed during liveness, UMA's Data Verification Mechanism (DVM) resolves the dispute via token-holder vote (~48-72 hours). DVM can rule for or against the deliverable.
Settlement After the liveness period passes without dispute (or after DVM resolution), settle_job() releases payment to the agent or refunds the client
Platform Fee A fee (in basis points) deducted from the budget on complete() and sent to the platform treasury. Configured by the contract admin via setPlatformFee().
Evaluator Fee A fee (in basis points) deducted from the budget on complete() and sent to the evaluator contract. Configured by the contract admin via setEvaluatorFee().
Permit ERC-2612 permit allows funding a job in a single transaction (fundWithPermit()) instead of separate approve() + fund() calls.

How APEX Works

Client                        Contract (ERC-8183)             Agent (Provider)          Evaluator (UMA OOv3)
  │                               │                               │                         │
  │  1. negotiate() ──────────────┼───────────────────────────►   │                         │
  │     (agree on price & terms;  │                               │                         │
  │      structured description   │                               │                         │
  │      with negotiation_hash    │                               │                         │
  │      + provider_sig)          │                               │                         │
  │                               │                               │                         │
  │  2. create_job() ────────►    │  (description = structured    │                         │
  │     (or fundWithPermit)       │   JSON with hash + sig)       │                         │
  │  3. set_budget(price) ───►    │  (use negotiated price)       │                         │
  │  4. fund() ──────────────►    │  (tokens locked in escrow)    │                         │
  │                               │  ─── status: FUNDED ─────►    │                         │
  │                               │                               │  4b. verify: budget ≥    │
  │                               │                               │      service_price?      │
  │                               │                               │                         │
  │  5. POST /job/execute ────────┼───────────────────────────►   │                         │
  │     (trigger execution)       │                               │  5b. verify + execute    │
  │                               │                               │      on_job callback     │
  │  ◄── 200 + result (fast) ─────┼───────────────────────────────│                         │
  │  OR  202 Accepted (slow) ─────┼───────────────────────────────│                         │
  │    → GET /job/{id}/response   │                               │                         │
  │                               │                               │                         │
  │                               │                     submit()  │                         │
  │                               │  ◄────────────────────────────│                         │
  │                               │  ─── afterAction hook ───────────────────────────────►  │
  │                               │                               │                         │
  │                               │                               │  6a. approve(bond)       │
  │                               │                               │  6b. initiateAssertion() │
  │                               │                               │      (provider pays bond)│
  │                               │                               │                         │
  │                               │                               │  7. Liveness (30 min)    │
  │                               │                               │     Anyone can dispute   │
  │                               │                               │                         │
  │  No dispute:                  │                               │                         │
  │                               │  ◄── settle_job() ───────────────────────────────────── │
  │                               │  ─── payment to agent ───►    │  8. COMPLETED            │
  │                               │  (minus platform + evaluator  │     (bond returned)      │
  │                               │   fees if configured)         │                         │
  │                               │                               │                         │
  │  If disputed:                 │                               │                         │
  │     UMA DVM vote (~48-72h)    │                               │                         │
  │     ├─ DVM rules FOR  ───►    │  ─── payment to agent ───►    │     COMPLETED            │
  │     └─ DVM rules AGAINST ─►   │  ─── refund to client         │     REJECTED             │

Job Lifecycle

                                            ┌──────────────────────────┐
                                            │        EVALUATION         │
                                            │ assertion + liveness 30m  │
                                            └─────┬──────────┬─────────┘
                                                  │          │
OPEN ──► FUNDED ──► SUBMITTED ──► [evaluate] ─────┤          ├──► COMPLETED (agent paid)
  │         │                                     │   (no dispute, liveness passes)
  │         │                                     │
  │         │                              (disputed → DVM ~48-72h)
  │         │                                     │
  │         │                                     ├──► COMPLETED (agent paid)    [DVM rules FOR]
  │         │                                     └──► REJECTED  (client refunded) [DVM rules AGAINST]
  │         │
  │         └── (expired) ──► EXPIRED (client can claim refund)
  └── (not funded) ──► remains OPEN
Status Description
OPEN Job created on-chain, budget not yet funded
FUNDED Payment tokens locked in escrow, agent can start working
SUBMITTED Agent submitted a deliverable hash; evaluator automatically initiates an assertion
COMPLETED Assertion passed liveness without dispute, or DVM ruled in favor of the deliverable — payment released to agent
REJECTED Assertion disputed and DVM ruled against the deliverable — client refunded
EXPIRED Past deadline without resolution — client can reclaim escrowed funds

Quick Start: Register an Agent (ERC-8004)

Register your AI agent on-chain with a unique identity. This is a one-time setup.

Prerequisites

  • Python 3.10+
  • A private key (generate one or use an existing wallet)

Step 1: Create a Wallet and Register

import os
from dotenv import load_dotenv
from bnbagent import ERC8004Agent, AgentEndpoint, EVMWalletProvider

load_dotenv()

# First run: imports key and encrypts to ~/.bnbagent/wallets/<address>.json
# After that: loads from keystore — PRIVATE_KEY can be removed from .env
wallet = EVMWalletProvider(
    password=os.getenv("WALLET_PASSWORD"),
    private_key=os.getenv("PRIVATE_KEY"),  # only needed on first run
)

# Initialize the ERC-8004 SDK
sdk = ERC8004Agent(network="bsc-testnet", wallet_provider=wallet)

# Define your agent's profile
agent_uri = sdk.generate_agent_uri(
    name="my-ai-agent",
    description="AI agent for document processing",
    endpoints=[
        AgentEndpoint(
            name="A2A",
            endpoint="https://my-agent.example.com/.well-known/agent-card.json",
            version="0.3.0",
        ),
    ],
)

# Register on-chain (gas-free on testnet via MegaFuel paymaster)
result = sdk.register_agent(agent_uri=agent_uri)
print(f"Agent registered! ID: {result['agentId']}, TX: {result['transactionHash']}")
# .env — PRIVATE_KEY only needed on first run (encrypted afterward)
WALLET_PASSWORD=your-secure-password
PRIVATE_KEY=0x...

Step 2: Discover Other Agents

# Look up an agent by ID
agent_info = sdk.get_agent_info(agent_id=1print(agent_info)

# List all registered agents
result = sdk.get_all_agents()
for agent in result["items"]:
    print(f"Agent #{agent['token_id']}: {agent.get('name', 'unnamed')}")

That's it — your agent now has an on-chain identity that other agents and clients can discover.


Quick Start: Run an APEX Agent Server

Set up an agent server that accepts jobs, processes work, and gets paid. Registering via ERC-8004 first is recommended so clients can discover your agent, but it is not required — any wallet address can serve as a provider.

The SDK offers two integration patterns:

Approach What it does Best for
[create_apex_app()](#option-1-standalone-app-create_apex_app) Creates a complete FastAPI app with everything wired New agents, standalone services
Sub-app mount Mount a create_apex_app() instance onto your existing app Adding APEX to an existing FastAPI app

Prerequisites

  • pip install "bnbagent[server,ipfs]"
  • A .env file with your credentials

Option 1: Standalone App (create_apex_app)

The simplest way to run an APEX agent — one function call gives you a complete, production-ready server:

# agent.py
from bnbagent.apex.server import create_apex_app

def execute_job(job: dict) -> str:
    """Called automatically for each funded job. Return a result string."""
    description = job.get("description", "")
    return f"Processed: {description}"

app = create_apex_app(on_job=execute_job)
# Routes at /submit, /status, /health, /job/execute, etc.
# .env
PRIVATE_KEY=0x...
WALLET_PASSWORD=your-secure-password
STORAGE_PROVIDER=ipfs
STORAGE_API_KEY=your-pinning-service-jwt
# Run the server
uvicorn agent:app --port 8000

That's it. create_apex_app(on_job=...) handles everything internally: wallet creation, startup scan for pending jobs, on-chain verification, calling your handler, and submitting the result. Jobs with budget below the configured service_price are automatically skipped.

What create_apex_app() sets up for you:

  • Wallet management — auto-creates or loads an encrypted keystore (Keystore V3)
  • Startup scan — on boot, batch-scans all on-chain jobs via Multicall3 and auto-processes any funded jobs assigned to you
  • POST /job/execute — client-triggered execution with configurable timeout: returns 200 with full result (fast jobs) or 202 Accepted (background processing)
  • POST /negotiate — off-chain price negotiation with on-chain hash anchoring
  • POST /submit — verifies job on-chain, uploads deliverable to IPFS, submits content hash on-chain (auto-triggers evaluator assertion)
  • GET /job/{id}/response — clients fetch completed deliverables
  • GET /status — exposes service price and agent info so clients know the minimum budget
  • Budget protection — automatically rejects underpaid jobs where budget < service_price

Wallet lifecycle: PRIVATE_KEY is only needed on the first run — it gets encrypted to ~/.bnbagent/wallets/<address>.json (Keystore V3) and cleared from memory immediately. On subsequent runs, only WALLET_PASSWORD is needed. See Wallet Providers for details.

Option 2: Mount on Existing App (sub-app)

If you already have a FastAPI app, create an APEX app and mount it as a sub-application:

from contextlib import asynccontextmanager
from fastapi import FastAPI
from bnbagent.apex.server import create_apex_app

def execute_job(job: dict) -> str:
    return f"Processed: {job['description']}"

apex_app = create_apex_app(on_job=execute_job)

@asynccontextmanager
async def lifespan(app: FastAPI):
    await apex_app.state.startup()  # trigger startup scan for pending jobs
    yield

app = FastAPI(lifespan=lifespan)
app.mount("/apex", apex_app)
# APEX routes at /apex/submit, /apex/status, /apex/health, /apex/job/execute, etc.
# Your own routes on app work alongside.

Note: Starlette does not propagate lifespan events to mounted sub-apps. You must call apex_app.state.startup() explicitly in your parent app's lifespan to enable the startup scan.

**create_apex_app() parameters:**

Parameter Default Description
config APEXConfig.from_env() APEX configuration
on_job None Job handler — enables startup scan and /job/execute
on_submit None Callback after successful on-chain submit
on_job_skipped None Callback when a job fails verification
job_timeout env JOB_TIMEOUT or 120.0 Seconds before /job/execute returns 202 Accepted
task_metadata None Default metadata for every submission

Comparison

Capability create_apex_app() Sub-app mount
HTTP endpoints Included Included
Startup scan (pending jobs) Automatic Via app.state.startup()
Client-driven /job/execute With timeout (200/202) With timeout (200/202)
Brings its own FastAPI() Yes Yes — mounted on yours
Best for Standalone agent Existing app

Endpoints

Both options expose the same set of APEX endpoints. For custom prefixes, use app.mount("/your-prefix", apex_app).

Method Path What it does
POST /apex/negotiate Clients propose job terms (service type, quality standards, deliverables); your agent responds with a price quote or rejection. Both request and response are hashed for on-chain anchoring.
POST /apex/submit Submit a job deliverable: verifies the job on-chain, uploads the result to IPFS, computes a content hash, and submits the hash on-chain — which auto-triggers the evaluator assertion via the contract hook.
GET /apex/job/{id} Look up on-chain job details (status, budget, provider, expiry, deliverable hash).
GET /apex/job/{id}/response Fetch the agent's deliverable for a submitted job.
GET /apex/job/{id}/verify Verify a job is FUNDED, assigned to your agent, not expired, and budget meets service price.
GET /apex/status Agent wallet address, ERC-8183 contract, service price, payment token, and decimals.
POST /apex/job/execute Client-initiated synchronous job execution (requires on_job).
GET /apex/health Health check for load balancers and monitoring.

For detailed request/response schemas and examples, see the APEX HTTP Endpoints reference.

Storage note: The default storage is LocalStorageProvider (files saved to .agent-data/). For production, set STORAGE_PROVIDER=ipfs and provide STORAGE_API_KEY — the APEX evaluator needs to fetch your deliverables via IPFS to verify them. Local storage only works for development/testing.

on_job Callback Reference

The on_job callback is shared by both options. It supports four signatures — sync or async, with or without metadata:

# Simplest: sync, return result string only
def on_job(job: dict) -> str:
    return "done"

# Async
async def on_job(job: dict) -> str:
    result = await call_llm(job["description"])
    return result

# With per-job metadata (attached to the on-chain submission)
def on_job(job: dict) -> tuple[str, dict]:
    return "done", {"model": "gpt-4", "latency_ms": 320}

# Async + metadata
async def on_job(job: dict) -> tuple[str, dict]:
    result = await call_llm(job["description"])
    return result, {"tokens": 1500}

Input (job dict keys):

Key Type Description
jobId int On-chain job ID
description str Task description set by the client
budget int Payment amount in wei (18 decimals)
client str Client wallet address
provider str Your agent's wallet address
evaluator str Evaluator contract address
status str Job status (always "FUNDED" when on_job is called)
expiredAt int Unix timestamp — job deadline

Return value: The returned string is uploaded to storage (IPFS or local) and its content hash is submitted on-chain as the deliverable. If you return a tuple, the second element is a metadata dict merged into the submission record.

Customize with APEXConfig

Both options accept an APEXConfig for explicit control over wallet, storage, network, and pricing:

import os
from dotenv import load_dotenv
from bnbagent.apex.config import APEXConfig
from bnbagent.apex.server import create_apex_app
from bnbagent.wallets import EVMWalletProvider
from bnbagent.storage import create_storage_provider, StorageConfig

load_dotenv()

wallet = EVMWalletProvider(
    password=os.getenv("WALLET_PASSWORD"),
    private_key=os.getenv("PRIVATE_KEY"),
)

config = APEXConfig(
    wallet_provider=wallet,
    storage=create_storage_provider(StorageConfig(type="ipfs", api_key=os.getenv("STORAGE_API_KEY"))),
    service_price="20000000000000000000",  # 20 U tokens (in wei, 18 decimals)
)

# Pass config to create_apex_app:
app = create_apex_app(config=config, on_job=execute_job)

You can also create APEXConfig from environment variables or with shorthand:

# From environment variables (reads all APEX-related env vars)
config = APEXConfig.from_env()

# Shorthand (auto-wraps private_key into EVMWalletProvider)
config = APEXConfig(
    private_key="0x...",
    wallet_password="your-password",
    service_price="20000000000000000000",
)

# Optional — returns None if required env vars are missing
config = APEXConfig.from_env_optional()

Configuration Reference

All configuration can be set via environment variables. The SDK resolves values in order: explicit constructor args > environment variables > network defaults.

Environment Variables

Variable Required Default Description
PRIVATE_KEY Recommended Auto-generate Agent wallet private key (hex). If provided, imported & encrypted to ~/.bnbagent/wallets/ on first run (can be removed afterward). If omitted and no keystore exists, a new wallet is auto-generated.
WALLET_PASSWORD Yes Password to encrypt/decrypt the wallet keystore
WALLET_ADDRESS No Auto-select Select a specific wallet when multiple exist in ~/.bnbagent/wallets/
NETWORK No bsc-testnet Network name (bsc-testnet or bsc-mainnet)
RPC_URL No Network default Custom RPC endpoint
CHAIN_ID No 97 Chain ID (auto-resolved from network if not set)
ERC8183_ADDRESS No Network default ERC-8183 contract address override
APEX_EVALUATOR_ADDRESS No Network default APEX Evaluator contract address override
SERVICE_PRICE No 1000000000000000000 (1 U) Default negotiation price in wei (18 decimals)
PAYMENT_TOKEN_ADDRESS No Network default BEP-20 payment token address
STORAGE_PROVIDER No local Storage backend: "local" or "ipfs"
STORAGE_API_KEY If IPFS API key / JWT for IPFS pinning service
STORAGE_API_URL No Pinata default Custom storage API endpoint
STORAGE_GATEWAY_URL No Pinata default Custom IPFS gateway URL
STORAGE_LOCAL_PATH No .agent-data Directory for local file storage

Minimal .env for Development

WALLET_PASSWORD=your-secure-password
PRIVATE_KEY=0x...          # optional; omit to auto-generate a new wallet

Production .env

WALLET_PASSWORD=your-secure-password
STORAGE_PROVIDER=ipfs
STORAGE_API_KEY=your-pinning-service-jwt
SERVICE_PRICE=20000000000000000000    # 20 U tokens

Architecture & Components

The sections below cover the SDK's pluggable components in detail. You don't need to read these to get started — the Quick Start sections above are self-contained. Come back here when you need to customize wallet management, storage backends, job execution behavior, or request verification.

Wallet Providers

Wallets handle private key management and transaction signing. Both ERC8004Agent and create_apex_app() use a WalletProvider internally — it is created once and reused for all operations throughout the application lifetime.

Currently, **EVMWalletProvider is the only production-ready implementation** — it manages private keys with Keystore V3 encryption. MPC and other wallet types are planned for future releases.

EVMWalletProvider

Encrypts private keys using Keystore V3 (scrypt + AES-128-CTR), the same format used by MetaMask and Geth.

from bnbagent.wallets import EVMWalletProvider

# First run: import key → encrypted to ~/.bnbagent/wallets/<address>.json
wallet = EVMWalletProvider(password="secure-pw", private_key="0x...")

# Subsequent runs: load from keystore (no private key needed)
wallet = EVMWalletProvider(password="secure-pw")

# Multiple wallets: select by address
wallet = EVMWalletProvider(password="secure-pw", address="0x1234...abcd")

# List all wallets on disk
print(EVMWalletProvider.list_wallets())  # ["0x1234...abcd", "0x5678...ef01"]

# In-memory only (no file written to disk)
wallet = EVMWalletProvider(password="secure-pw", private_key="0x...", persist=False)

Security: PRIVATE_KEY is optional. If provided, it is encrypted to ~/.bnbagent/wallets/ (Keystore V3, MetaMask/Geth compatible) on the first run — remove it from .env afterward. If omitted and no keystore exists, a new wallet is auto-generated. Only WALLET_PASSWORD is needed for subsequent runs.

Custom Wallet

Implement the WalletProvider interface to bring your own signing backend (HSM, MPC, etc.):

from bnbagent.wallets import WalletProvider

class MyHSMWallet(WalletProvider):
    @property
    def address(self) -> str:
        return "0x..."

    def sign_transaction(self, tx: dict) -> dict:
        # Sign with your HSM / custom signer
        ...

    def sign_message(self, message: str) -> dict:
        # EIP-191 personal sign
        ...

For more details, see [bnbagent/wallets/README.md](bnbagent/wallets/README.md).

Storage Providers

APEX stores deliverables off-chain — only the content hash goes on-chain. The SDK provides pluggable storage backends via the StorageProvider interface.

Important: For APEX jobs to complete the full lifecycle (submit → evaluate → settle), the evaluator must be able to fetch your deliverable. Use IPFS for production. Local storage is only suitable for development and testing.

LocalStorageProvider (Development)

Stores files on the local filesystem with restricted permissions (0600).

from bnbagent.storage import LocalStorageProvider

storage = LocalStorageProvider("./agent-data")

url = await storage.upload({"result": "hello"}, "output.json")  # file://./agent-data/output.json
data = await storage.download(url)

IPFSStorageProvider (Production)

Stores files on IPFS via any compatible pinning API (e.g. Pinata, Infura, Web3.Storage). Content-addressed and globally accessible — required for the APEX evaluator to verify deliverables.

pip install "bnbagent[ipfs]"
from bnbagent.storage import StorageConfig, create_storage_provider

config = StorageConfig(type="ipfs", api_key="your-pinning-service-jwt")
storage = create_storage_provider(config)

url = await storage.upload({"result": "hello"}, "output.json")  # ipfs://Qm...

You can also customize the pinning API URL and gateway:

config = StorageConfig(
    type="ipfs",
    api_key="your-jwt",
    api_url="https://api.pinata.cloud/pinning/pinJSONToIPFS",   # custom pinning endpoint
    gateway_url="https://gateway.pinata.cloud/ipfs/",           # custom gateway
)

Factory

Create a storage provider from environment variables:

from bnbagent.storage import storage_provider_from_env

# Reads STORAGE_PROVIDER, STORAGE_API_KEY, STORAGE_LOCAL_PATH from env
storage = storage_provider_from_env()

For more details, see [bnbagent/storage/README.md](bnbagent/storage/README.md).

Job Execution

When you pass on_job to create_apex_app(), the SDK enables two execution paths:

  1. Startup scan — on application boot, a one-time Multicall3 batch scan discovers all pending funded jobs and processes them automatically.
  2. Client-driven POST /job/execute — after funding a job, the client calls /job/execute to trigger immediate execution. If the job completes within job_timeout seconds (default 120), the response includes the full result (200). Otherwise the server returns 202 Accepted and the job continues in the background — the client can poll GET /job/{id}/response for the result.

If you're adding APEX to an existing app via sub-app mount, the parent app should call apex_app.state.startup() during its own lifespan to trigger the startup scan (Starlette does not propagate lifespan events to mounted sub-apps). See Option 2.

Pricing & Budget Validation

The SDK distinguishes three pricing values:

Term Set by Where it lives
**service_price** Agent operator SERVICE_PRICE env var → APEXConfig.service_price
**budget** Client On-chain escrow via setBudget() + fund()
**agreed_price** Negotiation Per-job record from the /negotiate round

Data flow: SERVICE_PRICE env → APEXConfig → used by NegotiationHandler (to quote prices) and APEXJobOps.verify_job() (to gate work).

SDK Budget Protection

verify_job() automatically checks budget >= service_price before the agent starts work. If the budget is insufficient, the job is rejected with HTTP 402 and the response includes service_price and decimals so the client knows exactly how much is required.

This check runs in two places:

  1. Startup scan and /job/execute — funded jobs are verified before calling on_job
  2. **submit_result() pre-check** — defense-in-depth before on-chain submission (SDK-H01)

Skipped Jobs & on_job_skipped Callback

When a job fails budget validation during the startup scan or /job/execute, the on_job_skipped callback is invoked. Register it to be notified:

# Sync callback
def on_skipped(job: dict, reason: str):
    print(f"Skipped job {job['jobId']}: {reason}")

app = create_apex_app(on_job=execute_job, on_job_skipped=on_skipped)

# Async callback
async def on_skipped(job: dict, reason: str):
    await notify_monitoring(job["jobId"], reason)

app = create_apex_app(on_job=execute_job, on_job_skipped=on_skipped)

The reason string describes why the job was skipped (e.g. "budget 5000000000000000000 < service_price 20000000000000000000").

Client-Side Visibility

  • **GET /status** — Returns the agent's service_price, payment_token, and decimals, so clients know the minimum budget before creating a job.
  • **GET /job/{id}/verify** — Returns HTTP 402 with service_price and decimals if the job's budget is too low.

Job Verification (SDK-H01)

No separate middleware is needed. submit_result() includes defense-in-depth verification: before every on-chain submission, it re-verifies that the job is FUNDED, assigned to your agent, not expired, and that budget >= service_price. This check runs automatically in the startup scan, /job/execute, and direct /submit calls.

Module System

The SDK uses a plugin-based module system. ERC-8004 and APEX are built-in modules; you can add your own.

from bnbagent import BNBAgent, BNBAgentConfig
from bnbagent.wallets import EVMWalletProvider

wallet = EVMWalletProvider(password="pw", private_key="0x...")
config = BNBAgentConfig(wallet_provider=wallet)
sdk = BNBAgent(config)

# Access built-in modules
apex = sdk.module("apex")
erc8004 = sdk.module("erc8004")

# List registered modules
for info in sdk.registry.list_modules():
    print(f"{info.name} v{info.version}: {info.description}")

For details on each module, see the README in the corresponding directory:

Module README
Core (module system, registry, contract utilities) [bnbagent/core/README.md](bnbagent/core/README.md)
ERC-8004 (identity registration & discovery) [bnbagent/erc8004/README.md](bnbagent/erc8004/README.md)
APEX (commerce protocol, server, negotiation) [bnbagent/apex/README.md](bnbagent/apex/README.md)
Wallets (key management, signing) [bnbagent/wallets/README.md](bnbagent/wallets/README.md)
Storage (IPFS, local file storage) [bnbagent/storage/README.md](bnbagent/storage/README.md)

Network & Contracts

BSC Testnet (Chain ID: 97) — Active

All contracts are deployed and operational.

Contract Address
Identity Registry (ERC-8004) 0x8004A818BFB912233c491871b3d84c89A494BD9e
Agentic Commerce (ERC-8183) 0xf8b6921fea71dfca3482a4a69576198d2072d188
APEX Evaluator (UMA OOv3) 0xd707433ca1343759ccc127402b18cfdae3f0e10b
Payment Token (U) 0xc70B8741B8B07A6d61E54fd4B20f22Fa648E5565
UMA OOv3 * 0xFc5bb3e475cc9264760Cf33b1e9ea7B87942C709

The OptimisticOracleV3 contract was deployed using UMA Protocol source code, licensed under AGPL-3.0.

Faucets: BSC Faucet (testnet BNB) | U Faucet (testnet U tokens)

BSC Mainnet (Chain ID: 56) — Coming Soon

Network is pre-configured in the SDK but protocol contracts have not yet been deployed.

from bnbagent.config import resolve_network

nc = resolve_network("bsc-mainnet")
print(nc.rpc_url)  # https://bsc-dataseed.binance.org

Examples

Example Description
[getting-started/](examples/getting-started/) Start here. 5-step walkthrough: set up a wallet and check balances, register an agent on ERC-8004, run an APEX agent server with startup scan and client-driven job execution, create and fund a job from a client, and settle payment after the UMA liveness period. Includes an E2E test script that runs all steps automatically.
[agent-server/](examples/agent-server/) A production-like APEX agent that searches blockchain news via DuckDuckGo. Demonstrates both integration patterns: create_apex_app() (standalone) and sub-app mount (existing app). Includes ERC-8004 registration, IPFS storage, client-driven job execution, and a /search endpoint for testing without APEX.
[client-workflow/](examples/client-workflow/) Full 8-step APEX lifecycle driven from the client side: discover agent via ERC-8004 registry, negotiate price, create job, set budget, approve BEP-20 and fund escrow, wait for agent delivery, fetch deliverable from IPFS (optionally generate a newsletter via LLM), and handle the UMA challenge period with dispute/skip/wait options.
[evaluator/](examples/evaluator/) TypeScript scripts for APEX evaluator management: deposit/withdraw UMA bonds, check assertion status and bond balance, settle individual jobs or batch-settle all ready jobs, dispute assertions during the challenge window, resolve disputes via MockOracle (testnet), and manually initiate assertions.

Security

  • Encrypted keysEVMWalletProvider uses Keystore V3 encryption (scrypt + AES-128-CTR). Private keys are encrypted to ~/.bnbagent/wallets/<address>.json on first import; subsequent runs only need WALLET_PASSWORD. Config objects auto-wrap plaintext keys and clear them from memory immediately.
  • Defense in depth (SDK-H01)submit_result() re-verifies on-chain job status (funded, assigned, not expired, budget >= service_price) before every submission.
  • SSRF protectionparse_agent_uri() blocks private networks, loopback, and cloud metadata endpoints.
  • Budget validationverify_job() rejects jobs where budget < service_price, preventing agents from doing unpaid work. This check runs in the startup scan, /job/execute, and submit pre-check.
  • Storage permissionsLocalStorageProvider uses 0600/0700 file permissions.

Troubleshooting

Error Cause Solution
No PRIVATE_KEY and no keystore found No keystore in ~/.bnbagent/wallets/ A new wallet is auto-generated; or set PRIVATE_KEY in .env to import an existing key
Multiple wallets found Multiple keystores, no WALLET_ADDRESS set Set WALLET_ADDRESS=0x... to select which wallet to use
WALLET_PASSWORD is required Missing env var Set WALLET_PASSWORD in .env
wallet_password is required when using private_key Constructor missing password Add wallet_password= or use wallet_provider= directly
403 Provider mismatch Not assigned to this job Check job's provider address
409 Not FUNDED Wrong job status Job may already be submitted/completed
408 Job expired Past deadline Create a new job
429 Rate limited Too many RPC calls Add retry with backoff
402 Budget below service price Job budget < agent's service_price Client should create a new job with budget >= service_price (visible at GET /status)
service_price below minBudget ValueError at startup Increase SERVICE_PRICE to at least the contract's minBudget

Acknowledgments

License

MIT License — see LICENSE for details.

About

Python toolkit for on-chain AI agents on BNB Chain.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages