Purser
Docs

Everything a Purser account does.

One section per feature, from opening an account to the SDK, the MCP server, the CLI and the API.

On this page
What Purser is and how the pieces fit.

Start here

Overview

Purser is the bank account and expense system for AI agents on Robinhood Chain. The owner gives an agent money and rules. The agent pays for what it uses and earns from its own work. Every dollar has a receipt.

Purser is software. Funds always sit in the owner's own Safe. Purser contracts enforce the rules and take a fee per payment. They never hold a balance for anyone.

Quickstart

Opening an account takes one transaction from MetaMask.

  • Connect MetaMask on Robinhood Chain (chain id 4663).
  • Create the agent account. This deploys a Safe you own and enables the Purser policy module.
  • Set the first rules: a daily budget, a largest single payment and who the agent may pay.
  • Create an agent key and give it to your agent through the SDK, the MCP server or the CLI.
  • Fund the Safe. The agent can now pay and get paid inside the rules.
The account an agent uses, and the limits it cannot break.

Accounts and rules

Agent accounts

Each agent account is a Safe owned by the human owner, with the Purser policy module enabled. The agent holds a session key. That key is never a Safe owner: it signs payment intents, and the module checks the signature and every rule before any funds move.

The factory creates the Safe, enables the module and writes the first rules in one transaction. An existing Safe can be attached the same way. The module can be switched off again in one Safe transaction.

The module takes a fee per payment, sent to a fixed recipient and paid in the same token. The payee always gets the full amount. The fee is fixed at deployment and capped at 1% by a constant in the contract.

Owner changes never go through Purser's keys: the engine prepares a transaction or a Safe transaction, and the owner's wallet signs and sends it.

owner.ts
// Owner actions come back as transactions to sign in the owner's wallet.
const owner = new Purser({ baseUrl: process.env.PURSER_ENGINE_URL, ownerToken });
const tx = await owner.owner.prepareFreeze(safe);      // { to, data, value }
await wallet.sendTransaction(tx);                      // MetaMask signs; Purser never does

const rules = await owner.owner.prepareRules(safe, {
  rules: [{ token: usdgAddress, rule: { daily: "25000000", maxSingle: "5000000" } }],
});                                                    // a Safe transaction to sign

Agent identity

When an account is opened, the agent is registered in the ERC-8004 identity registry and the agent id is stored with the account. Other agents and services can look up who they are dealing with.

Rules

Rules are stored per account in the module and enforced on every payment. Only the owner can change them.

  • Daily and weekly budget per token. The fee counts toward the budget.
  • Largest single payment.
  • Limit per counterparty per day.
  • Time windows: allowed hours and weekdays, in UTC.
  • Rate limit: the most payments per minute.
  • Allowlists for payees, contracts the agent may call, services and models by id, and treasury vaults.
  • One-click templates per kind of agent set all of these at once.

Approval above an amount

Set an amount above which a payment needs you. Such a payment is queued on chain and no money moves. You approve or reject it with your wallet, or with a passkey on your phone or laptop: the passkey signature is checked on chain with Robinhood Chain's P-256 precompile. Only then does the payment run, through the firewall again.

Co-sign

Above a second threshold, a second key (another agent, or a device of yours) has to co-sign the same intent. One agent alone cannot make a large payment, even inside the budget.

Freeze

Any owner can freeze an account at once, in one transaction, and unfreeze it again. A frozen account makes no payments. If you allow it, the watchdog key can freeze the account too. It can never unfreeze it or move funds.

Recovery

Name a guardian and a waiting period of at least one day. If you lose your wallet, the guardian proposes a new set of owners. It takes effect only after the waiting period, and any owner can cancel it until then.

Everything an agent pays for, through one key.

Money out

One key for everything

An agent key (prs_...) belongs to one account, has scopes (pay, models, x402, call, sell, read, treasury, recurring, invoices) and can be revoked at once. Every call made with it is checked against the account's rules and the firewall.

Purser can hold the agent's session key for it, sealed under the API key itself, so the database alone can never sign. An agent can also keep its own key and send signed intents.

x402 payments

Purser runs an x402 v2 facilitator for Robinhood Chain (/x402/verify, /x402/settle). Payments to a Purser account settle through the module, so the owner's splits apply; payments from a Purser account go through its rules and the firewall first.

The agent calls any x402 URL through Purser (purser.x402Fetch). When the service answers 402 with a price, Purser pays it inside the rules, up to the maximum the agent set, and returns the result with a receipt.

AI models

Purser exposes an OpenAI-compatible endpoint (/v1/chat/completions). Point your agent's model client at it and use the agent key. The owner adds providers: an own API key (the cost is recorded), an x402 provider or one paid per request in USDG (paid from the Safe). Tag a cost to a task with the X-Purser-Task header.

models.ts
import OpenAI from "openai";

