Skip to main content
Glama
README.md
# AgentWallet MCP Server

[![Tested by AddonAudit](https://addonaudit.com/badges/tested.svg)](https://addonaudit.com/addon/io-github-hifriendbot-agentwallet/)

Permissionless wallet infrastructure for AI agents. Create wallets, sign transactions, and broadcast on-chain, on any EVM chain and Solana. Built-in guards. No KYC. Free to use.

**Your keys can stay on your machine.** Set one environment variable and every signature happens in your own process. No server sees the key, no company can freeze the wallet, and you can verify that claim by reading `src/local-wallet.ts` or by running the server with the API pointed at a closed port.

**No KYC. No KYT. No approval process. No transaction monitoring. No one can block your wallet. Free: no fee per operation and no credit card.**

## Two modes

| | Local (self-custody) | Hosted (custodial) |
|---|---|---|
| Who holds the key | You, in your own process | Encrypted on AgentWallet servers |
| Set up | One env var, no account needed | API key |
| Chains | EVM + Solana | EVM + Solana |
| Can anyone freeze it | No | Yes, that is what pause is for |
| Spend guards | `AGENTWALLET_MAX_TX_NATIVE`, `AGENTWALLET_MAX_TX_TOKEN`, `AGENTWALLET_MAX_TX_SOL`, `AGENTWALLET_MAX_AUTOPAY` | Server-side limits, pause, rate limits |
| Paywalls, usage, billing | Needs an API key too | Included |

Run `wallet_mode` at any time and the server will tell you which one you are in, and which address it controls.

### Local signing in one variable

```json
{
  "mcpServers": {
    "agentwallet": {
      "command": "npx",
      "args": ["-y", "agentwallet-mcp"],
      "env": {
        "AGENTWALLET_PRIVATE_KEY": "0xyour_key",
        "AGENTWALLET_RPC_8453": "https://your-own-rpc",
        "AGENTWALLET_MAX_TX_NATIVE": "0.05"
      }
    }
  }
}
```

Use `AGENTWALLET_KEYFILE=/path/to/key` instead if you would rather keep the key out of your shell config (one or the other: the server refuses to start when both are set). The key is read once, never written to disk, never logged, and never included in an error message.

`AGENTWALLET_RPC_<chainId>` (or `AGENTWALLET_RPC_URL` for all chains) points at an endpoint you trust (https; plain http only on localhost). Without it a public RPC is used, and a public RPC can see which addresses you ask about. Decimals for a token outside the registry come from that RPC and are accepted only up to 18; pin anything else with `AGENTWALLET_TOKEN_DECIMALS`.

`AGENTWALLET_MAX_TX_NATIVE` is a per-transaction ceiling in native units (ETH, MATIC and so on). In hosted mode the server enforces limits; in local mode there is no server, so these guards are the only ones there are. Set them.

`AGENTWALLET_MAX_TX_TOKEN` is the equivalent ceiling for ERC-20 movement, in human units of the token. **Set this one too if you hold stablecoins.** The native cap cannot see a token transfer: an ERC-20 send carries `value = 0` with the amount in the calldata, so `AGENTWALLET_MAX_TX_NATIVE` alone leaves a USDC balance uncapped. `AGENTWALLET_MAX_TX_TOKEN` covers `transfer`, `transferFrom`, `approve`, `increaseAllowance`, EIP-2612 `permit`, Permit2 `approve`, and `deposit()` / `withdraw(uint256)` on the chain's own wrapped-native contract (so `wrap_eth` and `unwrap_eth` keep working under the cap), the approvals because an unbounded allowance is a drain waiting to happen. ERC-721 and ERC-1155 calldata (`setApprovalForAll`, `safeTransferFrom`) is refused while the cap is set, since nothing prices an NFT, and so are the ERC-721 `transferFrom` and `approve`, which share their selectors with ERC-20, whenever the target declines `decimals()`, since there the amount word is a token id (1.14.14); without the cap it passes the way an ERC-20 transfer of any size does, except that an operator grant is an unlimited approval and is refused everywhere by default (see `AGENTWALLET_ALLOW_UNLIMITED_APPROVAL`). Any other calldata sent to a token contract or to Permit2 is refused while the cap is set, because the guard cannot price it; set `AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS=1` to allow such calls deliberately. `increaseAllowance` is judged on the resulting total: the current allowance is read first and the call refuses if it cannot be, so two compliant increments cannot stack a standing allowance above the cap. Calls to other contracts (routers, bridges) pass, since they can only pull what an approval already allowed; a contract counts as "other" only when it demonstrably declines `decimals()`, so an absurd or malformed answer, or an RPC that cannot be reached, refuses rather than allows. Decimals are resolved locally from the trusted registry, then the token's own `decimals()`; a token that resolves neither way is evaluated at 0 decimals, the only value that cannot fail open. For a token outside the registry the RPC is the only source of decimals, and an RPC you do not control could scale the amount and the cap together; pin such tokens with `AGENTWALLET_TOKEN_DECIMALS` (for example `8453:0x<token>=6`) and the pin is used everywhere the RPC's answer would have been.

### Solana local signing

```json
"env": {
  "AGENTWALLET_SOLANA_KEY": "[12,34...]",
  "AGENTWALLET_SOLANA_RPC": "https://your-own-rpc",
  "AGENTWALLET_MAX_TX_SOL": "1",
  "AGENTWALLET_MAX_TX_TOKEN": "25"
}
```

Accepts whichever format you already have: a `solana-keygen` id.json array, a base58 secret key as exported by Phantom, or base64. Use `AGENTWALLET_SOLANA_KEYFILE` to point at a file instead. Native SOL and SPL token transfers are both signed locally, and a missing associated token account is created for the recipient automatically.

Set either key, or both. They are independent: run EVM locally and Solana hosted, or the reverse.

**An operation with no matching local key is refused, never silently routed to the hosted signer.** If you have an EVM key configured and ask for a Solana transfer with no Solana key, the server stops and tells you which variable is missing. Quietly moving funds onto a key you do not hold, while you believe you are in self-custody, is the worst thing this server could do.

### About the dependency tree

Local signing uses [viem](https://viem.sh) for EVM and `@solana/web3.js` for Solana, plus `bs58` for key parsing. SPL instructions are built by hand rather than with `@solana/spl-token`, because that package pulls in `bigint-buffer`, which carries a high severity buffer overflow advisory. A wallet has no business shipping that to save a dozen lines of instruction encoding.

`npm audit` in a checkout of this repository reports no advisories, and the release workflow fails on a high or critical finding. An install is different: as of 1.14.13, `npx agentwallet-mcp` carries four medium advisories, three in `stream-json` 1.9 and one in `uuid` 8, both pulled in by `jayson` 4 under `@solana/web3.js` 1.x. The overrides in package.json move them to clean versions inside this repository only; npm ignores a dependency's overrides, and until 1.14.13 this paragraph claimed they held for installs too. None of the four is reachable: `@solana/web3.js` loads only jayson's HTTP client, which uses `uuid` v4 for request ids (the advisory is in v3, v5 and v6 with a caller-supplied buffer), and `stream-json` is loaded by jayson's streaming server parsers, which that client never calls. `jayson` 5 drops both packages, but `@solana/web3.js` 1.x pins jayson 4 and its maintainers have closed the bump as out of scope for 1.x, so the four stay until this package stops depending on `@solana/web3.js` 1.x. Earlier releases carried advisories inside `@modelcontextprotocol/sdk`'s HTTP transport dependencies; this server speaks stdio, so that code never loads. Run the audit yourself, on an install rather than a checkout. Publishing a tree you can inspect is the point.

<p align="center">
  <img src="https://raw.githubusercontent.com/hifriendbot/agentwallet-mcp/main/assets/demo.svg" alt="AgentWallet demo, AI agent pays x402 invoice automatically" width="800">
</p>

## Features

- **40 MCP tools**: create wallets, send transactions, approve tokens, wrap ETH, transfer SPL tokens, quote, discover, pay and accept x402 payments, verify custody mode, and more
- **Spend policy**: rolling daily and monthly budgets, per-host ceilings, host and recipient lists, human approval through MCP elicitation, and a tool allowlist, in one JSON file, enforced from a hash-chained ledger. The key stays yours and the agent still cannot overspend.
- **EVM + Solana**: Ethereum, Base, Polygon, BSC, Arbitrum, Optimism, Avalanche, Zora, PulseChain, Solana, and any other EVM-compatible chain
- **SOL + SPL tokens**: native SOL transfers and SPL token transfers (USDC, USDT, etc.) with automatic account creation
- **Built-in guards**: in hosted mode, daily spending limits, gas price protection, emergency pause and rate limiting are enforced server-side by default. In local mode your protection is the per-transaction caps you set (`AGENTWALLET_MAX_TX_NATIVE`, `AGENTWALLET_MAX_TX_TOKEN`, `AGENTWALLET_MAX_TX_SOL`, `AGENTWALLET_MAX_AUTOPAY`). x402 replay protection and on-chain verification apply either way
- **x402 payments**: pay for x402-enabled APIs automatically, or accept x402 payments on your own endpoints (EVM and Solana)
- **Self-custody option**: run local mode and the key never leaves your machine. In hosted mode, keys are encrypted at rest, decrypted only during signing, and zeroed from memory immediately after. Either way you can export and walk away.
- **Permissionless**: No KYC. No KYT. No identity verification. No approval process. No compliance gatekeeping. Sign up, get an API key, and start transacting immediately.
- **30-second setup**: three lines of config. No SDK to install. No dependencies to manage.

## Pricing

AgentWallet is free.

- **$0 per operation** on hosted wallets
- **$0 per x402 verification** on paywalls
- **Local mode is free and needs no account**: your key, your machine
- No card, no invoice, no tiers
- Fair use: up to 100,000 hosted operations a month per account, which only bounds abuse
- Your wallet pays network gas to the chain, as with any wallet

A comparison with Coinbase is kept at [hifriendbot.com/wallet/vs-coinbase](https://hifriendbot.com/wallet/vs-coinbase/) with the date it was last verified. It lives there rather than here because a published npm README cannot be corrected when someone else changes their product.

## Quick Start

Get your free API key at [hifriendbot.com/wallet](https://hifriendbot.com/wallet), no credit card required, no KYC, no approval wait.

### Claude Desktop / OpenClaw

Add to your config:

```json
{
  "mcpServers": {
    "agentwallet": {
      "command": "npx",
      "args": ["-y", "agentwallet-mcp"],
      "env": {
        "AGENTWALLET_USER": "your_username",
        "AGENTWALLET_PASS": "your_api_key",
        "AGENTWALLET_WALLET_ID": "1"
      }
    }
  }
}
```

> `AGENTWALLET_WALLET_ID` is optional. The hosted service is free and asks for no payment, so today this setting changes nothing. It names the wallet x402 auto-pay would use if the API host ever answered a metered call with a 402, and the limits below bound what could be paid in that case. Only the metered routes (creating a wallet, signing, sending) are paid this way, at most `AGENTWALLET_MAX_AUTOPAY_PER_HOUR` times an hour (default 20; the slot is taken before the payment is sent, so parallel calls cannot overrun it, and processes sharing a ledger file share the hour, while without one the count is per process), and every automatic payment is reported in the tool result under `autopay_payment`. A 402 on any other route is reported, never paid.
>
> **Auto-pay safety cap.** `AGENTWALLET_MAX_AUTOPAY` (optional, default `1`) is the most one x402 payment may authorize, in stablecoin units: `1` means one dollar. It prices registry stablecoins only (USDC, USDT, USDbC, DAI on the supported chains, USDC and USDT on Solana). A requirement in the chain's native asset or in any other token is refused, because "1" measured in ETH is a few thousand dollars; list such assets in `AGENTWALLET_AUTOPAY_ASSETS` (comma-separated addresses or mints, or the word `native`) to allow them, and the cap then applies in that asset's own units. Any requirement above the cap is rejected instead of paid, so a malformed or tampered payment requirement cannot drain the wallet. This automatic path settles `exact` requirements only; an `upto` offer from the API is refused before any wallet call, because paying a usage maximum upfront is not what the offer means (use `pay_x402`, which signs a Permit2 authorization for it). The cap is the operator's ceiling: a `max_payment` argument can lower it for one call but never raise it. Raise the variable itself if you genuinely need larger automatic payments (for example `"5"` for up to 5 USDC per call).

### Claude Code

```bash
claude mcp add agentwallet \
  -e AGENTWALLET_USER=your_username \
  -e AGENTWALLET_PASS=your_api_key \
  -e AGENTWALLET_WALLET_ID=1 \
  -- npx -y agentwallet-mcp
```

### VS Code

Add to your settings:

```json
{
  "mcp": {
    "servers": {
      "agentwallet": {
        "command": "npx",
        "args": ["-y", "agentwallet-mcp"],
        "env": {
          "AGENTWALLET_USER": "your_username",
          "AGENTWALLET_PASS": "your_api_key",
          "AGENTWALLET_WALLET_ID": "1"
        }
      }
    }
  }
}
```

## Tools

| Tool | Description |
|------|-------------|
| `create_wallet` | Create a new EVM or Solana wallet |
| `list_wallets` | List all your wallets |
| `get_wallet` | Get wallet details by ID |
| `get_balance` | Check native token balance on any chain |
| `get_token_balance` | Check ERC-20 or SPL token balance |
| `get_token_info` | Get ERC-20 token name, symbol, and decimals |
| `transfer` | Send native tokens (ETH, SOL, POL, BNB, etc.) |
| `transfer_token` | Send ERC-20 or SPL tokens (USDC, USDT, etc.) |
| `send_transaction` | Sign and broadcast a raw transaction |
| `sign_transaction` | Sign a transaction without broadcasting |
| `call_contract` | Read-only contract call (eth_call) |
| `approve_token` | Approve ERC-20 token spending for DeFi |
| `get_allowance` | Check ERC-20 token allowance |
| `wrap_eth` | Wrap native tokens to WETH/WAVAX/etc. |
| `unwrap_eth` | Unwrap WETH back to native tokens |
| `pay_x402` | Pay x402 invoices (exact via EIP-3009, upto via Permit2), with approvals above the cap |
| `quote_x402` | What an x402 endpoint charges and whether the cap, policy and budgets would allow it; nothing signed |
| `discover_x402` | Find x402-paid APIs by subject in the public x402 index (the Bazaar) |
| `list_payments` | Spends on the ledger with their outcomes, newest first |
| `export_receipts` | The ledger as CSV or JSONL for expense records |
| `spend_policy` | The policy in force, its sha256, budgets spent and remaining, ledger integrity |
| `simulate_transaction` | Dry-run an EVM transaction: guards, policy, gas estimate, fee, return data, nothing signed |
| `check_approval` | Status of a human approval created by an over-cap `pay_x402` |
| `approve_permit2` | One-time token approval to Permit2 for `upto` endpoints |
| `check_token_risk` | Honeypot, tax, owner-power and holder-concentration flags for a token |
| `create_paywall` | Create an x402 paywall to charge for a resource |
| `list_paywalls` | List all your x402 paywalls |
| `get_paywall` | Get paywall details by ID |
| `update_paywall` | Update paywall pricing, resource, or status |
| `delete_paywall` | Delete a paywall |
| `get_paywall_payments` | View payment history for a paywall |
| `get_x402_revenue` | Aggregate revenue stats across all paywalls |
| `wallet_mode` | Report whether signing is local (self-custody) or hosted, and which address is in use |
| `export_wallet_key` | How to export a hosted wallet key and move to self-custody |
| `buy_verification_credits` | Buy x402 verification credits with USDC on-chain |
| `get_usage` | Check your monthly usage and billing |
| `get_chains` | List all supported chains |
| `pause_wallet` | Emergency pause a wallet |
| `unpause_wallet` | Resume a paused wallet |
| `delete_wallet` | Delete a wallet |

## Supported Chains

| Chain | ID | Native Token | Stablecoin |
|-------|-----|-------------|------------|
| Ethereum | 1 | ETH | USDC |
| Base | 8453 | ETH | USDC |
| Polygon | 137 | POL | USDC |
| BSC | 56 | BNB | USDT |
| Arbitrum | 42161 | ETH | USDC |
| Optimism | 10 | ETH | USDC |
| Avalanche | 43114 | AVAX | USDC |
| Zora | 7777777 | ETH | USDC |
| PulseChain | 369 | PLS | USDC |
| Solana | 900 | SOL | USDC |
| Solana Devnet | 901 | SOL | USDC |

## Use Case: GuessMarket

Pair with [guessmarket-mcp](https://www.npmjs.com/package/guessmarket-mcp) to let your AI agent trade prediction markets:

1. Create a wallet on Base
2. Approve USDC spending
3. Buy YES/NO shares on prediction markets
4. Provide liquidity and earn trading fees
5. Claim winnings

All on-chain. All through MCP. No frontend needed.

## x402 Payments

AgentWallet speaks the [x402 open payment standard](https://x402.org) the way the spec defines it. When your Ai agent hits an API that answers HTTP 402, `pay_x402` does the whole flow:

1. Fetches the URL and reads the payment requirements, from the v2 `PAYMENT-REQUIRED` header or the v1 JSON body.
2. Picks an `exact` option (you can steer it with `prefer_chain`).
3. Signs an **EIP-3009 `TransferWithAuthorization`** for exactly that amount. Nothing is broadcast and no gas is paid by the payer; the endpoint's facilitator settles it on-chain.
4. Retries the request with the payment header (`PAYMENT-SIGNATURE` for v2, `X-PAYMENT` for v1).
5. Returns the response, the settlement receipt (`PAYMENT-RESPONSE` / `X-PAYMENT-RESPONSE`) and, if the endpoint declined, its stated reason in `retry_error`.

Verified against Coinbase's public facilitator (`isValid: true` for v1 and v2 payloads) and live x402 servers on Base. Works in both custody modes: the local key signs in-process; hosted wallets sign through `POST /wallets/{id}/x402/authorize`, a narrow endpoint that only ever signs this one struct and runs the same pause and token-cap checks as a transfer.

```
pay_x402(
  url="https://api.example.com/premium-data",
  wallet_id=1,
  max_payment="1.00"
)
```

`max_payment` lowers the cap for one call. It cannot raise it: `AGENTWALLET_MAX_AUTOPAY` (default `1`) is the operator's ceiling, and a `max_payment` above it is ignored and reported back as `max_payment_ignored`, so neither a malicious 402 endpoint nor a prompt-injected agent can authorize more than the operator allowed. Hosted wallets can go above the ceiling only through an emailed owner approval (below); local mode raises the variable. In local mode `AGENTWALLET_MAX_TX_TOKEN` applies to authorizations too, because an authorization is a transfer somebody else executes. Authorizations are valid for at most `AGENTWALLET_X402_MAX_TIMEOUT` seconds (default 3600) however long the endpoint asks for, and a repeat `pay_x402` call for the same endpoint, amount and recipient inside that window re-sends the earlier signature instead of signing a second one (its nonce is single-use, so a server that withheld the resource after settling cannot be paid twice; pass `fresh_authorization=true` to force a new one). The label shown for the asset comes from the registry or the chain, never from the 402 body.

**Schemes.** `exact` (EIP-3009, gasless for the payer) is preferred and works on every EVM chain AgentWallet knows. `upto` (a Permit2 maximum the seller settles at actual usage) is signed as a `PermitWitnessTransferFrom` bound to the endpoint's facilitator; it needs a one-time `approve_permit2` per token (or `AGENTWALLET_PERMIT2_AUTO_APPROVE=1`). An `upto` option without `extra.facilitatorAddress` is refused, never approximated with an upfront transfer. Native-asset and Solana requests, and AgentWallet's own paywalls, are paid by an on-chain transfer proved by transaction hash, which is what those servers verify.

**Approvals above the cap.** On a hosted wallet, a payment above the cap does not have to fail. With `request_approval` (default on; `AGENTWALLET_APPROVALS=0` turns it off) the wallet owner gets an email with approve and deny links, `pay_x402` returns an `approval_id`, and the agent retries with it once `check_approval` says approved. An approval is bound to one wallet, chain, asset, recipient and maximum, expires in 24 hours, and is consumed by one signature. Local mode has no approval channel: the caps in your environment are the policy, and an `approval_id` passed there is refused.

**Asset risk.** `check_token_risk` (and every `approve_token` result) reports honeypot, tax, owner-power, verified-source, holder-concentration and liquidity flags from GoPlus Security, with an on-chain fallback. A warning for the caller to weigh, never a block.

**Proof.** Real settlements are listed in [PAYMENTS.md](https://github.com/hifriendbot/agentwallet-mcp/blob/main/PAYMENTS.md); the first is a 0.02 USDC `exact` payment on Base with the facilitator paying the gas. The same list, rendered with Basescan links, is at [hifriendbot.com/wallet/payments](https://hifriendbot.com/wallet/payments/), and a five-minute walkthrough from install to a real settlement is at [hifriendbot.com/wallet/quickstart](https://hifriendbot.com/wallet/quickstart/). Security policy, fixed disclosures and the release history: [hifriendbot.com/security](https://hifriendbot.com/security/) and [hifriendbot.com/changelog](https://hifriendbot.com/changelog/).

## Pay any x402 API from Claude in three steps

1. Add the server (one of the snippets under Quick Start). Self-custody: set `AGENTWALLET_PRIVATE_KEY` to a key that holds USDC on Base. Hosted: `AGENTWALLET_USER` and `AGENTWALLET_PASS` from hifriendbot.com/wallet.
2. Ask: *"Use pay_x402 to POST https://api.ozdreamtools.de/api/holidays with body {"year":2026,"state":"BY"} and a max_payment of 0.05."*
3. Read the result: `payment_made: true`, the amount, `payment_method`, and `settlement.transaction`, the on-chain hash. No ETH needed for `exact`: the endpoint's facilitator pays the gas.


**Token domains.** The EIP-712 domain comes from the endpoint's `extra.name` / `extra.version`, then a short registry of USDC deployments, then the token contract's own `name()` / `version()`. If none of those answer, the payment is refused rather than signed with a guessed domain.

**Delegated payers.** If the paying address is an EIP-7702 delegated account, `pay_x402` reports it in `payer_delegation` (the delegate and whether it answers ERC-1271). A delegate without ERC-1271 is declined by facilitators that check account code before recovering the signer, and the reason they return reads like a signature fault; the warning names the real cause. Pay from a plain EOA for reliable settlement.

## x402 Acceptance

AgentWallet also lets you **accept** x402 payments. Create a paywall, point it at any resource, and get a public URL that charges agents automatically:

```
create_paywall(
  wallet_id=1,
  name="Premium API",
  amount="0.01",
  token_name="USDC",
  token_address="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  chain_id=8453,
  resource_url="https://your-api.com/data"
)
```

When an agent hits the paywall URL:
1. Gets back HTTP 402 with payment requirements
2. Pays on-chain using `pay_x402` (or any x402-compatible client)
3. Retries with proof of payment
4. Receives the protected content

On-chain verification ensures every payment is real. Replay protection prevents double-spending. Revenue tracking shows you who paid, how much, and when. Paywall verifications are free.

## How We Compare

| | Coinbase Agentic Wallets | AgentWallet |
|---------|-------------|-------------|
| Setup | CLI or MCP server | **3 lines of MCP config** |
| Sign-in | Email one-time code | **Local mode: none. Hosted: a free account** |
| Where the private key lives | Coinbase infrastructure | **Your machine in local mode. Hosted keys are encrypted and exportable** |
| Fully local signing | No | **Yes, on EVM and Solana** |
| Spending limits | Caps per session and per transaction | **A policy file: rolling budgets, per-endpoint ceilings, recipient lists, approval above a threshold, hash-chained ledger** |
| Price of wallet operations | See Coinbase's pricing page | **$0** |
| Selling over x402 | Facilitator: 1,000 onchain transactions free a month, then $0.001 each | **Paywalls with on-chain verification: free** |
| Chains | Base, Polygon and Solana for payments | **Any EVM chain + Solana** |
| MCP Server | Yes | **Yes, 40 tools** |

Coinbase details are from the Coinbase Developer Platform documentation, last verified November 12, 2026. Coinbase wrote the x402 standard and has the wider distribution; AgentWallet speaks the same standard with any server. The dated, maintained version of this table is at [hifriendbot.com/wallet/vs-coinbase](https://hifriendbot.com/wallet/vs-coinbase/).

## Free, With No Card

There is nothing to pay AgentWallet for. Hosted operations and paywall verifications carry no fee, so there is no card to add, no invoice and no credits to buy (`buy_verification_credits` now answers that there is nothing to purchase). `get_usage` reports the month's operation count against the fair-use ceiling.

## Built-in Guards

Which guards apply depends on who holds the key.

**Hosted mode** (the server holds the key): every guard below is active by default, no configuration required.

- **Encrypted at rest**: private keys encrypted before storage and never leave the server
- **Memory zeroing**: keys wiped from memory immediately after every signing operation
- **Daily spending limits**: set a per-wallet daily cap in USD, enforced by the server on every transaction
- **Gas price protection**: transactions blocked when gas prices spike above safe thresholds
- **Emergency pause**: instantly freeze any wallet or all wallets with one click
- **Rate limiting**: API requests capped per minute to prevent abuse and brute force attacks

**Local mode** (you hold the key): there is no server in the signing path, so the server-side limits and pause above cannot see or stop a locally signed transaction, and nobody (including us) can freeze a local wallet. Your protection is the per-transaction caps enforced inside your own process before anything is signed. Set them before you fund the wallet; an unset cap means no limit.

- `AGENTWALLET_MAX_TX_NATIVE`: ceiling per transaction in native units (ETH, MATIC, and so on)
- `AGENTWALLET_MAX_FEE_NATIVE`: the most one transaction may burn in fees, gas x max fee per gas, in native units (default `0.01`). The value cap alone did not bound fees: a node answering an absurd tip or gas estimate could take the whole balance as priority fee on a cap-compliant transfer. Caller-supplied `gas_limit` / `max_fee` / `priority_fee` are honoured and then bounded by this cap
- `AGENTWALLET_MAX_TX_TOKEN`: ceiling per ERC-20 transfer or approval, in human units of the token
- `AGENTWALLET_ALLOW_UNKNOWN_TOKEN_CALLS`: set to `1` to let calldata the token cap cannot price reach a token contract or Permit2 (refused by default while the cap is set)
- `AGENTWALLET_ALLOW_UNLIMITED_APPROVAL`: set to `1` to let an unlimited ERC-20 allowance (`approve_token` with `"max"`, `approve_permit2` with no amount) or an NFT operator grant (`setApprovalForAll`) through while no spend policy is set; refused by default, and always refused under a policy
- `AGENTWALLET_TOKEN_DECIMALS`: pin decimals for tokens outside the registry (`8453:0x<token>=6,<mint>=9`); a pin is used for amounts and caps instead of asking the RPC
- `AGENTWALLET_MAX_TX_SOL`: ceiling per native SOL transfer (also charged for the rent of a recipient token account an SPL send has to create). It does not cover SPL token amounts: `AGENTWALLET_MAX_TX_TOKEN` does, on Solana as on EVM
- `AGENTWALLET_MAX_AUTOPAY`: ceiling per x402 auto-payment in stablecoin units (default `1`); `AGENTWALLET_AUTOPAY_ASSETS` opts native or non-stable assets in; `AGENTWALLET_X402_MAX_TIMEOUT` bounds how long a signed authorization stays valid (default 3600 seconds)
- `AGENTWALLET_SOLANA_RPC_<chainId>` (or `AGENTWALLET_SOLANA_RPC` for all clusters): the RPC's genesis hash is checked against the chain id before anything is sent, and an EVM RPC's `eth_chainId` likewise, so one URL cannot silently serve a different network than the one requested
- `AGENTWALLET_TOKEN_RISK=0`: skip the GoPlus lookup `approve_token` performs (a third-party call that reveals which token you are about to approve)
- `AGENTWALLET_CHAIN_ID`: the EVM chain used when a call names none (default 8453)
- `AGENTWALLET_LEGACY_PAYWALL_ORIGINS`: comma-separated https origins whose legacy-shaped 402 may be settled by an on-chain transfer proved by hash; every other host gets a signed authorization

- `AGENTWALLET_POLICY`: a spend policy file (see below) that bounds sums, hosts, recipients, chains, assets and tools; `AGENTWALLET_LEDGER` names the ledger it is enforced from
- `AGENTWALLET_KEYFILE_PASSPHRASE` or `AGENTWALLET_KEYFILE_PASSPHRASE_FILE`: opens an encrypted keyfile (see below)
- `AGENTWALLET_WEBHOOK_URL` (https, or http on localhost) and `AGENTWALLET_WEBHOOK_SECRET`: every ledger line is POSTed as JSON, signed `X-AgentWallet-Signature: sha256=<hmac>` when a secret is set; a webhook never delays or fails a signature

Every `AGENTWALLET_*` variable is validated at startup: a malformed cap stops the server rather than reading as "no limit", and an unrecognised name (a misspelled cap) stops it too. `wallet_mode` reports every guard with its parsed value. Tool arguments are strict: an unknown argument name (a typo in `max_payment`) is an error, never silently ignored. `pay_x402` signs one live authorization per resource; a second, different requirement for the same resource is refused until the first expires unless `fresh_authorization` is passed. Node.js 22.19 or newer is required.

### Spend policy: bounding sums, not just single payments

A per-transaction cap bounds one signature. It cannot see that the five hundredth payment of 0.99 under a cap of 1 is the five hundredth. A spend policy can. Point `AGENTWALLET_POLICY` at a JSON file:

```json
{
  "version": 1,
  "name": "research agent",
  "budget": { "daily": "5", "monthly": "50" },
  "native_budget": { "8453": { "daily": "0.01" } },
  "hosts": { "allow": ["*.example.com", "api.ozdreamtools.de"], "deny": ["evil.example.com"] },
  "per_host": { "*.example.com": { "daily": "2", "per_payment": "0.25" } },
  "recipients": { "deny": ["0x0000000000000000000000000000000000000000"] },
  "chains": { "allow": [8453, 900] },
  "contracts": { "allow": ["8453:0x2626664c2603336E57B271c5C0b26F421741e481"] },
  "approval": { "above": "0.50", "max": "20" },
  "tools": { "allow": ["pay_x402", "quote_x402", "discover_x402", "get_balance", "list_payments", "spend_policy", "wallet_mode"] }
}
```

- **`budget`**: rolling dollar budgets (last 24 hours, last 30 days, lifetime) across every chain, counting x402 payments, stablecoin transfers and stablecoin approvals in registry stablecoins. A spend the budget cannot price (a token outside the registry) is refused while a budget is set, unless `"unpriced_spends": "allow"`. An SPL send is priced at the mint's own decimals; a `token_decimals` hint that disagrees with the mint is refused before any request is made.
- **`native_budget`**: the same per chain, in the chain's own coin, counting native transfers, native x402 payments and the native value sent into any contract (`wrap_eth`, `send_transaction` with value and calldata), including value carried on a token transfer or approval call (a payable token contract keeps it, so it is a second spend, to that contract) and, in local Solana mode, the rent an SPL send pays to create the recipient's token account (the recipient owns that account and can close it for the lamports; the ledger records it as its own line, method `rent`, to the recipient, and `assets.allow` does not need to list `native` for it). The dollar budget has no price for the chain's coin, so on a chain with no `native_budget` entry a native spend is an unpriced spend: refused while a `budget` or a `per_host` ceiling is set, unless `"unpriced_spends": "allow"`. Give every chain the wallet spends on an entry.
- **Approvals are spends.** An ERC-20 allowance (`approve_token`, `approve_permit2`, `increaseAllowance`, `increaseApproval`, either `approveAndCall`, Permit2 `approve`, a signed `permit` in its EIP-2612, DAI or Permit2 shape, or the same calldata through `send_transaction`) is what the spender will take later, so it is priced, checked and reserved on the ledger when it is granted, the spender is checked against the recipient lists, and an unlimited approval (2^128 base units or more) is refused, since no budget can count it; grant an exact amount. An operator grant (`setApprovalForAll`, ERC-777 `authorizeOperator`, EIP-6909 `setOperator`) is unbounded by construction and refused the same way. Every token mover the standards define is a spend to its recipient: ERC-20 `transfer` and `transferFrom`, the ERC-677 and ERC-1363 `transferAndCall` and `transferFromAndCall` shapes, the ERC-223 `transfer` with data, ERC-777 `send` and `operatorSend`, and the ERC-721, ERC-1155 and EIP-6909 transfers, which no budget can price and so are refused under a dollar budget unless `unpriced_spends` is `"allow"`, with the recipient checked either way. A redemption of the wallet's own position is a spend too: ERC-4626 `redeem` and `withdraw` are spends of the vault to the receiver (unpriced, like an NFT), and Aave `Pool.withdraw` is a transfer of the underlying to the receiver. A call to any other contract (a router, a bridge, a protocol whose payout shape is not listed here) is refused under a policy unless the contract is in `contracts.allow` or `unknown_calls` is `"allow"`, since 1.14.11: the policy cannot tell what such a call moves, so the operator decides which contracts may receive one. A listed contract's unpriced call reserves nothing beyond the native value it carries: it can only pull what an approval already granted, or pay out a position in a shape the policy does not know, so a policy that lists protocol contracts should set `recipients.allow` too. Permit2 is never admitted this way: a call to it that the policy cannot price (`permitBatch`, `transferFrom`, `permitTransferFrom`, `lockdown`, nonce invalidation, or an unknown selector) is refused under a policy whatever the lists say, since each moves tokens or changes what may be moved.
- **`hosts`**, **`per_host`**: which endpoints `pay_x402` may pay, and how much per host per day, per month and per payment. A denied host is never contacted, not even for a quote. A host is matched by its canonical DNS name (lowercase, the root label's trailing dot removed), so `api.example.com.` and its percent-encoded spelling are `api.example.com` to every list and to the ledger's per-host sums.
- **`recipients`**: where funds may go (transfers, x402 `payTo`, approval spenders, contracts that receive native value). The allow list covers those funds destinations; the contract a call is aimed at (a token being approved, a router) is checked against the deny list, the chain list and the `contracts` lists.
- **`chains`**, **`assets`**: allow lists; an asset entry can be scoped `"8453:0x..."` or be the word `native`.
- **`contracts`**, **`unknown_calls`**: what the wallet may call with calldata the policy cannot price. The policy prices the token movers, approvals, permits and redemptions listed under "Approvals are spends" below; any other calldata (a swap, a bridge, a protocol call) is refused under a policy unless the contract is in `contracts.allow` (entries scoped `"8453:0x..."` or bare), because a shape the policy does not know could be moving anything. `"unknown_calls": "allow"` restores the pre-1.14.11 behaviour of passing such calls as zero-value contract calls, with their native value still counted. `contracts.deny` refuses every call to a contract, priced or not. Without a policy nothing is refused on this ground.
- **`approval`**: above `above`, every x402 payment is put to the human at the MCP client through [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation) (the client shows the amount, token, recipient and resource and asks yes or no; a client that declared no elicitation support cannot answer, so the payment is refused). In local mode an accepted answer may admit a payment above the per-payment cap, up to `max`. Budgets are never lifted by anyone's approval.
- **`tools`**: the tools the model sees. A payer-only deployment exposes seven; the rest are not registered. `wallet_mode` and `spend_policy` are always present so the operator can see what the server is.

An unknown key stops the server, so a misspelled `budgets` cannot run unbounded. `spend_policy` reports the file, its sha256 (compare it with the file you reviewed), what each budget has spent and has left, and which tools the policy hid. `quote_x402` answers "would this be paid?" for a URL before anything is signed.

**The ledger.** Budgets are enforced from an append-only, hash-chained JSONL file (`AGENTWALLET_LEDGER`, or the policy's `ledger`, or `agentwallet-ledger.jsonl` beside the policy when a budget needs one). A spend is reserved on it before the signature exists; a reservation whose signing fails is voided by a later line; the endpoint's or chain's answer is settled onto it afterwards. A spend that may already be on the wire (a broadcast that failed after the bytes left, a hosted request the API host took and then dropped or answered with a 5xx, any host answer that carries a transaction hash) is kept counted as `unknown` rather than voided; a failure before anything left this process, including a policy or local-mode refusal, or a 4xx refusal from the host, is always voided and never consumes budget. A hosted send is forwarded with the chain the policy priced it on (`chain_id`, else `AGENTWALLET_CHAIN_ID`, else 8453), and a custodian answer naming another chain is written onto the ledger line as a mismatch. The chain is verified on every read, and a ledger that does not verify refuses every spend until the operator has looked at it. Several agentwallet-mcp processes may share one ledger (two MCP clients on one wallet): the file is re-read before every check, and the check-and-append step holds an exclusive lock file (`<ledger>.lock`) across processes, so the budget is one budget however many processes enforce it; a lock older than 30 seconds is treated as left by a dead process and reclaimed in one step through `<ledger>.lock.reclaim`, and a release removes only the lock it took. A transaction signed without being broadcast (`sign_transaction`) is reserved and counted like a send, since whoever holds it can broadcast it. A call that moves two things (a token transfer that also carries native value) is two lines, reserved in turn and released together when either is refused. `list_payments` reads it, `export_receipts` turns it into CSV or JSONL, and `AGENTWALLET_WEBHOOK_URL` receives every line as it is written.

### Encrypted keyfiles

A plaintext key in a shell config or a file is readable by anything that can read the disk. Seal it:

```bash
npx agentwallet-mcp encrypt-keyfile ./plain.key ./agent.keyfile
```

The passphrase is asked for on the terminal (or taken from `AGENTWALLET_KEYFILE_PASSPHRASE` for scripted use); scrypt derives the key and AES-256-GCM seals the material, with Node's own crypto and no new dependency. Point `AGENTWALLET_KEYFILE` or `AGENTWALLET_SOLANA_KEYFILE` at the result, set `AGENTWALLET_KEYFILE_PASSPHRASE` or `AGENTWALLET_KEYFILE_PASSPHRASE_FILE`, confirm with `wallet_mode` that the same address appears, then delete the plaintext. A wrong passphrase fails with a fixed message that never includes the key.

**Either mode**, for x402 paywalls you run through the hosted API:

- **Replay protection**: every x402 payment verified on-chain with unique transaction tracking
- **On-chain verification**: x402 payments verified directly on the blockchain with finalized commitment

Bug bounty program: $50 to $500 for responsible disclosure ([details](https://hifriendbot.com/wallet)).

## Links

- **Website:** [hifriendbot.com/wallet](https://hifriendbot.com/wallet)
- **npm:** [agentwallet-mcp](https://www.npmjs.com/package/agentwallet-mcp)
- **Security:** [security@hifriendbot.com](mailto:security@hifriendbot.com), policy and disclosure history in [SECURITY.md](https://github.com/hifriendbot/agentwallet-mcp/blob/main/SECURITY.md)


## Security

`pay_x402` validates the target URL before every outbound request and again on
each redirect hop. IP literals are canonicalized (including IPv4-mapped IPv6
such as `[::ffff:127.0.0.1]`) and hostnames are resolved, with loopback,
private, link-local, carrier-grade NAT, multicast and cloud-metadata
destinations refused.

Validation and connection use the same DNS answer. Each hop resolves once, and
the socket is pinned to an address from that answer, so a hostname cannot
resolve public for the check and private for the connection. The hostname is
still used for the `Host` header and for TLS SNI and certificate validation, so
pinning is invisible to legitimate endpoints.

Credentials stay with the origin they were given to. If an endpoint redirects
`pay_x402` to a different origin, the caller-supplied `Authorization`, `Cookie`
and `X-PAYMENT` headers, and every other custom header, are dropped before the
next hop; only content-negotiation headers (`Accept`, `Accept-Language`,
`Accept-Encoding`, `User-Agent`, `Content-Type`) are carried. A redirect that
turns a POST into a GET also sheds the body headers. Same-origin redirects keep
everything, as a browser would.

Report security issues privately to security@hifriendbot.com.

## License

MIT

TDQS

B3.4/5.0

Scored across 40 tools

Disambiguation4/5

Most tools have clearly distinct targets: sign_transaction vs send_transaction vs transfer vs transfer_token are separated by explicit descriptions, and pay_x402/quote_x402/discover_x402 form a clear read-vs-pay gradient. The weakest area is the payments/reporting cluster (get_paywall_payments, get_x402_revenue, list_payments, export_receipts, get_usage), where an agent must read carefully to pick the right history/aggregate view, and approve_token vs approve_permit2 requires understanding Permit2.

Naming Consistency4/5

The dominant pattern is snake_case verb_noun (get_balance, create_paywall, list_wallets, check_token_risk), which is applied broadly and predictably. Deviations are minor but present: bare-verb or noun-only names such as transfer, spend_policy, wallet_mode, buy_verification_credits, and pay_x402/quote_x402/discover_x402 which lead with the domain noun instead of the verb.

Tool Count2/5

40 tools is well past the point where the surface stays legible for a single agent, and several do not earn their place: buy_verification_credits is an explicit no-op kept only for backwards compatibility, export_wallet_key explains rather than performs, and get_usage/wallet_mode are near-informational. The domain is genuinely broad (wallets, EVM+Solana transfers, DeFi approvals, x402 paywalls, ledger/policy), but the set could be consolidated to roughly 20-25 without losing capability.

Completeness4/5

Coverage is strong across the lifecycle: wallet create/list/get/delete/pause/unpause, native and token balances, transfers, approvals and allowance checks, contract calls, simulation, paywall CRUD, x402 discover/quote/pay, plus ledger, receipts and policy introspection. Gaps are modest — no wallet metadata/limit update, no lookup of a transaction or payment by hash, and no explicit transaction-status polling after broadcast.

Maintenance

ActivityActive
ResponsivenessMixed handling