DOC# X402FA SLUG x402_facilitator_gas_drain PRINTED 2026-05-06 03:47 UTC

x402 Vector 3: facilitator gas drain

x402 facilitators pay all transaction fees and the spec defines no per-client rate limit. A flood of valid-looking transactions that fail at maximum compute-unit consumption is a per-request economic attack on the facilitator.

FROM
Dax the Dev <[email protected]>
SOURCE
https://blog.skill-issue.dev/blog/x402_facilitator_gas_drain/
FILED
2026-03-21 18:00 UTC
REVISED
2026-03-21 18:00 UTC
TIME
5 min read
SERIES
x402 attack surface
TAGS
#security #x402 #solana #dos #economic-attack #research

The x402 protocol has a fee model where the facilitator pays gas. This is the entire UX win — AI agents don’t need SOL to make payments. It’s also the entire economic attack surface.

Post 4 of the x402 attack surface series.

The economics

For each settled x402 transaction, the facilitator pays:

So per tx, facilitator outflow is bounded at ~$0.041. That’s fine for legitimate traffic. It’s not fine when an attacker generates valid-looking PAYMENT-SIGNATUREs at 1000 req/sec.

The attack: maximum-CU failure

Three flavors of failing transaction that maximally hurt the facilitator:

Flavor A: legitimate-looking transfer that fails post-CU-burn

Recall from Vector 2: the client controls the instruction list. Append a custom-program call that:

  1. Burns 35,000 CU in a no-op loop.
  2. Asserts False, failing the entire tx.

Outcome: facilitator pays 5k base + 175,000 microlamports priority = full $0.041. Tx reverts. Merchant gets nothing. Repeat at scale.

Flavor B: valid-but-rejected mint

Specify the wrong mint in the SPL TransferChecked. The instruction validates client-side because the client controls the bytes. The instruction fails on-chain because the mint pubkey doesn’t match the ATA.

Solana’s runtime evaluates the entire instruction before it can detect the failure — so the facilitator pays the full CU consumption.

Flavor C: ATA derivation mismatch

The client supplies a destination ATA that derives from (owner=A, mint=USDC) but specifies (owner=B, mint=USDC) in the instruction. Solana’s transfer_checked verifies the ATA is consistent with the supplied owner+mint and rejects. CU consumed: full instruction cost.

All three flavors share the property: the facilitator’s /verify returns 200 (validators check structure, not bytes), the facilitator pays gas to settle, the tx reverts on-chain, the merchant doesn’t get paid, the attacker has cost the facilitator money for nothing in return.

Quantification

A single attacker on a residential Comcast connection can sustain ~100 req/sec to a single facilitator endpoint. At ~$0.04/tx:

Multiple attackers behind different IPs (e.g., a botnet of 1000) and the daily cost crosses $100M. Facilitator margins on legitimate x402 traffic are pennies per transaction. A sustained gas-drain attack burns the facilitator’s runway in hours.

Why the spec doesn’t address this

The spec assumes a “trusted client” model. AI agents operate semi-autonomously and don’t have an incentive to attack the facilitator they’re paying — except when they do. Specific incentive structures that make this attractive:

  1. A competitor (rival facilitator) wants the target out of business.
  2. A nation-state actor wants to disrupt agentic-economy infrastructure.
  3. A researcher (this writer) wants to demonstrate the bug.
  4. An AI agent that’s been adversarially prompted to drain its own facilitator’s funds.

Threat (4) is the one I find most interesting. An LLM that’s been jailbroken via prompt injection could — at no cost to itself — execute the gas-drain attack against the facilitator, which is operationally what x402 was designed to make easy.

Mitigations

In rough order of effectiveness:

  1. Per-client rate limit on /settle. The facilitator must enforce N transactions per client-keypair per minute. Default ~10 sounds fine; can be raised for trusted clients via API key.
  2. CU budget cap. Facilitator overrides client’s set_unit_limit and set_unit_price instructions; pins to ≤5,000 CU and 1 microlamport/CU. Reduces worst-case outflow per tx by ~10×.
  3. Pre-flight simulation. Before adding feePayer signature, run simulateTransaction. If sim returns Err, reject before paying gas. Shifts cost to a quick simulation call.
  4. Reputation-based throttle. Track each client-keypair’s settlement success ratio. Drop clients with under 50% success rate to a lower rate limit.
  5. Stake-or-pay deposits. Out-of-band: clients deposit a small SOL bond with the facilitator. Failed transactions debit from the bond. Removes the asymmetric-cost property entirely.

(1) is the bare minimum. (3) is the most operationally complex but also the most thorough. (5) is the Real Fix but requires protocol changes.

What I’d do if I were operating an x402 facilitator

# Pseudocode for the verify+settle endpoint
@app.post("/settle")
async def settle(req: SettleRequest):
    client_pk = extract_client_pubkey(req.tx)

    # 1. Per-client rate limit
    if not await rate_limit.allow(client_pk, max=10, window=60):
        return 429, "rate_limited"

    # 2. Re-validate (don't trust /verify)
    if not validate_tx(req.tx, expected_mint=USDC, max_cu=5_000):
        return 400, "invalid_tx"

    # 3. Pre-flight simulate
    sim = await rpc.simulate_transaction(req.tx)
    if sim.err is not None:
        # Failed simulation = don't pay gas
        return 400, "sim_failed"

    # 4. Add feePayer sig + submit
    signed = sign_with_feepayer(req.tx)
    sig = await rpc.send_transaction(signed)

    # 5. Wait for confirmation before returning success
    await rpc.confirm_transaction(sig, level="confirmed")

    return 200, {"signature": sig}

Cost of all this: ~50ms added latency per settlement, plus one extra RPC call. Worth it.

Bibliography

Previous: Partial-signing injection ← · Next: AI-agent wallet drain →

← Back to article