Potential settlement-ordering issue: paid resource can be released without completed settlement
Hi, I noticed a possible payment-flow issue while reviewing the current repository state. This is a conservative report based on the visible code path, and I may be missing deployment-specific guards outside this repository.
Reviewed HEAD: e38ecd00e2df
What I observed
In the visible payment path, I observed:
Facilitator routes parse paymentRequirements but core Facilitator.verify ignores requirements and calls standalone verifyPayment; settlement then transfers according to payment.authorization fields; resource middleware grants access after verify+settle so recipient/amount/asset requirements are not enforced in this path
Relevant code locations:
x402-facilitator/src/routes/verify.ts
x402-facilitator/src/routes/settle.ts
x402-facilitator/src/core/facilitator.ts
x402-facilitator/src/core/verifier.ts
x402-facilitator/src/middleware/x402-resource-server.ts
Relevant code excerpts
x402-facilitator/src/routes/verify.ts:45-59
45 export function createVerifyRoute(facilitator: Facilitator): Hono {
46 const route = new Hono();
47
48 route.post('/', zValidator('json', verifyBodySchema), async (c) => {
49 const { paymentPayload: rawPayment, paymentRequirements: rawReqs } = c.req.valid('json');
50
51 const payment = toX402Payment(rawPayment);
52 const requirements = toPaymentRequirements(rawReqs);
53 const result = await facilitator.verify(payment, requirements);
54
55 return c.json(result, result.isValid ? 200 : 400);
56 });
57
58 return route;
59 }
x402-facilitator/src/routes/settle.ts:41-55
41 export function createSettleRoute(facilitator: Facilitator): Hono {
42 const route = new Hono();
43
44 route.post('/', zValidator('json', settleBodySchema), async (c) => {
45 const { paymentPayload: rawPayment, paymentRequirements: rawReqs } = c.req.valid('json');
46
47 const payment = toX402Payment(rawPayment);
48 const requirements = toPaymentRequirements(rawReqs);
49 const result = await facilitator.settle(payment, requirements);
50
51 return c.json(result, result.success ? 200 : 402);
52 });
53
54 return route;
55 }
x402-facilitator/src/core/facilitator.ts:43-67
43 const r = `0x${sig.slice(0, 64)}` as Hex;
44 const s = `0x${sig.slice(64, 128)}` as Hex;
45 let v = parseInt(sig.slice(128, 130), 16);
46 if (v < 27) v += 27;
47 return { v, r, s };
48 }
49
50 /**
51 * Facilitator — orchestrates payment verification and on-chain settlement.
52 */
53 export class Facilitator {
54 private walletClients: Map<SupportedChainId, WalletClient> = new Map();
55 private publicClients: Map<SupportedChainId, PublicClient> = new Map();
56 private readonly address: Address;
57 private inflightNonces: Set<string> = new Set();
58
59 constructor(config: FacilitatorConfig) {
60 const account = privateKeyToAccount(config.privateKey);
61 this.address = account.address;
62
63 for (const chain of config.chains) {
64 const viemChain = getViemChain(chain.chainId);
65
66 this.walletClients.set(
67 chain.chainId,
x402-facilitator/src/core/verifier.ts:54-78
54 */
55 export class PaymentVerifier {
56 /**
57 * Verify a payment against its requirements.
58 *
59 * Checks: chain match, asset match, recipient match, amount, timing,
60 * requirements expiry, EIP-712 signature, and on-chain balance.
61 */
62 async verify(payment: X402Payment, requirements: PaymentRequirements): Promise<VerifyResult> {
63 const { chainId, token, authorization, signature } = payment;
64
65 // Chain ID match
66 if (chainId !== requirements.chainId) {
67 return { valid: false, reason: 'Chain ID mismatch' };
68 }
69
70 // Asset match (case-insensitive)
71 if (token.toLowerCase() !== requirements.asset.toLowerCase()) {
72 return { valid: false, reason: 'Asset mismatch' };
73 }
74
75 // Recipient match
76 if (authorization.to.toLowerCase() !== requirements.payTo.toLowerCase()) {
77 return { valid: false, reason: 'Recipient mismatch' };
78 }
x402-facilitator/src/middleware/x402-resource-server.ts:67-89
67 method: 'POST',
68 headers: { 'Content-Type': 'application/json' },
69 body: JSON.stringify({ payment, paymentRequirements }),
70 });
71
72 const settleData = (await settleRes.json()) as SettleResponse;
73
74 if (!settleData.success) {
75 return c.json({ error: 'Payment settlement failed', ...settleData }, 402);
76 }
77
78 // Attach settlement info to the request context for downstream handlers
79 c.set('x402Settlement', settleData);
80 } catch (err) {
81 logger.error({
82 error: err instanceof Error ? err.message : 'Unknown',
83 }, 'x402 resource server middleware error');
84 return c.json({ error: 'Payment processing error' }, 500);
85 }
86
87 await next();
88 };
89 }
Why this may matter
Payment verification should be rebound to server-trusted requirements such as payTo, amount, asset, network, nonce, and resource. If those values come from the client or are not rechecked, a different payment can satisfy the gate.
Suggested check
Consider making the paid-resource path depend on server-trusted payment requirements and a completed payment state. In particular, re-check recipient/payTo, amount, asset, network, nonce/idempotency, resource binding, and settlement result at the exact point where the protected API/tool/content is released.
Conservative caveat
I only reviewed the code visible in this repository at the HEAD above. If deployment-specific middleware or an upstream service enforces the missing binding/settlement invariant, this may already be mitigated there.
Potential settlement-ordering issue: paid resource can be released without completed settlement
Hi, I noticed a possible payment-flow issue while reviewing the current repository state. This is a conservative report based on the visible code path, and I may be missing deployment-specific guards outside this repository.
Reviewed HEAD:
e38ecd00e2dfWhat I observed
In the visible payment path, I observed:
Relevant code locations:
x402-facilitator/src/routes/verify.tsx402-facilitator/src/routes/settle.tsx402-facilitator/src/core/facilitator.tsx402-facilitator/src/core/verifier.tsx402-facilitator/src/middleware/x402-resource-server.tsRelevant code excerpts
x402-facilitator/src/routes/verify.ts:45-59x402-facilitator/src/routes/settle.ts:41-55x402-facilitator/src/core/facilitator.ts:43-67x402-facilitator/src/core/verifier.ts:54-78x402-facilitator/src/middleware/x402-resource-server.ts:67-89Why this may matter
Payment verification should be rebound to server-trusted requirements such as payTo, amount, asset, network, nonce, and resource. If those values come from the client or are not rechecked, a different payment can satisfy the gate.
Suggested check
Consider making the paid-resource path depend on server-trusted payment requirements and a completed payment state. In particular, re-check recipient/payTo, amount, asset, network, nonce/idempotency, resource binding, and settlement result at the exact point where the protected API/tool/content is released.
Conservative caveat
I only reviewed the code visible in this repository at the HEAD above. If deployment-specific middleware or an upstream service enforces the missing binding/settlement invariant, this may already be mitigated there.