[Bug] wrapFetchWithPayment fails on consecutive payments with "FiatTokenV2: invalid signature"
Environment
- SDK Version: thirdweb@5.111.8
- 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 error
Expected 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
TransferWithAuthorization uses random nonces, but there might be collision or caching
-
Signature Reuse
- Previous payment signatures might be cached and reused
validBefore timestamp might overlap between requests
-
Payment State Not Reset
- Internal state in
wrapFetchWithPayment or wallet adapter not properly reset after successful payment
-
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
wrapFetchWithPayment call 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:
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. 🙏
[Bug] wrapFetchWithPayment fails on consecutive payments with "FiatTokenV2: invalid signature"
Environment
Description
When using
wrapFetchWithPaymentto 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
Expected Behavior
Each payment request should be processed independently with fresh signatures and nonces, allowing consecutive payments without errors.
Actual Behavior
First Request (Success ✅)
Second Request (Failure ❌)
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:Analysis
Possible Root Causes
Nonce Caching Issue
TransferWithAuthorizationuses random nonces, but there might be collision or cachingSignature Reuse
validBeforetimestamp might overlap between requestsPayment State Not Reset
wrapFetchWithPaymentor wallet adapter not properly reset after successful paymentRace Condition
waitUntil: "confirmed", client-side state might not be synchronizedEvidence Supporting Cache Issue
Temporary Workarounds
1. Add Request Throttling (Client-side)
2. Re-create fetchWithPay Instance
Expected Fix
The SDK should:
wrapFetchWithPaymentcall completesAdditional Context
This issue is blocking production use cases that require:
Related Issues
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:
Screenshots/Logs
Available upon request. Can provide:
Related Projects:
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. 🙏