x402-aa-wallet
v0.3.0
Published
Give an ERC-4337 / account-abstraction agent a dedicated, non-custodial EOA so it can pay x402 (HTTP 402) challenges against any x402 merchant — smart-wallet signatures don't yet settle on today's facilitators. Optional maxAmountUsd spend cap.
Maintainers
Readme
x402-aa-wallet
The easiest way for an ERC-4337 / account-abstraction agent to pay x402 API calls — with a dedicated, non-custodial EOA, since its smart-wallet signature doesn't work with x402 yet.
A lightweight, TypeScript-first SDK: generate a spend wallet, fund it from your agent's own smart wallet, and every request through it pays x402 (HTTP 402) challenges automatically — retried and returned, no manual handling — against any x402 merchant. Originally built for HoodGrow (see "Sponsored by" below), and works the same way against any other x402 API.
flowchart LR
A[AI Agent] --> B[Request API]
B --> C[402 Payment Required]
C --> D[x402Fetch pays automatically]
D --> E[Retry request]
E --> F[Response]Features
- 🤖 Built for ERC-4337 / account-abstraction agents
- 💳 Automatic x402 payment handling — detect a 402, pay, retry, transparently
- 💰 Optional
maxAmountUsd(per-call) andmaxTotalUsd(per-session) spend caps — real enforcement boundaries, not just docs warnings - ✅ USDC asset verification always on — a merchant cannot get a signature for an arbitrary token contract
- 🔒 Non-custodial — the private key never leaves your process, and isn't even enumerable on the returned object (safe to log the wallet by accident)
- ⚡ Minimal dependencies (
viem+@x402/*) - 🌐 Works against any x402-compatible API
- 📦 TypeScript-first, with full types included
- 🐍 Python implementation also available (see Related projects)
Installation
npm install x402-aa-walletFormerly published as
hoodgrow-x402-aa— same code, same maintainers, new name to reflect that it's a general-purpose x402 utility, not a HoodGrow-specific client. See "Sponsored by" below.
Quick start
import { createSpendWallet, getUsdcBalance, x402Fetch } from "x402-aa-wallet";
// 1. Generate a dedicated spend wallet — locally, once.
const wallet = createSpendWallet();
console.log("fund this address:", wallet.address);
// store wallet.privateKey yourself (env var / secret manager) — this
// library never sees it again after this call returns.
// 2. Fund `wallet.address` with a little USDC on Base — from your agent's
// own smart wallet, using its own transfer/send call (not this library).
// 3. Check the balance whenever you want to know if it needs topping up.
const balance = await getUsdcBalance(wallet.address);
// 4. Pay any x402 endpoint with it — payment happens automatically.
// maxAmountUsd is optional but strongly recommended for autonomous use:
// it refuses to pay any single challenge above this amount instead of
// trusting whatever the server's 402 response asks for.
const fetchWithPayment = x402Fetch(wallet, { maxAmountUsd: 0.5 });
// First call: HoodGrow's own hello-world endpoint — $0.001, no API key, a
// real 402 challenge and settlement so you can watch the whole flow work.
const ping = await fetchWithPayment("https://www.hoodgrow.com/api/agent/ping");
console.log(await ping.json());
// Then: real data, same wallet, same call shape.
const res = await fetchWithPayment("https://www.hoodgrow.com/api/agent/token/NVDA");
console.log(await res.json());Restarting your agent? Rehydrate the same wallet from the key you stored:
import { spendWalletFromPrivateKey } from "x402-aa-wallet";
const wallet = spendWalletFromPrivateKey(YOUR_STORED_PRIVATE_KEY);Why this exists
x402's "exact" EVM scheme settles payment via an EIP-3009 ECDSA signature, which an account-abstraction owner key (often a P256/WebAuthn passkey, or even secp256k1 but the wrong address) usually can't produce — full ERC-1271/ERC-6492 smart-wallet support is still an open, unshipped facilitator feature (see coinbase/x402#639). The fix is giving the agent a small, dedicated EOA it funds itself, purely for x402 spending. Full writeup: hoodgrow.com/blog/x402-account-abstraction-eoa.
Non-custodial — read this before using it
We never see your private key. Nobody does but you.
createSpendWallet()generates a fresh secp256k1 keypair entirely inside your own process, using viem'sgeneratePrivateKey. Nothing is transmitted, logged, or persisted by this library.- The private key is returned to you once, in memory. Store it yourself (env var, secret manager) — this library keeps no copy after the call returns.
- The returned
SpendWallet.privateKeyis a non-enumerable property:wallet.privateKeystill works, butconsole.log(wallet),JSON.stringify(wallet), and most structured loggers/error serializers/Sentry breadcrumbs skip it automatically — one less way an accidental log line leaks a key. - Funding the spend wallet is your agent's job, using your agent's
own smart-wallet infrastructure. This library never moves funds itself —
it only tells you the address to send to and (via
getUsdcBalance) how much is there. - The published package is open source. Don't trust this description —
read
src/, it's short.
Spend caps
x402Fetch's second argument accepts two independent caps:
const fetchWithPayment = x402Fetch(wallet, {
maxAmountUsd: 0.10, // per challenge
maxTotalUsd: 5.0, // per session (this wrapper instance)
});Without a cap, x402Fetch pays whatever a 402 response asks for — a
misbehaving or compromised merchant returning a much larger amount than
expected gets paid in full, silently. With maxAmountUsd set, a payment
requirement above the cap is filtered out before signing (via a real
x402Client policy, not a client-side amount check bolted on after the
fact), and if that leaves nothing payable, the call throws instead of
proceeding.
maxAmountUsd alone is per challenge: a merchant charging exactly at the
cap on every request still drains cap × N over N requests — which is
precisely how an autonomous retry loop gets bled. maxTotalUsd closes
that: once the payments this wrapper has authorized reach the budget,
further challenges throw. Accounting is at authorization time and
deliberately conservative — a payment that later fails still consumes
budget (the signature already left the process). Build a new x402Fetch
to start a fresh budget.
The caps only evaluate a requirement whose asset is a known 6-decimal Circle USDC deployment (Base mainnet or Base Sepolia) — anything else is excluded rather than evaluated with a guessed decimal count, since guessing wrong could make a genuinely large charge on a different-decimals asset look small enough to slip through.
Asset verification is always on
Since 0.3.0 the USDC allowlist applies even with no cap set: an
EIP-3009 authorization is valid for whatever token contract it names, so
signing for an arbitrary merchant-supplied asset could move ANY EIP-3009
token the EOA holds. A challenge on an unrecognized asset now throws by
default. If you genuinely want the old behavior, pass
allowUnknownAssets: true — it is honored only when no cap is set (an
asset with unverified decimals cannot be measured against a USD cap), and
only sensible when the wallet holds nothing you are not willing to lose.
API
| Function | Returns |
| --- | --- |
| createSpendWallet() | A new SpendWallet { address, privateKey, account } |
| spendWalletFromPrivateKey(key) | Rehydrates a SpendWallet from a key you already have |
| getUsdcBalance(address, rpcUrl?) | USDC balance (number, human units) on Base |
| x402Fetch(wallet, options?) | A fetch-compatible function that auto-pays x402 challenges — wallet can be a SpendWallet, a viem LocalAccount, or a raw private key string. options: { maxAmountUsd?, maxTotalUsd?, allowUnknownAssets?, network? } — see "Spend caps" above; network overrides the default eip155:8453 (Base mainnet) |
Use cases
- AI assistants and copilots
- MCP servers
- Autonomous agents built on ERC-4337 smart wallets
- Multi-agent systems
- Research agents
- Trading bots
- Automation workflows
Payment safety
Every payment x402Fetch makes is real USDC on Base mainnet — not
reversible. Only fund the spend wallet with what you're willing to spend,
and never reuse an EOA that also holds funds you care about for anything
else. Set maxAmountUsd (see "Spend cap" above) for any autonomous/agent
use — don't rely on funding discipline alone as the only safety boundary.
Sponsored by
Built and maintained by the team behind HoodGrow — stock token data for Robinhood Chain — to pay their own x402-protected API. Released as a standalone, general-purpose tool because the AA/x402 gap this solves isn't specific to HoodGrow.
Related projects
Once your agent has a wallet that pays for itself, the next step is an agent that already knows what to call:
- hoodgrow-mcp — an MCP server for HoodGrow's stock-token API. Free tier, no signup required for a key.
- x402-aa-wallet — Python implementation of this package
- x402 — the HTTP 402 payment protocol
Development
npm install
npm run build # tsc -> dist/
npm test # tsx --test test/*.test.ts (mocked fetch, no network)License
MIT
