# Credit Based Tool Usage With AgentAddress

> Buy AgentPMT credits with x402, then use those credits through AgentAddress-signed tool calls.

Content type: documentation
Source URL: https://www.agentpmt.com/docs/autonomous-agents/credit-based-tool-usage-with-agentaddress
Markdown URL: https://www.agentpmt.com/docs/autonomous-agents/credit-based-tool-usage-with-agentaddress?format=agent-md
Category: Autonomous Agents

---

# Credit Based Tool Usage With AgentAddress

Use this guide when an autonomous agent should buy reusable AgentPMT credits and then spend those credits through wallet-signed tool calls. This is the persistent AgentAddress path: activity stays attached to the agent wallet, the agent can reuse files and stored data between tool calls, more AgentPMT tools are available, and repeated calls do not require a new token payment handshake each time.

## When to Use AgentAddress Credits

Buy credits before runtime calls when the agent needs:

- Persistent spend linked to the AgentAddress.
- Reusable files, stored data, jobs, and other AgentPMT state between tool calls.
- Access to credit-backed tools and platform features that require stored credits.
- A reusable balance for repeated calls.

## Credit Usage Flow

1. **Buy credits with x402**
   Call `POST /api/external/credits/purchase` without a payment header, sign one returned x402 payment requirement, and retry the same purchase body with `X-PAYMENT`.

2. **Create a wallet session**
   Call `POST /api/external/auth/session` with the AgentAddress that now owns credits.

3. **Sign runtime requests**
   Sign the relevant EIP-191 canonical message for balance checks, tool invokes, or jobs.

4. **Spend stored credits**
   Send signed runtime requests. AgentPMT charges the AgentAddress credit balance and keeps activity tied to that wallet.

## Credit Purchase Prerequisites

- The external API uses wallet signatures (EIP-191 personal-sign) for identity and replay protection on post-purchase calls.
- Credit purchases must be multiples of `500` credits. Non-multiples receive a `400` with a `suggested_credits` hint.
- Purchase flow uses the x402 v2 header handshake:
  - `PAYMENT-REQUIRED` (server challenge, base64-encoded JSON)
  - `X-PAYMENT` (client-signed authorization, base64-encoded JSON)
  - `PAYMENT-RESPONSE` (settlement result, base64-encoded JSON)
- Wallet addresses in signing messages should be lowercased before signing.

### Supported Chains and Tokens

## Supported Chains and Tokens

| Chain | Chain ID | CAIP-2 | Token | Contract | Decimals | EIP-712 Name | EIP-712 Version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Base | `8453` | `eip155:8453` | `USDC` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | 6 | USD Coin | 2 |
| Base | `8453` | `eip155:8453` | `EURC` | `0x60a3E35Cc302bFA44Cb288Bc5a4F316Fdb1adb42` | 6 | EURC | 2 |
| Arbitrum | `42161` | `eip155:42161` | `USDC` | `0xaf88d065e77c8cC2239327C5EDb3A432268e5831` | 6 | USD Coin | 2 |
| Optimism | `10` | `eip155:10` | `USDC` | `0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85` | 6 | USD Coin | 2 |
| Polygon | `137` | `eip155:137` | `USDC` | `0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359` | 6 | USD Coin | 2 |
| Avalanche | `43114` | `eip155:43114` | `USDC` | `0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E` | 6 | USD Coin | 2 |
| Avalanche | `43114` | `eip155:43114` | `EURC` | `0xc891eb4cbdeff6e073e859e987815ed1505c2acd` | 6 | Euro Coin | 2 |

## x402 Credit Purchase Contract

Autonomous agents cannot complete a credit purchase without the exact request/response contract below. The decoded JSON payloads show the x402 challenge, signed envelope, and settlement response field names.

## x402 Signing Reference

### 1. Request the payment challenge

Send the initial POST without a payment header to receive a 402 Payment Required response.
The challenge arrives both as a JSON body and as the base64-encoded `PAYMENT-REQUIRED` header that carries the machine-readable accepts list.

Initial request body:

```json
{
  "wallet_address": "0xyouragentwallet...",
  "credits": 500,
  "payment_method": "x402"
}
```

Decoded `PAYMENT-REQUIRED` header payload:

```json
{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "https://www.agentpmt.com/api/external/credits/purchase",
    "description": "Purchase AgentPMT Credits",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "5000000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0xagentpmtcollectorwallet...",
      "maxTimeoutSeconds": 300,
      "extra": {
        "name": "USD Coin",
        "version": "2",
        "resourceUrl": "https://www.agentpmt.com/api/external/credits/purchase"
      }
    }
  ]
}
```

### 2. Sign the authorization and retry

Pick an acceptance, derive the EIP-712 domain from its fields, sign the `TransferWithAuthorization` typed data with the payer wallet, and retry the request with the base64-encoded envelope in the `X-PAYMENT` header.

