> ## Documentation Index
> Fetch the complete documentation index at: https://docs.allium.so/llms.txt
> Use this file to discover all available pages before exploring further.

# x402

> Full manual control over 402 payment negotiation, EIP-712 signing, and USDC settlement.

x402 is Allium's micropayment protocol that gives you full control over the payment flow. Your client handles the `402 Payment Required` negotiation, constructs EIP-712 typed data, signs via a wallet provider, and retries the request with a payment signature.

## Quickstart with Agent

The fastest way to get started — let your AI agent handle setup and payments for you.

<Steps>
  <Step title="Install allium-cli">
    ```bash theme={null}
    curl -sSL https://agents.allium.so/cli/install.sh | sh
    ```

    <Warning>
      [allium-cli](https://github.com/Allium-Science/allium-cli) is still in early beta.
    </Warning>
  </Step>

  <Step title="Install agent skills">
    ```bash theme={null}
    npx skills add allium-labs/skills --yes
    ```
  </Step>

  <Step title="Open your agent">
    Start Claude Code, Codex, or any compatible AI agent.
  </Step>

  <Step title="Ask a question">
    ```
    Set up Allium and fetch the latest balances for vitalik.eth
    ```
  </Step>
</Steps>

## How It Works

<Steps>
  <Step title="Send request">
    Make a normal API request to any payment-enabled endpoint.
  </Step>

  <Step title="Receive 402">
    The server responds with `402 Payment Required` and an `accepts` array of payment options.
  </Step>

  <Step title="Sign payment">
    Your client constructs and signs an EIP-712 payment authorization via [Privy server wallets](https://docs.privy.io/api-reference/wallets/ethereum/eth-signtypeddata-v4) — no private keys or gas needed.
  </Step>

  <Step title="Retry with signature">
    Resend the original request with the payment signature header attached.
  </Step>

  <Step title="Settlement">
    Allium verifies the authorization and settles the USDC payment onchain via [Coinbase](https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/x402-facilitator).
  </Step>

  <Step title="Get data">
    Payment confirmed — you receive your data with a `200` response.
  </Step>
</Steps>

## Supported Tokens & Networks

x402 payments use **USDC** on the following networks:

| Network              | USDC Contract Address                          | EIP-712 Domain                 |
| -------------------- | ---------------------------------------------- | ------------------------------ |
| **Base** (mainnet)   | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`   | name: `USD Coin`, version: `2` |
| **Solana** (mainnet) | `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | name: `USDC`, version: `2`     |

## Implementation

This guide uses **[Privy](https://dashboard.privy.io) server wallets** for signing. Privy handles wallet creation and EIP-712 signing via its API — you never touch private keys or pay gas.

### Prerequisites

* **Python 3.8+**
* **[Privy](https://dashboard.privy.io) account** — free, takes 30 seconds
* **USDC** on Base or Solana (mainnet)

### 1. Set Up Privy

<Steps>
  <Step title="Create a Privy app">
    Go to [dashboard.privy.io](https://dashboard.privy.io), sign up, and click **Create App**.
  </Step>

  <Step title="Copy your credentials">
    Copy your **App ID** and **App Secret** from the dialog and store them securely.
  </Step>

  <Step title="If you closed the dialog too soon">
    Go to **Configuration → App settings → Basics → New Secret** to generate a new **App ID** and **App Secret** pair.
  </Step>

  <Step title="Set environment variables">
    ```bash theme={null}
    export PRIVY_APP_ID="your-app-id"
    export PRIVY_APP_SECRET="your-app-secret"
    ```
  </Step>
</Steps>

### 2. Install Dependencies

```bash theme={null}
pip install privy-client httpx
```

### 3. Create a Wallet

Create a server-managed wallet on Privy. This wallet will sign x402 payments on your behalf.

```python theme={null}
import os
from privy import PrivyAPI

client = PrivyAPI(
    app_id=os.environ["PRIVY_APP_ID"],
    app_secret=os.environ["PRIVY_APP_SECRET"],
)

wallet = client.wallets.create(chain_type="ethereum")
print(f"Wallet ID: {wallet.id}")
print(f"Address: {wallet.address}")
```

Save the wallet ID as an environment variable:

```bash theme={null}
export PRIVY_WALLET_ID="your-wallet-id"
```

Fund this wallet address with USDC on Base (or Base Sepolia for testing).

<Tip>
  If testing, get free testnet USDC from [Circle's faucet](https://faucet.circle.com/). Select **Base Sepolia** as the network.
</Tip>

### 4. Make a Paid API Call

Here's a complete working example that fetches the current price of ETH. The `x402_request` helper handles the full payment flow:

1. Send the API request
2. Receive a **402 response** containing an `accepts` array of payment options
3. Select a payment option matching your network (Base or Base Sepolia)
4. **Construct EIP-712 typed data** for a USDC `TransferWithAuthorization` ([EIP-3009](https://eips.ethereum.org/EIPS/eip-3009))
5. **Sign via Privy** using `eth_signTypedData_v4`
6. Build the x402 v2 `PaymentPayload` and retry with the `PAYMENT-SIGNATURE` header

```python theme={null}
import base64
import json
import os
import secrets

import httpx
from privy import PrivyAPI

PRIVY_APP_ID = os.environ["PRIVY_APP_ID"]
PRIVY_APP_SECRET = os.environ["PRIVY_APP_SECRET"]
WALLET_ID = os.environ["PRIVY_WALLET_ID"]
BASE_URL = "https://agents.allium.so"

# "eip155:8453" for Base mainnet, "eip155:84532" for Base Sepolia testnet
TARGET_NETWORK = "eip155:84532"

privy = PrivyAPI(app_id=PRIVY_APP_ID, app_secret=PRIVY_APP_SECRET)

# Retrieve the wallet address — needed for the authorization "from" field
wallet = privy.wallets.get(wallet_id=WALLET_ID)
WALLET_ADDRESS = wallet.address


def x402_request(http, method, url, **kwargs):
    """Make an API request with automatic x402 payment signed via Privy."""
    headers = kwargs.pop("headers", {})
    response = http.request(method, url, headers=headers, **kwargs)

    if response.status_code != 402:
        return response

    # The 402 response contains an "accepts" array of payment options,
    # each specifying a network, amount, asset, and payTo address.
    # There is no pre-built "typed_data" — the client must construct it.
    payment_details = response.json()
    accepts = payment_details.get("accepts", [])

    # Select the payment option matching our network
    option = next((a for a in accepts if a["network"] == TARGET_NETWORK), None)
    if option is None:
        raise ValueError(f"No payment option for {TARGET_NETWORK}")

    # Parse chain ID from CAIP-2 network string, e.g. "eip155:84532" -> 84532
    chain_id = int(option["network"].split(":")[1])
    nonce = "0x" + secrets.token_hex(32)

    # Construct EIP-712 typed data for USDC TransferWithAuthorization (EIP-3009).
    # The domain name and version come from the payment option's "extra" field —
    # these differ per network (Base mainnet: "USD Coin", Base Sepolia: "USDC").
    typed_data = {
        "types": {
            "EIP712Domain": [
                {"name": "name", "type": "string"},
                {"name": "version", "type": "string"},
                {"name": "chainId", "type": "uint256"},
                {"name": "verifyingContract", "type": "address"},
            ],
            "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"},
            ],
        },
        "domain": {
            "name": option["extra"]["name"],
            "version": option["extra"]["version"],
            "chainId": chain_id,
            "verifyingContract": option["asset"],
        },
        # Privy expects "primary_type" (snake_case), not "primaryType" (camelCase)
        "primary_type": "TransferWithAuthorization",
        "message": {
            "from": WALLET_ADDRESS,
            "to": option["payTo"],
            "value": str(option["amount"]),
            "validAfter": "0",
            "validBefore": str(option["maxTimeoutSeconds"]),
            "nonce": nonce,
        },
    }

    # Sign with Privy server wallet
    sign_result = privy.wallets.rpc(
        wallet_id=WALLET_ID,
        method="eth_signTypedData_v4",
        params={"typed_data": typed_data},
    )

    # Build the x402 v2 PaymentPayload with resource, accepted, and payload fields
    payment_payload = {
        "x402Version": payment_details["x402Version"],
        "resource": {
            "url": option["resource"],
            "description": option.get("description", ""),
            "mimeType": option.get("mimeType", "application/json"),
        },
        "accepted": {
            "scheme": option["scheme"],
            "network": option["network"],
            "amount": str(option["amount"]),
            "asset": option["asset"],
            "payTo": option["payTo"],
            "maxTimeoutSeconds": option["maxTimeoutSeconds"],
            "extra": option.get("extra", {}),
        },
        "payload": {
            "signature": sign_result.data.signature,
            "authorization": {
                "from": WALLET_ADDRESS,
                "to": option["payTo"],
                "value": str(option["amount"]),
                "validAfter": "0",
                "validBefore": str(option["maxTimeoutSeconds"]),
                "nonce": nonce,
            },
        },
    }

    # Base64-encode the payload and send as PAYMENT-SIGNATURE header
    headers["PAYMENT-SIGNATURE"] = base64.b64encode(
        json.dumps(payment_payload).encode()
    ).decode()
    response = http.request(method, url, headers=headers, **kwargs)
    return response


# Fetch ETH price
with httpx.Client(timeout=30.0) as http:
    result = x402_request(
        http,
        "POST",
        f"{BASE_URL}/api/v1/developer/prices",
        json=[
            {
                "chain": "ethereum",
                "token_address": "0x0000000000000000000000000000000000000000",
            }
        ],
    )
    print(json.dumps(result.json(), indent=2))
```

**Expected response:**

```json theme={null}
{
  "items": [
    {
      "timestamp": "2026-02-10T09:27:11Z",
      "chain": "ethereum",
      "address": "0x0000000000000000000000000000000000000000",
      "decimals": 18,
      "price": 2023.6042396986138,
      "open": 2023.6042396986138,
      "high": 2023.6042396986138,
      "close": 2023.6042396986138,
      "low": 2023.6042396986138
    }
  ]
}
```

### EIP-712 Domain Configuration

The USDC contracts use different EIP-712 domains per network. These values are required when constructing the `TransferWithAuthorization` typed data for signing. They are provided in the 402 response's `accepts[].extra` field.

| Network    | Domain Name | Version |
| ---------- | ----------- | ------- |
| **Base**   | `USD Coin`  | `2`     |
| **Solana** | `USDC`      | `2`     |

<Warning>
  Privy signs the EIP-712 typed data via `eth_signTypedData_v4`, but the **client must construct** the typed data itself from the 402 response fields. The server does not provide a ready-to-sign `typed_data` object.
</Warning>

### Frontend Integration

Building a web app with embedded wallets? Use [Privy's `useX402Fetch` hook](https://docs.privy.io/recipes/x402) for seamless client-side x402 payments in React.
