Buy quota (USDC top-up)
When your included monthly allotment runs out, an attestation request returns 402. Buy more by paying USDC to POST /v1/topup, which uses the x402 payment protocol settled through the Coinbase Developer Platform (CDP) facilitator. Quota you buy this way (usdc_quota_remaining) rolls over and is consumed after your monthly allotment.
Devnet beta: payments are devnet USDC (mint
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU).
The flow
POST /v1/topupwith your bearer key + the amount, no payment → server returns402with x402PaymentRequirements.- Build + sign an x402
exactSolana payment that satisfies those requirements. - Retry with the base64 payment in the
X-PAYMENTheader → server runs CDP/verify→/settle→ re-verifies on-chain → credits quota →200.
See the full HTTP contract in the REST API reference.
Building the signed payment
The 402 response tells you everything you need in accepts[0]: asset (USDC mint), maxAmountRequired (micro-USDC), payTo (treasury owner wallet), and extra.feePayer (the facilitator's fee payer). Build a v0 versioned transaction with 3 instructions — compute-unit limit, compute-unit price, and an SPL transfer_checked to the treasury's associated token account — with the facilitator as fee payer (it signs + pays gas at settle), then partial-sign with your payer key:
import base64, json, httpx, base58
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solders.message import MessageV0, to_bytes_versioned
from solders.transaction import VersionedTransaction
from solders.hash import Hash
from solders.signature import Signature
from solders.compute_budget import set_compute_unit_limit, set_compute_unit_price
from spl.token.instructions import (
transfer_checked, TransferCheckedParams, get_associated_token_address)
from spl.token.constants import TOKEN_PROGRAM_ID
RPC = "https://api.devnet.solana.com"
payer = Keypair.from_bytes(base58.b58decode(PAYER_SECRET_BASE58)) # your funded USDC wallet
def build_x_payment(reqs: dict) -> str:
mint = Pubkey.from_string(reqs["asset"])
owner = Pubkey.from_string(reqs["payTo"]) # treasury OWNER (not the ATA)
feepay = Pubkey.from_string(reqs["extra"]["feePayer"]) # facilitator fee payer
amount = int(reqs["maxAmountRequired"])
src = get_associated_token_address(payer.pubkey(), mint)
dst = get_associated_token_address(owner, mint) # facilitator derives the same ATA
transfer = transfer_checked(TransferCheckedParams(
program_id=TOKEN_PROGRAM_ID, source=src, mint=mint, dest=dst,
owner=payer.pubkey(), amount=amount, decimals=6, signers=[]))
ixs = [set_compute_unit_limit(60_000), set_compute_unit_price(1), transfer] # 3 instructions
bh = httpx.post(RPC, json={"jsonrpc":"2.0","id":1,"method":"getLatestBlockhash",
"params":[{"commitment":"finalized"}]}).json()["result"]["value"]["blockhash"]
msg = MessageV0.try_compile(feepay, ixs, [], Hash.from_string(bh)) # fee payer = facilitator
sig = payer.sign_message(to_bytes_versioned(msg)) # sign the versioned payload
vtx = VersionedTransaction.populate(msg, [Signature.default(), sig]) # facilitator slot empty
payload = {"x402Version": 1, "scheme": "exact", "network": "solana-devnet",
"payload": {"transaction": base64.b64encode(bytes(vtx)).decode()}}
return base64.b64encode(json.dumps(payload).encode()).decode()
API = "https://mcp.axiomstack.dev/v1/topup"
hdr = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
body = {"amount_usdc_micros": 1_000_000} # $1.00
r1 = httpx.post(API, headers=hdr, json=body) # step 1 -> 402 + requirements
reqs = r1.json()["accepts"][0]
r2 = httpx.post(API, headers={**hdr, "X-PAYMENT": build_x_payment(reqs)}, json=body)
print(r2.status_code, r2.json()) # 200 -> {credited_attestations, usdc_quota_remaining, ...}
Three details that the facilitator enforces (and that are easy to get wrong):
- v0 versioned transaction, 3–6 instructions. Legacy / single-instruction transactions are rejected.
- Sign
to_bytes_versioned(message), notbytes(message)— otherwise the signature fails simulation. payTois the owner wallet, not a token account — the facilitator derives the destination ATA itself.
What you get credited
The amount maps to attestations at your tier's rate (marginal-rate math) — see Pricing & tiers. The credit is idempotent: replaying a settled payment returns 409 already_credited, never a double credit.