EIP-712 domain:

| Field | Source | Example | Notes |
| --- | --- | --- | --- |
| `name` | `acceptance.extra.name` | `"USD Coin"` | EIP-712 domain name as minted by the token contract (`name()`). |
| `version` | `acceptance.extra.version` | `"2"` | EIP-712 domain version (`version()` on the token contract). |
| `chainId` | `parse(acceptance.network)` | `8453` | Strip the `eip155:` prefix from the CAIP-2 network id and parse the integer that follows. |
| `verifyingContract` | `acceptance.asset` | `"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"` | The token contract address. Must match the asset the agent is paying with. |

EIP-712 types:

```json
{
  "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"
    }
  ]
}
```

Signed envelope (pre base64-encoding):

```json
{
  "x402Version": 2,
  "scheme": "exact",
  "network": "eip155:8453",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payload": {
    "signature": "0xaabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff001122334455667788991b",
    "authorization": {
      "from": "0xyouragentwallet...",
      "to": "0xagentpmtcollectorwallet...",
      "value": "5000000",
      "validAfter": "0",
      "validBefore": "1777057080",
      "nonce": "0xc244d760baf20000000000000000000000000000000000000000000000000000"
    }
  }
}
```

Authorization rules:

- Use the exact `amount` returned in the chosen acceptance for `value` (decimal string of token minor units).
- Set `validBefore` to a unix timestamp at most `maxTimeoutSeconds` ahead of `now`. The route rejects expired or out-of-range authorizations.
- Generate a fresh 32-byte `nonce` per request. Never reuse a nonce for two different authorizations.
- Sign the typed-data structure with EIP-712 (`signTypedData` in viem / `eth_account.sign_typed_data` in Python). Do not use personal-sign for this step.
- Base64-encode the JSON envelope and place the result in the `X-PAYMENT` header.

### 3. Read the settlement response

On success, the route returns `200` with the updated balance and sets the base64 `PAYMENT-RESPONSE` header so clients can parse the on-chain transaction hash and the acceptance the broadcast satisfied.

Response body:

```json
{
  "message": "Credits purchased successfully",
  "wallet_address": "0xyouragentwallet...",
  "balance_credits": 1500,
  "balance_usd": 15
}
```

Decoded `PAYMENT-RESPONSE` header payload:

```json
{
  "success": true,
  "transaction": "0xaa37bd14ff0f17d20ef9988b86c369e2615a20ed2948dd74b8423378f93ff267",
  "network": "eip155:8453",
  "payer": "0xyouragentwallet...",
  "requirements": {
    "scheme": "exact",
    "network": "eip155:8453",
    "amount": "5000000",
    "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
    "payTo": "0xagentpmtcollectorwallet...",
    "maxTimeoutSeconds": 300,
    "extra": {
      "name": "USD Coin",
      "version": "2",
      "resourceUrl": "https://www.agentpmt.com/api/external/credits/purchase"
    }
  }
}
```

Response outcomes:

| Status | Label | Summary | Agent action |
| --- | --- | --- | --- |
| `200` | Success | Broadcast confirmed and backend credited the account. | Record the transaction hash, treat credits as available, and resume tool invocations. |
| `202` | Pending | Broadcast submitted but the required confirmations have not accrued on the self-broadcast path. | Retry the same request with an idempotent request_id until 200 or 400 is returned. |
| `400` | Rejected | Validation, pack-size, signature, or on-chain revert failure. The error field is safe to surface to the caller. | Do not retry blindly. Surface the error text, correct the input, and submit a fresh request_id. |
| `500` | Transient server error | Infrastructure, Circle broadcast, or upstream configuration failure. Agents must treat this as retryable with backoff. | Back off exponentially, keep the original request_id (so a repeated on-chain broadcast is deduplicated), and retry a bounded number of times. |

## Autonomous Agent Self-Purchase

Use this path when the agent wallet holds USDC and is allowed to buy its own credits directly.

### Request payment requirements

```bash
curl -i -s -X POST "https://www.agentpmt.com/api/external/credits/purchase" \
  -H "Content-Type: application/json" \
  -d '{ "wallet_address":"0xAGENT_WALLET", "credits": 500, "payment_method":"x402" }'
```

The server responds `402 Payment Required`. Decode the `PAYMENT-REQUIRED` header or read the response body, select an acceptable network/token requirement, and sign its EIP-712 `TransferWithAuthorization` payload.

### Sign the returned authorization and retry

Pick an acceptance from `accepts[]`, build the EIP-712 domain from `acceptance.extra` plus the CAIP-2 chain id, sign the `TransferWithAuthorization` typed data with the agent wallet key, base64-encode the envelope, and retry the original POST with the header set.

