# Pay from an agent wallet
Source: https://docs.simpleserve.ai/docs/wallet-payments
Summary: Pay for agent runs with USDC on Base or Solana.
Availability: available
Last reviewed: 2026-09-05

An agent can pay for a run with a Base or Solana wallet. The wallet needs USDC and an x402 client.

The Python examples below use Base. Install the tested client and wallet sign-in dependencies.

```bash
python -m pip install "x402[evm,extensions,httpx]==2.22.0" "abnf==2.3.1" eth-account
```

The `extensions` extra supplies wallet sign-in support. The `abnf` pin avoids a grammar error in the sign-in dependency.

Set `WALLET_PRIVATE_KEY` to your Base wallet's private key before running an example.

## Sign wallet requests

Save this code as `wallet_client.py`. The next example imports its sign-in helper.

To read an existing run, set `RUN_ID` to a run that belongs to this wallet.
Run `python wallet_client.py`. This read does not buy credits or start another run.

```python
import asyncio
import os

import httpx
from eth_account import Account
from x402.extensions.sign_in_with_x.client import create_siwx_payload
from x402.extensions.sign_in_with_x.encode import encode_siwx_header

async def wallet_headers(client, wallet, url, method="GET", body=None):
    response = await client.request(method, url, json=body)
    if response.status_code != 402:
        response.raise_for_status()
        raise RuntimeError("Expected a wallet sign-in challenge.")
    challenge = response.json()["extensions"]["sign-in-with-x"]
    chain = next(
        item for item in challenge["supportedChains"]
        if item["chainId"] == "eip155:8453"
    )
    info = {**challenge["info"], **chain}
    proof = await create_siwx_payload(info, wallet, url)
    return {"SIGN-IN-WITH-X": encode_siwx_header(proof)}

async def main():
    wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])
    url = "https://api.simpleserve.ai/v1/runs/" + os.environ["RUN_ID"]
    async with httpx.AsyncClient(timeout=60) as client:
        headers = await wallet_headers(client, wallet, url)
        response = await client.get(url, headers=headers)
        response.raise_for_status()
        print(response.json())

if __name__ == "__main__":
    asyncio.run(main())
```

Create a fresh proof for each request URL. A proof for one URL does not authorize another URL.

The run example below signs in before checking the credit shortfall.
A `402` with an empty `accepts` array requests sign-in, not payment.
After sign-in, a `402` with payment options means that credits do not cover the run limit.

## Start a run with existing credits or a payment

Save this example beside `wallet_client.py`. Set `IDEMPOTENCY_KEY` to a unique value for this task.
Keep that value when you retry the same task. Use a new value for a new task.

The example signs in first. It buys credits only when the balance does not cover the $0.01 run limit.
The payment limit is $0.02, including the purchase fee.

```python
import asyncio
import os

import httpx
from eth_account import Account
from x402 import x402Client
from x402.http import x402HTTPClient
from x402.mechanisms.evm.exact import ExactEvmScheme
from x402.schemas import PaymentRequired
from wallet_client import wallet_headers

async def main():
    wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])
    url = "https://api.simpleserve.ai/v1/runs"
    body = {
        "agent": "simpleserve/general",
        "input": "Reply with one short greeting. Do not use tools.",
        "max_cost_usd": 0.01,
    }
    payments = x402Client()
    payments.register("eip155:8453", ExactEvmScheme(wallet))
    payments.set_spend_controls({"max_amount_per_payment": "$0.02"})

    async with httpx.AsyncClient(timeout=120) as client:
        headers = await wallet_headers(client, wallet, url, "POST", body)
        headers["Idempotency-Key"] = os.environ["IDEMPOTENCY_KEY"]
        response = await client.post(url, json=body, headers=headers)
        if response.status_code == 402:
            if response.json().get("error") == "settlement_pending":
                response.raise_for_status()
            required = PaymentRequired.model_validate(response.json())
            required.accepts = [
                option for option in required.accepts
                if option.network == "eip155:8453"
            ]
            if not required.accepts:
                raise RuntimeError("No Base payment offer. Read the error response before retrying.")
            payment = await payments.create_payment_payload(required)
            headers.update(x402HTTPClient(payments).encode_payment_signature_header(payment))
            response = await client.post(url, json=body, headers=headers)
        response.raise_for_status()
        print(response.json())

asyncio.run(main())
```

