Where does it stop?
The node checks the gas limit before it accepts the transaction. If the limit is below the minimum, nothing runs and nothing is mined: no hash, no receipt and no gas spent.
What is the minimum?
A standard ETH transfer needs a gas limit of 21,000. That is the base every transaction pays. Data adds to it (16 gas for each non-zero byte, EIP-2028), and so does creating a contract.
This minimum is checked first, before the contract’s code runs. The gas the code then uses is a separate matter: if the limit covers the start but not the work, that is out of gas.
Why does it happen?
- A hardcoded limit. A transfer’s 21,000 copied onto a call that carries data.
- A number from a tool argument. An agent or a config sets
gas: 1000and nobody compares it with the minimum. - A limit for one kind of transaction used for another. A contract creation is more than a transfer.
How do I fix it?
- Do not set
gasunless you must. With a local account, viem estimates the gas when you leave it out. - If you set it, estimate first and add headroom:
import { createPublicClient, createWalletClient, http } from 'viem';import { mainnet } from 'viem/chains';
declare const account: `0x${string}`;declare const to: `0x${string}`;declare const data: `0x${string}`;const publicClient = createPublicClient({ chain: mainnet, transport: http() });const wallet = createWalletClient({ account, chain: mainnet, transport: http() });
// Estimate the gas, then add headroom: the state can change before the transaction is mined.const gas = await publicClient.estimateGas({ account, to, data });await wallet.sendTransaction({ to, data, gas: (gas * 120n) / 100n });- Read the minimum from the error where the client prints it. geth names it. If yours does not, estimate instead of guessing.
What does it look like in a trace?
A refusal by the node: a send span that ends with an error and no hash.
- error.type
- IntrinsicGasTooLowError
- exception.message
- The amount of gas (1000) provided for the transaction is too low.
- blockchain.tx.hash
- not set
By default only the error type is recorded, and for this refusal it is IntrinsicGasTooLowError. Nonce too low shows how to read the node’s wording too.