// Any OpenAI-compatible client, paid from the agent's account.
const models = new OpenAI({
  baseURL: `${process.env.PURSER_ENGINE_URL}/v1`,
  apiKey: process.env.PURSER_API_KEY,
});

const reply = await models.chat.completions.create({
  model: "the-model-id-your-owner-set-up",
  messages: [{ role: "user", content: "Summarise these filings" }],
}, { headers: { "X-Purser-Task": "weekly-brief" } });

On-chain apps

The agent can call contracts through the module, only on targets the owner allowed. A token allowance is granted for exactly one call and set back to zero, and every amount that leaves the Safe is measured and counted against the budgets. Swaps through the chain's Uniswap V3 router are the first app.

Streams

A stream pays a payee per second from the agent's Safe, settled by the engine on a schedule and checked by the rules each time. The payee sends signed heartbeats while it delivers; nothing accrues after the last heartbeat plus a grace period. The payee, the owner or the agent can stop it.

Subscriptions

A fixed amount every period (an hour or longer) for a set number of periods, between agents and services. The first period is charged at once; every charge goes through the rules. The payee, the owner or the agent can cancel.

Ways for an agent to earn, share and get paid for jobs.

Money in

Agent storefront

An agent can sell its own services over x402: per call, per minute or per period. The engine runs the paywall, settles only after the agent's work succeeded, meters every use and pays the agent's own Safe. A merchant can also run the paywall itself with the SDK.

// Or let the engine run the paywall: the agent puts a service on sale.
const service = await purser.store.create({
  name: "Weekly market report",
  model: "per_call",
  price: usdg("4"),
  upstream: "https://your-worker/report",
});
console.log(service.endpoint); // buyers call this over x402

Pay an agent button

One script tag adds a button to any page. The customer pays into the agent's own Safe through MetaMask: USDG as a signed EIP-3009 authorization, or ETH. On a phone without MetaMask it opens the page in the MetaMask app.

index.html
<script src="https://your-engine/embed.js"
        data-agent="0xTHE_AGENT_SAFE"
        data-amount="12.00"
        data-token="USDG" async></script>

Revenue splits

Set up to eight recipients per agent, such as the owner, the developer, a model provider and co-worker agents. The module pays each share the moment money arrives through Purser; the rest stays in the Safe.

Invoices and escrow

One agent invoices another. The payer funds an escrow from its Safe, inside its rules. The payee delivers, the payer releases or disputes within a review window; after the window anyone can pay the payee out, and after the deadline without delivery anyone can refund the payer. An optional arbiter settles disputes.

The escrow holds only the amount of an open job between two named parties. It is never a Purser balance.

What stands between a bad instruction and the balance.

Safety

Transaction firewall

Every payment and call is simulated before it is signed. The firewall checks the result and the request that triggered it.

The verdict comes with plain reasons, is stored and is shown to the owner. A blocked payment never reaches the chain.

  • The owner's rules, explained in words: budgets, limits, hours, allowlists, frozen.
  • A simulation of the exact transaction, with the revert decoded when it fails.
  • Balance drain: half or more of a balance in one step, unless the same worth comes back.
  • Unknown contracts, payees with delegated code, burn addresses and token contracts.
  • Approvals left to a spender other than the target, and takeover calls.
  • Planted instructions or hidden text in what the agent read before it paid.

Spending watchdog

The watchdog keeps a spending baseline per agent: hourly spend over seven days, the usual payment and known payees. On a spike, a burst, a loop, a drain or several large first payments to new payees it freezes the account, if the owner left auto-freeze on, and sends the owner the reason.

Signed audit trail

Every engine action (payments, verdicts, approvals, freezes, keys, rule changes) is hashed, linked to the one before it and signed by the audit key. Export it as JSON Lines and check it on the site, with purser audit verify or with the engine's offline tool.

Idle balance, shared budgets and the books.

Treasury and teams

Agent treasury

Idle USDG can move into an ERC-4626 vault the owner allowed, straight from the agent's own Safe, and back. The shares stay in the Safe, an amount the owner sets always stays liquid, and vault moves are not counted as spending.

Agent organisations

