A Bitcoin Transaction Coordinator

Fairgate Labs
·
August 13, 2026
·

Reliable broadcast, on chain tracking, and automatic fee management as a single library

GitHub FairgateLabs

🔗 Rust BitVMX Bitcoin

Signing a Bitcoin transaction and handing it to a node is the easy part. Driving it all the way to a durable confirmation is the hard part. A transaction can sit unconfirmed while fees rise, fall out of a node's mempool under fee-market pressure, or be undone for a few blocks by a chain reorganization. This article describes a coordinator library that owns that operational problem end to end. It accepts signed transactions, broadcasts them in dependency order, follows each one across the chain until it is buried deeply enough to be treated as final, raises fees automatically through Child Pays For Parent and Replace By Fee when a transaction stalls, and reports every transition back through a structured, acknowledgeable event stream.

1. Introduction

A Bitcoin transaction has no single moment of success. After a node accepts it, it lives in the mempool, competes against every other unconfirmed transaction for limited block space, and is only safe to rely on once enough blocks have been built on top of the block that included it. Between broadcast and that point, several ordinary things can interrupt the path to confirmation. Any application whose correctness depends on specific transactions actually landing on-chain has to handle all of them, repeatedly and without mistakes.

The coordinator turns that recurring burden into a reusable component. An application hands it a signed transaction and, from that moment, the coordinator takes responsibility for the rest of the transaction's on-chain life. It performs four jobs.

  • Broadcast. It submits signed transactions to a node and, when a transaction spends the output of another transaction it manages, holds the child back until the parent is actually present in the mempool or a block.
  • Track. It follows each transaction from acceptance into the mempool, through its first confirmation, to the depth at which a reorganization is no longer a practical concern.
  • Accelerate. When a transaction lingers because it pays less than current demand requires, the coordinator attaches additional fee from a separate funding source so a miner is motivated to include it, without ever altering the original transaction.
  • Report. Every meaningful transition, a confirmation, a fee escalation, a funding shortfall, a final eviction from local tracking, becomes a structured notification the application consumes and acknowledges.

The application stays in control of timing. It advances the coordinator by calling a single operation whenever it wants to make progress, as often or as rarely as suits it. Each call performs one complete, self contained pass of work and then returns. Between calls the coordinator holds still, so its behavior is fully determined by when the application chooses to advance it.

2. What stands between a broadcast and a confirmation

Four distinct situations, all of them normal rather than exceptional, separate a broadcast from a confirmation. The coordinator is built around handling each of them correctly.

Situation What happens Why it is hard to handle by hand
Fee pressure The transaction pays below the prevailing market rate, so miners prefer others and it waits. The right fee is a moving target that must be re estimated and raised over time without overpaying.
Mempool eviction A node drops low fee transactions from memory under load, so the transaction can vanish before it is mined. A disappearance must be distinguished from a permanent failure, then the transaction must be re broadcast.
Reorganizations A block that already contained the transaction is replaced by a competing block, briefly undoing a confirmation. A confirmation cannot be trusted as final too early, yet the transaction must not be abandoned during this period.
Dependencies A transaction can only be valid once a predecessor has confirmed or is at least visible to the node. Ordering must be enforced across broadcasts that may each be delayed, evicted, or retried independently.
Broadcast errors Handling different possible error cases while broadcasting without losing the transaction. Distinguishing between transient and permanent failures is difficult because node error text is unstable and unreliable to parse.
Cascading failures If a transaction fails, disappears, or is evicted, its dependent child transactions suffer the consequences and must be handled correctly. Dependency must be managed so that child transactions are correctly gated, failed, or re-queued when parents become invalid.

Handling one of these correctly is manageable. Handling all of them together, across restarts of the process and shifts of the chain, while keeping local state consistent with the node, is the actual engineering problem. The coordinator encapsulates that problem behind a small interface so an application can treat dependable confirmation as a service it calls rather than a system it rebuilds.

3. Design principles

