AERONDocs GitHub
Buyers

Let an agent pay for itself.

aeron-wallet: a non-custodial wallet that answers 402 on its own, as a CLI and an MCP server — plus how to sign the payment yourself if you would rather not run it.

aeron-wallet is the buyer side of the rail: a non-custodial wallet that holds USDG, reads a 402 challenge, checks it against your budget, signs, and retries the request — without a human in the loop. It ships as a CLI and as an MCP server, and it works against any x402 endpoint on eip155:4663, not only Aeron’s.

First payment

bash
npx -y aeron-wallet address     # prints an address, creates a key on first run
# send USDG to that address, then:
npx -y aeron-wallet pay https://inference.aeron.sh/v1/chat/completions \
  '{"model":"deepseek/deepseek-chat","messages":[{"role":"user","content":"hi"}]}'

You do not need ETH. The facilitator relays the transaction and pays the gas.

CommandWhat it does
addressPrint the wallet address. Creates the key if none exists.
balanceETH and USDG balances, read from chain.
pay <url> [json]Call an x402 endpoint, paying if it answers 402.
historyThe last 10 payments, from the local log.
session createMint a scoped session: hosts, budget, per-call cap, expiry.
session list / revokeWhat each session spent, and killing one.
mcpRun as an MCP server over stdio. The default with no arguments.

Give it to an agent

bashclaude code
/plugin marketplace add aeronlabs/aeron-wallet
/plugin install aeron-wallet@aeronlabs
jsonany MCP client
{
  "mcpServers": {
    "aeron-wallet": { "command": "npx", "args": ["-y", "aeron-wallet", "mcp"] }
  }
}

Four tools: get_address, get_balance, pay, history. An unbound server also gets create_session, list_sessions, revoke_session. Gemini CLI installs it as an extension; VS Code takes code --add-mcp.

Sessions

A session is a scope you can hand to an agent without handing over the wallet: the hosts it may pay, a total budget, a per-call cap, an expiry.

bash
aeron-wallet session create --host inference.aeron.sh --budget 0.25 --ttl 2h
# prints a token, once
jsonbind a server to it
{
  "mcpServers": {
    "aeron-wallet": {
      "command": "npx", "args": ["-y", "aeron-wallet", "mcp"],
      "env": { "AERON_WALLET_SESSION": "<token>" }
    }
  }
}

A bound server has no session tools — an agent that could mint itself a wider session would not be contained by one. It also refuses hosts outside the scope before the request goes out, so an agent talked into paying an attacker’s endpoint never contacts it. Revocation takes effect on the next call, including for an already-running server, because the scope is re-read every time.

Sessions narrow the wallet; they never widen it. The caps below still apply underneath, so a $5 session on a $1/day wallet spends $1 a day.

Caps and configuration

VariableDefaultMeaning
MAX_PER_CALL_USD0.05Largest single payment.
DAILY_CAP_USD1Total for the current UTC day. Only settled calls count.
AERON_WALLET_DIR~/.aeron/walletWhere the key lives.
AERON_WALLET_KEYunsetSupply the key yourself instead of storing one.
AERON_WALLET_SESSIONunsetBind the whole process to one session.
RPC_URLrpc.mainnet.chain.robinhood.comChain reads.
Hot key

The key is generated on your machine and written to ~/.aeron/wallet/key with 0600 permissions, unencrypted, so an agent can sign without a prompt. Keep the balance small — fund it like a prepaid card, not like savings.

In an ephemeral container, $HOME is wiped between runs and the wallet regenerates, stranding whatever USDG was on the old address. Mount a volume for ~/.aeron, set AERON_WALLET_DIR, or pass AERON_WALLET_KEY.

Reading a result

A 4xx from a paid call is three different situations, and they differ in the only way that matters: whether the money left the wallet. The signal is the receipt — a service that settled returns X-PAYMENT-RESPONSE with a transaction hash, and one that did not, does not.

statusChargedWhat happened
settledyesThe service answered. A settled row with a reason is the one bad case: the money moved and nothing came back.
rejectednoHTTP 402. The service refused the payment; the authorization is unspent.
failednoThe service errored and declined to charge — usually its own upstream failed.

Signing it yourself

If you would rather not run the wallet, the payer side is small. Read accepts[0] from the 402, sign the authorization under the token’s EIP-712 domain, and retry with the header.

javascriptviem
import { privateKeyToAccount } from 'viem/accounts'
import { randomBytes } from 'node:crypto'

const TYPES = {
  TransferWithAuthorization: [
    { name: 'from', type: 'address' }, { name: 'to', type: 'address' },
    { name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' },
    { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' },
  ],
}
const DOMAIN = {
  name: 'Global Dollar', version: '1', chainId: 4663,
  verifyingContract: '0x5fc5360d0400a0fd4f2af552add042d716f1d168',
}

const account = privateKeyToAccount(process.env.KEY)
const first = await fetch(url, { method: 'POST', body })
const offer = (await first.json()).accepts[0]

const now = Math.floor(Date.now() / 1000)
const authorization = {
  from: account.address,
  to: offer.payTo,
  value: BigInt(offer.maxAmountRequired),
  validAfter: BigInt(now - 60),                                  // strictly past
  validBefore: BigInt(now + (offer.maxTimeoutSeconds ?? 60) + 540),
  nonce: `0x${randomBytes(32).toString('hex')}`,
}
const signature = await account.signTypedData({
  domain: DOMAIN, types: TYPES, primaryType: 'TransferWithAuthorization', message: authorization,
})

const header = Buffer.from(JSON.stringify({
  x402Version: 1, scheme: 'exact', network: 'eip155:4663',
  payload: {
    signature,
    authorization: Object.fromEntries(
      Object.entries(authorization).map(([k, v]) => [k, typeof v === 'bigint' ? String(v) : v]),
    ),
  },
})).toString('base64')

const paid = await fetch(url, { method: 'POST', body, headers: { 'x-payment': header } })

Check the offer before you sign it: network, asset, and maxAmountRequired come from the seller, and a wallet that signs whatever it is handed is a wallet that can be drained one small payment at a time. Cap the amount, and pin the host.

Source

aeronlabs/aeron-wallet, MIT, published to npm from a tag by GitHub Actions with provenance attestation — verify with npm audit signatures.

Updated 1 September 2026. Everything on this page is read from the running services; report a drift at github.com/aeronlabs.