# Flying Money: full docs (spec v1.4.1) Site: https://useflyingmoney.vercel.app · Source: https://github.com/raldblox/flying-money · Machine-readable deployments: https://useflyingmoney.vercel.app/.well-known/flying-money.json ## Deployments | Chain | Chain id | FlyingMoney | USDC | Status | |---|---|---|---|---| | Arbitrum Sepolia | 421614 | 0xb9ae3158f9cA841d9Da3C3725014D8352ca967F2 | 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d | testnet | --- # 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](/app/connect), 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](/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](/app/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 ```bash 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](/app/give?for=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` ```ts 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_000n` is 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](/app/connect), or see the [MCP server](/docs/mcp) reference. See also: [Client reference](/docs/client) · [Protocol](/docs/protocol) · [Guarantees](/docs/guarantees) --- # People & shops Flying Money is for anyone who spends on your behalf, not only agents. A parent, an employer or a friend locks a budget **for one place**; the holder pays by showing a QR code. The holder needs no crypto wallet and pays no fees. Only the giver needs USDC. After the end date, the giver can take back whatever wasn't spent. ## Examples - **Regulars' tabs:** 20 USDC at the café you visit every morning. - **Allowances:** lunch money that only works at the school canteen. - **Teams:** fuel money for a field worker, at one station. - **Gifts:** a gift for one shop, sent as a link. ## For the giver 1. Open [Give a budget](/app/give?for=person), choose the shop (or open the shop's own "get a budget for this shop" link), set the amount and the end date, and give it. 2. Tap **Give it to someone** and send the link or QR privately. The spending key travels only in the link's `#fragment`, which browsers never send to a server. **What you can and can't control:** you choose where, how much and how long; you can top up, extend or simply not renew. You **can't** freeze or cancel a budget early, and that is deliberate: a shop can accept a payment instantly, even offline, only because the money can't be pulled back. ## For the holder: the wallet (`/wallet`) - Open the link, choose a PIN. The key is stored on this phone only, encrypted with your PIN. - To pay: tap **Pay**, scan the till's price code, check "Pay 3.50 USDC to Lantern Café, remaining after: 16.50", enter your PIN, and show your code. - After the till says **Accepted**, tap *Yes, the shop accepted it*. Until you do, the wallet shows the same code again and won't start another payment. - Add the wallet to your home screen and export a backup. Browsers may clear data for sites you don't install. A lost phone never loses money: the giver can take back what's left after the end date. A leaked key can only spend what's left, at that one shop. ## For the shop: the till (`/shop`) Open a till in three steps: your shop's name, the wallet that receives sales, done. The till is remembered on that device under **Your tills**. The ready screen also gives you a **get a budget for this shop** link and QR to share with regulars and their families: it opens the gift form with your shop filled in, marked unchecked until they confirm your address with you. | Status | When | What it means | |---|---|---| | **Accepted: covered by a checked budget** | The till has checked this budget on the blockchain before, and the payment passes | Backed by money set aside for your shop until the end date. Collect before then | | **Accepted at your own risk: not checked yet** | Offline, and this till has never seen this budget | Not covered. Your own risk, capped by your first-visit limit (default 5 USDC). Checked when you reconnect | | **Rejected** | Wrong shop, not enough left, expired, bad signature | Don't hand over the goods | **Collect** sends one transaction that collects everything accepted so far. Anyone may send it; the money only goes to your shop's address. Privacy: payments are public on the blockchain but not linked to names. Anyone can see that some address paid a café, but not who. --- # Protocol ## Objects A **certificate** lives on one chain in the FlyingMoney contract: | Field | Meaning | |---|---| | `funder` | Locked the money; gets the remainder back after expiry | | `payee` | The only address that can ever receive the money | | `spender` | A secp256k1 key that signs notes. It holds nothing and sends no transactions | | `faceValue` | The budget, in USDC base units (6 decimals) | | `redeemed` | How much has been paid out so far | | `expiresAt` | Unix seconds | `id = keccak256(abi.encode(chainId, contract, funder, funderNonce))`. A **note** is an EIP-712 signature by the spender over a *running total*: ``` domain = { name: "FlyingMoney", version: "1", chainId, verifyingContract } Note(bytes32 certificateId, uint256 cumulative, bytes32 memo) ``` `cumulative` only ever grows. Redeeming a note pays `cumulative − redeemed` to the payee. `memo` is the request id, so one note maps to one request. ## Wire format Every object on the wire is `fm1.` + base64url(JSON), with integers as decimal strings. Headers: | Header | Direction | Content | |---|---|---| | `Flying-Money-Offer` | seller → buyer, with `402` | `{ scheme: "flying-money", v: 1, price, minRemainingLifetime, accepts: [{ chainId, contract, token, payee }], memoHint? }` | | `Flying-Money-Note` | buyer → seller | `{ v: 1, chainId, contract, certificateId, cumulative, memo, sig }` | | `Flying-Money-Receipt` | seller → buyer | `{ certificateId, requestId, status, accepted, consumed, reserved, credit, remaining, expiresAt }` | | `Flying-Money-Reason` | seller → buyer, with `402` | why the note was refused: `insufficient`, `wrong-payee`, `expiring`… | Headers are at most 2,048 bytes. A signed note (the “payment slip” in the app) is about 544 characters, small enough for one QR code at error-correction level M. ## Seller algorithm The seller keeps, per certificate: `accepted` (the best total it holds), `consumed` (value served), `reserved` (in flight), and one outcome per `requestId`. 1. Parse the note. Unknown chain or contract → `402` offer. 2. Read the certificate (cached; payee and spender never change). 3. A known `requestId` returns its stored outcome, after an ECDSA check (replays are free). 4. Check payee, `closed`, remaining lifetime ≥ `minRemainingLifetime`, signature, `cumulative ≤ faceValue`. 5. Atomically: `consumed + reserved + price ≤ max(accepted, cumulative)`, then `reserved += price` and `accepted = max(accepted, cumulative)` (no overspend under concurrency). 6. Serve. Success → `consumed += price`; failure → the price becomes **credit**. 7. Return a receipt. The redeemer only ever redeems served value, before `expiresAt − 30 min`. ## Buyer algorithm Per certificate, the buyer durably stores `accepted`, `consumed` and at most one pending note. 1. On a `402`, pick a certificate for that payee and chain with enough left and enough lifetime. 2. **If a note is pending, resend exactly that note** until a final receipt. 3. Sign `next = max(accepted, consumed + price)` with a fresh `requestId`. Refuse if `next > faceValue`. 4. **Save the pending note, then send it.** 5. On a timeout, resend the same note. Never sign a new one. 6. On a receipt, update `accepted` and `consumed` and clear the pending note. **No growth on failure:** a network failure, timeout or crash never makes the spender sign a higher total. ## At a counter The same objects travel as QR codes: the till shows a price QR (an offer with `memoHint` = order id), and the customer's phone shows a payment-slip QR (the signed note) with `memo = keccak256(orderId)`. Tills report **GUARANTEED** (shown as "Accepted: covered by a checked budget"; certificate verified on-chain by this till, note passes the seller algorithm), **UNVERIFIED** (shown as "Accepted at your own risk: not checked yet"; offline, never-seen certificate, capped by a first-visit limit) or **REJECTED**. See [People & shops](/docs/shops). --- # Contract (`FlyingMoney.sol`) Solidity 0.8.24 with OpenZeppelin 5.1.0. **No owner, no admin, no pause, no upgrade, no fee.** One settlement token per deployment (the chain's Circle USDC), fixed in the constructor. Addresses for every chain: [Deployments](/chains) and [`/.well-known/flying-money.json`](/.well-known/flying-money.json). ## Functions | Function | Who | What | |---|---|---| | `issue(payee, spender, faceValue, expiresAt) → id` | anyone (the funder) | Pulls `faceValue` USDC and opens a certificate. Lifetime 1 hour to 365 days. `spender` must differ from the funder and the payee | | `topUp(id, amount)` | funder | Adds to the face value | | `extend(id, newExpiresAt)` | funder | Moves expiry later (never earlier) | | `redeem(id, cumulative, memo, signature) → paid` | anyone | Pays `cumulative − redeemed` to the payee | | `redeemMany(SignedNote[]) → totalPaid` | anyone | Batch redeem; bad notes are skipped with `NoteSkipped`, not reverted | | `reclaim(id) → refunded` | funder, after expiry | Returns the unredeemed remainder and closes the certificate | | `getCertificate(id)` | view | The certificate struct | | `noteDigest(id, cumulative, memo)` | view | The EIP-712 digest a spender signs | | `totalOutstanding()` | view | Σ (faceValue − redeemed) over open certificates | Spenders are verified with **ECDSA only** (`ECDSA.tryRecover`, low-s). There is no ERC-1271: contract signatures can be revoked, so a note accepted offline could stop being valid. ## Events `CertificateIssued(id, funder, payee, spender, faceValue, expiresAt)` · `CertificateToppedUp(id, amount, newFaceValue)` · `CertificateExtended(id, newExpiresAt)` · `NoteRedeemed(id, cumulative, paid, memo, redeemer)` · `NoteSkipped(id, cumulative, reason)` · `CertificateReclaimed(id, refunded)` `NoteSkipped` reasons: 1 unknown, 2 closed, 3 expired, 4 exceeds face value, 5 nothing to redeem, 6 bad signature. ## Errors `InvalidParams` · `UnknownCertificate` · `NotFunder` · `Expired` · `NotExpired` · `Closed` · `ExceedsFaceValue` · `ExceedsCap` · `InvalidSignature` · `NothingToRedeem` · `UnsupportedToken` ## Launch caps Two immutable caps, set at deployment (0 = unlimited): - `maxFaceValue`: per-certificate cap. Mainnets: **100 USDC**. - `maxTotalOutstanding`: deployment-wide cap. Mainnets: **1,000 USDC**. There is no admin, so caps can never be raised, only superseded by a new deployment. Testnets are uncapped. ## Invariants (tested with Foundry, 256 runs × depth 50) - **I1** `redeemed ≤ faceValue` for every certificate. - **I2** Solvency: the contract's USDC balance equals `totalOutstanding`, the sum of `faceValue − redeemed` over open certificates. **I2b** `totalOutstanding ≤ maxTotalOutstanding` when the cap is set. - **I3** A payee receives exactly the highest valid total redeemed for its certificates. - **I4** A funder never pays out more than the face value it issued. - **I5** No certificate pays anyone other than its payee (redeem) or its funder (reclaim). - **I6** After reclaim, no further transfers happen for that certificate. - **I7** `redeemed` never decreases. Unit tests also cover ECDSA-only permanence (a spender address that later gains contract code changes nothing), high-s rejection, domain separation and key isolation. Details and gas numbers: [SECURITY.md](https://github.com/raldblox/flying-money/blob/main/docs/SECURITY.md). --- # Client SDK (`@flying-money/client`) The buyer side of the [protocol](/docs/protocol). It wraps `fetch`, answers `402` offers with signed notes, and keeps a **durable outbox** so crashes and timeouts never make the agent owe more. ## `createFlyingMoneyClient(config)` | Option | Type | | |---|---|---| | `chains` | `ChainKey[]` | Registry keys this agent may pay on, e.g. `['arbitrum-sepolia']` | | `preferredChains` | `ChainKey[]` | Order to use when a seller accepts several chains | | `spender` | `LocalAccount` | The agent's spending key (`privateKeyToAccount`) | | `store` | `ClientStore` | `fileStore(path)` for agents; `memoryStore()` for tests | | `certificates` | `Hex[]` | Certificate ids issued to this key | | `maxPricePerRequest` | `bigint` | Refuse offers above this (base units) | | `onPayment` | `(e) => void` | Called after each paid request (price, total, certificate) | | `onEvent` | `(e) => void` | Every step: sealed, retry, receipt, rejected | | `env` | `Record` | `RPC_` overrides | | `retry` | `{ attempts, backoffMs }` | Resend policy for the same pending note | Returns: ```ts import type { CertificateStatus } from '@flying-money/client' interface FlyingMoneyClient { fetch(input: string | URL, init?: RequestInit): Promise status(): CertificateStatus[] // face value, spent, credit, redeemed on-chain, remaining, expiry resolvePending(): Promise // resend any saved note (runs on start) refresh(): Promise // re-read certificates from the chain ready: Promise } ``` ## Stores - `fileStore(path)`: JSON file, written to a temp file, fsynced, then renamed over the old one. A crash leaves the old or the new state, never a torn file. - `memoryStore()`: for tests and short scripts. The store is the client’s outbox: a note is saved **before** it is sent, and resent byte for byte until the seller returns a final receipt. ## Counter payments (people & shops) `@flying-money/client/counter` is the browser-safe wallet logic behind `/wallet`: ```ts import { confirmCounterPayment, memoryCounterStore, prepareCounterPayment, } from '@flying-money/client/counter' ``` `prepareCounterPayment` signs `max(accepted, consumed + price)` with `memo = keccak256(orderId)`, saves it, and returns the QR text. Until the customer confirms or abandons it, the same QR is shown again and other orders are refused. ## CLI ```bash npx @flying-money/client keygen --out .env # writes AGENT_KEY, prints only the address ``` --- # Server SDK (`@flying-money/server`) The seller side of the [protocol](/docs/protocol). Put the middleware in front of paid routes; run the redeemer to collect. ## Hono middleware ```ts import { upstashStore } from '@flying-money/server' import { flyingMoney } from '@flying-money/server/hono' import { Hono } from 'hono' import type { Hex } from 'viem' const app = new Hono() const paid = flyingMoney({ accepts: ['arbitrum-sepolia'], payee: process.env.PAYEE_ADDRESS as Hex, price: () => 10_000n, // 0.01 USDC per request store: upstashStore({ url: process.env.UPSTASH_REDIS_REST_URL ?? '', token: process.env.UPSTASH_REDIS_REST_TOKEN ?? '', }), }) app.use('/v1/*', paid) app.get('/v1/tea-price', (c) => c.json({ price: 42, requestId: c.get('flyingMoney').requestId })) ``` What the middleware does for every paid request: 1. No note → `402` with a `Flying-Money-Offer` header (price, chains, contract, token, payee). 2. Parses the note, checks the chain and contract, reads the certificate (cached), checks payee, lifetime, signature and face value. 3. A `requestId` it has seen returns the stored outcome (replays are never charged twice). 4. Reserves the price atomically against the best note it holds (concurrent requests can't overspend). 5. Runs your handler. A response with status ≥ 400 counts as a failed service: the price becomes **credit** for the buyer. 6. Adds a `Flying-Money-Receipt` header with accepted / consumed / credit / remaining. **Your handler must be idempotent on `requestId`** (`c.get('flyingMoney').requestId`). For side effects, use `createIdempotency()`. ## Stores The store is the seller's authoritative ledger. It must be durable. | Store | Use | |---|---| | `upstashStore({ url, token })` | Serverless (Vercel), over HTTPS; Lua scripts make `begin`/`finish` atomic | | `redisStore(url)` | Any Redis over TCP | | `memoryStore()` | Tests and demos only; `onCommit` persists snapshots (the shop till uses IndexedDB) | ## Redeemer ```ts import { redisStore, startRedeemer } from '@flying-money/server' import type { Hex } from 'viem' import { privateKeyToAccount } from 'viem/accounts' const redeemer = startRedeemer({ chains: ['arbitrum-sepolia'], store: redisStore(process.env.REDIS_URL ?? 'redis://localhost:6379'), redeemerAccount: privateKeyToAccount(process.env.REDEEMER_KEY as Hex), // needs gas only policy: { minAmount: 100_000n, maxAgeSeconds: 3_600, safetyBeforeExpiry: 1_800 }, intervalMs: 60_000, }) ``` It redeems **only served value**, batches up to 20 notes into one `redeemMany`, records the transaction hash before broadcasting, and never rebroadcasts. Anyone may send the transaction; the money always goes to the payee. ## Discovery Sellers should serve `/.well-known/flying-money.json` with their `accepts[]` and price table, so agents can see prices without a 402 round trip. The Silk Road Oracle (`apps/oracle`) is a complete example, with `/openapi.json`. --- # MCP server (`@flying-money/mcp`) **Easiest:** send your assistant the one message on [Connect an assistant](/app/connect); it follows [/agent.md](/agent.md) and sets this up itself. An MCP server that lets any MCP-capable agent (Claude Desktop or Claude Code, Hermes-based agents, and others) pay APIs from a budget its owner gives it, and ask its owner for one. The budget's limits are enforced on-chain, not by the prompt. ## Start it with one setting The only setting you need is your owner's wallet address: ```bash claude mcp add flying-money -e FM_OWNER=0xYourWallet -- npx -y @flying-money/mcp ``` On first run the server makes its own spending key and keeps it in `~/.flying-money/agent-key` (readable only by you). It never prints or returns the key. A spending key holds no money: it can only spend budgets its owner funds, and only at the seller each one names. Call `fm_status` to see your spending address. Then, when a paid service needs a budget, `fm_request_budget` sends your owner a request (or gives you a link for them). The owner approves it in the app. Nobody copies keys or ids by hand. ## Tools | Tool | Input | Output | |---|---|---| | `fm_status` | none | Your spending address and budgets: network, seller, amount, spent, remaining, end date (read-only) | | `fm_explain` | none | Plain-language rules: who you can pay, how much, until when | | `fm_quote` | `url` | The price and accepted networks, without paying | | `fm_paid_fetch` | `url`, `method?`, `body?`, `max_price?` | The response and the payment. Refuses prices above `max_price` or the per-request cap | | `fm_request_budget` | service, amount, days, reason | Asks your owner for a budget; moves no money | | `fm_request_status` | `requestId` | Whether your owner approved it | There is **no tool to give or top up a budget**, and no tool returns the spending key. Only the owner can fund one, from their own wallet. ## Settings (environment) | Variable | | |---|---| | `FM_OWNER` | Your owner's wallet address, so you can ask them for budgets | | `FM_OWNER_GRANT` | Optional: the owner's permission to send requests straight to their inbox (from People & agents in the app) | | `AGENT_KEY` | Optional: bring your own spending key instead of the one the server makes | | `FM_KEY_FILE` | Where the made key is kept (default `agent-key` next to `FM_STORE`) | | `AGENT_CERTIFICATES` | Optional: budget ids already given to you, comma-separated | | `AGENT_CHAINS` | Registry keys, comma-separated (default `arbitrum-sepolia`) | | `FM_MAX_PRICE` | Per-request cap in USDC (default `0.05`) | | `FM_STORE` | Durable outbox file (default `~/.flying-money/outbox.json`) | Until the package is published to npm, build it from the repository (`pnpm install && pnpm build`) and use `node /path/to/flying-money/packages/mcp/dist/bin.js` instead of `npx -y @flying-money/mcp`. ## Claude Desktop In `claude_desktop_config.json`: ```json { "mcpServers": { "flying-money": { "command": "npx", "args": ["-y", "@flying-money/mcp"], "env": { "FM_OWNER": "0xYourWallet" } } } } ``` ## Any MCP client over HTTP ```bash npx -y @flying-money/mcp --http 8788 ``` This serves streamable HTTP at `http://127.0.0.1:8788/mcp`, for this machine only. ## Notes - `max_price` is checked against the seller's quoted price before paying. The per-request cap (`FM_MAX_PRICE`) and the budget's amount always apply. - Failed requests are not charged: the seller turns the price into credit for your next request. --- # Guarantees We make three claims, and no stronger ones: 1. **Every redeemable note is backed by funds reserved exclusively for its payee until the certificate expires.** 2. **The spender cannot authorize more than the certificate's face value.** 3. **Anyone can redeem a redeemable note, but its value can only be delivered to the certificate's payee.** ## Per party | Party | Guarantee | Conditions | |---|---|---| | Payee (seller, shop) | Every redeemable note is backed by funds reserved for it; redeeming pays exactly `cumulative − redeemed` | Redeems before expiry; USDC not frozen; chain live; its acceptance ledger is authoritative | | Funder | Never loses more than the face value; gets the remainder back after expiry | — | | Funder, if the spending key is stolen | Loss ≤ the remaining face value of certificates bound to that key, at those payees only | — | | Spender (agent or person) | Can't be charged more than the highest total it signed; retries never add charges; network failures never raise what it owes | Durable outbox; request-id idempotency | | Everyone | Funds go only to the payee named at issuance | — | ## Not guaranteed - That the seller delivers what you paid for. - That a note reaches the seller (if it doesn't, the seller simply doesn't serve). - That the seller collects before the end date (its SDK does this automatically). - That the USDC issuer never freezes funds. - That the code is bug-free: it is **unaudited** software, invariant-tested, running on testnets and small capped mainnet deployments (100 USDC per certificate, 1,000 USDC per deployment). ## At a shop counter - **Accepted (GUARANTEED):** the till read this certificate on the blockchain earlier, and the note passes the seller checks against the till's own ledger. This holds if the till's ledger is authoritative (one till, or synced devices within per-device floats), its clock is roughly right, and the shop collects before the end date. - **Accepted at your own risk: not checked yet:** offline, a budget (certificate) the till has never checked. **Not a Flying Money guarantee.** Someone could present a made-up certificate. It is the shop's own credit decision, capped by its first-visit limit, and re-checked when the till reconnects. ## Control: what a funder can and can't do | The funder can | The funder can't | |---|---| | Choose exactly where money can be spent | Freeze or cancel a certificate before expiry | | Choose how much, and top up | Lower a limit after issuing | | Choose how long, and extend | Block one purchase at an allowed place | | Not renew | See purchases before the shop collects | There is no freeze button on purpose: a shop can accept a note instantly, even offline, only because the money can't be pulled back. ## Privacy Payments are public on the blockchain but not linked to names. The spending address is random and holds nothing; names and labels never leave your device. We don't claim anonymity: flows between addresses are public. ## What we left out, and why Some features sound useful but can’t be made safe with software alone. Paying strangers offline is one: without a connection, nothing stops the same money being shown to two people at once, short of an online authority, trusted hardware or an identity system. So Flying Money doesn’t offer it, and it doesn’t offer shared spending pools, passing a budget along, or bundled settlement either. It only offers what the math guarantees. --- # Glossary Flying Money uses one set of words everywhere. People-facing screens, agent tools and the code sometimes name the same thing differently; this table says which words mean the same object. AI agents reading these docs: "budget" and "certificate" are the same thing. | People see | Agents see | Code | What it means | |---|---|---|---| | Budget | budget | certificate | Money set aside in a public contract for one seller, one user and an end date. | | Funded by | owner | funder | Who put the money in. | | Can use | agent (spending key) | spender | The key that can sign payment slips. | | Pays / Seller | service | payee | The only address that can ever be paid from it. | | Payment slip | slip | note | A signed "total so far" for one budget. The seller checks it in milliseconds; no transaction. | | Payment code | — | note (as a QR) | A payment slip shown as a QR code on the holder's phone at a counter. | | Collect | collect | redeem | The seller sends slips to the contract and receives what was spent, in one transaction. | | Take back what's left | reclaim | reclaim | After the end date, the funder takes the unspent money back with one transaction. It is not automatic. | | Network fee | gas | gas | What the blockchain charges for a transaction, paid by whoever sends it. | ## Budget status | Status | Meaning | |---|---| | Active | It can be used. | | Ending soon | It ends within 3 days. | | Ended | The end date passed. The seller can no longer be paid from it; the funder can take back what's left. | | Closed | What was left has been taken back. | ## What a budget can't do - It can't be cancelled early, and its user or seller can't be changed. It can be topped up or extended. - It never pays anyone but its one seller, and never more than its amount. - Money left at the end date stays in the contract until the funder takes it back. --- # FAQ **Is this x402?** It uses HTTP 402, but it's a *prefunded tab*: one settlement for many requests, instead of a payment per request. It could become a scheme alongside x402. We don't claim compatibility yet. **Why not payment channels?** It is a one-way channel in spirit. The differences: anyone can redeem, the spending key is separate from the funder, there is no close negotiation, and it comes as an HTTP-native SDK for agents. **Which chain?** All of them. The same contract and protocol run on Arbitrum, Monad, Arc and Base, each with that chain's Circle USDC and caps. Arc lets sellers operate with USDC only (it is also the gas token). Adding a chain is one registry entry. See [Deployments](/chains). **Do certificates move between chains?** No, and nothing is bridged. A seller can accept notes on several chains; each chain settles independently. No bridge risk. **Where's the token?** There isn't one, by design. Everything settles in USDC. **Can a parent freeze a certificate?** No, and that's deliberate. A shop's instant, offline guarantee depends on the money not being pulled back. Control happens through where, how much and how long, and by not renewing. **Does the kid need a wallet?** No. A link or QR code plus a PIN. The money goes from the parent into the contract and then to the shop. It never touches the kid. **Is it private?** Names never go on-chain, and people get fresh random spending addresses. But flows between addresses are public. We don't claim anonymity. **Does it work offline?** Notes can be signed and verified without a connection, and a shop's till keeps accepting certificates it has already checked. We don't offer offline payments between strangers, because that can't be guaranteed without an online authority, trusted hardware or an identity system. A budget the till has never seen, while offline, is shown as **Accepted at your own risk: not checked yet**. **What are the fees?** None from Flying Money. The seller pays gas when collecting (one transaction for many payments); on Arc, gas is paid in USDC. **Is it audited?** No. It is invariant-tested and runs on testnets and small, capped mainnet deployments. An audit comes before any caps are lifted. --- # Flying money, 804 CE ## Chang'an, 804 In the early ninth century the Tang dynasty ran short of copper coin: a tax reform that accepted part of the taxes in money raised demand for cash, and coin was scarce from 805 to 820. Carrying strings of coin between the regions and the capital was a burden for merchants. By 804, merchants were using a better way. They entrusted their money to the representative offices of their local governments (and to armies, commissioners and wealthy families) and carried a certificate instead. When the tallies were matched at an office, they could withdraw their money; at the capital the exchange fee was 100 *wén* per 1,000. Tea merchants, trading between the capital and the regions, benefited most. People called it **飛錢**, *feiqian*: "flying money". The value travelled; the coins stayed put. The government was wary at first, but in 812 flying cash was officially accepted as a means of exchange. ## What made it work - **Deposit before travel** (prefunded): the money existed before anyone spent it. - **A certificate tied to one place of redemption** (scoped): it paid out in one place, and nowhere else. - **Tallies that must match** (verifiable): a certificate paid out only when it matched its counterpart. - **Value moved, coins stayed** (deferred settlement): many journeys, one settlement. ## Twelve centuries later AI agents are the new merchants, and APIs are the new cities. Agents buy data, compute and API calls thousands of times a day, for fractions of a cent. Giving them a card is reckless, paying on-chain per request is too slow and too costly, and sellers can't trust an anonymous agent's promise to pay later. ## Flying Money today | 804 CE | Flying Money | |---|---| | Coin deposited at an office | USDC locked in the contract | | The certificate | The certificate (one payee, one spending key, an end date) | | Matching tallies | The spender's signature, checked against the record on the blockchain | | The redemption office | The contract, which pays only the named payee | | Many trips, one payout | Many signed notes, one transaction | Sources: [Wikipedia, "Flying cash"](https://en.wikipedia.org/wiki/Flying_cash) · [Britannica, "Feiqian"](https://www.britannica.com/topic/feiqian)