The AgentWallet Protocol

One trace.
Three rails.
Every agent.

The AgentWallet Protocol is the layer that makes agent commerce auditable. Every Company, Principal and Agent gets a portable on-chain identity. Every payment — fiat, card or crypto — is authorized by a signed mandate, evaluated against policy, settled on its native rail, and written to a single ledger row anchored on Base. Same trace ID across ACH, Mastercard and USDC.

01

On-chain trace

Every settled payment carries a 32-byte trace ID. The mandate hash is anchored on Base; the rail provider returns a settlement reference; both land in the same ledger row. Audit can replay any payment end-to-end.

02

Portable agent score

0–1000 reputation derived from settled volume, dispute rate, mandate compliance and Principal KYB tier. Counterparties read the score over MCP before accepting a 402.

03

One model, three rails

Fiat (ACH, SEPA, Faster Payments, SWIFT), card (virtual Mastercard, push-to-card) and crypto (USDC, EURC, x402, on-chain transfer) all use the same Mandate → Policy → Rail pipeline.

04

ERC-native

ERC-20 for stablecoin balances, ERC-721 / ERC-1155 for agent identity NFTs, EIP-712 typed-data signing, EIP-3009 for x402, EIP-2612 permits, ERC-4337 for gas-sponsored agent ops.

05

AP2 mandates

IntentMandate, CartMandate and PaymentMandate are signed by the Principal's WebAuthn key. The Agent never holds a key that can move money on its own.

06

Immutable audit

Every mandate, policy decision, retry, partial settle, refund and reversal is appended to the trace. Nothing is mutated; corrections are new entries that reference the prior trace.

The hierarchy

Company  →  Principal  →  Agent

Three identity tiers, programmed into the protocol. Money never moves outside this hierarchy. KYB lives at the Company. Authority lives at the Principal. Execution lives at the Agent.

Tier 1

Company (tenant)

Who
the legal entity. KYB-verified, beneficial owners on file, treasury accounts attached.
Holds
master fiat float, USDC treasury wallet, billing relationship with Payouts.com, default policy tree.
On-chain
a single registry entry on Base — company_id → eoa — that anchors all child Principals and Agents.
Programmable
create / disable Principals, set tenant-wide spending caps, rotate signing keys, export the full trace.
Tier 2

Principal (human)

Who
a human user inside the Company. KYC-verified. Holds the WebAuthn key that signs every AP2 mandate.
Holds
a sub-budget allocated by the Company, a list of Agents they own, a personal policy.
On-chain
principal_id → eoa, parented to a company_id. Public key is the AP2 signer.
Programmable
mint Agents, sign IntentMandates, approve high-value CartMandates, revoke any Agent in <1s.
Tier 3

Agent (machine)

Who
an autonomous AI agent. Holds an API key (pak_…) and an MCP endpoint.
Holds
its own fiat wallet, USDC wallet, virtual card, inbox, phone number — all scoped to one Principal.
On-chain
agent_id → eoa, parented to a Principal. ERC-721 identity NFT optional for cross-platform portability.
Programmable
spend within its envelope, pay/receive x402, call MCP tools, sign EIP-3009 authorizations, accrue an on-chain score.
The 3-rail programming model

Identity  →  Mandate  →  Money

Every payment traverses three layers, in order. You can swap providers inside any layer; you cannot skip a layer. This is the contract.

Layer 1

Identity & Trust

Company, Principal and Agent registries. KYB at the Company tier; KYC at the Principal tier; software identity (PAK + ERC-721 NFT) at the Agent tier.

Outputs
a verifiable (company_id, principal_id, agent_id) tuple, the Principal's public key, and the Agent's current on-chain score.
Standards
EIP-712, ERC-721 / ERC-1155 (optional identity NFTs), DID, OIDC for Principals (Auth0 today).
Layer 2

Mandates & Policy

The Agent proposes a payment. The Principal (or pre-signed mandate) authorizes it. The Spending Policy engine evaluates per-call, per-day, per-counterparty, per-rail caps. The decision is signed.

Outputs
a signed AP2 PaymentMandate or EIP-3009 authorization, plus a policy decision record.
Standards
Google AP2 (IntentMandate / CartMandate / PaymentMandate), x402, EIP-3009 transferWithAuthorization, EIP-2612 permits.
Layer 3

