Atharva Khewle

ProjectOct 2026 · Next.js, TypeScript, bitcoinjs-lib, mempool.space

When is an unconfirmed Bitcoin payment safe?

TL;DR A merchant who accepts a Bitcoin payment before it's mined is trusting the mempool. I built a threat model of six attack classes and an engine that classifies unconfirmed payments, then tested it on mainnet with my own funds: 32 transaction legs, 217 logged events.

The question

A payment you can see but that isn't in a block yet can still disappear. The sender can replace it, it may never reach miners, or your view of the mempool may simply be wrong. The real question isn't "is 0-conf safe?" but "is this payment, at this size, safe right now?"

Six ways a 0-conf payment fails

  1. View-layer fabrication. The payment only exists in the UI or API you're looking at.
  2. Never relayed. A future nLockTime or a fee below the minimum relay fee means nodes never pass it on.
  3. RBF eviction. The sender replaces it with a higher-fee version (BIP-125) that pays someone else.
  4. Miner-direct double spend. A conflicting transaction goes straight to a miner.
  5. Pinning. Package or topology tricks keep it stuck so it can't be bumped.
  6. Congestion and races. It expires during fee spikes, or loses a Finney or eclipse race.

The engine

Each unconfirmed payment gets one of six states: VERIFIED_PENDING, REPLACED, SPENT_ELSEWHERE, STALE_VIEW, UNVERIFIED_FABRICATION_SUSPECT or CONFIRMED_CLEARED. It cross-checks several data sources and looks for conflicting spends of the same inputs (gettxspendingprevout).

The core idea is economic finality: a 0-conf credit is reasonable when it's worth less than what the attacker would have to pay to replace it under BIP-125. A backlog model from mempool fee histograms estimates the wait, and a Monte-Carlo fee simulator (400 runs per scenario) stress-tests it.

Testing for real

I validated it on Bitcoin mainnet with my own funds, where fee markets are real: 32 transaction legs and 217 logged events. The app includes an in-browser wallet (BIP-39/BIP-84, keys encrypted with PBKDF2 and AES-256-GCM) that builds RBF, nLockTime and multi-output transactions for the tests.

← All field notes