Solana x402 Facilitator

Secure agent payments
on Solana

Verifies and settles presigned USDC transactions so your API can charge per request — agents set feePayer = facilitator and never need SOL.

PROTOCOL
x402 v1
SECURITY
No Custody
SETTLEMENT
SPL Exact
ASSET
USDC (devnet)
LIVE ENDPOINT — https://facilitator.agentstrail.ai
...
Balance ...
Network ...
Slot ...
RPC ...
feePayer ...
Integration flow

Drop-in setup

The facilitator slots into standard x402 middleware. Set feePayer once and let the facilitator handle gas and broadcast.

01

Fetch feePayer from /supported

Query the facilitator to get the feePayer address, supported networks, and USDC asset address. Point your agent at this once at startup.

GET https://facilitator.agentstrail.ai/supported
02

Agent builds and partial-signs the tx

Create a TokenTransferChecked transaction. Set tx.feePayer = facilitatorPubkey and sign as transfer authority only — no SOL required.

tx.feePayer = new PublicKey(feePayer); tx.partialSign(agentKeypair);
03

Merchant calls /verify then /settle

Forward the signed payload via X-PAYMENT header. The facilitator runs 10 checks, co-signs slot 0 as feePayer, broadcasts, and confirms on-chain.

POST /verify  →  POST /settle  →  { success: true, txHash }
Core capabilities

Built for secure x402 settlement

Every payment request stays inside standard HTTP semantics while the facilitator handles verification, co-signing, and settlement.

10-Step Verification

feePayer check, TransferChecked detection, Ed25519 signature, amount, ATA derivation, mint, replay guard, and risk scoring — every request.

Zero SOL for Agents

Agent signs only as transfer authority. The facilitator co-signs slot 0 as feePayer and pays all network fees (~0.000005 SOL/tx).

Replay Protection

SQLite deduplication on payment signature. Every settled signature is recorded permanently — duplicates return ALREADY_SETTLED.

Byte-Level Tx Parsing

Decodes legacy and v0 Solana wire transactions using compact-u16 parsing. No blind trust in SDK structs or agent-supplied metadata.

Risk Scoring

Optional X-AGENT-TRACE header enables automated screening: dangerous tool names, velocity limits, and high-value amount blocks.

Rate Limiting

60 req/min per IP on /verify. Hard 10 req/min per IP on /settle to prevent broadcast spam. Returns 429 with retryAfter.

Verification chain
# Check Error Code
01Decode base64 wire transactionINVALID_PAYLOAD
02feePayer === facilitator public keyWRONG_FEE_PAYER
03Contains SPL TokenTransferChecked instructionMISSING_TRANSFER
04Agent Ed25519 signature is valid over message bytesINVALID_SIGNATURE
05transfer.amount ≥ maxAmountRequiredINSUFFICIENT_AMOUNT
06Destination ATA = findAssociatedTokenPda(payTo, mint)WRONG_RECIPIENT
07transfer.mint === requirements.assetWRONG_ASSET
08Payment signature not already in DBALREADY_SETTLED
09Risk assessment passes (X-AGENT-TRACE)RISK_BLOCKED
10All checks passed — co-sign and broadcast
For agents

Client integration

Build the USDC transfer, set feePayer to the facilitator address from /supported, sign as authority, and include in the X-PAYMENT header.

TypeScript — @solana/web3.js + @solana/spl-token
// 1. Get feePayer address from facilitator (API key required)
const API_KEY = 'sk_your_api_key_here';
const { feePayer } = await fetch('https://facilitator.agentstrail.ai/supported', {
  headers: { 'Authorization': `Bearer ${API_KEY}` },
}).then(r=>r.json());

// 2. Build TransferChecked transaction
const conn   = new Connection('https://api.devnet.solana.com');
const USDC   = new PublicKey('4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU');
const srcAta = await getAssociatedTokenAddress(USDC, agentKeypair.publicKey);
const dstAta = await getAssociatedTokenAddress(USDC, new PublicKey(merchantWallet));

const tx = new Transaction({
  feePayer: new PublicKey(feePayer),  // <-- facilitator pays gas
  recentBlockhash: (await conn.getLatestBlockhash()).blockhash,
});
tx.add(createTransferCheckedInstruction(
  srcAta, USDC, dstAta, agentKeypair.publicKey, 1_000_000n, 6
));

// 3. Sign as authority only (no SOL needed)
tx.partialSign(agentKeypair);
const txBase64  = tx.serialize({ requireAllSignatures: false }).toString('base64');
const sigBase58 = bs58.encode(tx.signatures[0].signature);

// 4. Call paid API with X-PAYMENT + Authorization headers
const payment = {
  x402Version: 1, scheme: 'exact',
  network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG',
  payload: { signature: sigBase58, transaction: txBase64 },
};
await fetch('https://merchant.api/resource', {
  headers: { 'X-PAYMENT': JSON.stringify(payment) },
});