Money Movement

The signed mandate is dispatched to the matching rail. Fiat goes through Payouts.com's regulated network. Cards through the issuer. USDC and stablecoins on Base / Ethereum / Polygon / Arbitrum / Optimism.

Outputs
a settlement reference (tx hash, ACH trace number, card auth code), reconciled into the unified ledger row.
Standards
ACH NACHA, SEPA, Faster Payments, SWIFT MT103, Mastercard MoneySend, ERC-20 transfer, x402 HTTP.
The trace protocol

One trace ID.
Every rail. Forever.

The trace is the canonical identifier of an agent payment. It is generated at mandate time, anchored on Base, and carried by every downstream system — fiat, card, or crypto — so the audit is reconstructible from a single key.

What's in a trace

trace_id
32 bytes, keccak256(mandate_json). Globally unique. Anchored on Base.
mandate
the signed AP2 PaymentMandate or x402 EIP-3009 authorization.
actors
(company_id, principal_id, agent_id, counterparty_id).
policy_decision
which Spending Policy approved it, with the matched rule ID and the cap consumed.
rail
one of ach, sepa, fps, swift, card_push, card_virtual, usdc_base, usdc_eth, x402, internal.
settlement
the rail-native reference: tx hash, ACH trace #, card auth code, MoneySend transaction ID.
state_history
append-only log of proposed → authorized → submitted → settled / failed / refunded / reversed.
score_delta
the contribution this payment made to the Agent's on-chain score.
// One trace, regardless of railjs
trace = {
  trace_id:     "0xa3f1…7c",
  company_id:   "co_42",
  principal_id: "pr_9",
  agent_id:     "ag_7b",
  mandate:      { type: "PaymentMandate", sig: "0x…", anchor_tx: "0x9f2a…" },
  policy:       { rule: "per_call_max_50_usd", consumed: "12.40" },
  rail:         "usdc_base",   // or "ach", "card_virtual", "x402", …
  settlement:   "0x4d2…e1",    // or "ACH-2026-0512-000871", "MC-AUTH-71B"
  state:        "settled",
  score_delta:  +3
}
The scoring protocol

Agents earn reputation.
Counterparties read it before accepting payment.

Every Agent has a public 0–1000 score that decays without activity and grows with clean settled volume. Counterparties — MCP servers, x402 endpoints, marketplace vendors — query the score before accepting a 402, just like a card network checks a BIN.

SignalWeightDirection
Cumulative settled volume (USD-equiv, 90d)+250 max+
Successful settlements / proposed mandates ratio+200 max+
Disputes / chargebacks (rolling 180d)−300 max-
Mandate revocations / fraud flags−250 max-
Principal KYB tier (Tier 1 / 2 / 3)+150 max+
Agent age (days since identity issuance)+100 max+
Policy compliance (declared vs actual spend pattern)+150 max+
Inactivity decay (no settled volume in 30d)−2 / day-

The score is published as an ERC-1155 metadata field on the Agent's identity NFT and exposed as an MCP tool: agentwallet.score(agent_id) → { score, tier, last_updated, anchor_tx }. Counterparties can require a minimum score for an Agent to call their service.

ERC interop

Connects to every ERC standard agents actually need.

The protocol is opinionated about what an agent needs, and unopinionated about which chain it lives on. Every relevant ERC is mapped to a primitive in the Mandate → Policy → Rail pipeline.

StandardWhat it gives the agentWhere the protocol uses it
ERC-20USDC, EURC, USDT and 50+ stablecoin balancesCrypto wallet, x402 settlement
ERC-721Unique identity NFT per Agent — portable across platformsAgent identity, score metadata anchor
ERC-1155Multi-token (license, capability, credential) bundlesCapability grants, score history
EIP-712Typed-data signing (human-readable signatures)All AP2 mandates and policy decisions
EIP-2612ERC-20 permit — no separate approve txGas-efficient stablecoin spends
EIP-3009transferWithAuthorization — signed transfer over HTTPx402 settlement primitive
ERC-4337Account abstraction — gas sponsorship, batched opsGas-sponsored agent transactions
ERC-5267EIP-712 domain discoveryCross-platform mandate verification

Fiat · Card · Crypto — same protocol, different rail.

