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.
x402 — USDC on Base
$0.01 per price query. Any EVM wallet. Ed25519 signed responses.
L402 — Lightning
10 sats per query. Lightning wallet required. secp256k1 signed responses.
Preview endpoints
Unsigned sample data at /preview. No payment needed. Not for production.
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
- → A Base wallet with USDC (minimum $1 recommended to start)
- → The wallet's private key accessible to your agent runtime
- ✓ No API key or account required
- ✓ No subscription or signup
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:
{
"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:
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:
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:
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.
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
- → A Lightning node or hosted wallet with API access (Voltage recommended)
- → Your node's REST API endpoint and macaroon
- → Inbound and outbound liquidity (minimum ~1,000 sats outbound)
- ✓ No API key or account with Mycelia Signal
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:
{
"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
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
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"])
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:
# 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:
# 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:
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))