{
  "markdown": "# aeron-wallet\n\nA non-custodial wallet for agents. It holds USDG on Robinhood Chain and pays\n`402 Payment Required` responses on its own, so an agent can call a metered API\nwithout a card, an account, or a human in the loop.\n\nShips two ways: a CLI, and an MCP server for Claude and other MCP clients.\n\n## Quickstart\n\n```bash\nnpx -y aeron-wallet address\n```\n\nThat prints your wallet address and creates a key on first run. Send USDG to\nthat address, then pay for a call:\n\n```bash\nnpx -y aeron-wallet pay https://inference.aeron.sh/v1/chat/completions \\\n  '{\"model\":\"deepseek/deepseek-chat\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}'\n```\n\nThe wallet reads the 402 challenge, checks it against your budget caps, signs an\nEIP-3009 transfer, and retries the request with the payment attached. You do not\nneed ETH: the facilitator relays the transaction and pays gas.\n\n## Commands\n\n| Command | What it does |\n|---|---|\n| `address` | Print the wallet address. Creates the key if none exists. |\n| `balance` | ETH and USDG balances, read from chain. |\n| `pay [--method GET] <url> [json]` | Call an x402 endpoint, paying if it answers 402. POST unless told otherwise. |\n| `history` | The last 10 payments, from the local log. |\n| `session create` | Mint a scoped session: hosts, budget, per-call cap, expiry. |\n| `session list` | Every session, what it spent, and whether it is still live. |\n| `session revoke <id>` | Kill a session. It stops paying on its next call. |\n| `mcp` | Run as an MCP server over stdio. The default with no arguments. |\n\n## Install it in an agent\n\n**Claude Code**\n\n```\n/plugin marketplace add aeronlabs/aeron-wallet\n/plugin install aeron-wallet@aeronlabs\n```\n\n**Cursor**\n\n[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](cursor://anysphere.cursor-deeplink/mcp/install?name=aeron-wallet&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImFlcm9uLXdhbGxldCIsIm1jcCJdfQ==)\n\n**Gemini CLI**\n\n```bash\ngemini extensions install https://github.com/aeronlabs/aeron-wallet\n```\n\n**VS Code**\n\n```bash\ncode --add-mcp '{\"name\":\"aeron-wallet\",\"command\":\"npx\",\"args\":[\"-y\",\"aeron-wallet\",\"mcp\"]}'\n```\n\n**Anything else that speaks MCP**\n\n```json\n{\n  \"mcpServers\": {\n    \"aeron-wallet\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"aeron-wallet\", \"mcp\"]\n    }\n  }\n}\n```\n\nFour tools: `get_address`, `get_balance`, `pay`, `history`. An unbound server\nalso gets `create_session`, `list_sessions`, and `revoke_session`.\n\n## Sessions\n\nA session is a scope you can hand to an agent without handing over the wallet.\nIt names the hosts that may be paid, a total budget, a per-call cap, and an\nexpiry:\n\n```bash\naeron-wallet session create --host inference.aeron.sh --budget 0.25 --ttl 2h\n```\n\nThat prints a token, once. Bind a server to it and every call through that\nserver inherits the scope:\n\n```json\n{\n  \"mcpServers\": {\n    \"aeron-wallet\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"aeron-wallet\", \"mcp\"],\n      \"env\": { \"AERON_WALLET_SESSION\": \"<token>\" }\n    }\n  }\n}\n```\n\nA bound server deliberately has no session tools. An agent that could mint\nitself a wider session would not be contained by one. It also cannot reach a\nhost outside the scope: the wallet refuses before the request goes out, so an\nagent talked into paying an attacker's endpoint never contacts it.\n\nRevoking takes effect on the next call, including for a server already\nrunning, because the scope is re-read every time rather than captured at\nstartup.\n\n`aeron-wallet pay --session <token> <url>` applies a scope to a single call.\n\nSessions narrow the wallet; they never widen it. The caps below still apply\nunderneath, so a $5 session on a $1/day wallet spends $1 a day.\n\n## Your key\n\nThe key is generated on your machine on first run and written to\n`~/.aeron/wallet/key` with `0600` permissions. It never leaves the machine and\nnobody else can derive your address. Every install creates a different wallet.\n\nTwo consequences worth planning for:\n\n- **Ephemeral containers.** If `$HOME` is wiped between runs, the wallet\n  regenerates and any USDG left on the old address is stranded. Mount a volume\n  for `~/.aeron`, set `AERON_WALLET_DIR` to a path that persists, or supply the\n  key yourself with `AERON_WALLET_KEY`.\n- **Hot wallet.** The key sits unencrypted on disk so an agent can sign without\n  a prompt. Keep the balance small. Fund it the way you would top up a prepaid\n  card, not the way you would fund savings.\n\n## Budget caps\n\nThe wallet refuses to sign above either cap, so a loop cannot drain it.\n\n| Variable | Default | Meaning |\n|---|---|---|\n| `MAX_PER_CALL_USD` | `0.05` | Largest single payment. |\n| `DAILY_CAP_USD` | `1` | Total for the current UTC day. |\n\n## Paying merchants you did not write\n\nReading a 402 sounds like one line — take `accepts[0]` from the body — and that\nline works against servers written the same way this wallet was. It works\nagainst almost nothing else. On a survey of the machine-payable endpoints\nlisted on Robinhood Chain, **not one merchant put this rail's offer first**:\nevery one of them leads with Base, and the payable entry sits somewhere down a\nlist of a dozen.\n\nSo the offer is searched for, not assumed, across every shape merchants\nactually use:\n\n| What differs | What is done |\n|---|---|\n| Offers in the JSON body, or in a base64 `payment-required` header, or both | Both are read, and the same offer stated twice is one offer |\n| The amount is `maxAmountRequired` (v1) or `amount` (v2) | Either is accepted |\n| The list mixes chains and address formats this wallet has no key for | Non-EVM entries are skipped rather than treated as errors |\n| Several offers are payable | The cheapest one wins |\n| Nothing is payable | The refusal names what *was* offered, so the reason is actionable |\n\n### The two protocol versions are not a version number\n\nv1 carries the payment in `X-PAYMENT` and names the scheme and network at the\ntop level. v2 carries it in `payment-signature`, names neither, and states the\nchosen offer verbatim in `accepted` — a rebuilt copy does not match, because\nthe server compares it against what it advertised. Answering a v2 merchant in\nv1's form does not degrade; it is refused.\n\nWorse, the split is not clean in the wild: merchants advertise a v2 header\nbeside a v1 body, and one host in a family of five wants `X-PAYMENT` while its\nsiblings want `payment-signature`. So the payment is offered in the form the\nversion asks for and, if that is refused outright, in the other one. Both\ncarry the **same** signed authorization, whose EIP-3009 nonce can be spent\nexactly once — so the fallback cannot pay twice, however the server answers.\n\n## What a result means\n\nA request that comes back 4xx is not one situation, it is three, and they\ndiffer in the only way that matters: whether the money left the wallet. The\nsignal is the settlement receipt — a service that settled returns\n`X-PAYMENT-RESPONSE` with a transaction hash, and one that did not, does not.\n\n| `status` | Charged | What happened |\n|---|---|---|\n| `settled` | yes | The service answered. `reason` is set only in the bad case below. |\n| `rejected` | no | HTTP 402. The service refused the payment; the authorization is unspent. |\n| `failed` | no | The service returned an error *and declined to charge* — usually its own upstream failed. |\n\nThe case worth naming: a `settled` row **with** a `reason` means the money\nmoved and nothing came back. That is the only outcome where the wallet is out\nof pocket for nothing, so it is reported as itself rather than folded in with\nrefusals that cost nothing.\n\nOnly `settled` counts against `DAILY_CAP_USD`. A refusal and an upstream\nfailure leave the balance untouched, so neither eats into the cap.\n\n`reason` quotes the service's own message when it gave one, instead of a\ngeneric phrase — an agent operator reading a log needs to know whether to\nretry, top up, or fix the seller.\n\n## Configuration\n\n| Variable | Default |\n|---|---|\n| `RPC_URL` | `https://rpc.mainnet.chain.robinhood.com` |\n| `CHAIN_ID` | `4663` |\n| `USDG_ADDRESS` | `0x5fc5360d0400a0fd4f2af552add042d716f1d168` |\n| `AERON_WALLET_DIR` | `~/.aeron/wallet` |\n| `AERON_WALLET_KEY` | unset. Overrides the stored key. |\n| `AERON_WALLET_SESSION` | unset. Binds the whole process to one session. |\n\n## Releases\n\nPublished from a tag by GitHub Actions using npm trusted publishing, so no\nlong-lived npm token exists to leak and every tarball carries a provenance\nattestation: proof of the commit and workflow it was built from. Verify with\n`npm audit signatures` after installing.\n\n## Where payments go\n\nPayments settle on Robinhood Chain mainnet in USDG through the Aeron\nfacilitator at `x402.aeron.sh`. The wallet works with any x402 endpoint on the\nsame network, not only Aeron's.\n\nMore at [aeron.sh/wallet](https://aeron.sh/wallet/).\n",
  "bytes": 8777,
  "sha": "f2680514164008e4bb619a7453d4b1d38da2244790c19a42cc70a3232fcec320",
  "repo_slug": "aeronlabs/aeron-wallet",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_aeronlabs_aeron_wallet_30feadc1/readme"
}