Skip to main content

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 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.
1

Install allium-cli

curl -sSL https://agents.allium.so/cli/install.sh | sh
allium-cli is still in early beta.
2

Install agent skills

npx skills add allium-labs/skills --yes
3

Open your agent

Start Claude Code, Codex, or any compatible AI agent.
4

Ask a question

Set up Allium and fetch the latest balances for vitalik.eth

How It Works

1

Send request

Make a normal API request to any payment-enabled endpoint.
2

Receive 402

The server responds with 402 Payment Required and an accepts array of payment options.
3

Sign payment

Your client constructs and signs an EIP-712 payment authorization via Privy server wallets — no private keys or gas needed.
4

Retry with signature

Resend the original request with the payment signature header attached.
5

Settlement

Allium verifies the authorization and settles the USDC payment onchain via Coinbase.
6

Get data

Payment confirmed — you receive your data with a 200 response.

Supported Tokens & Networks

x402 payments use USDC on the following networks:
NetworkUSDC Contract AddressEIP-712 Domain
Base (mainnet)0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913name: USD Coin, version: 2
Solana (mainnet)EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vname: USDC, version: 2

Implementation

This guide uses Privy 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 account — free, takes 30 seconds
  • USDC on Base or Solana (mainnet)

1. Set Up Privy

1

Create a Privy app

Go to dashboard.privy.io, sign up, and click Create App.
2

Copy your credentials

Copy your App ID and App Secret from the dialog and store them securely.
3

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.
4

Set environment variables

export PRIVY_APP_ID="your-app-id"
export PRIVY_APP_SECRET="your-app-secret"

2. Install Dependencies

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.
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:
export PRIVY_WALLET_ID="your-wallet-id"
Fund this wallet address with USDC on Base (or Base Sepolia for testing).
If testing, get free testnet USDC from Circle’s faucet. Select Base Sepolia as the network.

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)
  5. Sign via Privy using eth_signTypedData_v4
  6. Build the x402 v2 PaymentPayload and retry with the PAYMENT-SIGNATURE header
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:
{
  "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.
NetworkDomain NameVersion
BaseUSD Coin2
SolanaUSDC2
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.

Frontend Integration

Building a web app with embedded wallets? Use Privy’s useX402Fetch hook for seamless client-side x402 payments in React.