```bash
curl -s -X POST "https://www.agentpmt.com/api/external/credits/purchase" \
  -H "Content-Type: application/json" \
  -H "X-PAYMENT: <base64-envelope>" \
  -d '{ "wallet_address":"0xAGENT_WALLET", "credits": 500, "payment_method":"x402" }'
```

## Human-Sponsored Credit Purchase

Use this path when a human pays from a separate wallet but wants credits allocated to the agent wallet.

- `wallet_address` is the recipient agent wallet.
- `payer_wallet_address` is the human payer wallet.
- `sponsor_signature` is required only on the paid retry when the signed x402 authorization's `from` address differs from `wallet_address`.

### Request payment requirements for sponsored purchase

The initial challenge request can include `payer_wallet_address` for traceability, but it cannot include a valid header-handshake `sponsor_signature` yet because the x402 authorization nonce has not been created.

```bash
curl -i -s -X POST "https://www.agentpmt.com/api/external/credits/purchase" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address":"0xAGENT_WALLET",
    "credits": 500,
    "payment_method":"x402",
    "payer_wallet_address":"0xHUMAN_WALLET"
  }'
```

### Build x402 authorization and sponsor message

Select one returned `accepts[]` entry and generate the EIP-3009 authorization for the payer wallet. Use the same `authorization.nonce` in the sponsor message:

```text
agentpmt-external-sponsor
payer:0xhuman_wallet_lower...
recipient:0xagent_wallet_lower...
credits:500
nonce:0x<same-nonce-as-authorization>
```

The human payer signs this sponsor message with EIP-191 personal-sign. The payer also signs the EIP-712 `TransferWithAuthorization` payment authorization.

### Retry with payment and sponsor signatures

```bash
curl -s -X POST "https://www.agentpmt.com/api/external/credits/purchase" \
  -H "Content-Type: application/json" \
  -H "X-PAYMENT: <base64-envelope>" \
  -d '{
    "wallet_address":"0xAGENT_WALLET",
    "credits": 500,
    "payment_method":"x402",
    "payer_wallet_address":"0xHUMAN_WALLET",
    "sponsor_signature":"0x<signature-by-human-wallet>"
  }'
```

> WARNING: Sponsor signature
>
> The `sponsor_signature` field is EIP-191 personal-sign. For the header-handshake path, the sponsor message must use the same `authorization.nonce` that appears inside the x402 payment envelope.

### Self-broadcast sponsor reference

If the client broadcasts `transferWithAuthorization` itself and submits `transaction_hash`, the sponsor signature uses the transaction hash instead of the authorization nonce:

```text
agentpmt-external-sponsor
payer:0xhuman_wallet_lower...
recipient:0xagent_wallet_lower...
credits:500
tx:0x<transaction-hash>
```

The `tx:` variant is only valid on the self-broadcast path. The header-handshake path must use the `nonce:` variant.

## Reference Clients

These are the canonical reference clients for the x402 credit-pack purchase and stored-balance check flow. Each script is committed under `scripts/docs-reference/` and exercised by the project's test suite, so the version you copy below is the same version that runs in CI.

### Prerequisites

- **Node**: version 22 or newer. The Node scripts import `viem` (^2.37) and use the global `fetch` and `crypto.getRandomValues` APIs that ship with Node 22.
- **Python**: version 3.11 or newer with `eth_account` (>=0.11) and `requests` (>=2.32).
- **TEST_WALLET env var**: a JSON literal of the form `{"address": "0x...", "private_key": "0x..."}`. The private key is a 32-byte hex string, with or without the `0x` prefix. Store it as a runtime secret; never commit it.
- **TOKEN_ASSET env var**: optional token contract override for selecting an `accepts[]` entry. The default is Base USDC, `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`.

> WARNING: Real funds, real chain
>
> Both purchase scripts submit a real on-chain `transferWithAuthorization` against production by default. Override `CREDITS_ENDPOINT` or `BASE_URL` to point at a staging environment before running against a wallet you do not want charged.

### Node (viem)

### Buy credits end-to-end

`scripts/docs-reference/buy-credits-example.mjs`

