Balance snapshot
Read matching-engine UserRegistry total + locked + available per currency, hash, write to user_balance_snapshots.
M1 amendmentVerbatim from Richard-HFT, 2026-04-30 07:32 GMT+9
Creation, suspension, reactivation, balance adjustments, and cross-system reconciliation.
Today we have a 3-value enum with no transition guards, audit trail, or side-effect hooks.
Across all received documentation: tokenomics, staking, system spec PDF, delivery plan.
The platform's substrate for EOD already exists — balance-snapshot tables, ledger schema, status enums, NATS event bus. But there is no scheduled service running the close, no formal FSM enforcing valid transitions, and no written specification we can implement against. This proposal scopes all three.
Substrate audit — verified live on production EC2, 2026-04-30
| Component | Type | Status | Location |
|---|---|---|---|
| ledger_entries table | Postgres schema | Schema present | quantatrade.public |
| user_balance_snapshots table | Postgres + Merkle leaves | Schema present | FK → proof_of_reserves |
| accounts table | Per-user × per-currency | Seeded | 3 rows live |
| UserRegistry (matching engine) | In-memory balance | Live | exchange-core2 |
| users.status enum | active / suspended / banned | 3 values, no FSM | UserStatus enum |
| NATS event bus | order.* / trade.executed | Live | ws-gateway forwards 6 types |
| Scheduled job runner | cron / @Cron / BullMQ | None | Zero across 10 services |
| Account state transitions audit table | Postgres | Not present | Required for compliance |
| Cross-system reconciliation job | ME ↔ DB ↔ custody | Not present | Mentioned in code comment only |
Standard centralised-exchange EOD coverage, mapped to milestones
Read matching-engine UserRegistry total + locked + available per currency, hash, write to user_balance_snapshots.
M1 amendmentRealised P&L from the day's trades; unrealised from open positions × mark price.
M1 amendmentSum maker/taker fees per user, write a journal entry to ledger_entries with audit trail.
M1 amendmentUpdate 30-day volume tracker that drives fee tiers (already partial in fee/VolumeTracker.java).
M1 amendmentPro-rata $QTRA payout per active staker; revenue-router gated.
M5For every (user, currency): assert UserRegistry.total == Σ(ledger_entries.amount). Mismatch pages + freezes.
M1 amendmentFor every asset: Σ(users.balance) ≈ BitGo wallet balance ± hot-wallet float tolerance.
M5staking_positions.amount totals reconcile to StakingPool.totalStaked() on-chain.
M5Compliance hook fires FSM transition to suspended + cancels open orders.
M4Clears suspended status, restores trading + withdrawal privileges with audit entry.
M4Manual credit/debit lands as ledger_entries with reference_type='admin_adjustment' + reason.
M4Idempotent, leader-elected, observable
platform/services/eod-service/ — Node.js, same template as ledger-service.
Postgres advisory lock or Redis SET-NX so multi-instance deploys don't double-run the close.
Each step keyed on (YYYY-MM-DD, step_name) in eod_runs — re-running a failed step doesn't double-account.
eod.snapshot.completed, eod.recon.failed, etc. Admin panel subscribes.
gRPC AdminService.RunEOD(date) for back-fills and disaster recovery.
30 min per step. Anything longer pages on-call automatically.
Today: 3-value enum, no guards, no audit. Proposed: typed transitions, immutable history, side-effect hooks.
| State | Meaning | Trade | Withdraw |
|---|---|---|---|
| pending_kyc | account created, KYC submitted, pending review | ❌ | ❌ |
| kyc_rejected | KYC failed, can resubmit | ❌ | ❌ |
| kyc_approved | KYC passed, awaiting first deposit | ❌ | ❌ |
| active | normal operation | ✅ | ✅ |
| suspended | compliance hold (re-KYC, sanctions match, dispute) | ❌ | ❌ |
| restricted | withdrawal-only (off-boarding, abandoned account) | ❌ | ✅ |
| closed | user-initiated termination, balances zeroed | ❌ | ❌ |
| banned | terminal — fraud, sanctions hit, court order | ❌ | ❌ |
Backed by an immutable account_state_transitions audit table; each transition publishes a NATS event (account.suspended, account.restored, …) so downstream services react automatically — cancel open orders, freeze custody, notify the user.
Three-way split aligned with existing contract milestones
The M1 amendment is the natural home for the four EOD steps that close existing yellow gates on Richard's M1 acceptance checklist (the ledger / reconciliation gates). The FSM ships in M1 too because every downstream milestone — KYC in M4, staking in M5, margin in M8 — depends on it.
External-boundary reconciliation (custody, on-chain) lands in M5 because both endpoints only exist by then: BitGo is wired in M2 and the staking contract deploys in M5.
Each blocks a portion of the work above
| # | Question | Blocks |
|---|---|---|
| Q1 | Is there a longer system spec or operations manual we haven't been sent? The 8-page system spec PDF appears truncated — no buyback section, no technical-requirements section, no EOD section. | All EOD scope |
| Q2 | What is the EOD cutover time? UTC 00:00 is the industry default but some venues use 16:00 NY (CME-style) or 08:00 UTC (Asia-handover). Affects fee-window boundaries. | EOD step 04 |
| Q3 | Tolerance for DB ↔ custody recon mismatch — what counts as "expected drift" (gas fees, network confirmations) vs. "page on-call"? | EOD step 07 |
| Q4 | For sanctions hits via the screening provider — is auto-suspend the desired action, or auto-restrict (withdrawal-only) so legitimate users aren't trapped? | EOD step 09 |
| Q5 | Should account closure (user-initiated) require a 30-day cooling-off period before terminal? Common compliance pattern; user can reverse during the window. | FSM transition |