// Or call the facilitator directly:
await fetch('https://facilitator.agentstrail.ai/settle', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${API_KEY}`,
  },
  body: JSON.stringify({ paymentPayload: payment, paymentRequirements }),
});
Python — solders + httpx  |  pip install solders solana httpx base58
import httpx, base64, json, os
from solders.keypair import Keypair
from solders.pubkey  import Pubkey
from spl.token.instructions import transfer_checked, TransferCheckedParams, get_associated_token_address
from spl.token.constants import TOKEN_PROGRAM_ID
import base58

FACILITATOR_URL = os.environ['FACILITATOR_URL']
API_KEY         = os.environ['FACILITATOR_API_KEY']
DEVNET_RPC      = 'https://api.devnet.solana.com'
USdc_MINT       = '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU'
NETWORK         = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG'

# 1. Get feePayer from facilitator (API key required)
supported = httpx.get(
    f'{FACILITATOR_URL}/supported',
    headers={'Authorization': f'Bearer {API_KEY}'}
).json()
fee_payer = supported['feePayer']

# 2. Get recent blockhash
rpc = httpx.post(DEVNET_RPC, json={
    'jsonrpc': '2.0', 'id': 1,
    'method': 'getLatestBlockhash',
    'params': [{'commitment': 'confirmed'}]
}).json()
blockhash = rpc['result']['value']['blockhash']

# 3. Derive associated token accounts
src_ata = get_associated_token_address(
    Pubkey.from_string(str(agent_keypair.pubkey())),
    Pubkey.from_string(USDC_MINT)
)
dst_ata = get_associated_token_address(
    Pubkey.from_string(merchant_wallet),
    Pubkey.from_string(USDC_MINT)
)

# 4. Build TokenTransferChecked instruction
ix = transfer_checked(TransferCheckedParams(
    program_id=TOKEN_PROGRAM_ID,
    source=src_ata,
    mint=Pubkey.from_string(USDC_MINT),
    dest=dst_ata,
    owner=agent_keypair.pubkey(),
    amount=1_000_000,   # 1 USDC (6 decimals)
    decimals=6,
    signers=[]
))

# 5. Build transaction — facilitator as feePayer (no SOL needed)
from solders.transaction import Transaction
from solders.message     import Message
msg = Message.new_with_blockhash(
    [ix],
    Pubkey.from_string(fee_payer),  # facilitator pays gas
    blockhash
)
tx = Transaction.new_unsigned(msg)

# 6. Agent partial-signs as transfer authority only
tx.partial_sign([agent_keypair], blockhash)
tx_b64  = base64.b64encode(bytes(tx)).decode()
sig_b58 = base58.b58encode(bytes(tx.signatures[0])).decode()

# 7. Attach to X-PAYMENT and call merchant
payment = {
    'x402Version': 1,
    'scheme': 'exact',
    'network': NETWORK,
    'payload': {'signature': sig_b58, 'transaction': tx_b64},
}
response = httpx.get(
    'https://merchant.api/resource',
    headers={'X-PAYMENT': json.dumps(payment)}
)
print(response.json())
cURL
# 1. Get feePayer from facilitator (API key required)
curl "$FACILITATOR_URL"/supported \
  -H "Authorization: Bearer $FACILITATOR_API_KEY"

# 2. Verify without broadcast (API key required)
curl -X POST "$FACILITATOR_URL"/verify \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $FACILITATOR_API_KEY" \
  -d '{
    "paymentPayload": {
      "x402Version": 1, "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
      "payload": { "signature": "<base58>", "transaction": "<base64>" }
    },
    "paymentRequirements": {
      "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG",
      "maxAmountRequired": "1000000",
      "payTo": "<merchant>",
      "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"
    }
  }'

# 3. Settle — co-signs, broadcasts, confirms (API key required)
curl -X POST "$FACILITATOR_URL"/settle \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $FACILITATOR_API_KEY" \
  -d '{ ...same body as /verify... }'
For merchants

Merchant setup

Protect your API with x402 and accept USDC payments from AI agents. The facilitator handles verification, co-signing, and broadcast.

Node.js — x402-express  |  npm install x402-express
import express from 'express';
import { paymentMiddleware } from 'x402-express';

const app = express();

// Point x402 middleware at this facilitator
const facilitatorConfig = {
  url: process.env.FACILITATOR_URL,
  createAuthHeaders: async () => ({
    verify:    { 'Authorization': `Bearer ${process.env.FACILITATOR_API_KEY}` },
    settle:    { 'Authorization': `Bearer ${process.env.FACILITATOR_API_KEY}` },
    supported: { 'Authorization': `Bearer ${process.env.FACILITATOR_API_KEY}` },
  }),
};

// Protect your endpoint — agents pay 0.01 USDC per request
app.use(paymentMiddleware(
  process.env.MERCHANT_WALLET,   // your Solana wallet address
  {
    'GET /api/data': {
      price:   '$0.01',
      network: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG',
    },
  },
  facilitatorConfig
));

app.get('/api/data', (req, res) => {
  res.json({ result: 'premium data' });
});

app.listen(3001);

// The middleware handles the full x402 flow automatically:
// Agent hits /api/data  →  gets 402 + PaymentRequirements
// Agent builds tx, sets feePayer = facilitator pubkey
// Agent sends X-PAYMENT header  →  middleware calls /verify
// Middleware calls /settle  →  facilitator co-signs + broadcasts
// Agent receives the API response  ✅
Manual integration — Node.js / TypeScript
// Step 1 — Return 402 with PaymentRequirements when no X-PAYMENT
if (!req.headers['x-payment']) {
  return res.status(402).json({
    scheme:            'exact',
    network:           'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG',
    maxAmountRequired: '1000000',       // 1 USDC in base units
    payTo:             process.env.MERCHANT_WALLET,
    asset:             '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
    maxTimeoutSeconds: 300,
    extra: {
      feePayer: '<FACILITATOR_PUBKEY>',  // from GET /supported
    },
  });
}

// Step 2 — Verify the payment
const verifyRes = await fetch(`${process.env.FACILITATOR_URL}/verify`, {
  method:  'POST',
  headers: {
    'Content-Type':  'application/json',
    'Authorization': `Bearer ${process.env.FACILITATOR_API_KEY}`,
  },
  body: JSON.stringify({
    paymentPayload:      JSON.parse(req.headers['x-payment']),
    paymentRequirements: yourRequirements,
  }),
});
const { isValid } = await verifyRes.json();
if (!isValid) return res.status(402).json({ error: 'Invalid payment' });

// Step 3 — Settle (co-signs + broadcasts on-chain)
await fetch(`${process.env.FACILITATOR_URL}/settle`, {
  method:  'POST',
  headers: {
    'Content-Type':  'application/json',
    'Authorization': `Bearer ${process.env.FACILITATOR_API_KEY}`,
  },
  body: JSON.stringify({
    paymentPayload:      JSON.parse(req.headers['x-payment']),
    paymentRequirements: yourRequirements,
  }),
});

// Step 4 — Deliver your resource
res.json({ result: 'premium data' });
Documentation

API reference

Five endpoints. POST bodies are identical for /verify and /settle — just switch the URL to confirm settlement.

🔑 API Key Required Endpoints marked 🔒 require an Authorization: Bearer <key> header. Request a key →
GET/healthWallet balance, Solana slot, RPC status, DB healthunlimited
GET/statsTotal transactions, USDC volume, SOL spent on gasunlimited
GET/request-accessApply for an API key (public form)unlimited
GET/supported  🔒feePayer address, supported networks and USDC assets60 / min
POST/verify  🔒Run 10-step verification without broadcasting to chain60 / min
POST/settle  🔒Verify + co-sign as feePayer + broadcast + confirm10 / min
MISSING_API_KEYNo Authorization header — include Bearer <key>
INVALID_API_KEYKey not recognised — request one at /request-access
REVOKED_API_KEYKey has been deactivated by the administrator
INVALID_PAYLOADMalformed request body or undecodable transaction
INVALID_SIGNATUREAgent Ed25519 signature fails verification
WRONG_FEE_PAYERfeePayer in tx does not equal facilitator pubkey
MISSING_TRANSFERNo SPL TokenTransferChecked instruction found
INSUFFICIENT_AMOUNTTransfer amount is less than maxAmountRequired
WRONG_RECIPIENTDestination ATA does not match payTo wallet
WRONG_ASSETToken mint does not match required asset
ALREADY_SETTLEDPayment signature already recorded in database
RISK_BLOCKEDRisk scoring blocked this request
RPC_ERRORSolana RPC error during broadcast or confirmation
UNKNOWN_ERRORUnexpected internal server error
Network Reference
Network CAIP-2 Identifier USDC Mint Address
Devnet solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
Mainnet solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
Optional Header
X-AGENT-TRACE — Risk Screening Header click to expand

Attach this header to /verify or /settle to enable automated risk screening. Merchants or agent middleware can forward it from the agent’s reasoning context. If omitted, the request is treated as LOW risk and allowed.

Header value (JSON string)
X-AGENT-TRACE: {"task":"purchase API access","modelId":"gpt-4o","toolCalls":["read_file","http_request"]}
Schema
taskstring — what the agent is doing
modelIdstring — AI model identifier
toolCallsstring[] — tools called by agent
Automatic Blocks → 403 RISK_BLOCKED
toolCalls includes drain, transfer_all,
override, or bypass
Amount > 1,000 USDC
Same agent > 10 requests in 60s
Live stats
Transactions
0
USDC Settled
0 USDC
SOL on Gas
0 SOL
Resources
🔑

Ready to integrate?

API keys are required to access /verify, /settle, and /supported. Fill out a short form and your key will be generated within 24 hours.

Request API Access → View API Docs
✅ Free during devnet beta ✅ No credit card required ✅ Key delivered instantly on approval