Five principles run through the whole implementation and explain most of its structure.

  • Persist intent before acting. The coordinator writes a transaction to durable storage in one state before it broadcasts it in the next pass. A crash at any instant leaves a local record that the next pass picks up.
  • Read a stable view, then act on it. Each pass first advances and reads a single coherent view of the chain, then makes all of its decisions against that snapshot. Reviewing and broadcasting never interleave inside a pass.
  • Decide from node state, not from text. When the coordinator has to determine what happened to a transaction, it queries the node for the transaction's confirmation count and the spent status of its inputs. It never tries to interpret the human readable error string a node returns from a failed broadcast, because that text varies across node versions and configurations.
  • Separate concerns, share one truth. Small components each own one responsibility, yet they all read and write through a single shared state, so they cannot drift into disagreement about what has been broadcast, confirmed, or funded.

4. Architecture

The coordinator functions as a lightweight layer over two separate engines that share one bundle of services. The transaction engine owns everything the application submits. The speedup engine owns the fee bumping transactions the coordinator builds on its own. Both engines hold a reference to the same service bundle, so a fact written by one is immediately visible to the other.

Component Responsibility
Transaction engine Reviews, broadcasts, and retries the application's own transactions, and gates children on their tracked parents.
Speedup engine Builds, broadcasts, and reviews the Child Pays For Parent and Replace By Fee transactions that accelerate stalled ones.
Monitor Indexes blocks and the mempool and answers status queries for any transaction id, including the spent status of arbitrary outputs.
Dispatcher Runs structural pre checks, gates on parent readiness, and performs the raw broadcast to the node.
Fee manager Estimates the network fee rate, applies floors and a ceiling, and computes the exact fee a bump must pay.
Funding manager Maintains the ordered set of spendable outputs that pay for fee bumps and advances it as outputs are consumed and produced.
Storage Persists every transaction record and every pending notification so a restart resumes from the exact prior state.
Figure 1. Two engines share one service bundle and one persistent store, behind a thin shell that routes the public interface.

A useful mental model: every transaction the application submits is owned and broadcast by the transaction engine. The speedup engine is a second stage whose only purpose is to accelerate those transactions with additional coordinator-built transactions.

5. The transaction lifecycle

Every managed transaction moves through five states. The forward path is the common case. The remaining transitions exist so the coordinator stays correct through eviction, reorganization, and restart.

State Meaning
ToDispatch Recorded and waiting for its first broadcast, or re queued for re broadcast after a recoverable event.
InMempool Present in the node, accepted and waiting to be mined.
Confirmed Included in a block but not yet deep enough to be treated as permanent.
Finalized Buried under the configured number of confirmations and treated as settled.
Failed Determined to be unable to confirm. A terminal outcome that triggers cleanup and a notification.
Figure 2. The lifecycle with the recovery edges that keep a transaction tracked through all cases.

Two transitions deserve emphasis. A confirmation is held as Confirmed, not as final, until it reaches the configured depth. If a reorganization returns a Confirmed transaction to the mempool before then, the coordinator moves it back to InMempool and refreshes its broadcast height rather than treating it as a failure. Separately, a transaction that reaches Finalized or Failed is retained for a configured number of further confirmations so the application can still query and reconcile it, after which it is evicted from storage with a final notification that closes out its history.

6. The processing cycle

One advance performs six steps in a fixed order. The order is load bearing. The review steps read state and never broadcast. The broadcast steps send transactions but never decide finality. The build steps run last and only prepare work to be sent on a later pass. Because of that separation, each pass is a clean unit whose effects are easy to reason about.

  1. Synchronize. Advance the local index toward the chain tip. The monitor advances by at most one block per pass, and if the local view is not yet level with the node, the pass stops here. Every decision below therefore reads the real tip rather than a lagging view.
  2. Review transactions. Update the state of the application's in flight transactions from the chain: promote to Confirmed or Finalized, handle a reorg back to InMempool, or re queue a transaction the chain reports as absent.
  3. Review speedups. Do the same for the coordinator's own acceleration transactions, including resolving Replace By Fee predecessors once their replacement settles.
  4. Broadcast transactions. Send the application's transactions that are due and whose tracked parents are present. This runs before speedups so a re-broadcast parent is back in the mempool before any child that accelerates it.
  5. Broadcast speedups. Send acceleration transactions that were built and saved on an earlier pass.
  6. Build a bump or a batch. If the latest acceleration has gone stale, prepare a stronger replacement or follow on for the next pass. Otherwise, if transactions are waiting for acceleration, prepare one new Child Pays For Parent that covers a batch of them.

