The life of a transaction

For platform engineers.

In short

A transaction in CryptoRail is a persisted state machine. Your system submits an intent (what to call, on which network, from which identity, with an idempotency key); CryptoRail answers at once and then takes the operation through policy, approval, preparation, signing, broadcast and confirmation, recording every step. Nonces, fees, stuck transactions, dropped transactions and reorganisations are handled inside that machine, and every failure ends with a report that says where, why and what to do.

This page explains each stage and what it guarantees. The developer guide has the calls and the configuration.

The state machine, as data

Every legal change of state is one row of a transition table, with a guard and a named cause. A change is persisted before anything that depends on it happens; a change the table does not contain is refused. There are no state changes scattered through the code, so the diagram below is the behaviour, not an approximation of it.

Every operation follows one state machine written as a transition table: each change of state is persisted with its cause and time before anything depends on it, and a transition the table does not allow is refused.

stateDiagram-v2
    [*] --> RECEIVED
    RECEIVED --> REJECTED_BY_POLICY
    RECEIVED --> PENDING_APPROVAL
    PENDING_APPROVAL --> PREPARED
    RECEIVED --> PREPARED
    RECEIVED --> PREPARATION_FAILED
    PREPARED --> PENDING_SIGNATURE
    PENDING_SIGNATURE --> SIGNED
    PENDING_SIGNATURE --> SIGNING_REJECTED
    SIGNED --> BROADCAST
    SIGNED --> BROADCAST_FAILED
    BROADCAST --> MINED
    BROADCAST --> STUCK
    BROADCAST --> DROPPED
    STUCK --> PREPARED
    DROPPED --> SIGNED
    MINED --> CONFIRMED
    MINED --> REVERTED
    MINED --> BROADCAST
    CONFIRMED --> [*]

The diagram shows the main paths; the full table adds approval rejection and expiry, signing expiry, cancellation from every state before and after broadcast, and the re-nonce of a displaced transaction. Three properties matter most:

  • Waiting for people costs nothing. An operation waiting for approval holds no nonce and no fee. The nonce is taken when the operation is prepared, just before it is signed.
  • What is on the network is never withdrawn. A broadcast transaction is replaced at its nonce or followed to its receipt; a reorganisation that removes its block sends it back to BROADCAST.
  • CONFIRMED is final. It is reached only when the network’s finality rule is met, and it is never revisited.

Every change of state of your own operations arrives through the same channel as the contract events.

The full history of every operation, every transition and every attempt with its time and cause, can be queried.

Your business operation travels with its transactions: its type, its id and its step appear in queries, events and traces.

Intake and idempotency

Submission is asynchronous: the intent is persisted, the operation gets its id, and the answer comes back without waiting for policy, signature or chain. The idempotency key is what makes a retry safe: the same key with the same intent returns the same operation; the same key with a different intent is refused as a conflict. Through the service the key is a required header on every call that changes something, and the stored answer is replayed.

A submission is answered at once with the operation's id; progress is read through queries and events, and the API never waits for the chain.

Submitting the same intent twice with the same idempotency key returns the same transaction: a retry never becomes a second payment.

Before anything is prepared, the policy engine decides: allow, deny (a terminal state with no node call), or require approval. Approvals are covered in the signing deep dive.

Declarative policies decide before anything happens: limits per operation, payer and period, held even when several workers decide at once.

Preparation: the checks before any signature

Preparation runs in a fixed order: the call is encoded against the contract’s ABI, the gas is quoted by simulating the call, the payer’s balance is compared with value plus worst-case gas, and the nonce is taken last. A failure in any step ends the operation in PREPARATION_FAILED with nothing signed and no hole left in the nonce sequence.

Call a function by the contract's name in your registry: arguments checked against the ABI before anything is signed, and the contract's own errors decoded when it refuses.

Each operation is simulated before it is signed: a call that would revert, a fee above the cap or a payer without enough funds fails with the reason before any signature, and the nonce is taken last.

Nonces and gap repair

On one sending address the nonce sequence is the order of execution, and a single missing nonce blocks everything behind it. CryptoRail allocates nonces per network and address under an exclusive lock in the database, so two workers, or two processes, never hand out the same one. On startup and periodically it reconciles its own count with the node’s, adopting the node’s when the key was used outside CryptoRail.