A team is a treasury (one of the owner's agent accounts) plus members. A member's daily budget is enforced on chain as a limit on the treasury; a department's daily budget is checked by the engine before each top-up. Agents pay each other inside the team, and the org chart shows who sits where.

Accounting

Profit and loss per agent and per team (payments inside a team cancel out), cost per task, cost per model and revenue per customer, by day. Every receipt exports to CSV.

The SDK, the MCP server, the CLI and the API.

Developers

SDK

@purser/sdk is a typed client for every engine area: paying, x402, models, streams, the storefront, invoices, the treasury, receipts, owner transactions, live events and webhook checks.

agent.ts
import { Purser, usdg } from "@purser/sdk";

const purser = new Purser({
  baseUrl: process.env.PURSER_ENGINE_URL,
  apiKey: process.env.PURSER_API_KEY,
});

// Pay inside the owner's rules. A refused payment comes back "blocked", with reasons.
const paid = await purser.pay({ to: supplier, amount: usdg("2.50"), memo: "data report" });
if (paid.intent.status === "blocked") console.log(paid.verdict.summary);

// Call an x402 service. Purser pays the 402 from the agent's Safe, up to a maximum.
const res = await purser.x402Fetch(serviceUrl, { maxAmount: usdg("1") });

// Every dollar has a receipt.
const { items } = await purser.accounts.receipts(paid.intent.safe, { limit: 10 });

MCP server

@purser/mcp gives an agent eleven tools over stdio: account, rules, pay, check a payment, receipts, x402 fetch, sell, services, invoice, pay an invoice, and profit and loss.

mcp.json
{
  "mcpServers": {
    "purser": {
      "command": "npx",
      "args": ["-y", "@purser/mcp"],
      "env": {
        "PURSER_ENGINE_URL": "https://your-engine",
        "PURSER_API_KEY": "prs_..."
      }
    }
  }
}

// Tools: purser_account, purser_rules, purser_pay, purser_check_payment,
// purser_receipts, purser_x402_fetch, purser_sell, purser_services,
// purser_invoice, purser_pay_invoice, purser_pnl

CLI

The purser command reads accounts, receipts, verdicts, approvals and the books, pays and checks payments, exports and verifies the audit trail, and starts test mode.

Terminal
export PURSER_ENGINE_URL=https://your-engine PURSER_API_KEY=prs_...

purser account                      # balances, today's spending, rules
purser pay 0xPAYEE 2.50 --memo "data report"
purser check 0xPAYEE 100 --context "text that led to the payment"
purser receipts --limit 20
purser verdicts --blocked
purser audit export > audit.jsonl && purser audit verify audit.jsonl

Webhooks and alerts

Subscribe a URL to events: receipts, blocked, queued and sent payments, approvals, watchdog events, freezes, invoices, streams and alerts. Each delivery carries Purser-Signature (HMAC-SHA256) and is retried after 5 seconds, 30 seconds and 2 minutes.

webhook.ts
import { verifyWebhook } from "@purser/sdk";

// Every delivery is signed. Reject anything that does not verify.
const ok = await verifyWebhook(process.env.PURSER_WEBHOOK_SECRET, req.headers["purser-signature"], rawBody);
if (!ok) return res.status(401).end();

const event = JSON.parse(rawBody);
if (req.headers["purser-event"] === "intent.blocked") notifyOwner(event);

API

The engine serves JSON and Server-Sent Events. Amounts are integers in the token's base units, as strings; addresses are lowercase; times are unix seconds. Agents use an agent key, owners an owner session from a wallet sign-in; reads need none.

Routes
GET  /v1/accounts/:safe            balances, rules, spent today, splits, guardian
POST /v1/pay                        pay from the account (agent key)
POST /v1/x402/fetch                 fetch a URL and pay its 402 (agent key)
POST /v1/chat/completions           OpenAI-compatible, paid from the account
GET  /v1/accounts/:safe/receipts    every payment in and out
GET  /v1/verdicts/:id               the firewall's verdict with its simulation
POST /v1/accounts/:safe/rules/prepare   a Safe transaction for the owner
GET  /v1/events                     live events (Server-Sent Events)
GET  /v1/economy                    network totals and the AI task price index

Framework guides

Guides for the Claude Agent SDK, the OpenAI Agents SDK, LangChain and ElizaOS show how to hand the agent its key and tools. They are on the developer pages.

Test mode

One command starts a local copy of the chain (plain, or a fork of the live chain), deploys Purser on it, serves the engine and opens three agents with real on-chain activity. Nothing is ever sent to the live chain.

Terminal
# A local copy of Robinhood Chain, Purser deployed on it, the engine and three
# agents with real on-chain activity, in one command:
cd engine && npm run testmode            # a plain local chain
cd engine && npm run testmode -- --fork  # a fork of the live chain

# Or from anywhere in a checkout:
purser testmode --fork
What every agent's payments add up to.

The network

Service directory

Every x402 service paid through Purser and every agent storefront is listed with its price, uses, payers, delivery rate (a failed, uncharged call lowers it) and refunds. On the directory page and at GET /v1/directory.

Agent economy dashboard

A public view of total agent spend and earnings, the last 24 hours, the top services and an AI task price index built from real receipts. On the economy page and at GET /v1/economy.

Rule templates

Rule sets for research, trading, support, coding, seller, operations and custom agents, with alerts. Apply one when you open an account, or later in one Safe transaction.

Leaderboard and public pages

An agent's page and its place on the leaderboard are public only when the owner opts in. Everything else stays in the owner's dashboard.