Docs · Start · Markdown
Give your agent a budget it can't raise
Your owner funds a budget in USDC for one service. Your agent gets its own spending key, which holds no money and pays no network fee. Each paid request carries a signed payment slip with the running total, the service checks it in milliseconds, and collects later in one transaction. Only the owner can fund or top up a budget.
Using Claude, Cursor or another assistant? One message
Open Connect an assistant, copy the message, and send it to your assistant:
Set up Flying Money payments for me. Read https://useflyingmoney.vercel.app/agent.md and follow it. My wallet is 0x…
The assistant reads /agent.md, adds the MCP server (which makes its own spending key), tells you its spending address, and asks you for a budget when a paid service needs one. You approve in Requests. Nothing to run in a terminal.
Building your own agent
The rest of this page is for developers using the TypeScript SDK directly.
- Capped. A stolen agent key can spend at most what's left, at that one service.
- Crash-safe. A timeout never raises what the agent owes. It resends the same slip; it never signs a higher one.
- Standard. Plain HTTP 402 "Payment Required", a TypeScript SDK and an MCP server.
1. Make a spending key where the agent runs
npx @flying-money/client keygen --out .env
This writes AGENT_KEY=… to .env and prints only the address. The key holds no money, so a leak can cost at most what's left on the budgets given to it, at those sellers only.
2. Ask your owner for a budget
Your owner opens Fund an agent, picks the seller, pastes the agent's address as who can spend, and sets an amount and an end date. They send you the budget id (0x…, 32 bytes; the SDK calls it a certificate). Or configure owner and call requestBudget to ask for one.
3. Pay with fetch
import { createFlyingMoneyClient, fileStore } from '@flying-money/client'
import type { Hex } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
const fm = createFlyingMoneyClient({
chains: ['arbitrum-sepolia'],
spender: privateKeyToAccount(process.env.AGENT_KEY as Hex),
store: fileStore('.flying-money.json'), // durable outbox: survives crashes
certificates: [process.env.AGENT_CERTIFICATES as Hex],
maxPricePerRequest: 50_000n, // 0.05 USDC; refuse anything pricier
})
const res = await fm.fetch('https://oracle.example/v1/tea-price?city=Luoyang')
console.log(res.status, await res.json())
console.log(fm.status()) // remaining budget per certificate
fm.fetch behaves like fetch. When the server answers 402 with a Flying-Money-Offer header, the client signs a payment slip for the price, saves it, and retries with a Flying-Money-Note header. You get the paid response.
Rules the client follows (and your agent should too)
- You can only pay the seller named on the budget, and never more than its amount in total.
- A network failure never raises what you owe. On a timeout the client resends the same slip. It never signs a higher one because of a failure.
- Never ask a user for their main wallet key. Agents only ever need their own spending key.
- Amounts are integers in USDC base units (6 decimals):
10_000nis 0.01 USDC.
Errors you may see
| Error | Meaning |
|---|---|
NoCertificateError |
None of your budgets pays this seller on this network, has enough left, or lasts long enough |
PriceTooHighError |
The price is above maxPricePerRequest |
InsufficientBudgetError |
Paying would go past the budget's amount |
PaymentRejectedError |
The seller refused the slip without taking payment |
PendingUnresolvedError |
A previous slip hasn't been confirmed yet; it will be resent, never re-signed |
Error codes
Every SDK error has a stable code and a docUrl pointing here.
no_certificate
None of your budgets pays this seller on this chain, has enough left, or lasts long enough. With an owner configured, ask for one (requestBudget, or fm_request_budget in MCP).
price_too_high
The price is above your ceiling: maxPricePerRequest, or this call's own maxPrice. Nothing was signed.
insufficient_budget
Paying would go past the budget's amount. Ask your owner to top it up.
payment_rejected
The seller refused the slip without taking payment. Nothing is owed for it.
pending_unresolved
A previous slip hasn't been confirmed yet. It will be resent as is, never re-signed.
Using Claude or another MCP agent? Skip the code: Connect an assistant, or see the MCP server reference.
See also: Client reference · Protocol · Guarantees