The differences your engineers actually care about. Same Mandate. Same Policy. Same trace. Different settlement physics. Pick the right rail per call — or let the policy engine route automatically.

PropertyFiat (ACH / SEPA / FPS / SWIFT)Card (virtual Mastercard / push-to-card)
Settlement timeseconds (FPS) → 3 days (ACH/SEPA) → 1–5d (SWIFT)authorization in ms; capture in 1–3 days
ReversibilityACH return up to 60d; SEPA recall windowchargebacks up to 120d
Per-call cost$0.20 – $5 fixed2.0 – 3.5% + fixed interchange
Identity surfacebank account + name matchPAN, expiry, CVV, 3DS challenge
M2M friendlinessbatch-friendly; not per-API-callgood for SaaS subscriptions; bad for sub-cent
Geo coverage208 countries via Payouts.com~210 countries, MC + push-to-card
PropertyCrypto (USDC / EURC / x402 / on-chain)How the protocol abstracts it
Settlement time~2s on Base, ~12s on Ethereum mainnetMandate → settlement state in single API call
Reversibilityirreversible — refund is a new txTrace links original + refund as one logical record
Per-call cost~$0.001 (Base L2) → $1+ (mainnet)Gas sponsored by AgentWallet, capped per agent
Identity surfaceEOA address + EIP-3009 signatureWrapped by Agent identity NFT + Principal mandate
M2M friendlinessnative — sub-cent payments per API callx402 is the default for agent ↔ agent / agent ↔ API
Geo coverageglobal, no banking rails requiredOff-ramp to fiat in 14 countries via Payouts.com
The programming model

Every Principal. Every Agent.
The same six verbs.

The protocol exposes the same six verbs regardless of identity tier. The Principal API is REST + OIDC. The Agent API is the same shape, plus an MCP wrapper so agents call them as tools.

~/agentwallet — six verbstypescript
// 1. Identity
company.create({ legal_name, kyb_docs }) → company_id
company.principal.create({ company_id, name, email, webauthn_pubkey }) → principal_id
principal.agent.create({ principal_id, name, policy }) → { agent_id, pak, mcp_url }

// 2. Mandate (signed by the Principal's WebAuthn key)
principal.mandate.intent({ agent_id, intent, max_value, expires_at, sig }) → mandate_id
agent.mandate.cart({ items, counterparty }) → cart_id   // requires Principal sig if > cap
agent.mandate.payment({ cart_id, rail }) → payment_mandate_id

// 3. Policy (evaluated automatically, no call needed; query-able)
policy.evaluate({ mandate, agent_id }) → { decision: "allow"|"deny"|"escalate", rule, cap_consumed }
policy.upsert({ scope: "company"|"principal"|"agent", rules: [...] })

// 4. Rail (the only verb that touches money)
rail.dispatch({ payment_mandate_id }) → { rail, settlement_ref, trace_id, state }

// 5. Trace (single source of truth for audit)
trace.get(trace_id) → full trace object (mandate + policy + rail + state history)
trace.list({ agent_id?, principal_id?, company_id?, since, until }) → trace[]

// 6. Score (read-only, public)
agent.score(agent_id) → { score, tier, last_updated, anchor_tx }

All six verbs are exposed identically as MCP tools, REST endpoints, and TypeScript SDK methods. The Agent's MCP endpoint at /mcp/agent is scoped to one row in agent_identities — the agent literally cannot call beyond its own identity tier.

The mandate chain

Intent → Cart → Payment

The Agent Payments Protocol uses ES256-signed JWS tokens to make agent intent cryptographically provable. A complete payment is a chain of three signed mandates: Intent → Cart → Payment. Every signature is verified against the agent's published JWK and audited.

IntentMandate

"I authorize agent X to spend ≤ $Y for purpose Z"

Principal-signed

CartMandate

Specific items + merchant + total + intent_id

Agent-signed (under intent)

PaymentMandate

Rail + recipient + cart_id + final amount

Agent-signed (under cart)
PaymentMandate JWS payloadjws
{
  "iss": "did:agentwallet:ag_01J…",   // agent identity
  "sub": "[email protected]",
  "aud": "https://vendor.example/ap2/v1",
  "iat": 1746345600,
  "exp": 1746349200,                  // 1 hour
  "jti": "01J9…uuidv7",
  "mandate_type": "payment",
  "intent_id": "01J9intent…",
  "cart_id":   "01J9cart…",
  "rail":      "x402_usdc_base",
  "amount":    { "value": "125.00", "currency": "USD" },
  "recipient": { "address": "0xabc…" }
}
// Header
{ "alg": "ES256", "kid": "ag_01J9…#k1", "typ": "JWS" }

