Skip to content

Reconciliation ​

The problem ​

A Stellar submission can time out after the network has already applied the transaction. If the server assumed failure it could double-spend on retry; if it assumed success it could mark a trade complete when no XLM moved.

The approach ​

  1. Record intent first. The escrow address is saved before the lock is submitted.
  2. Only roll back on definitive failure. STELLAR_TX_FAILED means the ledger rejected the transaction; every other error leaves the trade in PENDING_ESCROW, RELEASING or REFUNDING.
  3. Wait out the timeout. Transactions carry a 60-second time bound, after which they can never apply.
  4. Ask the ledger. jobs/reconcileTrades.js runs every 60 seconds and calls trade.service.reconcileTrade for trades whose updatedAt is more than 2 minutes old.

Decision table ​

reconcileTrade loads accountExists(escrow) and findEscrowTransactions(escrow):

StatusEscrow exists?Matching memo foundResult
PENDING_ESCROWyeslockfinalizeLock → ESCROW_LOCKED with a fresh payment window
PENDING_ESCROWyes—No action (retry next run)
PENDING_ESCROWno—CANCELLED (ESCROW_FAILED), order reopened
RELEASINGnoreleasefinalizeRelease → COMPLETED
REFUNDINGnorefundfinalizeRefund → CANCELLED
RELEASING / REFUNDINGyes—Restore PAID if paidAt is set, else ESCROW_LOCKED
RELEASING / REFUNDINGnononeLogged as Escrow closed without a recognised transaction

The finalizers are the same functions the normal release/refund path uses, so ledger entries, system messages and socket events are identical.

Verified behaviour ​

Tested against testnet data by forcing trades back into transitional states: a merged escrow was finalized with its original release hash, a refund was reconciled with its refund hash, and a never-funded escrow was cancelled with ESCROW_FAILED.

Nexlm is a peer-to-peer facilitator and never holds customer Naira.