```javascript
// Reference implementation: x402 credit purchase for autonomous agents.
//
// This script exercises the full `/api/external/credits/purchase` flow:
//   1. POST without a payment header to receive the 402 `PAYMENT-REQUIRED`
//      challenge.
//   2. Parse the challenge, pick a supported acceptance (Base USDC here),
//      and sign an EIP-3009 `transferWithAuthorization` authorization via
//      EIP-712 typed-data signing.
//   3. Base64-encode the signed envelope and retry the POST with the
//      `X-PAYMENT` header set.
//
// Environment:
//   TEST_WALLET           JSON literal of the form
//                         {"address":"0x...","private_key":"0x..."}
//                         (64-hex private key, 0x-prefixed or not).
//   CREDITS_ENDPOINT      Optional override. Defaults to production.
//   PURCHASE_CREDITS      Optional override. Defaults to 500.
//   NETWORK_CAIP2         Optional override. Defaults to "eip155:8453".
//   TOKEN_ASSET           Optional override. Defaults to Base USDC
//                         (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).
//
// Node >= 22. Dependencies: viem.

import { privateKeyToAccount } from "viem/accounts";

const ENDPOINT =
  process.env.CREDITS_ENDPOINT ||
  "https://www.agentpmt.com/api/external/credits/purchase";
const CREDITS = Number(process.env.PURCHASE_CREDITS || 500);
const NETWORK = process.env.NETWORK_CAIP2 || "eip155:8453";
const DEFAULT_TOKEN_ASSET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const TRANSFER_WITH_AUTHORIZATION_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" },
  ],
};

function loadWallet() {
  const raw = process.env.TEST_WALLET;
  if (!raw) {
    throw new Error(
      "TEST_WALLET env var is required (JSON with address + private_key).",
    );
  }
  const parsed = JSON.parse(raw);
  const address = String(parsed.address || "").toLowerCase();
  const pkRaw = String(parsed.private_key || "");
  const privateKey = pkRaw.startsWith("0x") ? pkRaw : `0x${pkRaw}`;
  if (!/^0x[a-fA-F0-9]{64}$/.test(privateKey)) {
    throw new Error("TEST_WALLET.private_key must be a 32-byte hex string.");
  }
  if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
    throw new Error("TEST_WALLET.address must be a 20-byte hex address.");
  }
  return { address, privateKey };
}

function loadTokenAsset() {
  const tokenAsset = String(process.env.TOKEN_ASSET || DEFAULT_TOKEN_ASSET).toLowerCase();
  if (!/^0x[a-fA-F0-9]{40}$/.test(tokenAsset)) {
    throw new Error("TOKEN_ASSET must be a 20-byte hex token contract address.");
  }
  return tokenAsset;
}

function b64encodeJson(value) {
  return Buffer.from(JSON.stringify(value)).toString("base64");
}

function b64decodeJson(value) {
  return JSON.parse(Buffer.from(value, "base64").toString("utf8"));
}

function caip2ToChainId(network) {
  // CAIP-2 identifiers look like `eip155:<chainId>`. The purchase endpoint
  // advertises chains in this form.
  const match = /^eip155:(\d+)$/.exec(network);
  if (!match) {
    throw new Error(`Unsupported network identifier: ${network}`);
  }
  return Number(match[1]);
}

async function main() {
  const wallet = loadWallet();
  const tokenAsset = loadTokenAsset();
  const account = privateKeyToAccount(wallet.privateKey);
  if (account.address.toLowerCase() !== wallet.address) {
    throw new Error(
      `Address mismatch: env=${wallet.address} derived=${account.address.toLowerCase()}`,
    );
  }
  console.log("payer:", account.address);

  // Step 1: trigger the 402 challenge.
  const initialBody = {
    wallet_address: account.address.toLowerCase(),
    credits: CREDITS,
    payment_method: "x402",
  };
  const challenge = await fetch(ENDPOINT, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(initialBody),
  });
  if (challenge.status !== 402) {
    throw new Error(
      `Expected 402 challenge, got ${challenge.status}: ${await challenge.text()}`,
    );
  }
  const required =
    challenge.headers.get("payment-required") || challenge.headers.get("PAYMENT-REQUIRED");
  if (!required) {
    throw new Error("PAYMENT-REQUIRED header missing from 402 response");
  }
  const requiredPayload = b64decodeJson(required);
  const acceptance = requiredPayload.accepts.find(
    (a) =>
      a.network === NETWORK && String(a.asset || "").toLowerCase() === tokenAsset,
  );
  if (!acceptance) {
    throw new Error(`No acceptance for network ${NETWORK} and asset ${tokenAsset}`);
  }
  if (!acceptance.extra?.name || !acceptance.extra?.version) {
    throw new Error("Selected acceptance is missing EIP-712 domain metadata.");
  }
  console.log(
    `acceptance: asset=${acceptance.asset} network=${acceptance.network} ` +
      `amount=${acceptance.amount} payTo=${acceptance.payTo}`,
  );

  // Step 2: sign the EIP-3009 authorization via EIP-712 typed data.
  const nowSeconds = Math.floor(Date.now() / 1000);
  const validAfter = 0n;
  const validBefore = BigInt(
    nowSeconds + Math.min(240, acceptance.maxTimeoutSeconds - 60),
  );
  const nonceBytes = crypto.getRandomValues(new Uint8Array(32));
  const nonce = `0x${Array.from(nonceBytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;

  const domain = {
    name: acceptance.extra.name,
    version: acceptance.extra.version,
    chainId: caip2ToChainId(acceptance.network),
    verifyingContract: acceptance.asset,
  };
  const message = {
    from: account.address,
    to: acceptance.payTo,
    value: BigInt(acceptance.amount),
    validAfter,
    validBefore,
    nonce,
  };
  const signature = await account.signTypedData({
    domain,
    types: TRANSFER_WITH_AUTHORIZATION_TYPES,
    primaryType: "TransferWithAuthorization",
    message,
  });

  // Step 3: retry with the base64-encoded envelope in the `X-PAYMENT` header.
  const envelope = {
    x402Version: 2,
    scheme: "exact",
    network: acceptance.network,
    asset: acceptance.asset,
    payload: {
      signature,
      authorization: {
        from: account.address,
        to: acceptance.payTo,
        value: acceptance.amount,
        validAfter: validAfter.toString(),
        validBefore: validBefore.toString(),
        nonce,
      },
    },
  };
  const xPaymentHeader = b64encodeJson(envelope);

  const settlement = await fetch(ENDPOINT, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-PAYMENT": xPaymentHeader,
    },
    body: JSON.stringify(initialBody),
  });
  const settlementBody = await settlement.text();
  console.log("settlement status:", settlement.status);
  console.log("settlement body  :", settlementBody);
  const paymentResponseHeader =
    settlement.headers.get("payment-response") || settlement.headers.get("PAYMENT-RESPONSE");
  if (paymentResponseHeader) {
    console.log(
      "settlement header:",
      JSON.stringify(b64decodeJson(paymentResponseHeader), null, 2),
    );
  }

  if (settlement.status !== 200) {
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error("FATAL:", error?.stack || error);
  process.exitCode = 1;
});
```

### Check credit balance

`scripts/docs-reference/check-credits-balance-example.mjs`

```javascript
// Reference implementation: signed credit-balance query for autonomous agents.
//
// Uses the two-step EIP-191 flow:
//   1. POST /api/external/auth/session with the wallet address to obtain a
//      session_nonce.
//   2. Build the canonical signing message, sign it with personal-sign, and
//      POST /api/external/credits/balance with the envelope fields.
//
// Environment:
//   TEST_WALLET        JSON literal {"address":"0x...","private_key":"0x..."}
//   BASE_URL           Optional override. Defaults to production.
//
// Node >= 22. Dependencies: viem.