7. Crash safe ordering

Step 6 builds an acceleration transaction and saves it as ToDispatch. Step 5 of a later pass broadcasts it. Building and broadcasting are deliberately placed in different passes. The coordinator therefore always records what it intends to send before it sends it. If the process stops between the two passes, the next run finds a ToDispatch record and simply broadcasts it. The system never reaches a position where a transaction is live on the network with no local record of it.

The same discipline bounds how much acceleration work is ever outstanding. At most one pre-built acceleration transaction sits in ToDispatch at a time, and the batch builder yields if a bump was already built earlier in the same pass. The result is a measured, predictable rate of fee spending rather than a burst of competing transactions.

8. Deciding what actually happened

The most subtle part is what it does when a broadcast does not succeed. A node can reject a broadcast for many reasons, and the textual reason it returns is neither stable nor reliable to parse. The coordinator ignores that text entirely. Instead, on a failed broadcast it asks the node two authoritative questions and decides from the answers.

First, does the node already have this transaction? A single confirmation lookup answers it. A result of zero confirmations means the transaction is in the mempool, so the failed broadcast was a duplicate and the transaction is accepted as InMempool. A result of one or more confirmations means it is already mined, so it is recorded as Confirmed. Only if the node has no record of the transaction at all does the coordinator move to the second question.

Second, are the transaction's inputs still spendable? The coordinator probes each input's output against the node. If a funding input the coordinator itself produced is gone, the failure is recoverable: the transaction is failed and its funding is rebuilt. If an external input or a parent's output is gone, the transaction cannot proceed and is failed: an adversary already spent that output. If every input is still unspent, the cause is transient, a fee or policy condition, and the transaction is retried until its retry budget is exhausted.

Node answer on a failed broadcast Verdict
Confirmation count is zero (in the mempool) Accept as InMempool. Covers a duplicate broadcast and a crash between send and record.
Confirmation count is one or more (mined) Record as Confirmed and let review finalize it by depth.
Absent, and a coordinator funding input is gone Fail, then rebuild funding so the work can be reattempted.
Absent, and an external or parent input is gone Fail. The transaction can no longer be valid.
Absent, but every input is still unspent Transient cause. Retry until the configured attempt budget is spent, then fail.

Two checks are decided from the transaction alone and need no node round trip: a transaction whose weight exceeds the configured maximum, and a transaction with no inputs, are failed immediately. Everything else routes through the state based classification above. Because the verdict depends only on node state, it is precise and portable across node versions.

9. Reorganizations and the deferred failure window

A reorganization can make an already broadcast transaction disappear from both the chain and the mempool for a few blocks. Taken at face value, a re-broadcast of such a transaction might fail with a spent input, because a competing branch temporarily consumed one of its inputs. Declaring the transaction failed at that moment would be wrong, because the original branch may win and make the transaction valid again.

The coordinator handles this with a bounded, block paced window. When review finds an already broadcast transaction that the chain reports as absent, it re queues the transaction for dispatch and records a deadline a fixed number of confirmations ahead. While the chain height is below that deadline, a spent input verdict is deferred: the transaction stays queued and is re-broadcast at the retry cadence rather than being failed. Recovery is the re broadcast itself, because once the input frees, the next send is accepted and the window is cleared. Past the finality deadline exactly one branch has survived, and the transaction settles as Failed.

Event Handling
A Confirmed transaction reappears in the mempool Reset to InMempool and refresh its broadcast height. Keep tracking it.
The block of a transaction is orphaned Keep the transaction InMempool and wait for re-inclusion.
A deep reorg evicts both the block and the mempool entry Re queue the same transaction and arm the deferred failure window.

10. Accelerating stalled transactions

The key capability of the coordinator is automatic acceleration. When a transaction is stuck because it pays too little, the coordinator does not modify it. It attaches more fee from a separate funding source using one of two standard Bitcoin techniques.

Technique Mechanism
Child Pays For Parent A new child spends an output of the stalled transaction and pays a high fee, so a miner must include both to collect it.
Replace By Fee A replacement reuses the inputs of the latest acceleration transaction and pays strictly more, evicting it from the mempool.

