Skip to content

Pay with USDC on Base

An agent can register a Headless Domain with real USDC on Base mainnet without MetaMask, Rainbow, or a browser. The payer signs a bounded EIP-3009 authorization locally; machine.checkout.best relays it and pays gas.

In this route, MPP is the machine checkout workflow and x402 is the exact payment challenge format returned for Base. EIP-3009 is the USDC authorization the payer signs. These are layers of one checkout—not three separate payments.

This moves real money

Base mainnet is chain ID 8453. Base Sepolia (84532) is test-only. Set a maximum charge, verify every challenge field, and stop on any mismatch.

Constants to verify

Field Required Base mainnet value
Network eip155:8453
USDC contract 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Transfer method eip3009
USDC decimals 6
Current Headless Domains receiver 0x018d3DC9447D2455772cf1f61E0EB453D7103F0e

The receiver may change through a controlled treasury rotation. Treat this page as an independent allowlist source, but always require the quote, registration response, and flat x402 challenge to agree. Stop if they do not.

1. Check runtime availability

curl -sS https://machine.checkout.best/.well-known/payment-methods | jq

Continue only if the Base method reports both:

{
  "network": "eip155:8453",
  "settlement_enabled": true
}

If it reports settlement_enabled: false, Base intake is closed. Use the default Tempo route or wait; do not probe the registration endpoint by signing or paying.

2. Authenticate the agent

If the agent does not have an API key, provision one:

curl -sS -X POST https://headlessdomains.com/api/v1/agents/provision \
  -H 'Content-Type: application/json' \
  -d '{}'

Store the returned API key securely and use it as X-API-Key. Do not print it in logs or chat. Give the human the claim code only after the task is complete.

3. Get a non-mutating quote

curl -sS -X POST https://headlessdomains.com/api/v1/domains/quote \
  -H "X-API-Key: $HEADLESSDOMAINS_API_KEY" \
  -H 'X-Payment-Method: mpp' \
  -H 'X-MPP-Network: base-mainnet' \
  -H 'Content-Type: application/json' \
  -d '{"domain":"myagent","namespace":"agent","years":1}'

Require all of the following before continuing:

  • HTTP 200, status: quoted, binding: false, and mutation: none
  • available: true, mpp_network: base, and chain_id: 8453
  • price.currency: USDC and price.amount at or below your approved maximum
  • next.method: POST, an HTTPS next.url, and next.creates_pending_order: true

The quote does not reserve the domain or create an order. Availability and price are checked again when the next request creates a checkout session.

4. Create one checkout challenge

Send the registration request once with the same authenticated identity and explicit Base selection:

curl -sS -X POST https://headlessdomains.com/api/v1/domains/register \
  -H "X-API-Key: $HEADLESSDOMAINS_API_KEY" \
  -H 'X-Payment-Method: mpp' \
  -H 'X-MPP-Network: base-mainnet' \
  -H 'Content-Type: application/json' \
  -d '{
    "domain":"myagent",
    "namespace":"agent",
    "years":1,
    "payment_method":"mpp",
    "mpp_network":"base-mainnet",
    "agreed_to_terms":true
  }'

Expect HTTP 402. Preserve the returned session_id, one-time session_token, settle_url, receipt_url, and flat payment_challenges object, plus the request/domain/quote binding, in an OS keychain or atomic mode-0600 recovery record before parsing the challenge. Do not expect payment_challenge.accepts[]. If parsing or transport then fails, recover this same session; never create or sign a second payment because of the failure.

Convert the authenticated quote display amount to six-decimal minor units and require exact equality with payment_challenges.amount before signing. A value that is merely below the same maximum is not sufficient. After registration, status, and public lookup all reach terminal success, overwrite the recovery record to remove the session token, signed authorization, signature, client reference, and settlement idempotency key. Retain only non-secret completion evidence.

5. Validate before signing

The flat challenge must contain:

{
  "scheme": "exact",
  "network": "eip155:8453",
  "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
  "payTo": "0x018d3DC9447D2455772cf1f61E0EB453D7103F0e",
  "amount": "520000",
  "maxTimeoutSeconds": 600,
  "extra": {
    "assetTransferMethod": "eip3009",
    "name": "USD Coin",
    "version": "2"
  }
}

The amount above is illustrative. Use the fresh quote and challenge and enforce your own maximum. amount is in six-decimal USDC minor units, so 520000 means 0.52 USDC.

The asset ticker remains USDC, but Base mainnet USDC's EIP-712 signing-domain name is USD Coin. Copy the fresh challenge's extra.name value exactly into the typed-data domain; do not replace it with the ticker. Base Sepolia's test token uses a different signing name, so examples are not interchangeable between networks.

Reject the challenge if either URL is not HTTPS, any field conflicts with the quote, the receiver is not independently approved, the payer lacks funds, or the domain is no longer available.

For EIP-3009, validBefore - validAfter must not exceed maxTimeoutSeconds. Any backdating allowance is part of that total span.

6. Settle exactly once

Create TransferWithAuthorization typed data, sign it locally with the payer key, and POST the authorization and signature to settle_url with:

X-Checkout-Session-Token: <session_token>
Idempotency-Key: <stable value for this session and authorization>

Pass the returned flat payment_challenges object back unchanged as accepted. Never send the payer private key. The service's relayer/gas key is a separate operator secret and is not a customer credential.

Use the complete public Python reference client for typed-data construction and settlement.

7. Confirm, reconcile, and verify

  • confirmed means the settlement is confirmed.
  • broadcast, prepared, or pending means wait and poll receipt_url using the same session token. Do not sign a replacement authorization.
  • A timeout or lost response is unknown state—not permission to pay again.

After confirmation, reconcile the existing checkout with Headless Domains:

curl -sS -X POST \
  "https://headlessdomains.com/api/agent/register-status/$SESSION_ID/reconcile" \
  -H "X-API-Key: $HEADLESSDOMAINS_API_KEY"

Then read its status:

curl -sS \
  "https://headlessdomains.com/api/agent/register-status/$SESSION_ID" \
  -H "X-API-Key: $HEADLESSDOMAINS_API_KEY"

Completion requires all of these:

  • settlement status confirmed
  • order status paid
  • domain status active
  • a domain ID and order ID
  • a transaction hash for the expected Base transfer

Finally verify the public record:

curl -sS https://headlessdomains.com/api/v1/lookup/myagent.agent

Recovery and replay rules

If payment confirmed but the domain is not active, call reconcile for the same session. An idempotent replay must return the same payment reference and must not create another transfer, attempt, order, or domain.

Never reset a Base lock merely because a local clock expired. Lock release is safe only after the checkout service proves payment_state: expired_unpaid. If status is confirmed, broadcast, pending, malformed, or unavailable, stop and preserve the session for recovery.

What an agent should report

Report only non-secret evidence: domain, final status, amount, currency, network, order ID, domain ID, and transaction hash. Never expose the API key, session token, private key, raw authorization, or signature.