flowchart TD
    R[reserve: lock network and address] --> P[persist next nonce before returning]
    P --> S{transaction ends without broadcast?}
    S -->|signing refused or expired, cancelled| G[nonce given back]
    S -->|broadcast| OK[nonce consumed on chain]
    REC[periodic reconciliation with the node] --> GAP{a reserved nonce nobody holds?}
    GAP -->|yes| AL[gap alert to the operator]
    AL --> F[release it, or fill it with a zero-value self-transfer]

Every path that ends before broadcast gives its nonce back on its own. What is left, a reservation whose process died at the wrong moment, is raised as an alert and repaired by an operator: released, or filled with a zero-value transfer. Filling is deliberately manual, because it spends gas to undo something that should not have happened and doing it silently would hide the defect.

Nonces strictly increasing per sending address, gaps detected and filled, and never two transactions mined for one operation.

Several workers on the same database without sending anything twice.

Ordered batches from one identity, from the library: consecutive nonces, and the steps behind a failed one cancelled when you ask for it.

Gas policies and priority tiers

Fees come from the network’s gas policy and from the sending identity’s priority tier, never from a caller’s guess. Each network declares one policy; each identity is bound to one tier of its network.

Gas policyFeesReplacement of a stuck transaction
EIP-1559 marketFrom recent blocks, with the tier’s multiplier and percentileSame nonce, fee raised by the tier’s bump
Legacy marketGas price from the nodeSame nonce, price raised by the tier’s bump
Fixed priceA constant gas priceSame nonce, price raised by the tier’s bump
Free (consortium networks)Zero; no fee-market query, no gas balance neededRebroadcast, or a fresh zero-fee attempt at the same nonce

A tier (low, normal, high, urgent) sets how aggressive the fee is, the cap it may never exceed, when a transaction counts as stuck, how much each replacement raises the fee, how many attempts it gets, and what happens when they run out. Urgency belongs to the identity, not to the single operation, because nonces are mined in order per address: mixing tiers in one address would let a cheap transaction block an urgent one. A team that needs several urgency levels uses several identities. When a third-party contract authorises exactly one sender, an operation may instead ask to preempt: it takes the lowest nonce of the queue, and the transactions it displaces are re-signed one nonce higher, keeping their identity and their tier. Preemption needs an explicit policy allowance.

Fees follow the network's gas policy (EIP-1559 market, legacy market, fixed price or free) and the sending identity's tier, never the caller's guess; an override is allowed only under the cap.

Priority tiers per sending identity with fee caps you set; an urgent operation can go ahead of a queue already on the network.

Permissioned consortium networks with free gas (Hyperledger Besu with QBFT): no fee market, no gas balance to keep topped up.

The actual cost of every operation is recorded, and a payer running low on funds raises an alert before operations fail.

Broadcast, stuck, dropped and cancelled

A node can refuse a transaction, ignore it, or forget it. Each case has one defined answer.

