Agent Payment Setup

How to configure your AI agent to pay for signed price attestations autonomously — no human in the loop after setup.

Not sure which rail to use? Start with x402 (USDC on Base). It works with any EVM wallet, the Coinbase CDP facilitator handles verification, and $1 of USDC covers hundreds of queries. Switch to L402 if you need Lightning-native or Bitcoin-native integration.

Most Common

x402 — USDC on Base

$0.01 per price query. Any EVM wallet. Ed25519 signed responses.

Bitcoin Native

L402 — Lightning

10 sats per query. Lightning wallet required. secp256k1 signed responses.

Free / No Payment

Preview endpoints

Unsigned sample data at /preview. No payment needed. Not for production.

x402 — USDC on Base

Setting up x402 payment

x402 uses USDC on Base L2. Your agent holds a Base wallet with USDC, signs a payment authorization on each request, and receives a signed attestation in response. The entire flow is one HTTP round-trip after the initial 402.

Prerequisites

Getting USDC on Base: Bridge from Ethereum via bridge.base.org, or buy directly on Coinbase and withdraw to Base. The Coinbase CDP facilitator verifies all payments — no additional setup needed on your end.

ElizaOS — x402 setup

The ElizaOS plugin reads payment credentials from your agent's character configuration. Add the following to your character file:

character.json — x402 configuration
{
  "name": "your-agent",
  "plugins": ["@jonathanbulkeley/plugin-mycelia-signal"],
  "settings": {
    "MYCELIA_PAYMENT_RAIL": "x402",

    // Your Base wallet private key (keep this secret)
    "MYCELIA_X402_PRIVATE_KEY": "0xabc123...",

    // Your wallet address on Base
    "MYCELIA_X402_ADDRESS": "0xYourWalletAddress",

    // Network — always Base mainnet for production
    "MYCELIA_X402_NETWORK": "base"
  }
}

Verify it's working: Ask your agent "What is the price of Bitcoin?" — it should respond with a signed price, sources, and timestamp. If it returns an error about payment configuration, check that MYCELIA_X402_PRIVATE_KEY is set correctly.

LangChain — x402 setup

Pass credentials when initialising the plugin:

Python — LangChain x402 setup
from langchain_mycelia_signal import MyceliaSignalPlugin

# Initialise with x402 payment config
plugin = MyceliaSignalPlugin(
    payment_rail="x402",
    x402_private_key="0xabc123...",    # Base wallet private key
    x402_address="0xYourWalletAddress",  # Base wallet address
    x402_network="base"                 # Always base for production
)

# Get all tools including price feeds and DLC
tools = plugin.as_list()

# Or just price tools
price_tools = plugin.price_tools()

# Or just DLC tools
dlc_tools = plugin.dlc_tools()

Use environment variables in production rather than hardcoding credentials:

Python — using environment variables
import os
from langchain_mycelia_signal import MyceliaSignalPlugin

plugin = MyceliaSignalPlugin(
    payment_rail="x402",
    x402_private_key=os.environ["MYCELIA_X402_PRIVATE_KEY"],
    x402_address=os.environ["MYCELIA_X402_ADDRESS"],
    x402_network="base"
)

Generic HTTP client — x402

If you're building your own agent without a framework, implement the x402 flow directly:

Python — x402 from scratch
import requests
from eth_account import Account
from web3 import Web3
import json, time

PRIVATE_KEY = os.environ["MYCELIA_X402_PRIVATE_KEY"]
WALLET_ADDRESS = os.environ["MYCELIA_X402_ADDRESS"]
BASE_URL = "https://api.myceliasignal.com"

def get_signed_price(endpoint):
    # Step 1: Request the endpoint — expect 402
    r = requests.get(f"{BASE_URL}{endpoint}")
    if r.status_code != 402:
        return r.json()

    # Step 2: Parse payment requirements from 402 response
    payment_req = r.json()
    amount = payment_req["amount"]          # e.g. "10000" (in USDC base units)
    recipient = payment_req["recipient"]    # Mycelia Signal payment address

    # Step 3: Sign EIP-3009 transferWithAuthorization
    w3 = Web3()
    nonce = w3.keccak(text=f"{WALLET_ADDRESS}{time.time()}").hex()
    valid_after = 0
    valid_before = int(time.time()) + 300   # 5 minute window

    domain = {"name": "USD Coin", "version": "2", "chainId": 8453}
    message = {
        "from": WALLET_ADDRESS,
        "to": recipient,
        "value": int(amount),
        "validAfter": valid_after,
        "validBefore": valid_before,
        "nonce": nonce
    }
    signed = Account.sign_typed_data(PRIVATE_KEY, domain, {"TransferWithAuthorization": [...]}, message)

    # Step 4: Retry with payment header
    payment_header = json.dumps({
        "x402Version": 1,
        "scheme": "exact",
        "network": "base",
        "payload": {
            "signature": signed.signature.hex(),
            "from": WALLET_ADDRESS,
            "to": recipient,
            "value": amount,
            "validAfter": str(valid_after),
            "validBefore": str(valid_before),
            "nonce": nonce
        }
    })

    r2 = requests.get(
        f"{BASE_URL}{endpoint}",
        headers={"X-Payment": payment_header}
    )
    return r2.json()

# Usage
result = get_signed_price("/oracle/price/btc/usd")
print(result["price"], result["signature"])