Acceleration is also patient and bounded. A new bump is considered only after the latest acceleration has had at least a configured number of blocks to work and only while its effective fee rate still sits below the configured ceiling. Each successive bump pays strictly more per virtual byte than the one before it. When the next bump would cross the ceiling, the coordinator emits a notification that the maximum was reached, and stops escalating that chain rather than overpaying.

Acceleration is not limited to one transaction at a time. A single Child Pays For Parent can cover several waiting transactions at once, spending one output from each and paying one combined fee. This is cheaper than one accelerator per transaction and, just as importantly, consumes only one slot in the node's chain of unconfirmed transactions. The batch is assembled greedily up to the configured maximum transaction weight, and at most one new accelerator is built per pass, which keeps both transaction size and fee spending within predictable bounds.

Batching several transactions under a single accelerator introduces a shared dependency: if one parent becomes permanently unconfirmable, the shared accelerator also becomes invalid because one of its inputs will never exist. Before broadcasting an accelerator, the coordinator checks whether any of its parents have already failed. If one has, it discards that accelerator and constructs a replacement covering only the remaining eligible parents. This preserves acceleration for transactions that can still confirm while excluding only the failed one.

11. How much a bump pays

The coordinator follows a clear principle rather than a fixed formula. It looks at the whole package it is responsible for, the stalled transactions together with the acceleration, and works out the additional fee needed to lift that package to a chosen target rate. It credits the stalled transactions for the fee they already paid, so the acceleration only covers the shortfall. When several earlier accelerations are still unconfirmed, the calculation also lifts them toward the new rate, so the whole chain moves up together instead of leaving slower links behind.

12. Funding management

Fee bumps are paid for from an ordered queue of spendable outputs. The queue is deliberately able to hold two kinds of entry at once: outputs the operator registers directly, and the change outputs that finalized accelerators leave behind. A directly registered output joins at the end of the queue. When an accelerator finalizes, its change output replaces the entries it consumed in place, so the queue keeps advancing along the real chain of change rather than growing without bound.

Each bump spends the current live tip of that chain and leaves a fresh change output that becomes the next tip. The lookup that selects funding walks from the live tip and skips any accelerator that has failed or has already been replaced, so the next bump always builds on a genuinely live output even as the chain shifts.

Two edge conditions are handled explicitly. If a single funding output is too small to cover the next fee while still leaving a non dust change, and it is a change output left by a prior accelerator, the coordinator combines it with the next output in the queue as a second input in the same transaction rather than stranding it. This recovers funding that would otherwise be lost to dust. If funding runs out entirely, the coordinator emits a notification so the operator can top it up, and keeps the last output in the queue so the attempt can be retried on a later pass when the required fee may have dropped. A directly registered output below the configured minimum is rejected outright, because an output that small can never pay a useful fee.

Figure 3. Funding flow

13. The notification stream

The coordinator communicates through a stream of structured notifications. The application pulls pending notifications, acts on them, and acknowledges each one. Acknowledgement is the point of the design: a notification stays pending until the application confirms it handled it, which gives a replayable, gap free record across restarts. Notifications are also deduplicated by value within a block, so a repeated condition in the same block is reported once.

Notification Raised when
Stuck in mempool A transaction has waited longer than its configured mempool block threshold.
Dispatch error A transaction or an acceleration could not be broadcast and has exhausted its retries.
Maximum fee rate reached A bump was saved at the configured ceiling. That chain stops escalating.
Estimate fee rate too high The node's fee estimate exceeded the configured ceiling and was clamped down.
Funding not available A bump was needed but the funding queue held no usable output.
Insufficient funds The available funding cannot cover the next fee plus a non dust change.
Transaction evicted A settled transaction was removed from storage after its retention window, closing its history.
Invalid cancel A cancel request targeted a transaction that is not eligible for cancellation.

14. The public interface

The surface an integrator works against is intentionally small, and it is best understood as a set of capabilities rather than a list of calls. An application constructs the coordinator, advances it when it wants progress, registers transactions and optionally funding, queries status, and consumes notifications.

