ERRNA expert insight
Crypto Exchange Ledger Design: A CTO's Guide to Preventing Overspent Balances and Unreconciled Custody
A crypto exchange ledger is the system of record that decides what each customer is entitled to, what is reserved for open orders and pending withdrawals, and what has already left the platform. Most outages that damage trust are not matching-engine failures. They are ledger failures: two concurrent orders that both passed a balance check, a withdrawal that timed out and was resubmitted, or a deposit webhook that was acknowledged and then lost.
This guide is written for CTOs and chief architects who own that system boundary. It does not tell you which database to buy. It describes a proposed ledger design, built from double-entry accounting, target-side conditional mutation, durable operation identity, and three-way reconciliation. It also gives you a set of all-required go/no-go controls you can use in a design review.
Two scope notes before we start. First, the design below is a proposal for your own review. It is not a description of a certified or proprietary method that any vendor already ships. Second, the code is illustrative. It shows the invariants, not the exact API of your database, custody provider, or banking partner. Verify those contracts in primary documentation before you build on them.
- Who this is for: CTOs and chief architects designing or reviewing an exchange back end.
- What you will leave with: a five-plane ledger model, concurrency rules, a custody-boundary state table, failure patterns, and a go/no-go checklist.
- What it is not: legal advice, a regulatory interpretation, or a guarantee of audit outcomes.
Key Takeaways
- ๐ฏ The ledger is a liability record. It says what you owe customers. Custody systems, blockchains, and banks are separate authorities for what you hold.
- ๐ Balance checks must be conditional writes at the target. Reading a balance, comparing it in application code, then writing it back is not compare-and-swap.
- ๐งพ Every consequential mutation needs a durable, tenant-scoped operation identity bound to an immutable approved payload hash. Reusing a key with a changed payload must be rejected.
- โ ๏ธ An unknown result is not a failure. It means completion could not be confirmed. Keep funds held, reconcile against authoritative state, and never blindly retry.
- ๐ฌ Acknowledge only after durable recording. Deduplication, inbox or outbox records, and scheduled work must commit atomically, or be explicitly recoverable, before you return success.
- ๐ Reconcile three ways: internal ledger, custody or wallet state, and chain or bank settlement. Breaks are investigated, not silently adjusted.
- ๐ก๏ธ Identifiers are sensitive. Wallet addresses, transaction hashes, and account IDs can identify people. Assign privacy ownership before the first journal entry is written.
- โ Use go/no-go controls, not a score. A single failed required control blocks launch. A high count of passed controls does not certify readiness.
Why Exchange Ledgers Fail Before the Matching Engine Does
Section takeaway: Your platform has several partial sources of truth. If nobody defines which system is authoritative for which fact, the ledger will quietly disagree with reality.
Teams usually invest first in the visible parts of an exchange: the trading interface, the order book, the matching engine. These are measurable and demo well. The ledger is quieter. It is a set of tables and rules that nobody notices until a balance is wrong.
The problem exists because an exchange does not have one source of truth. It has at least four, each authoritative for something different.
| System | Authoritative for | Not authoritative for |
|---|---|---|
| Internal ledger | What each customer is entitled to, and what is held against open orders or pending withdrawals | Whether a transfer actually settled on a chain or at a bank |
| Custody or wallet system | Key control, signing status, and the provider's own record of an operation | Customer entitlements inside your platform |
| Blockchain network | Whether a transaction was included and how deeply it is confirmed | Who inside your platform the funds belong to |
| Banking or payment partner | Fiat settlement status | Crypto balances or order state |
A ledger design is really a contract between these authorities. It states which one wins on each question, how a disagreement is detected, and who is allowed to resolve it. Without that contract, every incident turns into an argument about which dashboard to believe.
This is also why ledger quality is hard to see from outside. A platform can pass a penetration test, pass a load test, and still have a race condition on balance reservation that only appears under concurrent API traffic. For related reading on adjacent boundaries, see our guides on deposit crediting and reorg controls and on why proof of reserves is not enough. This article covers the layer underneath both: the ledger itself.
How Most Teams Build the Ledger, and Why It Breaks
Section takeaway: The common shortcuts are all reasonable in a prototype. Each one fails under concurrency, retries, or partner timeouts, which are the normal operating conditions of a production exchange.
Most first-generation ledgers start as a balances table with one numeric column per user and asset. Application code reads the balance, checks it, and writes a new value. It works in a demo. It works in single-user testing. It fails when two requests arrive at the same moment, or when a client retries after a timeout.
| Common shortcut | Why it seems fine | What breaks |
|---|---|---|
| One mutable balance column per user and asset | Simple to build and query | No history, no audit trail, and no way to explain how a number was reached |
| Read balance, compare in code, write result | Passes functional tests | Two concurrent requests can both read the same balance and both proceed |
| Balance cached in the API layer | Fast order entry | Replicas disagree, and the cache becomes an unofficial second ledger |
| Retry the call when a partner times out | Improves apparent reliability | A payout that actually succeeded is sent twice |
| Credit deposits directly from a webhook | Fast user experience | Duplicate deliveries double-credit, and lost deliveries drop credits |
| Periodic spreadsheet reconciliation | Feels like a control | Breaks are found days later and fixed by manual balance edits that hide the root cause |
Notice the pattern. None of these shortcuts are careless. Each one optimizes for the happy path. The ledger is the one component where the unhappy path is the specification.
The deeper issue is that these designs treat a balance as a stored fact. A safer design treats a balance as a derived view of immutable journal entries, updated through conditional mutations that cannot violate invariants. That shift is the core of the model in the next section.
A Proposed Five-Plane Ledger Model
Section takeaway: Separate operation identity, immutable journal, balance projection, external boundary, and reconciliation. Each plane has one invariant you can test and show to an auditor.
The following is a proposed design for CTO review. It is a mental map, not a standard, and it is not presented as a method any specific vendor already uses. Adapt it to your jurisdiction, custody model, and database.
- Operation plane. Every consequential request (order placement, withdrawal, internal transfer, adjustment) gets a durable, tenant-scoped operation record bound to a hash of the approved payload.
- Journal plane. An append-only double-entry journal. Entries are never edited. Corrections are new entries that reference the original.
- Balance plane. Projected balances with separate available and held amounts, changed only by conditional mutation that enforces non-negative invariants at the data store.
- Boundary plane. Inbox and outbox records for everything that crosses to custody, chain, bank, or screening vendors, with explicit states for acknowledged, terminal failure, and unknown.
- Reconciliation plane. Scheduled and event-driven comparison of ledger, custody, and chain or bank state, with break classification and named owners.
| Plane | Core invariant | Evidence a reviewer can ask for |
|---|---|---|
| Operation | One tenant-scoped identity maps to exactly one approved payload hash | Rejected same-key, different-payload test; unique constraint definition |
| Journal | Every entry balances per asset; no edits or deletes | Balance-per-entry check; permission model showing no update or delete rights |
| Balance | Available and held amounts never go negative | Concurrent-order test results; conditional-mutation statement |
| Boundary | Nothing is acknowledged before it is durably recorded | Fault-injection test killing the process between receipt and acknowledgment |
| Reconciliation | Every break has a class, an owner, and a closure record | Break log with timestamps and resolution evidence |
How double-entry postings look in practice
Double-entry is an accounting discipline, not a regulatory requirement in itself. Whether your regulator or auditor expects specific records is a question for your compliance and legal advisers. As an engineering control, though, it gives you a self-checking structure: if every entry must sum to zero, a whole class of silent corruption becomes detectable.
The postings below are illustrative for a withdrawal lifecycle. Customer balances are liabilities, so debiting them reduces what you owe.
| Lifecycle event | Debit | Credit | Note |
|---|---|---|---|
| Withdrawal requested and approved | Customer available | Customer held | Funds reserved; nothing has left yet |
| Withdrawal confirmed as settled | Customer held | Wallet or custody control account | Only after authoritative confirmation |
| Withdrawal confirmed as terminally failed | Customer held | Customer available | Release only with authoritative failure evidence |
| Result unknown | (no posting) | (no posting) | Funds stay held; operation flagged for reconciliation |
The last row is the one most designs omit. If your state machine has only success and failure, a timeout will be forced into one of them, and it will sometimes be wrong. For how this plays out against custody providers, our playbook on unknown withdrawal outcomes covers the operational side.
Concurrency and Idempotency: The Invariants You Cannot Skip
Section takeaway: Correctness comes from the data store refusing an invalid change, not from application code politely checking first. Idempotency must be bound to the payload, scoped to the tenant, and claimed atomically.
Read-compare-then-write is not compare-and-swap
The most common ledger defect is also the easiest to write. The code reads a balance, compares it to the order size, and writes the new value. Between the read and the write, another request can do the same thing. Both pass the check.
Illustrative scenario, not a real incident: a customer holds 1.0 units of an asset. Two API nodes receive two sell orders of 0.8 within the same few milliseconds. Each node reads 1.0, each compares 0.8 is less than or equal to 1.0, and each writes 0.2. The platform has now reserved 1.6 units against a 1.0 balance.
The fix is a target-side conditional mutation: the data store itself applies the change only if the condition holds at the moment of writing. Primary documentation describes the options. PostgreSQL's documentation on transaction isolation explains that Serializable transactions must be retried on serialization failure, and its page on explicit locking documents row-level locks such as FOR UPDATE. Its guidance on application-level consistency notes that SELECT FOR UPDATE alone does not guarantee a concurrent transaction will not change a row unless a row is actually updated while the lock is held. For key-value stores, Amazon's documentation on DynamoDB conditional writes describes writes that succeed only if stated conditions are met.
If your store offers neither, you can serialize all writes for an account through a single-writer partition. That is a legitimate design, but disclose its limits: it caps per-account throughput, and its failover behavior becomes part of your correctness argument.
Illustrative claim-and-hold transaction
The sketch below shows the invariants in SQL. It is illustrative. Table names, types, and error handling will differ in your system, and you should verify behavior against your database version's documentation.
-- ILLUSTRATIVE ONLY: adapt to your schema and database documentation. BEGIN; -- 1. Claim the operation identity, scoped by tenant. INSERT INTO operations (tenant_id, operation_id, kind, payload_hash, state) VALUES (:tenant_id, :operation_id, 'withdrawal', :payload_hash, 'received') ON CONFLICT (tenant_id, operation_id) DO NOTHING; -- 2. If no row was inserted, load the existing record. -- Same hash -> return the recorded outcome (true duplicate). -- Different hash -> reject as a key-reuse conflict. Never overwrite. -- 3. Conditional mutation: the store enforces the invariant. UPDATE balances SET available = available - :amount, held = held + :amount, version = version + 1 WHERE tenant_id = :tenant_id AND account_id = :account_id AND asset = :asset AND available >= :amount; -- Zero rows updated: terminal rejection (insufficient available balance). -- 4. In the SAME transaction: journal entries, outbox row, state change. INSERT INTO journal_entries (...) VALUES (...); INSERT INTO outbox (tenant_id, operation_id, target, payload_hash, status) VALUES (:tenant_id, :operation_id, 'custody', :payload_hash, 'pending'); UPDATE operations SET state = 'held' WHERE tenant_id = :tenant_id AND operation_id = :operation_id; COMMIT; -- Only after COMMIT returns do you acknowledge the client.The six rules behind that sketch
- Tenant-scoped identity. The uniqueness key is the pair of tenant and operation ID. In a white-label or multi-tenant platform, one tenant's key must never collide with, replay, or reveal another tenant's operation.
- Immutable payload hash. Compute the hash server-side over a canonical form of the approved fields: asset, network, destination, amount, and fee policy. Approvals bind to that hash. If the payload changes after approval, it is a new operation requiring new approval.
- Reject changed-payload key reuse. The same key with a different hash returns a conflict. Silently accepting it lets a retry bug or an attacker alter a destination address under an approved identity.
- Atomic claim by workers. Workers that broadcast or submit must claim an operation through a conditional state transition (for example, approved to claimed, with an owner and lease), so two workers cannot process the same item. PostgreSQL documents
SKIP LOCKEDas suitable for queue-like tables, with the caveat that it provides an inconsistent view for general use, per its SELECT reference. - Distinguish legitimate later operations. Do not deduplicate by content alone. A customer who intentionally sends the same amount to the same address tomorrow is making a new operation with a new identity, generated from a distinct user intent such as a confirmed action. Content-based dedupe will wrongly suppress it.
- Atomic recording before acknowledgment. The operation record, hold, journal entries, and outbox work item commit together. If they cannot, the design must be explicitly recoverable, for example by a sweeper that finds operations without outbox rows. Either way, success is returned only after the durable commit.
For the client-facing side of this contract, including idempotent order entry and webhook handling, see our related designs on order entry timeouts and idempotent withdrawals and webhooks.
The Custody Boundary: Acknowledgment, Terminal Failure, and Unknown Outcomes
Section takeaway: Everything that crosses to a custody provider, chain, bank, or screening vendor needs three explicit outcomes: confirmed, terminally failed, and unknown. Only authoritative evidence moves funds out of a hold.
The ledger can be perfect internally and still lose money at the boundary. The boundary is where networks time out, providers return partial responses, and chains reorganize. It deserves its own state machine.
| Operation state | Meaning | Ledger effect | Who or what may exit this state |
|---|---|---|---|
| Held | Funds reserved, work recorded durably | Available moves to held | Approval workflow |
| Claimed | One worker owns the submission | None | That worker, or lease expiry into reconciliation |
| Submitted, acknowledged | Provider returned a verified acknowledgment tied to our reference | None | Provider status or chain evidence |
| Confirmed | Authoritative evidence of settlement | Held is consumed | Terminal |
| Terminal failure | Authoritative evidence that no transfer will occur | Held is released | Terminal |
| Unknown | Completion could not be confirmed | None; funds stay held | Reconciliation with named owner |
Rules for each state
- Verified acknowledgment. An HTTP 200 is not enough if the provider response does not echo your reference or a durable provider ID. Store what the provider returned and validate it matches the operation you sent.
- Terminal failure requires evidence. A timeout, a connection reset, or a 5xx is not terminal failure. A provider response that explicitly states the operation was rejected, or a chain-level result showing the transaction can no longer confirm, may be. Define precisely which signals your team accepts.
- Unknown means unknown. It is not success and it is not verified failure. Keep the hold, stop automated retries, and open a reconciliation task.
- Reconcile before retrying. For any consequential mutation such as a payout, an ambiguous timeout must never trigger a blind resend. First query the authoritative operation state: the provider's record by your reference, then the chain by transaction ID if one is known. Confirm in the provider's documentation whether it supports idempotent client references. Do not assume it does.
Inbound events need the same discipline
For deposits and provider callbacks, record each event in an inbox keyed by tenant, source, and event ID with a unique constraint. Persist the event and the scheduled crediting work in the same transaction, and return success to the sender only after that commit. If a design cannot make this atomic, make it recoverable: acknowledge nothing until the event is stored, and run a poller that rebuilds missed events from the provider's list endpoint or from chain data. Credits should also respect confirmation depth and reorg handling, as covered in our deposit crediting guide.
Identifiers are data, and someone must own them
Wallet addresses, transaction hashes, user IDs, and withdrawal memos can each be linked to a person, especially when combined with exchange records. Treat them as potentially sensitive. Practical design choices include:
- Keep personal data out of the immutable journal. Reference customers by opaque internal IDs, and keep identity data in a separate store that supports your retention and erasure obligations.
- Limit what goes into logs, outbox payloads, and vendor calls to what each recipient needs.
- Name a privacy owner for the ledger, and record who may access mapping tables between opaque IDs and real identities.
What retention or erasure duties apply to you depends on your jurisdictions and licenses. Raise it with your legal and compliance advisers early, because an immutable journal and a deletion duty can collide if personal data is written into the wrong place.
A note on standards scope: ISO/IEC 27001 specifies an information security management system. A SOC 2 report covers the systems and criteria defined in its scope. Neither, by itself, proves your ledger prevents overspending. Use them as part of a control environment, and test ledger invariants directly. Custody design choices are also covered in our hot, warm, and cold wallet blueprint.
Why This Fails in the Real World
Section takeaway: Capable teams still ship ledger defects because incentives, test design, and ownership gaps point away from the unhappy path. The scenarios below are illustrative composites, not accounts of specific clients.
Failure pattern 1: the balance check that passed every test
Illustrative scenario. An exchange team builds a balance check into the order service. It is reviewed, unit-tested, and load-tested. In production, a market-making client sends bursts of orders across several API nodes, and the platform accepts orders that together exceed the account's available funds. Nobody wrote a bug on purpose.
Why intelligent teams still miss it: load tests typically simulate many users each acting once, not one account acting many times concurrently. The check lived in application code, so the database could not refuse the second write. Ownership was split: the order team assumed the ledger team enforced non-negative balances, and the ledger team assumed the order team checked first.
Failure pattern 2: the support-tool retry
Illustrative scenario. A withdrawal call to a custody provider times out. The customer opens a ticket. A support agent uses an internal tool to re-run the withdrawal. The first request had in fact been processed, so the payout happens twice.
Why intelligent teams still miss it: the automated path had an idempotency key, but the manual tool created a fresh operation for the same intent. The system modeled success and failure but not unknown, so a human saw a failed status and acted reasonably. This is a governance gap: the manual path bypassed the control the automated path had.
Failure pattern 3: the reconciliation that fixes itself
Illustrative scenario. A nightly job compares ledger totals with wallet balances and, when it finds a difference, posts an adjusting entry to bring them into line. Reports are clean for months. Meanwhile a systematic defect keeps creating small differences that the job quietly absorbs.
Why intelligent teams still miss it: the job was built to reduce alert noise. Reconciliation that corrects without classifying removes the one signal that tells you a defect exists. Breaks should be classified (timing difference, unknown operation, genuine variance), assigned an owner, and closed with evidence.
Failure pattern 4: the acknowledged-then-lost event
Illustrative scenario. A webhook handler returns 200 immediately and queues the work in memory. A deployment restarts the process. The provider believes the event was delivered. The deposit is never credited, and no retry arrives.
Why intelligent teams still miss it: fast acknowledgment looks like good hygiene, because providers penalize slow responses. The missing piece is atomic durable recording before acknowledgment, plus a recovery poller as a safety net.
Trade-offs and constraints to disclose
- Hot accounts. Fee accounts and market-maker accounts are updated constantly. Conditional mutation on one row can become a contention point. Mitigations include sharded fee sub-accounts and batched postings, each with reconciliation implications.
- Serialization cost. Serializable isolation requires retry logic and has monitoring overhead, per the PostgreSQL documentation cited above. Row-level conditional updates are narrower but protect only the invariants you encode.
- In-memory matching engines. If the engine holds authoritative balances in memory for speed, you need a durable, ordered journal and tested replay, or the engine becomes an unrecorded second ledger.
- Append-only versus erasure duties. Immutability helps integrity but conflicts with deletion obligations if personal data enters the journal. Separate the two stores.
- Latency. Atomic commits before acknowledgment add milliseconds. That is the price of verified acknowledgment, and it should be a deliberate decision, not an accident.
Go/No-Go Controls, Sourcing Options, and a Smarter Path
Section takeaway: Treat the controls below as all-required checks. One failure blocks launch. Passing all of them does not certify production readiness. It means these specific risks have evidence behind them.
Mandatory go/no-go controls for a design review
Each control is binary. Ask for evidence, not assurances.
- Non-negative invariants enforced by the data store. Evidence: a concurrency test in which many parallel orders and withdrawals target one account, with no overspend, plus the conditional-mutation statement or constraint.
- Tenant-scoped operation identity. Evidence: a uniqueness constraint including tenant, and a test proving one tenant's key cannot read or replay another's.
- Payload-hash binding. Evidence: a test where the same key with a changed destination or amount is rejected.
- Atomic claim by workers. Evidence: a test with concurrent workers showing exactly one submission per operation.
- Durable recording before acknowledgment. Evidence: a fault-injection test that kills the process between receipt and response, followed by recovery with no loss and no duplicate.
- Unknown state exists and holds funds. Evidence: a simulated provider timeout leaves the hold in place and blocks automated resubmission.
- Reconcile-before-retry. Evidence: a runbook and test showing the authoritative operation state is queried first.
- Terminal failure criteria written down. Evidence: a list of accepted signals approved by the owner of the custody relationship.
- Balanced, append-only journal. Evidence: a per-entry balance check and database permissions that exclude updates and deletes.
- Three-way reconciliation with named owners. Evidence: a break log showing classification and closure, with no automatic balance adjustments.
- Privacy ownership and data placement. Evidence: a data map showing where identifiers live, who owns them, and that personal data is kept out of the immutable journal.
- Manual paths use the same controls. Evidence: support and admin tools create operations through the same identity, approval, and hold logic.
Build, white-label, or SaaS: what changes for the ledger
| Dimension | Custom build | White-label platform | Hosted SaaS exchange |
|---|---|---|---|
| Who owns ledger invariants | Your team, fully | Shared: vendor code, your configuration and operations | Vendor largely, your oversight |
| Evidence you can obtain | Everything, at your own effort | Depends on source access and contract | Depends on vendor disclosure |
| Tenant isolation risk | Only if you build multi-tenant | Check how tenants are separated | Core question to ask |
| Customization of postings and holds | High | Medium | Lower |
| Main hidden failure mode | Underbuilt reconciliation and recovery | Unreviewed vendor assumptions about timeouts | Limited visibility into failure handling |
| Time to first review | Longest, you create the artifacts | Faster, if the vendor shares artifacts | Fastest, if evidence is available |
The decision rule is simple: the sourcing model changes who produces the evidence, not whether the controls are required. If you are weighing options, ask any vendor, including Errna, to demonstrate the controls above against the actual product, rather than describing them in a deck. You can read about Errna's offerings on the crypto exchange development and white-label crypto exchange pages, and about wallet and signing integrations on the crypto custody integration page. Product capabilities should be confirmed in writing for your specific scope. An independent blockchain security audit can also test several of these controls from outside your team.
2027 update: what to re-verify each year
The principles in this article do not depend on a calendar year, but the surrounding facts do. When you review your design for the coming year, re-verify these items against primary sources rather than memory:
- Your database and driver versions: isolation behavior, locking semantics, and retry guidance in current documentation.
- Your custody, banking, and screening providers' API contracts: idempotency support, status endpoints, and webhook retry behavior.
- Applicable licensing, reporting, and data-protection requirements in each jurisdiction you serve, through your legal advisers.
- Your own fault-injection and concurrency tests, rerun after any major release.
Is your ledger design review evidence-based or assumption-based?
If you cannot show a concurrency test, a kill-the-process test, and a reconciliation break log, you have opinions, not controls.
Talk to Errna's engineers about reviewing your exchange ledger and custody boundary.
Contact UsConclusion: Five Decisions to Make Before You Write the Next Journal Entry
A trustworthy exchange ledger is less about clever technology and more about refusing to guess. These actions turn the model above into decisions your team can make this quarter.
- Name the authority for each fact. Write down which system is authoritative for entitlements, key control, settlement, and fiat status, and who resolves disagreements.
- Move invariants into the data store. Replace every read-compare-write balance check with a conditional mutation or a documented serialization strategy, and disclose its throughput limits.
- Add the unknown state. Ensure every call to custody, chain, or banking partners can end as confirmed, terminally failed, or unknown, and that unknown keeps funds held and halts blind retries.
- Bind identity to payload. Require tenant-scoped operation keys tied to an approved payload hash, including for manual and admin paths.
- Assign privacy and reconciliation owners. Give a named person responsibility for identifier handling and for closing reconciliation breaks with evidence.
About the author team
Errna is a blockchain and digital-asset technology company that has operated since 2003, with in-house engineering teams serving clients across many countries. Errna lists ISO 27001, CMMI Level 5, and SOC 2 among the process-maturity credentials covering its own delivery practices. Those credentials describe Errna's organization and scope; they do not certify any exchange or ledger that a client builds, and they are not a substitute for independent testing of your system. This article is a general engineering discussion, not legal, regulatory, or investment advice.
Reviewed by Errna Expert Team.
Frequently Asked Questions
What is a crypto exchange ledger?
A crypto exchange ledger is the internal system of record that tracks what each customer is entitled to, what is held against open orders or pending withdrawals, and the journal of every change. It is separate from the blockchain, the custody system, and banking partners, which are other sources of truth for different facts.
Why use double-entry accounting in an exchange ledger?
Double-entry makes every change a balanced set of postings, so silent corruption becomes detectable: an entry that does not sum to zero is invalid. It is an engineering control, not by itself a regulatory requirement. Ask your legal and compliance advisers what records your licenses and auditors expect.
Is checking a balance in application code before writing enough?
No. Reading a balance, comparing it, then writing a new value allows two concurrent requests to pass the same check. You need a target-side conditional mutation, such as an update that applies only when available funds are sufficient, or a documented serialization approach with retry handling. Confirm the exact behavior in your database's primary documentation.
What should happen when a withdrawal request times out?
- Keep the funds held.
- Mark the operation as unknown, meaning completion could not be confirmed.
- Stop automated resubmission.
- Query the authoritative state first: the provider's record by your reference, then the chain if a transaction ID exists.
- Release the hold only on evidence of terminal failure, or consume it on evidence of settlement.
How should idempotency keys work in a multi-tenant exchange?
Scope uniqueness by tenant and operation ID, bind each key to a hash of the approved payload, and reject reuse with a different payload. Claim work atomically so concurrent workers cannot process the same operation, and give a legitimately repeated customer action a new operation identity so it is not mistaken for a duplicate.
Are wallet addresses and transaction hashes personal data?
They can be, depending on context, because they may be linked to individuals through exchange records or other data. Treat them as potentially sensitive, keep personal data out of immutable journals, and have your legal and privacy advisers confirm obligations in each jurisdiction you serve.
Does ISO 27001 or SOC 2 prove my ledger is correct?
No. ISO/IEC 27001 concerns an information security management system, and a SOC 2 report covers the scope and criteria it defines. They can support a control environment, but ledger correctness still needs direct tests of concurrency, recovery, and reconciliation.
Should I build, white-label, or use SaaS for the ledger?
The sourcing model changes who produces the evidence, not whether the controls are required. Whichever option you choose, ask for demonstrations of concurrency tests, fault-injection recovery, tenant isolation, and reconciliation logs for your specific scope.
Launching or hardening an exchange? Start with the ledger.
Overspent balances, duplicate payouts, and unreconciled custody are design problems. They are cheaper to fix on a whiteboard than in production.