MCP & discovery

Per-agent tool catalog.

Each agent has its own MCP endpoint at /mcp/agent, scoped to one row in agent_identities. Drop the URL and the agent's pak_… key into any MCP-compatible client and the agent is online.

ToolWhat it doesLimit-gated?
wallet.statusComputed balance + holds + headroom
wallet.send_paymentInitiate fiat payout via available railyes
wallet.send_usdcUSDC transfer on Base via CDPyes
wallet.x402_paySign EIP-3009 auth for x402 endpointyes
wallet.list_activityRecent transactions across sources
wallet.request_approvalSurface pending action to Principal widget
card.detailsLast4 + balance + status (no PAN)
card.recent_authorizationsCard-level transactions only
comms.send_smsOutbound SMS via telephony provider
comms.send_emailOutbound email via email provider
comms.list_inboxUnread inbound messages
identity.who_am_iAgent metadata + AP2 public key
identity.sign_mandateES256 sign an AP2 IntentMandate
identity.discoverResolve another agent via .well-known
approvals.pollCheck status of a pending approval

Discovery · /.well-known/agent.json

The ACP standard defines how an agent publishes its capabilities, rails, and AP2 public keys. Merchants and counterparty agents fetch this before transacting.

capabilities documentjson
{
  "agent_id": "did:agentwallet:ag_01J9…",
  "name": "Acme Procurement Agent",
  "public_keys": [
    { "kid": "ag_01J9…#k1", "alg": "ES256",
      "jwk": { "kty": "EC", "crv": "P-256", "x": "…", "y": "…" }
    }
  ],
  "endpoints": {
    "ap2_inbound": "https://agent.acme.example/ap2/v1/payment",
    "a2a":         "https://agent.acme.example/a2a/v1"
  },
  "accepts": ["x402_usdc_base", "spt", "ach"],
  "erc8004": { "chainId": 8453, "tokenId": "42" }
}

Three external rails. One mandate per payment.

Outbound payments leave through one of three protocol stacks. The choice is decided by the merchant's capability advertisement (or by the rail the agent's policy permits).

x402 over EIP-3009

The merchant returns HTTP 402 Payment Required with a payment offer. The agent signs an EIP-3009 transferWithAuthorization (USDC on Base) for the offered amount and replays the request with the signed authorization in the X-PAYMENT header.

x402 round triphttp
// 1. Agent's first request
GET /api/dataset/v1 HTTP/1.1
Host: data.example
Authorization: Bearer pak_…

// 2. Merchant returns 402 with offer
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
  "x402": {
    "amount": "0.10", "currency": "USDC",
    "network": "base",
    "recipient": "0xabc…",
    "validBefore": 1746349200
  }
}

// 3. Agent signs EIP-3009 (CDP-managed key) and replays
GET /api/dataset/v1 HTTP/1.1
Host: data.example
X-PAYMENT: eyJ0eXAiOiJ4NDAyIiwiYWxnIjoi…   // signed auth

Shared Payment Tokens (SPT)

For card-rail payments to merchants that accept network-issued single-use tokens. The agent requests an SPT, scoped to a single use and to a specific merchant + amount. The token expires; replay is structurally impossible.

SPT issuancetypescript
const spt = await issuer.paymentTokens.create({
  type: 'card',
  card: { vault_alias: card.vaultAlias },
  single_use: true,
  restrictions: {
    amount: cartTotalCents,
    merchant: merchantId,
    expires_at: Math.floor(Date.now() / 1000) + 300
  }
});

// Persist for audit
await db.insert(acpSptIssuances).values({
  agentIdentityId, tokenId: spt.id,
  cartMandateId, amountCents: cartTotalCents
});

Why a protocol — not just an API.

An API is a wrapper around money movement. A protocol is a contract about who moved it, why they were allowed to, and how it can be replayed. Without that contract, agent commerce is unauditable. With it, an Agent can transact with another Agent at any company on any rail, and both sides can prove what happened.

Build on the protocol.