Capability Purpose
Advance Run one processing pass, whenever the application wants to make progress.
Readiness check Ask whether the local view is level with the chain.
Register a transaction Hand over a signed transaction, choosing whether the coordinator should accelerate it automatically or not.
Add funding Provide a spendable output that future accelerations may consume.
Query a transaction Read the current tracked state of a transaction.
Pull and acknowledge events Retrieve everything that has happened and confirm each one as handled.
Cancel Withdraw a transaction that has not yet been broadcast.

Only a transaction the application registered and that is still waiting for its first broadcast can be cancelled. Once a transaction has been broadcast, or if the target is one of the coordinator's own internal transactions, a cancellation is refused with a notification. In practice an integration is a short loop: advance the coordinator, check that it is ready, register any new signed transactions and any funding, then pull notifications and acknowledge each one after acting on it.

15. Configuration

The coordinator is tunable. Its behavior is shaped by a small set of validated settings, each with a sensible default and an enforced limit, all checked when the coordinator is constructed so a misconfiguration is rejected up front rather than surfacing later as confusing behavior.

The settings cover: how aggressively fees escalate and the ceiling they may reach, how long a transaction is given to confirm before it is accelerated, the minimum size of a usable funding output, how tolerant the coordinator is of transient broadcast failures, how long settled transactions are retained for reconciliation before they are evicted, and more. The defaults are chosen to be safe out of the box, so most times only little adjusts are needed.

16. Where it fits in BitVMX

BitVMX is an open framework for verifiable, disputable computation secured by Bitcoin. Protocols built on it depend on specific transactions reaching the chain reliably and on time, even under fee pressure or transient network turbulence, and in a strict order that encodes the steps of the protocol. The coordinator provides exactly that guarantee as a reusable building block. A protocol expresses what it needs on-chain, and the coordinator owns the operational reality of getting it confirmed, accelerating when necessary and reporting throughout.

The outcome is a clean separation of concerns. Protocol logic stays focused on what each transaction means, while the coordinator owns broadcast, tracking, fee management, and recovery. The coordinator is one component of the broader BitVMX ecosystem.

Join our community

Implementing Garbled Circuits for BitVMX

Union Bridge Reaches Testnet: A Milestone for BitVMX-Powered Bitcoin Bridging

BitVMX’s Open Source Stack keeps expanding: Message Broker & Operator Communication Library

BitVMX Protocol Builder Deep Dive

New BitVMX Open Source Components Delivery: Introducing the BitVMX Protocol Builder – Graph-Based Transaction Design for Bitcoin

BitVMX's Open Source Journey Continues: Bitcoin Monitoring, Coordination and Indexing components now available

Introducing BitVMX New Open Source Components: Key Management, Storage, and Configuration

Building Secure and Watchtower-efficient Bitcoin Payment Channels with BitVMX

From Blueprint to Backend

Introducing the BitVMX 2025 Roadmap

Why RISC-V is the Optimal Architecture for the BitVMX Proving System

Improving BitVMX with Bitcoin Soft-forks

ESSPI: ECDSA / Schnorr Signed Program Input for BitVMX

BitVMX off-chain communication system: Multi-Exchange Handler

PKMN_BTTL: A Pokemon Battle Game, Written in Zig and Executed with BitVMX

Zero Knowledge Proof Verification On Bitcoin

BitVMX off-chain communication system: Key Components and Secure Strategies

Unlocking Trustless Bridges: BitVMX Goes Open Source

Union Bridge: A Trustless Gateway Between Bitcoin and Rootstock Powered by BitVMX

BitVMX: a practical exploration

First Release of BitVMX Implementation: Union Bridge by Rootstock

Optimizing Algorithms for Bitcoin Script (part 3)

BitVMX off-chain communication system: Protocol Implementation and Practical Applications

Optimizing Algorithms for Bitcoin Script (part 2)

Optimizing Algorithms for Bitcoin Script

Interactive SNARK Verification on Bitcoin using BitVMX!

A New Era for Bitcoin: Successful SNARK Proof Verification with BitVMX

We bitcoiners have a card under our sleeve: unpredictable innovation

Latest Innovations in BitVMX

The near future of bitcoin CPU: BitVMX

How BitVMX Differs from BitVM

BitVMX: A CPU for Universal Computation on Bitcoin

Keynote at Bitcoin++ ATX24 Script Edition for the BitVMX Presentation