flowchart TD
    B[BROADCAST] --> M{mined within the tier's threshold?}
    M -->|yes| MI[MINED]
    M -->|no| S[STUCK]
    S --> P{market fees, attempts and cap left?}
    P -->|yes| R[new attempt, same nonce, higher fee]
    P -->|no, keep bumping| W[stay at the cap and rebroadcast]
    P -->|no, abandon| C[cancel: zero-value transfer at the same nonce]
    R --> B
    B --> D{node forgot it?}
    D -->|yes| DR[DROPPED: rebroadcast the same signed bytes]
    DR --> B
  • Refused at broadcast. The refusal is classified: recoverable ones are rebuilt and retried, unrecoverable ones end the operation with the reason and give the nonce back.
  • Stuck. Replaced at the same nonce with a higher fee, within the cap you set. Every attempt is tracked until one is mined; that one becomes the transaction’s hash and the others are marked superseded, so a replacement never executes twice.
  • Abandoned. A tier that gives up does not stop tracking: an unmined attempt keeps its nonce and would block everything behind it, so abandoning sends a zero-value transfer at the same nonce.
  • Dropped. Rebroadcast with the same signed bytes, then rebuilt at the same nonce.
  • Cancelled. Before broadcast nothing reaches the network; after it, cancelling is a race between a zero-value replacement and the original, and the final state says which one won.

A node's refusal is classified: a recoverable one (underpriced, stale nonce) is rebuilt and retried, an unrecoverable one (insufficient funds, intrinsic gas too low) fails with the reason and gives the nonce back.

A transaction the network does not pick up is replaced with a higher fee within the caps you set, or cancelled, never silently abandoned.

A transaction the node forgets is rebroadcast with the same signed bytes, then rebuilt at the same nonce: never a second payment.

Cancelling before broadcast means nothing reaches the network; after broadcast it sends a zero-value replacement at the same nonce, and the final state says which of the two landed.

An identity can be paused: new operations are accepted and wait without taking a nonce until it is resumed.

Several node endpoints per network: calls go to the first healthy one, an operation stays on the endpoint it started on, and a dead or lagging node is left aside without losing anything.

Finality per network

Being mined is not being done. Each network declares its finality rule, and MINED becomes CONFIRMED only when that rule is met. A reorganisation before that point moves the operation back and is reported; a reorganisation deeper than the rule is raised as an alert, never turned into a silent change of a confirmed operation.

Each network declares its finality rule: a number of blocks on top, the node's finalized block, or instant on networks with deterministic finality, with coherent defaults for public and permissioned networks.

An operation is reported confirmed only when the network's finality rule is met; a reorganisation before that is followed, not hidden.

A deeper confirmation can be asked per operation or chosen by a policy rule (large amounts wait longer); the strictest applies, never one looser than the network's.

The failure report

Every terminal failure carries a report, and the state machine refuses to enter a failure state without one. It answers the questions an operator asks, in the order they are asked.

FieldAnswersExample
StageWhere it failedpolicy, approval, signing, preparation, broadcast, execution, confirmation
CauseWhy, as a stable codepreparation.insufficient-funds
RetryableWhether a retry can workyes, no, or conditional: after-funding-payer
Suggested actionWhat to doA sentence for the operator
Detail and traceHow to digDecoded revert or node message, attempt hash, trace id

The report is part of the operation’s state, of the state-change event and of the API response, so your systems can route a failure without parsing a message.

Every failure comes with a report: the stage, a stable cause, whether a retry can work and on what condition, and the suggested action.

Traces, metrics and structured logs through OpenTelemetry, into the monitoring tools you already run.

What it does not do

  • It does not orchestrate your business process. It groups the transactions of one business operation, traces them together and reports on them; it does not decide the next step or compensate a failed one.
  • An ordered batch is not atomic on chain. It guarantees the order; a batch whose second step fails leaves the first one mined and paid for.
  • Several calls that succeed or fail together, in one UserOperation of the customer's smart account: the policy reads every call and their sum, and a call that cannot succeed leaves none of them done.
  • It does not fill nonce gaps on its own. Gaps are detected and alerted; repairing one is an explicit operator action.
  • It does not exceed your caps. An operation whose fee would pass the tier’s cap is not sent; a keep-bumping tier waits at the cap rather than raising it.
  • It does not withdraw a broadcast transaction. Once on the network a transaction can only be replaced at its nonce or followed to its receipt.
  • It does not revisit a confirmed operation. A reorganisation deeper than the finality rule is an alert for an operator, not a change of state.

Evidence

Every claim above links to its evidence on the evidence page. The main ones:

SubjectRequirementsJourneys
State machine, events, historyFR-TX-04, FR-TX-05, FR-TX-17, FR-TX-18J07, J13, J14
IdempotencyFR-TX-03, FR-API-03J07, J26
Preparation checksFR-TX-06, FR-TX-16J02, J03, J08
Nonces, gaps, several workersFR-TX-09, FR-TX-10, FR-TX-11, FR-OPS-05J09, J29
Gas policies, tiers, preemptionFR-CHN-12, FR-CHN-16, FR-TX-07, FR-TX-08, FR-TX-26J08, J21
Rejections, stuck, dropped, cancelledFR-TX-12, FR-TX-13, FR-TX-14, FR-TX-15, FR-TX-25, FR-FIN-06J04, J10, J11, J17, J24
Finality and reorganisationsFR-FIN-01, FR-FIN-02, FR-FIN-04, FR-FIN-07, FR-FIN-08, FR-FIN-09J05, J11, J22, J28
Failure reportFR-TX-29J02, J03