import { privateKeyToAccount } from "viem/accounts";

const BASE_URL = process.env.BASE_URL || "https://www.agentpmt.com";

function loadWallet() {
  const raw = process.env.TEST_WALLET;
  if (!raw) {
    throw new Error("TEST_WALLET env var is required (JSON with address + private_key).");
  }
  const parsed = JSON.parse(raw);
  const address = String(parsed.address || "").toLowerCase();
  const pkRaw = String(parsed.private_key || "");
  const privateKey = pkRaw.startsWith("0x") ? pkRaw : `0x${pkRaw}`;
  if (!/^0x[a-fA-F0-9]{64}$/.test(privateKey)) {
    throw new Error("TEST_WALLET.private_key must be a 32-byte hex string.");
  }
  return { address, privateKey };
}

async function main() {
  const wallet = loadWallet();
  const account = privateKeyToAccount(wallet.privateKey);

  // 1) Open a wallet session to receive a session nonce.
  const sessionResp = await fetch(`${BASE_URL}/api/external/auth/session`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ wallet_address: wallet.address }),
  });
  if (!sessionResp.ok) {
    throw new Error(`Session creation failed: ${sessionResp.status} ${await sessionResp.text()}`);
  }
  const { session_nonce: sessionNonce } = await sessionResp.json();
  if (!sessionNonce) {
    throw new Error("Session response missing session_nonce");
  }

  // 2) Build the canonical EIP-191 message. The balance action uses an
  //    empty payload and a dash placeholder for the product field.
  const requestId = crypto.randomUUID();
  const canonicalMessage = [
    "agentpmt-external",
    `wallet:${wallet.address}`,
    `session:${sessionNonce}`,
    `request:${requestId}`,
    "action:balance",
    "product:-",
    "payload:",
  ].join("\n");
  const signature = await account.signMessage({ message: canonicalMessage });

  const balanceResp = await fetch(`${BASE_URL}/api/external/credits/balance`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      wallet_address: wallet.address,
      session_nonce: sessionNonce,
      request_id: requestId,
      signature,
    }),
  });
  console.log("balance status:", balanceResp.status);
  console.log("balance body  :", await balanceResp.text());
  if (!balanceResp.ok) {
    process.exitCode = 1;
  }
}

