diff --git a/docs/base-chain/network-information/update-troubleshooting-transactions b/docs/base-chain/network-information/update-troubleshooting-transactions new file mode 100644 index 000000000..a2ccbc055 --- /dev/null +++ b/docs/base-chain/network-information/update-troubleshooting-transactions @@ -0,0 +1,196 @@ +--- +title: Troubleshooting Transactions +slug: /troubleshooting-transactions +description: Guide to diagnosing and resolving transaction issues on Base, including fees, nonces, gas, Flashblocks preconfirmations, and common contract errors. +--- + +# Troubleshooting Transactions + +## Transaction Not Being Included + +If your transaction is pending for longer than expected, check the following: + +### Max Fee Too Low + +If your `maxFeePerGas` is lower than the current base fee, your transaction will remain pending until the base fee drops to your specified level. + +**Solution**: The `maxFeePerGas` must cover both the base fee and your priority fee. Since the base fee can change with each block, set `maxFeePerGas` high enough to remain valid even if the base fee rises while your transaction is pending. A common approach is: + +```solidity +maxFeePerGas = baseFee * 2 + maxPriorityFeePerGas +``` + +This formula (used by [ethers.js](https://github.com/ethers-io/ethers.js/blob/98c49d091eb84a9146dfba8476f18e4c3e3d1d31/src.ts/providers/abstract-provider.ts#L945-L950)) provides headroom for the base fee to double before your transaction becomes unexecutable. You only pay the actual base fee at inclusion time, not the maximum. + +Base has a [minimum base fee](/base-chain/network-information/network-fees#minimum-base-fee). Transactions with `maxFeePerGas` below this value will never be included. + +**Example using viem (recommended for modern Base development):** + +```ts +import { createWalletClient, http, parseEther } from 'viem' +import { base } from 'viem/chains' + +const client = createWalletClient({ + chain: base, + transport: http('https://mainnet.base.org') +}) + +async function sendSafeTransaction() { + const [account] = await client.getAddresses() + + // Get fresh fee estimates + const fees = await client.estimateFeesPerGas() + + const maxFeePerGas = + (fees.maxFeePerGas || (fees.baseFeePerGas! * 2n)) + fees.maxPriorityFeePerGas! + + const hash = await client.sendTransaction({ + account, + to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // example recipient + value: parseEther('0.001'), + maxFeePerGas, + maxPriorityFeePerGas: fees.maxPriorityFeePerGas, + }) + + console.log('Transaction sent:', hash) + return hash +} + +sendSafeTransaction() +``` + +**Verification**: Run the script and check the transaction on [Basescan](https://basescan.org/). It should be included without getting stuck pending. + +### Priority Fee Too Low + +During periods of high demand, transactions compete for block space through priority fees. + +**Solution**: Most users simply wait for congestion to subside. For time-sensitive transactions, use eth_maxPriorityFeePerGas to get a priority fee estimate that can outbid enough recent transactions to be included. + +If [DA throttling](https://docs.optimism.io/chain-operators/guides/configuration/batcher#batcher-sequencer-throttling) is currently in effect, there's no RPC endpoint that calculates priority fee estimates with throttling in mind. During DA throttling, even transactions with high priority fees may be delayed as the sequencer limits L2 transactions to manage its L1 data availability throughput. + +### Nonce Gap + +If you have a pending transaction with nonce N, all transactions with nonce N+1 or higher will queue behind it, regardless of their fees. + +**Solution**: Either wait for the pending transaction to be included, or replace it by submitting a new transaction with the same nonce and a higher fee (at least 10% higher `maxPriorityFeePerGas` and `maxFeePerGas`). + +### Nonce Too Low + +If you submit a transaction with a nonce that has already been used, it will be rejected. + +**Solution**: Query your current nonce using `eth_getTransactionCount` with the `pending` tag to get the next available nonce. + +## Transaction Rejected + +### Gas Limit Exceeds Maximum + +Base enforces a per-transaction gas maximum of 25,000,000 gas. Transactions specifying a higher gas limit are rejected by the mempool before inclusion. + +**Error**: `exceeds maximum per-transaction gas limit` + +**Solution**: Reduce the gas limit to 25,000,000 or below. If your transaction genuinely requires more gas, you'll need to break it into multiple transactions. + +### Transaction Included But Failed + +If your transaction was included in a block but shows a failed status: + +#### Out of Gas + +The transaction ran out of gas during execution. + +**Solution**: Increase the gas limit. Use `eth_estimateGas` to get a gas estimate, then add a buffer (e.g., 20%) to account for variability. + +#### Reverted by Contract + +The contract execution encountered a revert condition. + +**Solution**: Check the transaction on [Basescan](https://basescan.org) to see the revert reason. Common causes include failed require statements, arithmetic errors, or invalid state transitions. + +**Example with simulation and error handling:** + +```ts +import { createPublicClient, createWalletClient, http } from 'viem' +import { base } from 'viem/chains' +import { parseAbi } from 'viem' + +const publicClient = createPublicClient({ chain: base, transport: http() }) +const walletClient = createWalletClient({ chain: base, transport: http() }) + +async function safeContractCall() { + const [account] = await walletClient.getAddresses() + + try { + const { request } = await publicClient.simulateContract({ + address: '0xYourContractAddress', + abi: parseAbi(['function myFunction(uint256 amount) external']), + functionName: 'myFunction', + args: [100n], + account, + }) + + const hash = await walletClient.writeContract(request) + console.log('Success:', hash) + } catch (error: any) { + console.error('Simulation failed:', error.message || error) + // Example: "execution reverted: Insufficient balance" + } +} +``` + + +## Slow Confirmation + +### Understanding Confirmation Times + +Base produces blocks every 2 seconds, but [Flashblocks](/base-chain/flashblocks/app-integration) provide preconfirmations every 200ms. + +| Confirmation Level | Time | Description | +|-------------------|------|-------------| +| Flashblock preconfirmation | ~200ms | Transaction included in a preconfirmation | +| L2 block inclusion | ~2s | Transaction included in a sealed L2 block | +| L1 batch inclusion | ~2m | Transaction posted to Ethereum | +| L1 finality | ~20m | Ethereum batch is finalized | + +See [Transaction Finality](/base-chain/network-information/transaction-finality) for more details. +### Using Flashblocks for Faster Confirmations + +To get the fastest possible confirmation, use a Flashblocks-aware RPC endpoint: + +| Network | Flashblocks RPC | +|---------|-----------------| +| Mainnet | `https://mainnet-preconf.base.org` | +| Sepolia | `https://sepolia-preconf.base.org` | + +These endpoints return transaction receipts as soon as a transaction is included in a Flashblock, rather than waiting for the full L2 block. + +**Polling example:** + +```ts +const receipt = await publicClient.waitForTransactionReceipt({ + hash, + confirmations: 1, // or use Flashblocks polling + pollingInterval: 200, // ms +}) +``` + +## Querying Current Network State (New) + +Proactively fetch fees before building transactions: + +```ts +const fees = await client.estimateFeesPerGas() +const gasPrice = await client.getGasPrice() +const feeHistory = await client.getFeeHistory({ blockCount: 5, rewardPercentiles: [50] }) +``` + +## Debugging Tools + +- **[Basescan](https://basescan.org)**: View transaction status, logs, and revert reasons +- **[Tenderly](https://tenderly.co)**: Simulate and debug transactions +- **`eth_call`**: Test contract calls without submitting a transaction +- **`eth_estimateGas`**: Estimate gas usage before submitting + +## Getting Help + +If you're still experiencing issues, reach out in the `#developer-chat` channel in the [Base Discord](https://base.org/discord).