Content
# SpendTree
SpendTree is a local, non-custodial budget tree and flight recorder for agents
that make real x402 payments.
It is not a mock ledger or testnet demo. The payment path creates real EIP-3009
USDC authorizations for Base mainnet and sends them to real x402 merchants. The
wallet belongs to the user, funds settle directly to the merchant, and
SpendTree never holds either party's money.
> Status: pre-alpha. Use only a dedicated, low-balance wallet while the security
> surface is being hardened.
## What exists today
- x402 v2 `exact` payments
- Base mainnet (`eip155:8453`)
- Canonical native USDC only
- A one-use signer that accepts only the reserved EIP-3009 typed data
- Exact HTTPS origin, path, HTTP method, and receiving-address policies
- Hierarchical, expiring agent quotas
- Atomic ancestor reservations that prevent concurrent overspend
- Root-scoped idempotency and conservative handling of ambiguous settlement
- A database-to-wallet binding that prevents accounting under the wrong signer
- SQLite audit trail containing the quote, signed payload, nonce, transaction
hash, response hash, and agent ancestry
- Base receipt verification that checks both the exact canonical USDC `Transfer`
and the signed EIP-3009 `AuthorizationUsed` nonce
- A local Unix-socket daemon, mandate-bound MCP adapter, and CLI smoke path
Not in v0: arbitrary token transfers, swaps, `upto`, batch settlement,
subscriptions, fiat/card payments, onramping, refunds, custody, or merchant
delivery guarantees.
## Architecture
```text
Agent
│ stdio MCP (scoped capability; no wallet key)
▼
SpendTree MCP adapter
│ authenticated JSON over a 0600 Unix socket
▼
SpendTree daemon
├─ policy + hierarchical budget engine
├─ SQLite reservation/attempt ledger
├─ constrained one-use EIP-3009 signer
└─ guarded x402 HTTP handshake
│
▼
Merchant facilitator ──▶ Base USDC settlement
```
The daemon moves a reservation to committed spend and persists the signed
payload **before** transmitting `PAYMENT-SIGNATURE`. A timeout, missing receipt,
or merchant error after that point never releases the budget: the merchant may
still settle the authorization until it expires.
## Install from GitHub
Requirements: Node.js 24 or newer.
```sh
npm install --global github:bilalxashraf/spendtree
spendtree --help
```
The GitHub install builds the CLI and MCP adapter locally. To develop from a
clone instead, run `npm ci`, `npm run check`, and `npm run build`. All
security-sensitive dependency versions are pinned in `package.json` and
`package-lock.json`.
## Ten-minute real payment canary
This path calls Exa's live x402 web-search API on Base mainnet. On July 18,
2026, its unsigned quote was 0.007 USDC to
`0x6d6E695b09861467c7d462f5AAF31cF3540B9192`. The checked-in policy allows at
most 0.01 USDC per call and fails closed if the recipient or price changes.
Download the narrow example policy and create a mandate that expires in one
hour:
```sh
curl --fail --output exa-policy.json \
https://raw.githubusercontent.com/bilalxashraf/spendtree/main/config/policy.exa.example.json
spendtree init exa-policy.json --ttl-minutes 60
```
Save the capability printed by `init`. Create or export a **dedicated** EOA,
then print its address and fund it with a small amount of USDC on Base. Do not
use a primary wallet.
```sh
SPENDTREE_PRIVATE_KEY=0x... spendtree address
SPENDTREE_PRIVATE_KEY=0x... spendtree daemon
```
Keep the daemon running. In another terminal, make a useful paid search with
the capability but without exposing the wallet key to the client process:
```sh
SPENDTREE_CAPABILITY=st_cap_... \
spendtree pay \
'https://api.exa.ai/search' \
'exa-canary-001' \
'Find current primary sources about agent payment controls' \
--json '{"query":"agent payment controls x402","numResults":3}'
```
Run the identical command again. SpendTree returns the stored response with
`"replayed": true` and does not sign or pay twice. Inspect the verified Base
transaction and remaining allowance with:
```sh
SPENDTREE_CAPABILITY=st_cap_... spendtree summary
```
This is a real mainnet call funded by the user. The project maintainers do not
subsidize the purchase, hold funds, or receive the merchant payment.
## Configure another merchant from a source clone
Copy `config/policy.example.json`, then replace the origin and `payTo` address
with a real x402 merchant. Amounts are atomic USDC units: `1000000` is 1 USDC.
```sh
cp config/policy.example.json config/policy.json
# Edit config/policy.json before continuing.
spendtree init config/policy.json --ttl-minutes 60
```
`init` prints a root capability once. SpendTree stores only its SHA-256 hash;
save the raw capability in a password manager or other secret store.
## Start the payment daemon
Fund a dedicated EOA with USDC on Base. Standard EIP-3009 payments do not
require that buyer wallet to hold ETH; the merchant's facilitator submits the
transaction.
Start the daemon in a terminal or service environment that is separate from
the agent. Do not place the wallet key in the MCP process:
```sh
SPENDTREE_PRIVATE_KEY=0x... spendtree daemon
```
The environment variable is the initial developer key provider. Keep daemon
and MCP environments separate; example files are provided for each. An encrypted
keystore/native keychain provider is planned before a stable release.
## Make a real payment from the CLI
With the daemon running, use the root or child capability:
```sh
SPENDTREE_CAPABILITY=st_cap_... \
spendtree pay \
'https://merchant.example/paid/resource' \
'smoke-001' \
'Buy the merchant resource for the initial smoke test'
```
For POST resources, append `--json '{"key":"value"}'`. `--text` and an
explicit `--method GET|POST` are also supported.
This is a mainnet operation. If the quote satisfies the mandate, real USDC can
move from the configured wallet.
Inspect the quota and flight recorder:
```sh
SPENDTREE_CAPABILITY=st_cap_... spendtree summary
```
## Connect an MCP agent
Run one adapter per agent/mandate. The adapter attaches its capability
internally; the agent cannot choose a different mandate in tool arguments.
```json
{
"mcpServers": {
"spendtree": {
"command": "spendtree-mcp",
"args": [],
"env": {
"SPENDTREE_SOCKET": "/absolute/path/to/home/.spendtree/spendtree.sock",
"SPENDTREE_CAPABILITY": "st_cap_..."
}
}
}
}
```
Replace the socket path with the daemon's path; the default is
`~/.spendtree/spendtree.sock`. For a source-only install, set `command` to
`node` and `args` to the absolute path of `dist/mcp.js` instead.
The MCP surface exposes `paid_fetch`, `get_spend_tree`, `delegate_budget`, and
`revoke_child`. It exposes no generic signing or token-transfer operation.
## Important limits
- EIP-3009 cryptographically binds payer, receiver, amount, validity window,
nonce, token, and chain. It does **not** bind the HTTP method, URL, body, or
merchant delivery. SpendTree enforces and records that relationship locally.
- `PAYMENT-RESPONSE` is a facilitator/merchant report, not itself a signed
receipt. SpendTree verifies the Base transaction receipt, the exact USDC
`Transfer`, and the matching EIP-3009 `AuthorizationUsed` nonce through an
RPC. When the RPC is temporarily unavailable, the delivered result is
retained as `reported_settled` with `onchainVerified: false` instead of being
mislabeled as confirmed.
- A same-OS-user agent with unrestricted shell/filesystem access can bypass a
Unix-socket capability boundary. Use a separate OS user/container for that
threat model. The dedicated low-balance wallet is the hard v0 loss ceiling.
- Child limits are hierarchical quotas, not carved-out treasury allocations.
Every payment consumes its leaf quota and all ancestor quotas.
See [SECURITY.md](SECURITY.md) before placing funds in a wallet.
## Development
```sh
npm run typecheck
npm test
npm run build
```
The tests use a real EIP-3009 signing implementation against mocked HTTP
transport. They do not broadcast transactions or use fake balances.
## License
MIT