-
Notifications
You must be signed in to change notification settings - Fork 8
Description
[Bug] wrapFetchWithPayment fails on consecutive payments with "FiatTokenV2: invalid signature"
Environment
- SDK Version: [email protected]
- Network: Base Sepolia (eip155:84532)
- USDC Contract: 0x036CbD53842c5426634e7929541eC2318f3dCF7e (FiatTokenV2)
- Framework: React 19.1.1 + Vite 7.1.7
- Wallet: MetaMask / In-App Wallet
Description
When using wrapFetchWithPayment to make consecutive API calls with x402 payments, the first payment succeeds, but subsequent payments fail with signature validation errors. This appears to be a caching or state management issue in the client SDK.
Steps to Reproduce
- Set up a basic React app with thirdweb x402:
import { createThirdwebClient } from "thirdweb";
import { wrapFetchWithPayment } from "thirdweb/x402";
import { useActiveWallet } from "thirdweb/react";
const client = createThirdwebClient({
clientId: "your-client-id"
});
// In component
const wallet = useActiveWallet();
const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);- Make the first payment request:
const response1 = await fetchWithPay('http://localhost:3000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'Hello' })
});
// β
Works fine - Status: 200- Immediately make a second payment request (within 3-5 seconds):
const response2 = await fetchWithPay('http://localhost:3000/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: 'World' })
});
// β Fails with 402 status and signature errorExpected Behavior
Each payment request should be processed independently with fresh signatures and nonces, allowing consecutive payments without errors.
Actual Behavior
First Request (Success β )
π€ ειζΆζ―οΌεε€ζ―δ»...
π ειθ―·ζ±ε°ζε‘ε¨...
POST http://localhost:3000/chat 402 (Payment Required)
π© ζΆε°εεΊοΌηΆζ: 200
β
ζΆζ―ειζεοΌ
Second Request (Failure β)
π€ ειζΆζ―οΌεε€ζ―δ»...
π ειθ―·ζ±ε°ζε‘ε¨...
POST http://localhost:3000/chat 402 (Payment Required)
POST http://localhost:3000/chat 402 (Payment Required) // Retried
π© ζΆε°εεΊοΌηΆζ: 402
β ειε€±θ΄₯: Error: ζ―δ»ε€±θ΄₯: payment_simulation_failed
Server-Side Error
{
"x402Version": 1,
"error": "payment_simulation_failed",
"errorMessage": "Payment simulation failed: {
\"type\":\"RPC_ERROR\",
\"chain_id\":84532,
\"message\":\"execution reverted: FiatTokenV2: invalid signature\",
\"data\": \"0x08c379a0...\"
}"
}Or sometimes:
{
"x402Version": 1,
"error": "Settlement error",
"errorMessage": "Failed to settle payment: TRANSACTION_SIMULATION_FAILED: execution reverted: FiatTokenV2: invalid signature"
}Backend Configuration
Using waitUntil: "confirmed" for reliable transaction confirmation:
import { facilitator, settlePayment } from "thirdweb/x402";
import { baseSepolia } from "thirdweb/chains";
const thirdwebFacilitator = facilitator({
client,
serverWalletAddress: PAYMENT_ADDRESS,
waitUntil: "confirmed", // Wait for full confirmation
});
const result = await settlePayment({
paymentData: req.headers["x-payment"],
resourceUrl: `http://localhost:3000/chat`,
method: "POST",
payTo: PAYMENT_ADDRESS,
network: baseSepolia,
price: "$0.001",
facilitator: thirdwebFacilitator,
routeConfig: {
description: "AI Chat - Pay per message",
mimeType: "application/json",
maxTimeoutSeconds: 60,
},
});Analysis
Possible Root Causes
-
Nonce Caching Issue
- The SDK may be reusing nonces from previous signatures
- EIP-3009
TransferWithAuthorizationuses random nonces, but there might be collision or caching
-
Signature Reuse
- Previous payment signatures might be cached and reused
validBeforetimestamp might overlap between requests
-
Payment State Not Reset
- Internal state in
wrapFetchWithPaymentor wallet adapter not properly reset after successful payment
- Internal state in
-
Race Condition
- First transaction not fully confirmed before second payment is initiated
- Even with
waitUntil: "confirmed", client-side state might not be synchronized
Evidence Supporting Cache Issue
- Time-dependent: Waiting 5-10 seconds between requests works fine
- First request always works: Fresh state on page load
- Pattern repeats: Third, fourth requests fail similarly
- Browser refresh fixes: New page load = fresh state
Temporary Workarounds
1. Add Request Throttling (Client-side)
let lastRequestTime = 0;
const MIN_REQUEST_INTERVAL = 5000; // 5 seconds
const sendMessage = async () => {
const now = Date.now();
const timeSinceLastRequest = now - lastRequestTime;
if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) {
const waitTime = MIN_REQUEST_INTERVAL - timeSinceLastRequest;
await new Promise(resolve => setTimeout(resolve, waitTime));
}
lastRequestTime = Date.now();
// Make request...
};2. Re-create fetchWithPay Instance
// Don't reuse the same instance
const sendMessage = async () => {
const fetchWithPay = wrapFetchWithPayment(fetch, client, wallet);
const response = await fetchWithPay(url, options);
};Expected Fix
The SDK should:
- Generate fresh nonces for each payment request
- Clear signature cache after successful payment
- Reset internal state after each
wrapFetchWithPaymentcall completes - Handle concurrent requests properly with queuing or locking mechanism
Additional Context
This issue is blocking production use cases that require:
- Real-time chat applications with per-message payments
- High-frequency API calls with micropayments
- Interactive applications with rapid user actions
Related Issues
- Similar to permit signature issues in EIP-2612 implementations
- May be related to FiatTokenV2's strict nonce validation
Environment Details
{
"dependencies": {
"react": "^19.1.1",
"react-dom": "^19.1.1",
"thirdweb": "^5.111.8"
},
"network": {
"name": "Base Sepolia",
"chainId": 84532,
"usdcContract": "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
}
}Request for Information
Could the thirdweb team provide guidance on:
- Is there internal caching of payment signatures or nonces?
- What's the recommended approach for consecutive x402 payments?
- Are there any debug flags or logs we can enable to trace this issue?
- Is this a known limitation with FiatTokenV2 contracts?
Screenshots/Logs
Available upon request. Can provide:
- Full browser console logs
- Network waterfall showing timing
- Server-side transaction traces
- Video reproduction
Related Projects:
- Reproducing in app based on x402.chat example
- Using patterns from x402 examples
Thank you for looking into this! x402 is a game-changing protocol, and resolving this issue would enable so many real-time payment use cases. π