Problem
When making multiple X402 payments in quick succession from the same wallet, transactions fail with BadNonce errors:
broadcast failed: failed to broadcast transaction: broadcast failed: transaction rejected - BadNonce
This happens because @stacks/transactions fetches nonces from an API endpoint that returns stale data.
Root Cause
The underlying issue is in @stacks/transactions - its fetchNonce function uses a stale API endpoint:
// @stacks/transactions/dist/esm/fetch.js (lines 46-52)
async function _getNonceApi({ address, network, client }) {
const url = `${client.baseUrl}/extended/v1/address/${address}/nonces`;
const response = await client.fetch(url);
const result = await response.json();
return BigInt(result.possible_next_nonce); // <-- Can return stale data
}
This endpoint (/extended/v1/address/{address}/nonces) is powered by the Stacks indexer which can lag behind confirmed transactions. The correct endpoint is /v2/accounts/{address} which returns real-time nonce data directly from the blockchain.
However, @stacks/transactions accepts an explicit nonce parameter in makeSTXTokenTransfer and makeContractCall - if provided, it skips the fetch entirely. The fix for x402-stacks is to fetch the correct nonce from /v2/accounts and pass it explicitly, bypassing the buggy default behavior.
Evidence:
# Extended API (stale) - shows last_executed_tx_nonce: 4635, possible_next_nonce: 4636
curl "https://api.mainnet.hiro.so/extended/v1/address/SP.../nonces"
# v2/accounts (correct) - shows nonce: 4637
curl "https://api.mainnet.hiro.so/v2/accounts/SP...?proof=0"
# Recent transactions show nonce 4636 already confirmed
curl "https://api.mainnet.hiro.so/extended/v1/address/SP.../transactions?limit=1"
# Returns: { "nonce": 4636, "tx_status": "success" }
Impact
- Single payments work fine
- Rapid sequential payments (e.g., test suites, batch operations) fail
- The problem is intermittent depending on indexer lag
Proposed Fix
The X402PaymentClient should:
- Fetch nonce from
/v2/accounts instead of relying on @stacks/transactions default behavior
- Track nonces locally for rapid sequential payments
- Pass explicit nonce to
makeSTXTokenTransfer / makeContractCall
Here's a working implementation:
class X402PaymentClient {
constructor(config) {
// ... existing code ...
// Add nonce tracking
this.nonceCache = new Map();
}
/**
* Get next nonce with local tracking for rapid sequential payments
*/
async getNextNonce(address, network) {
const cached = this.nonceCache.get(address);
const now = Date.now();
// If we have a recent cached nonce (within 30s), increment and use it
if (cached && (now - cached.lastUpdated) < 30000) {
cached.nonce = cached.nonce + BigInt(1);
cached.lastUpdated = now;
return cached.nonce;
}
// Fetch fresh nonce from v2/accounts (NOT the stale extended API)
const baseUrl = network instanceof StacksMainnet
? 'https://api.mainnet.hiro.so'
: 'https://api.testnet.hiro.so';
const url = `${baseUrl}/v2/accounts/${address}?proof=0`;
const response = await this.httpClient.get(url);
const fetchedNonce = BigInt(response.data.nonce);
// Cache for subsequent requests
this.nonceCache.set(address, {
nonce: fetchedNonce,
lastUpdated: now
});
return fetchedNonce;
}
async signSTXTransfer(details) {
// ... existing network/address setup ...
// Fetch correct nonce
const nonce = details.nonce !== undefined
? details.nonce
: await this.getNextNonce(senderAddress, network);
const txOptions = {
// ... existing options ...
...(nonce !== undefined && { nonce }), // Pass explicit nonce
};
const transaction = await makeSTXTokenTransfer(txOptions);
// ...
}
// Apply same pattern to signSBTCTransfer and signUSDCxTransfer
}
Workaround
Until this is fixed upstream, users can apply a patch using patch-package. See: https://github.com/whoabuddy/stx402/blob/master/patches/x402-stacks%2B1.1.0.patch
Environment
- x402-stacks: 1.1.0
- @stacks/transactions: 7.x
- Network: mainnet (also affects testnet)
Problem
When making multiple X402 payments in quick succession from the same wallet, transactions fail with
BadNonceerrors:This happens because
@stacks/transactionsfetches nonces from an API endpoint that returns stale data.Root Cause
The underlying issue is in
@stacks/transactions- itsfetchNoncefunction uses a stale API endpoint:This endpoint (
/extended/v1/address/{address}/nonces) is powered by the Stacks indexer which can lag behind confirmed transactions. The correct endpoint is/v2/accounts/{address}which returns real-time nonce data directly from the blockchain.However,
@stacks/transactionsaccepts an explicitnonceparameter inmakeSTXTokenTransferandmakeContractCall- if provided, it skips the fetch entirely. The fix forx402-stacksis to fetch the correct nonce from/v2/accountsand pass it explicitly, bypassing the buggy default behavior.Evidence:
Impact
Proposed Fix
The
X402PaymentClientshould:/v2/accountsinstead of relying on@stacks/transactionsdefault behaviormakeSTXTokenTransfer/makeContractCallHere's a working implementation:
Workaround
Until this is fixed upstream, users can apply a patch using
patch-package. See: https://github.com/whoabuddy/stx402/blob/master/patches/x402-stacks%2B1.1.0.patchEnvironment