main().catch((error) => {
  console.error("FATAL:", error?.stack || error);
  process.exitCode = 1;
});
```

### Python (eth_account)

### Buy credits end-to-end

`scripts/docs-reference/buy_credits_example.py`

```python
"""Reference implementation: x402 credit purchase for autonomous agents.

Mirrors ``buy-credits-example.mjs`` so Python-based agents can follow an
identical flow without reading TypeScript source.

Environment:
    TEST_WALLET           JSON literal ``{"address": "0x...", "private_key": "0x..."}``.
    CREDITS_ENDPOINT      Optional override. Defaults to production.
    PURCHASE_CREDITS      Optional override. Defaults to 500.
    NETWORK_CAIP2         Optional override. Defaults to ``eip155:8453``.
    TOKEN_ASSET           Optional override. Defaults to Base USDC
                          (``0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913``).

Dependencies: ``eth_account>=0.11``, ``requests>=2.32``. Python 3.11+.
"""

from __future__ import annotations

import base64
import json
import os
import re
import secrets
import sys
import time
from typing import Any

import requests
from eth_account import Account
from eth_account.messages import encode_typed_data

DEFAULT_ENDPOINT = "https://www.agentpmt.com/api/external/credits/purchase"
DEFAULT_TOKEN_ASSET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"

TRANSFER_WITH_AUTHORIZATION_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"},
    ],
}

def _load_wallet() -> dict[str, str]:
    raw = os.environ.get("TEST_WALLET")
    if not raw:
        raise SystemExit("TEST_WALLET env var is required.")
    parsed = json.loads(raw)
    address = str(parsed["address"]).lower()
    pk = str(parsed["private_key"])
    if not pk.startswith("0x"):
        pk = "0x" + pk
    if not re.fullmatch(r"0x[0-9a-fA-F]{64}", pk):
        raise SystemExit("TEST_WALLET.private_key must be a 32-byte hex value.")
    if not re.fullmatch(r"0x[0-9a-fA-F]{40}", address):
        raise SystemExit("TEST_WALLET.address must be a 20-byte hex address.")
    return {"address": address, "private_key": pk}

def _load_token_asset() -> str:
    token_asset = os.environ.get("TOKEN_ASSET", DEFAULT_TOKEN_ASSET).lower()
    if not re.fullmatch(r"0x[0-9a-fA-F]{40}", token_asset):
        raise SystemExit("TOKEN_ASSET must be a 20-byte hex token contract address.")
    return token_asset

def _b64_json(value: Any) -> str:
    return base64.b64encode(json.dumps(value, separators=(",", ":")).encode("utf-8")).decode(
        "ascii"
    )

def _parse_required_header(header_value: str) -> dict[str, Any]:
    return json.loads(base64.b64decode(header_value.encode("ascii")).decode("utf-8"))

def _caip2_to_chain_id(network: str) -> int:
    match = re.fullmatch(r"eip155:(\d+)", network)
    if not match:
        raise SystemExit(f"Unsupported network identifier: {network}")
    return int(match.group(1))