Easier option: Use the Coinbase x402 SDK which handles the EIP-3009 signing automatically. The SDK supports Python, TypeScript, and Go.

L402 — Lightning Network

Setting up L402 payment

L402 uses Bitcoin Lightning. Your agent holds a Lightning wallet connection, pays invoices automatically when it receives a 402, and retries with a macaroon credential. 10 sats per price query — fractions of a cent.

Prerequisites

New to Lightning? Voltage offers hosted LND nodes with a simple REST API — no server management required. Spin up a node, fund it with a small amount of BTC, and you're ready. A $5 top-up covers thousands of queries.

ElizaOS — L402 setup

Add Lightning credentials to your character configuration:

character.json — L402 configuration
{
  "name": "your-agent",
  "plugins": ["@jonathanbulkeley/plugin-mycelia-signal"],
  "settings": {
    "MYCELIA_PAYMENT_RAIL": "l402",

    // Your LND node REST endpoint
    "MYCELIA_LND_REST_URL": "https://your-node.t.voltageapp.io:8080",

    // Your LND macaroon (hex encoded) — use invoice macaroon for safety
    "MYCELIA_LND_MACAROON": "0201036c6e64...",

    // Optional: TLS cert if self-hosted (not needed for Voltage)
    "MYCELIA_LND_TLS_CERT": ""
  }
}

Security note: Use the invoice macaroon, not the admin macaroon. It can pay invoices but cannot manage channels or perform admin operations.

LangChain — L402 setup

Python — LangChain L402 setup
import os
from langchain_mycelia_signal import MyceliaSignalPlugin

plugin = MyceliaSignalPlugin(
    payment_rail="l402",
    lnd_rest_url=os.environ["MYCELIA_LND_REST_URL"],
    lnd_macaroon=os.environ["MYCELIA_LND_MACAROON"],
    # lnd_tls_cert=os.environ["MYCELIA_LND_TLS_CERT"]  # if self-hosted
)

tools = plugin.as_list()

Generic HTTP client — L402

Python — L402 from scratch
import requests, base64

LND_URL = os.environ["MYCELIA_LND_REST_URL"]
MACAROON = os.environ["MYCELIA_LND_MACAROON"]
BASE_URL = "https://api.myceliasignal.com"

def pay_invoice(payment_request):
    """Pay a Lightning invoice via LND REST API"""
    r = requests.post(
        f"{LND_URL}/v1/channels/transactions",
        headers={"Grpc-Metadata-Macaroon": MACAROON},
        json={"payment_request": payment_request},
        verify=False  # Set to True with proper TLS cert
    )
    return r.json()

def get_signed_price_l402(endpoint):
    # Step 1: Request — expect 402 with Lightning invoice
    r = requests.get(f"{BASE_URL}{endpoint}")
    if r.status_code != 402:
        return r.json()

    # Step 2: Parse WWW-Authenticate header for invoice + macaroon
    auth_header = r.headers.get("WWW-Authenticate", "")
    # Header format: L402 macaroon="...", invoice="lnbc..."
    parts = dict(p.strip().split("=", 1) for p in auth_header[5:].split(","))
    macaroon = parts["macaroon"].strip('"')
    invoice = parts["invoice"].strip('"')

    # Step 3: Pay the Lightning invoice
    payment = pay_invoice(invoice)
    preimage = payment["payment_preimage"]

    # Step 4: Retry with L402 Authorization header
    r2 = requests.get(
        f"{BASE_URL}{endpoint}",
        headers={"Authorization": f"L402 {macaroon}:{preimage}"}
    )
    return r2.json()

# Usage
result = get_signed_price_l402("/oracle/price/btc/usd")
print(result["price"], result["signature"])
Testing & Verification

Verify your setup is working

Before deploying your agent, verify payment is configured correctly with these tests:

Step 1 — Test the preview endpoint (no payment)

Confirm basic connectivity before adding payment:

bash
# Should return unsigned sample data — no payment needed
curl https://api.myceliasignal.com/oracle/price/btc/usd/preview

Step 2 — Verify payment triggers correctly

Confirm the paid endpoint returns a 402 before payment:

bash
# Should return 402 Payment Required
curl -s -o /dev/null -w "%{http_code}" https://api.myceliasignal.com/oracle/price/btc/usd

Step 3 — Verify your agent pays and receives data

Ask your agent a price question and check that the response includes a signature field. An unsigned response means payment is not configured correctly.

Signed response looks like this: {"pair":"BTC/USD","price":"84231.50","signature":"a3f8c2...","pubkey":"03c195..."} — if you see a signature field, payment is working.

Verify the signature

Optionally verify the cryptographic signature to confirm data integrity:

Python — signature verification
from coincurve import PublicKey
import hashlib

# Published Mycelia Signal pubkeys
PUBKEYS = {
    "us": "03c1955b8c543494c4ecd86d167105bcc7ca9a91b8e06cb9d6601f2f55a89abfbf",
    "asia": "02b1377c30c7dcfcba428cf299c18782856a12eb4fab32b87081460f4ba2deab73"
}

def verify_attestation(response):
    canonical = response["canonical"]
    signature = bytes.fromhex(response["signature"])
    pubkey_hex = response["pubkey"]

    msg_hash = hashlib.sha256(canonical.encode()).digest()
    pub = PublicKey(bytes.fromhex(pubkey_hex))
    return pub.verify(signature, msg_hash)

# True = data is authentic and unmodified
print(verify_attestation(result))