vault

11 — Target Architecture

docs/11-Target-Architecture.mdtype: architecture-targetupdated: 2026-05-03

Target Architecture

This page describes a future state, not the shipped MVP. The current implementation is documented in 02-Architecture (Next.js App Router, Postgres ledger, mocked providers). This page describes the AWS-target architecture brief that will replace it during the refactor (target window: 10-Roadmap#Beta / 10-Roadmap#GA).

Why a target architecture exists

The MVP was optimised for getting end-to-end product flows shipped. The target architecture rebuilds the platform on AWS with explicit security boundaries, DR, regulated-grade access control, and a separated ledger/accounting layer (TRES).

Target principles

  1. Long-running containers, managed AWS around them. The application is a stateful Next.js standalone server plus a pg-boss worker — both long-lived Docker containers on EC2. Default to managed AWS for everything around the runtime (DNS, edge, database, secrets), not for the runtime itself.
  2. Private-by-default runtime. Application + data workloads stay in private subnets. Internet only via controlled entry/egress.
  3. Test mirrors Prod. Same topology for HA, networking, providers, CoSigner. See 12-Environments.
  4. Controlled provider dependency. Every provider has a defined purpose, integration boundary, failure role, and internal owner. See 05-External Providers.
  5. Transaction-centric platform model. One canonical Glossary#Transaction object connects product flow, provider event, status, and balance effect. See 02-Architecture for current shape.
  6. Internal ledger / accounting clarity. TRES is the target accounting layer; Fireblocks is custody/transactions. The two are documented and called separately.
  7. Auditability first. Every privileged or balance-affecting action lands in audit_log.
  8. Least privilege + role segregation. Admins are not equal; sensitive surfaces require explicit grants. See the privileged-actions list in 14-Operations and 07-Non-Functional.
  9. Documentation matches implementation. When the cutover happens, 02-Architecture is updated in lockstep with this page.

Stack (target)

Layer Component Purpose
DNS Route 53 Authoritative DNS, health checks, weighted routing
Edge / CDN CloudFront + AWS WAF + AWS Shield Static delivery, WAF rules, DDoS, rate limit
Public entry API Gateway Public HTTP front door into AWS
Routing internal ALB Controlled routing into private application runtime
API runtime EC2 (Auto Scaling Group, Multi-AZ) Next.js standalone container serving user / merchant / admin traffic
Workers EC2 (same ASG or a dedicated worker group) pg-boss worker container: background jobs, exports, retries, provider integrations
Async coordination pg-boss on Postgres Durable queues, retries, scheduling — no separate broker
Custody runtime EC2 + Nitro Enclave Fireblocks CoSigner parent (isolated from the app instances)
Primary data PostgreSQL (Multi-AZ in Test+Prod) Application + transaction data
Rate limiting / short-lived state Postgres (rate_limits) Rate-limit windows and login lockout — no separate cache tier
Static delivery S3 (behind CloudFront) Internal artifacts, exports, public assets
Custody / signing Fireblocks (provider) Wallet operations, sign + send
Ledger / accounting TRES (provider) Balance/accounting interpretation, reconciliation
KYC Sumsub Onboarding gates
AML / risk (sanctions) Elliptic Per-address risk score + sanctions screening
On/off ramp + card acquirer Mercuryo Buy/Sell iframe; card acquiring (PCI scope sits with Mercuryo)
Rate oracle CEX.IO API Spot rates for quotes and rate-freeze
Email Resend Transactional mail (API-key scoped)
Admin entry AWS IAM Identity Center (SSO) + WAF + private subdomain Identity-aware admin posture
Secrets / keys Secrets Manager + KMS Credentials, encryption-at-rest
Observability CloudWatch + CloudTrail + GuardDuty + Security Hub (Prod) Logs, metrics, threat detection

Traffic flow

                         user/merchant traffic            admin traffic
                                |                              |
                          Route 53 + CloudFront         Route 53 + CloudFront
                          AWS WAF / Shield              AWS WAF (admin ruleset)
                                |                              |
                          API Gateway              admin entry path (separate subdomain)
                                                   IAM Identity Center (SSO + MFA)
                                \____________  ____________/
                                             \/
                                  internal ALB (private)
                                             |
                          app containers on EC2 (ASG, Multi-AZ)
                                             |
                    ┌────────────────────────┴────────────────────────┐
                    |                                                 |
              Postgres (RDS)                          worker containers on EC2
       data + pg-boss queues + rate_limits                            |
                                                            CoSigner (EC2 + Nitro)
                                                                      |
                        ┌──────────────────┬──────────────────────────┴───────┐
                   Fireblocks            TRES        Elliptic / Sumsub / Mercuryo / CEX.IO
                                                          (controlled outbound, NAT)
  • All public entry is filtered by CloudFront + AWS WAF + AWS Shield.
  • Admin uses a separate subdomain + AWS IAM Identity Center (SSO with MFA).
  • Database, workers, and CoSigner stay private — no direct internet ingress.
  • Outbound to providers goes via NAT egress.

See 13-Network-Model for the security-group breakdown.

Why TRES (not just Postgres)

The MVP keeps the ledger inside Postgres (ledger_entries). That works for product velocity but blurs two responsibilities:

  • Operational execution (Fireblocks moves coins) — happens fast, on the request path.
  • Accounting interpretation (what does this mean for the books) — happens reconciliation-time, often retroactively.

TRES is introduced as a separate accounting/ledger layer so:

  • Reconciliation is not a Postgres SELECT on transactions, it's a TRES report.
  • Accounting changes (fee splits, internal transfers, corrections) don't pollute the operational transactions table.
  • Audit reviewers see one consistent accounting view across operations + admin.

The application keeps its own internal status enum and posts to TRES; TRES is not the source of truth for product behaviour, it is the source of truth for accounting interpretation.

Why CoSigner forces EC2 + Nitro Enclave

Fireblocks CoSigner is the signing-policy enforcement runtime. It must run on dedicated hosts with isolated execution; AWS Nitro Enclave is the supported topology. It stays on its own instances, separate from the app and worker containers, so signing material is never co-resident with application code.

CoSigner topology:

  • One CoSigner parent EC2 instance per AZ in Test + Prod.
  • Nitro Enclave isolates signing material from the parent OS.
  • Locked-down security group (SG-COSIGNER in 13-Network-Model).
  • Outbound only to Fireblocks; no public inbound.

How current → target maps

Current (02-Architecture) Target (this page) Notes
Next.js App Router on a single EC2 host Same containers across an ASG in Multi-AZ The runtime does not change — only its redundancy and placement.
Postgres ledger_entries Postgres + TRES (accounting layer on top) Keeps Postgres as system-of-record; TRES gets the accounting interpretation.
MockFireblocks Live Fireblocks + CoSigner on EC2 + Nitro Cutover gated by 09-Open Questions#Custody.
MockEmailmock_emails Resend 09-Open Questions#Email deliverability.
AWS ALB + regional WAF MVP CloudFront + AWS WAF + AWS Shield in front of API Gateway; IAM Identity Center for admin Adds DDoS + identity-aware admin entry while staying fully in AWS.
In-process rate limit map shipped — Postgres rate_limits Done ahead of this page. 08-Risks and Drawbacks#Security.
runJob fire-and-forget shipped — pg-boss workers on Postgres Done ahead of this page; survives restarts. The target only changes where the worker container runs. 09-Open Questions#Background queue.
Single deploy region Multi-AZ + DR to secondary region Test + Prod both.

Out of scope for this page

Drawbacks

  • The cutover is large. A staged migration (data layer → CoSigner → workers → API surface) is the only realistic path.
  • TRES is a new vendor dependency — see 09-Open Questions#TRES alignment.

Open Questions

Cross-references