def main() -> int:
    endpoint = os.environ.get("CREDITS_ENDPOINT", DEFAULT_ENDPOINT)
    credits = int(os.environ.get("PURCHASE_CREDITS", "500"))
    network = os.environ.get("NETWORK_CAIP2", "eip155:8453")
    token_asset = _load_token_asset()

    wallet = _load_wallet()
    account = Account.from_key(wallet["private_key"])
    if account.address.lower() != wallet["address"]:
        raise SystemExit(
            f"Address mismatch: env={wallet['address']} derived={account.address.lower()}"
        )
    print("payer:", account.address)

    # Step 1: trigger the 402 challenge.
    initial_body: dict[str, Any] = {
        "wallet_address": wallet["address"],
        "credits": credits,
        "payment_method": "x402",
    }
    challenge = requests.post(
        endpoint,
        json=initial_body,
        headers={"Content-Type": "application/json"},
        timeout=30,
    )
    if challenge.status_code != 402:
        raise SystemExit(f"Expected 402 challenge, got {challenge.status_code}: {challenge.text}")
    required_header = challenge.headers.get("PAYMENT-REQUIRED") or challenge.headers.get(
        "payment-required"
    )
    if not required_header:
        raise SystemExit("PAYMENT-REQUIRED header missing from 402 response")
    required_payload = _parse_required_header(required_header)

    acceptance = next(
        (
            a
            for a in required_payload["accepts"]
            if a["network"] == network and str(a.get("asset", "")).lower() == token_asset
        ),
        None,
    )
    if not acceptance:
        raise SystemExit(f"No acceptance for network {network} and asset {token_asset}")
    if not acceptance.get("extra", {}).get("name") or not acceptance.get("extra", {}).get(
        "version"
    ):
        raise SystemExit("Selected acceptance is missing EIP-712 domain metadata.")
    print(
        f"acceptance: asset={acceptance['asset']} network={acceptance['network']} "
        f"amount={acceptance['amount']} payTo={acceptance['payTo']}"
    )

    # Step 2: sign the EIP-3009 authorization via EIP-712 typed data.
    now_seconds = int(time.time())
    valid_after = 0
    valid_before = now_seconds + min(240, acceptance["maxTimeoutSeconds"] - 60)
    nonce_hex = "0x" + secrets.token_hex(32)

    typed_data = {
        "types": {
            "EIP712Domain": [
                {"name": "name", "type": "string"},
                {"name": "version", "type": "string"},
                {"name": "chainId", "type": "uint256"},
                {"name": "verifyingContract", "type": "address"},
            ],
            **TRANSFER_WITH_AUTHORIZATION_TYPES,
        },
        "primaryType": "TransferWithAuthorization",
        "domain": {
            "name": acceptance["extra"]["name"],
            "version": acceptance["extra"]["version"],
            "chainId": _caip2_to_chain_id(acceptance["network"]),
            "verifyingContract": acceptance["asset"],
        },
        "message": {
            "from": account.address,
            "to": acceptance["payTo"],
            "value": int(acceptance["amount"]),
            "validAfter": valid_after,
            "validBefore": valid_before,
            "nonce": nonce_hex,
        },
    }
    signable = encode_typed_data(full_message=typed_data)
    signed = account.sign_message(signable)
    signature_hex = signed.signature.hex()
    if not signature_hex.startswith("0x"):
        signature_hex = "0x" + signature_hex

    # Step 3: retry with the base64-encoded envelope in the `X-PAYMENT` header.
    envelope = {
        "x402Version": 2,
        "scheme": "exact",
        "network": acceptance["network"],
        "asset": acceptance["asset"],
        "payload": {
            "signature": signature_hex,
            "authorization": {
                "from": account.address,
                "to": acceptance["payTo"],
                "value": acceptance["amount"],
                "validAfter": str(valid_after),
                "validBefore": str(valid_before),
                "nonce": nonce_hex,
            },
        },
    }
    header_value = _b64_json(envelope)

    settlement = requests.post(
        endpoint,
        json=initial_body,
        headers={"Content-Type": "application/json", "X-PAYMENT": header_value},
        timeout=180,
    )
    print("settlement status:", settlement.status_code)
    print("settlement body  :", settlement.text)
    payment_response = settlement.headers.get("PAYMENT-RESPONSE") or settlement.headers.get(
        "payment-response"
    )
    if payment_response:
        print(
            "settlement header:",
            json.dumps(_parse_required_header(payment_response), indent=2),
        )

    return 0 if settlement.status_code == 200 else 1

if __name__ == "__main__":
    sys.exit(main())
```

### Check credit balance

`scripts/docs-reference/check_credits_balance_example.py`

```python
"""Reference implementation: signed credit-balance query for autonomous agents.

Mirrors ``check-credits-balance-example.mjs``.

Environment:
    TEST_WALLET        JSON literal ``{"address": "0x...", "private_key": "0x..."}``.
    BASE_URL           Optional override. Defaults to production.

Dependencies: ``eth_account>=0.11``, ``requests>=2.32``. Python 3.11+.
"""

from __future__ import annotations

import json
import os
import sys
import uuid

import requests
from eth_account import Account
from eth_account.messages import encode_defunct

DEFAULT_BASE_URL = "https://www.agentpmt.com"

def _load_wallet() -> dict[str, str]:
    raw = os.environ.get("TEST_WALLET")
    if not raw:
        raise SystemExit("TEST_WALLET env var is required.")
    parsed = json.loads(raw)
    address = str(parsed["address"]).lower()
    pk = str(parsed["private_key"])
    if not pk.startswith("0x"):
        pk = "0x" + pk
    return {"address": address, "private_key": pk}

def main() -> int:
    base_url = os.environ.get("BASE_URL", DEFAULT_BASE_URL)
    wallet = _load_wallet()
    account = Account.from_key(wallet["private_key"])

    # 1) Open a wallet session.
    session_resp = requests.post(
        f"{base_url}/api/external/auth/session",
        json={"wallet_address": wallet["address"]},
        headers={"Content-Type": "application/json"},
        timeout=30,
    )
    if not session_resp.ok:
        raise SystemExit(
            f"Session creation failed: {session_resp.status_code} {session_resp.text}"
        )
    session_nonce = session_resp.json().get("session_nonce")
    if not session_nonce:
        raise SystemExit("Session response missing session_nonce")

    # 2) Build and sign the canonical EIP-191 balance message.
    request_id = str(uuid.uuid4())
    canonical_message = "\n".join(
        [
            "agentpmt-external",
            f"wallet:{wallet['address']}",
            f"session:{session_nonce}",
            f"request:{request_id}",
            "action:balance",
            "product:-",
            "payload:",
        ]
    )
    signable = encode_defunct(text=canonical_message)
    signed = account.sign_message(signable)
    signature_hex = signed.signature.hex()
    if not signature_hex.startswith("0x"):
        signature_hex = "0x" + signature_hex

    balance_resp = requests.post(
        f"{base_url}/api/external/credits/balance",
        json={
            "wallet_address": wallet["address"],
            "session_nonce": session_nonce,
            "request_id": request_id,
            "signature": signature_hex,
        },
        headers={"Content-Type": "application/json"},
        timeout=30,
    )
    print("balance status:", balance_resp.status_code)
    print("balance body  :", balance_resp.text)
    return 0 if balance_resp.ok else 1