The 402 body and `PAYMENT-REQUIRED` header contain the amount, network, USDC address, payment address, and expiry time.
If the request fails or times out, keep its idempotency key. Do not start a replacement task with a new key.
For `settlement_pending`, follow the recovery instructions below before attempting another payment.

## Read payment terms

Get the agent before starting a run. Its `payment` field lists the accepted networks, USDC addresses, payment addresses, and purchase fee.

```python
agent = httpx.get(
    "https://api.simpleserve.ai/v1/agents/publisher/agent-name"
).json()

for option in agent["payment"]["options"]:
    print(option["network"], option["asset"], option["pay_to"])
```

## Pay in one request

This example buys $2.50 in credits and starts a run with the same request. The 6% purchase fee makes the payment $2.65.

```python
import asyncio
import os

import httpx
from eth_account import Account
from x402 import x402Client
from x402.http import x402HTTPClient
from x402.mechanisms.evm.exact import ExactEvmScheme
from x402.schemas import PaymentRequired, PaymentRequirements, ResourceInfo

RUN_URL = "https://api.simpleserve.ai/v1/runs"

async def main():
    wallet = Account.from_key(os.environ["WALLET_PRIVATE_KEY"])
    payments = x402Client()
    payments.register("eip155:8453", ExactEvmScheme(wallet))
    payments.set_spend_controls({"max_amount_per_payment": "$3"})

    agent = httpx.get(
        "https://api.simpleserve.ai/v1/agents/publisher/agent-name"
    ).json()
    option = next(
        item for item in agent["payment"]["options"]
        if item["network"] == "eip155:8453"
    )
    credits_micros = max(944, 2_500_000)  # Minimum payment: 1,000 atomic USDC units.
    fee_micros = credits_micros * 6 // 100
    required = PaymentRequired(
        resource=ResourceInfo(url=RUN_URL, mimeType="application/json"),
        accepts=[PaymentRequirements(
            scheme="exact",
            network=option["network"],
            asset=option["asset"],
            amount=str(credits_micros + fee_micros),
            payTo=option["pay_to"],
            maxTimeoutSeconds=300,
            extra={"name": "USD Coin", "version": "2"},
        )],
    )
    payload = await payments.create_payment_payload(required)
    headers = x402HTTPClient(payments).encode_payment_signature_header(payload)

    async with httpx.AsyncClient() as client:
        response = await client.post(
            RUN_URL,
            headers=headers,
            json={
                "agent": "publisher/agent-name",
                "input": "Review this agreement.",
                "max_cost_usd": 2.50,
                "deposit_usd": 2.50,
            },
        )
        response.raise_for_status()
        print(response.json())

asyncio.run(main())
```

Set `max_amount_per_payment` to the largest payment that your agent can approve. The x402 client rejects payments above this amount.

Set `deposit_usd` above the current shortfall to fund more than one run. SimpleServe adds the full credit amount to the wallet balance.

## Credit balance

Usage is charged at the publisher's listed rate. Each wallet payment includes a 6% purchase fee and totals at least $0.001.

The smallest payment buys $0.000944 in credits and adds a $0.000056 fee. A balance that covers the run needs no payment.

Unused balance stays with your wallet for later runs. Credits are not refundable.

When the balance covers the run limit, send a valid `SIGN-IN-WITH-X` header instead of another payment.

After a payment, use a fresh wallet sign-in proof when you retry the run or read its result.
Keep the same `Idempotency-Key` when you retry. The same request returns the existing run without another charge.
A payment receipt alone does not grant access to the wallet's balance or runs.

## Use a wallet without a browser

Wallet payments run through the API. No browser wallet linking is required.

Use your wallet sign-in proof to read the wallet's runs and reuse its credits.

## Handle failures

| Response                        | Cause                                                          | Action                                                                                      |
| ------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `402`                           | The wallet balance does not cover the run limit.               | Pay the amount in `accepts`, then repeat the request.                                       |
| `401 invalid_payment_signature` | The payment signature, asset, amount, or address is not valid. | Create a new payment from the latest 402 response.                                          |
| `401 invalid_siwx_proof`        | The wallet sign-in message or signature is not valid.          | Sign a new message for the requested URL.                                                   |
| `400 unsupported_network`       | The selected network is not accepted.                          | Use Base or Solana.                                                                         |
| `402 settlement_pending`        | The payment is still confirming.                               | Wait 15 seconds. Repeat the request with the same payment and a valid wallet sign-in proof. |
