Lesson 75Crypto-fiat boundaryAdvanced

Failed on-chain transactions: gas paid, value not moved

Ethereum transactions can fail. The gas is gone. The user expects their balance back.

By Solomon Ajayi · Free to read, no signup

Unlike BTC (transactions that confirm always succeed), Ethereum-family chains can EXECUTE-AND-REVERT, the EVM runs the transaction, charges gas, but reverts state if a contract assertion fails. Your user submitted a swap or NFT mint; it ran out of gas mid-execution or an OUTPUT contract rejected; gas is paid (you lose it), but no value was transferred. The user's wallet should NOT show the swap's intended debit, it never happened. The gas fee, however, was real. This lesson posts the gas-only entry that handles a failed Ethereum transaction.

On an EVM chain, paying for a transaction and getting what you paid for are two separate facts. The network runs your transaction and charges gas the moment a miner includes it. If a contract assertion fails partway through, the chain reverts every state change the transaction tried to make, but it does not refund the gas. You paid for the computation; the computation just happened to undo itself. The swap's 0.5 ETH never left the user, yet the 0.005 ETH of gas is gone.

So the ledger has to record exactly one of those two facts and refuse to record the other. The value transfer did not happen, so the user wallet must stay untouched. The gas spend did happen, so it has to land somewhere real. Here it is a two-line entry: Hot Wallet down by the gas, Network Fees expense up by the gas. The user sees no balance change at all, which is correct, because from their account's perspective nothing moved.

Who eats the gas is a product decision the entry makes explicit. Most consumer wallets book it to a platform expense account and absorb it as cost of service, which is what this entry does. If your policy instead charges the user, you would debit their wallet, but only for the gas, never for the failed transfer. The entry is where that policy gets written down, not buried in a code path nobody can audit later.

Worked example, step by step

User attempts a swap: gas-only cost on failed tx

User initiated a 0.5 ETH swap. They had pre-approved spending. You broadcast the tx, paid 0.005 ETH in gas. The swap contract reverted (slippage tolerance exceeded). The 0.5 ETH was NOT moved. The 0.005 ETH gas is gone.

Failed swap: 0.005 ETH gas burned (no value moved)
AccountDebitCredit
On-Chain Network Fees (Gas) (5910)₦50,000.00
Hot Wallet (ETH) (1920)₦50,000.00

Hot Wallet (ETH) DOWN 0.005 ETH (credit, you spent it on gas). Network Fees UP 0.005 ETH (debit, the cost). User Wallet stays UNCHANGED, they never lost the 0.5 ETH because the swap didn't execute. Two-line entry, gas charged to platform expense (you, not the user). Whether you charge the gas back to the user depends on your product policy, most consumer crypto wallets absorb it as cost of service.

Different attempt: same swap with bumped slippage, succeeds

User retries with looser slippage. Same 0.5 ETH out, plus 0.004 ETH gas this time. Swap completes successfully, the 0.5 ETH leaves their wallet for whatever asset they swapped to. (We omit the receiving asset for simplicity; in production it's a second entry.)

Successful swap: 0.5 ETH out + 0.004 ETH gas
AccountDebitCredit
User Wallet (ETH) (2000)₦150,000.00
On-Chain Network Fees (Gas) (5910)₦1,200.00
Hot Wallet (ETH) (1920)₦151,200.00

User Wallet (ETH) DOWN 0.5 ETH = ₦150,000 (debit) [using ₦300k/ETH for math]. Hot Wallet (ETH) DOWN 0.004 ETH = ₦1,200 (credit, gas). Network Fees UP ₦1,200 (debit, expense). Wait, we also have to account for the user's 0.5 ETH leaving the hot wallet: Hot Wallet (ETH) DOWN ₦150,000 (credit). Combined: 4 lines. User Wallet ₦150k debit + Network Fees ₦1.2k debit = Hot Wallet ₦151.2k credit. Balanced. (Plus a 4th entry to represent the swap RECEIVING asset arriving, omitted here.)

Takeaway

EVM transactions have THREE possible outcomes: success (value moves, gas charged), revert (no value moves, gas STILL charged), or pending-forever (value not moved, gas not charged, tx waiting to mine). The ledger needs to distinguish all three. On revert, post a gas-only entry that books to Network Fees expense (yours, not the user's), the user should never see a balance impact from a failed transaction. On pending-forever, replace-by-fee (RBF) the tx to either confirm or cancel cleanly. Build the listener to ack each terminal state distinctly; without it, you get user-support tickets that say 'where's my money' when actually nothing moved.

Practice this on a real ledger

Reading is half of it. Open this lesson in the lab to post the entries yourself against a real Postgres-backed double-entry ledger, with the validation on. Free, your sandbox is yours.

More in this section

Search lessons

Type to find any of the 85 lessons. Press Enter to open.