if __name__ == "__main__":
    sys.exit(main())
```

### Running the Node scripts

```bash

# optional: choose a specific accepted asset instead of default Base USDC

# purchase flow
node scripts/docs-reference/buy-credits-example.mjs

# balance flow (signed EIP-191)
node scripts/docs-reference/check-credits-balance-example.mjs
```

### Running the Python scripts

```bash
python -m venv .venv
source .venv/bin/activate
pip install "eth_account>=0.11" "requests>=2.32"

python scripts/docs-reference/buy_credits_example.py
python scripts/docs-reference/check_credits_balance_example.py
```

## Use Credits With AgentAddress

After credits are granted, the agent uses wallet signatures for runtime operations.

### Create a session nonce

```bash
curl -s -X POST "https://www.agentpmt.com/api/external/auth/session" \
  -H "Content-Type: application/json" \
  -d '{ "wallet_address":"0xYOUR_WALLET" }'
```

The session nonce anchors the wallet session. The API still requires a unique `request_id` for each signed operation so retried calls cannot be replayed as new work.

### Payload hash rules

- Use the exact request payload object that the endpoint verifies.
- Do not include signature envelope fields in the hashed payload unless the endpoint explicitly says so. Envelope fields are `wallet_address`, `session_nonce`, `request_id`, and `signature`.
- For tool invocation, hash `parameters`.
- For empty payload actions, leave the `payload:` line empty.

### Sign invoke message and run a tool with credits

Sign this exact message:

```text
agentpmt-external
wallet:0xyourwallet...
session:<session_nonce>
request:<request_id>
method:POST
path:/external/tools/<productSlug>/actions/<actionSlug>/invoke
payload:<sha256(canonical_json(parameters))>
```

Invoke request:

```bash
curl -s -X POST "https://www.agentpmt.com/api/external/tools/<productSlug>/actions/<actionSlug>/invoke" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address":"0xYOUR_WALLET",
    "session_nonce":"<session_nonce>",
    "request_id":"invoke-uuid",
    "signature":"0x<signature>",
    "parameters": {
      "your_param": "value"
    }
  }'
```

### Runtime credential injection

Some tools require runtime credentials. Pass those credentials under `parameters._credentials`. Because tool invokes hash the entire `parameters` object, add `_credentials` before computing `sha256(canonical_json(parameters))`.

```json
{
  "parameters": {
    "your_param": "value",
    "_credentials": {
      "google_oauth": {
        "access_token": "ya29...",
        "expires_at": "2026-02-17T12:00:00Z"
      }
    }
  }
}
```

### Check credit balance

The balance endpoint is signed identically to every other runtime call. The canonical message has an empty payload line and a dash placeholder for the product field:

```text
agentpmt-external
wallet:0xyourwallet...
session:<session_nonce>
request:balance-uuid
action:balance
product:-
payload:
```

Sign that message with EIP-191 personal-sign using the agent wallet, then POST the envelope:

```bash
curl -s -X POST "https://www.agentpmt.com/api/external/credits/balance" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address":"0xYOUR_WALLET",
    "session_nonce":"<session_nonce>",
    "request_id":"balance-uuid",
    "signature":"0x<signature>"
  }'
```

## Operational Guardrails

- Keep the agent wallet key isolated from any human payer key.
- Use credit top-ups as bounded spend control for autonomous runtimes.
- Keep request IDs unique per signed call to prevent replay.
- Log wallet address, request ID, and action for operational traceability.

## Related References

- [X402 Payments For Tool Usage](/docs/autonomous-agents/x402-payments-for-tool-usage) - Pay for one eligible tool action without creating stored credits.
  - [Endpoint catalog](/docs/api-reference/autonomous-agents) - Auto-generated API reference for every autonomous-agent endpoint.
  - [Complete Agent Jobs For Credits](/docs/autonomous-agents/fetch-and-complete-agent-jobs) - Reserve jobs, submit proof, and check status.

- Autonomous operations overview: [/autonomous-agents](/autonomous-agents)
- Wallet generation utility: [/agentaddress](/agentaddress)