Skip to Content
EngineeringReference tables

Reference tables

Cross-cutting tables that exist in no single spec. Come back to this page; do not read it through.

Chart of accounts

Account names are built only in ledger/accounts.ts. A string literal naming an account in any other file is a future typo that no type will catch.

user:{u}:sub:{subaccountId}:{currency} balance held at the exchange user:{u}:transit THE single basis for free balance user:{u}:external mirror of the system boundary user:{u}:income:{premium|interest|funding} user:{u}:expense:{derive_fee|settlement|swap|borrow} platform:gas:{chain} platform:hot:{chain} platform:expense:gas:{chain} gas attributed to a user but not charged to them platform:expense:overhead platform:fee_revenue platform:expense:reconciliation unexplained journal-versus-exchange difference

Sign, not debit/credit columns. Who bears a cost is read from the prefix: platform: means the platform, anything else means the user. The subaccount account is keyed by subaccountId, not by asset (migration 1787900000002).

Event types

deposit_detected, deposit_swept, bridged, subaccount_funded, subaccount_released, option_sold, option_settled, swap_executed, swap_reverted, interest_accrued, funding_paid, withdrawal_sent, gas_spent, trade_rejected, signal_received, reconciliation_adjustment, stop_raised, stop_cleared.

Not all of them have payload schemas, deliberately: the journal must accept an event whose posting rule has not been written yet. There are no postings for deposit_swept, swap_reverted, trade_rejected, or for the OTM branch of option_settled.

Idempotency keys

SourceShape
An exchange tradefill.tradeId — one event per fill (P5)
On-chaintx:{chain}:{txHash}:{logIndex}
Accrualsaccrual:{sub}:{type}:{bucketTs}
Option settlement{sub}:{instrument}:{expirySec}
Funding a subaccountsubaccount_funded:{transactionId}
A releaserelease:{rowId}
Stopping a subaccountstop:{sub}:{minute bucket}
A reconciliation adjustmentreconcile:{sub}:{account}:{bucket}:{delta}the delta is mandatory in the key
A stop raised by reconciliationreconcile:{sub}:stop:{bucket}:{run signature} — a different shape; do not confuse it with the previous row

The run signature is assembled from the accounts and deltas of every divergence found. Two runs within the same minute with different contents must each leave their own stop_raised rather than collapsing into one.

For a transaction we sent ourselves, logIndex = 0. A second event about that same transaction takes slot 1. Reuse the slot and append deduplicates the second event silently — the money is simply never recorded.

Key constants, and where they live

Product thresholds live as constants in modules, not in the environment. They do not differ between deployments.

ConstantValueAddress
EPSILON_LADDER_BPS[3, 6, 12, 25, 50, 100]swap/slippage-ladder.ts:4
SANITY_THRESHOLD_BPS100swap/price-sanity.guard.ts:12
LADDER_DEADLINE_MS30_000swap/swap.module.ts:81
SWAP_RETRY_STALE_AFTER_MS30 minswap/swap.module.ts:105
GAS_FLOOR_WEI (swap)1.5e14swap/swap.module.ts:67
GAS_FLOOR_WEI (sweep)1e12treasury/treasury.module.ts:56
CIRCLE_GAS_FLOOR_WEI2e15 — for a whole circleconversions/conversions.module.ts:66
MARGIN_BUFFER_USD'100'conversions/conversions.module.ts:76
ACTIVE (withdrawal reserve)"('requested')"treasury/withdrawals.repository.ts:133
MAKER_WINDOW_MS20 minpositions/steps/placing.step.ts:19
LOCK_TTL_MS2 minpositions/cycles.repository.ts:5
RETRY_MS (wheel)60 swheel/wheel.runner.ts:25
Wheel deadline20:00 UTCwheel/wheel.runner.ts:52-60
NONCE_TTL_MS5 minauth/auth.module.ts:16
TOKEN_TTL'7d'auth/auth.module.ts:17

Environment variables

NODE_ENV PORT TRADING_PROFILE DATABASE_URL REDIS_URL DERIVE_HTTP_URL DERIVE_WS_URL DERIVE_CHAIN_RPC_URL MASTER_PRIVATE_KEY SESSION_PRIVATE_KEY GAS_WALLET_PRIVATE_KEY DERIVE_WALLET_ADDRESS PLATFORM_FEE_RATE RECONCILE_EPSILON WEB_ORIGIN MASTER_MNEMONIC JWT_SECRET SIWE_DOMAIN DERIVE_CHAIN_ID WEBHOOK_SECRET WITHDRAWAL_MIN_USD WITHDRAWAL_MAX_USD WITHDRAWAL_STUCK_MINUTES RPC_URL_ETHEREUM RPC_URL_OPTIMISM RPC_URL_BASE RPC_URL_ARBITRUM SWAP_WALLET_PRIVATE_KEY PRE_CONVERT_LEAD_MINUTES CONVERSION_FAST_TICK_SECONDS CONVERSION_STUCK_MINUTES WHEEL_ENABLED RECONCILE_MODE TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID

The schema has no defaults — every variable is required. The exceptions that do have one: PRE_CONVERT_LEAD_MINUTES (10), CONVERSION_FAST_TICK_SECONDS (5), CONVERSION_STUCK_MINUTES (30), WHEEL_ENABLED (false), RECONCILE_MODE (observe).

Exactly two are optional outrightTELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID. Without them alerting is not disabled; it falls back to logger.warn.

HTTP surface

GET /assets GET /:asset/chart GET /:asset/fees GET /:asset/strategies WS /ws/strategies WS /ws/prices GET /auth/nonce POST /auth/verify GET /deposit-address GET /withdrawals/routes POST /withdrawals GET /withdrawals POST /positions GET /positions DELETE /positions/:id POST /subaccounts/:asset/release GET /portfolio/summary GET /portfolio/statement POST /webhooks/signals GET /signals/state GET /trading-config GET /health

POST /positions, POST /subaccounts/:asset/release and POST /withdrawals all answer 202 — the work is done by a background tick.

Authentication is explicit per controller through @Authenticated(); there is no global guard. Everything market-data — the showcase, both WebSockets, charts, fees, the asset list — is public by design.