DEV Community

Cover image for Architecture Decisions When You’re Asked to Build a Crypto Exchange
Budventure Technologies
Budventure Technologies

Posted on • Originally published at budventure.technology

Architecture Decisions When You’re Asked to Build a Crypto Exchange

You know the brief:

“Client wants to build a crypto exchange. How hard can it be?”

On a slide, it looks like just another product: sign-up, balances, charts, an order form. But under the UI, you’re gluing together custody, a trading engine, compliance logic, and 24/7 operations. If you treat it like a normal CRUD app, you’re going to have a bad time.

This post is a practical walkthrough of the main architecture decisions you need to force to the surface early, before you’re buried in features and tickets.

1. Decide what you’re actually matching

Before database schemas, microservices, or picking a message bus, you need to answer:

  • Are we building a CEX (internal order book and matching)?
  • A P2P marketplace (offers + escrow, no central book)?
  • An OTC workflow (RFQs, quotes, manual approvals)?
  • Some hybrid of these?

Those aren’t cosmetic differences; they’re different systems.

CEX:
You’ll need:

  • per-market order books
  • a matching engine (per market or shared)
  • trade settlement that moves balances between internal accounts

P2P:
You care more about:

  • offers and reservations
  • escrow states
  • dispute flows and timeouts

If you pretend you can “stay generic” and decide later, you’ll end up rewriting core logic.

A blunt but useful exercise with stakeholders:
"In v1, we are a [CEX/P2P/OTC] for [retail/pro/institutional] users
on [N] spot pairs. Everything else is a v2 discussion."

Get that written down and agreed before anyone bike sheds about frameworks.

2. Treat the balance ledger as its own subsystem

The fastest way to burn trust is a flaky balance system.

Do not sprinkle balance += amount across your app. Design a small, boring, auditable ledger as a separate subsystem.

Simple model:

  • accounts – one per user per asset
  • entries – immutable rows representing credits/debits
  • balances – materialised view or computed via SUM(entries.amount) per account

Pseudo-schema:

CREATE TABLE accounts (
  id BIGINT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  asset VARCHAR(16) NOT NULL,
  UNIQUE (user_id, asset)
);

CREATE TABLE ledger_entries (
  id BIGSERIAL PRIMARY KEY,
  account_id BIGINT NOT NULL,
  amount NUMERIC(36, 18) NOT NULL, -- positive or negative
  type VARCHAR(32) NOT NULL,       -- deposit, trade_fill, fee, withdrawal, adjustment...
  ref_type VARCHAR(32) NOT NULL,   -- order, tx, ticket...
  ref_id BIGINT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
Enter fullscreen mode Exit fullscreen mode

Then you define operations instead of ad-hoc updates:

  • credit(account, amount, type, ref)
  • debit(account, amount, type, ref) (with checks)
  • transfer(from, to, amount, type, ref) Those are the only ways balances move.

Trading, deposits, withdrawals, and fees all become flows that emit ledger entries, not direct column updates. That’s what lets you reconcile, debug, and sleep.

3. Keep the matching engine pure

If you run an internal order book, treat the matching engine like a pure service:

  • it doesn’t know about KYC, AML, UI, or business rules
  • it receives validated, authorised orders from upstream
  • it emits trades/fills downstream, which the ledger consumes

At a high level:
[ API / UI ]
|
v
[ Order Service ] --permissions, risk checks-->
|
v
[ Matching Engine ] --fills-->
|
v
[ Settlement / Ledger ]

You can run the engine:

  • as an in-process component (simpler, faster to start)
  • as a separate service / cluster (more isolation, easier to scale independently)

What you don’t want is business logic like “block this user” or “enforce KYC tier” baked into the engine. That belongs in the order service and risk layer upstream.

4. Wallets: model them as a service boundary, not a helper class
Wallets are where backend devs underestimate complexity the most.

Think of your wallet system as another separate service:

  • accepts requests to generate deposit addresses
  • watches networks and produces deposit events
  • accepts withdrawal requests after upstream checks
  • emits status changes (requested → signed → broadcast → confirmed/failed)

You can sketch it like:
[ Ledger ] <--> [ Wallet Service ] <--> [ Blockchain Nodes/Providers ]

Key patterns:

  • Separate hot and cold strategies.
  • Enforce limits and approvals upstream (by user, asset, tier).
  • Don’t let a single webhook both change balances and mark a transaction final without sanity checks.

If you use a custody provider (Fireblocks, Copper, etc.), treat them as an external wallet service and keep the same mental model. The more “dumb” and event-driven your integration layer, the easier your life when providers or chains misbehave.

5. Build observability before launch week
You can’t run an exchange blind. Logs + “we’ll check the DB” is not observability.

At minimum, you want:

Metrics for:

  • orders per second
  • rejected orders (by reason)
  • deposit queue depth and age
  • withdrawal queue depth and age
  • wallet job failures

Structured logs with correlation IDs:

  • so a single user complaint can be traced across API → order service → engine → ledger → wallet

Dashboards + alerts:

  • deposit lag per asset
  • error rate per critical endpoint
  • unusual spikes in failed logins or withdrawals

If your support team’s default answer to everything is “we’ll get back to you”, you’ll drown when volume grows.

From a code point of view: instrument as you go. Wrap critical paths with logging and metrics now; you won’t magically have time later.

6. Design for compliance and ops, not just “requirements”
You don’t have to be the compliance officer, but your architecture has to make their life possible.

That typically means:

  • account states / flags that actually affect behaviour (not just labels)
  • hooks for KYC/KYB providers that can be swapped without rewriting everything
  • case management: a way to mark deposits, withdrawals, or users for review
  • audit trails: who changed what, when

You don’t want support or compliance teams writing SQL in production to unblock users. Build the minimal internal tools early, even if the UI is ugly.

7. Be honest about what you can realistically own
Finally, the uncomfortable part: you don’t have infinite time, money, or talent.

Ask hard questions:

  • Do we really want to own a matching engine implementation? Or can we use a proven one and focus on UX + operations?
  • Do we want to own custody, with all the key-management and audits that implies? Or integrate with a provider and accept that trade-off?
  • Are we okay building something that will need a real on-call rotation and incident process?

There’s no prize for “most lines of code we maintain ourselves”. There is a prize for building something that traders actually trust.

If you treat this like building a serious financial system from day one, your technical decisions will look very different from if you treat it like yet another CRUD SaaS.

If you’ve been handed an “exchange” brief and want a sanity check on your architecture, I’d love to see what you’re planning. Drop a comment or DM and I’ll tell you where I expect it to break before your users find out the hard way.

Top comments (0)