Traps
Places where the obvious search gives the wrong answer. This page exists because it was measured: an agent reading the codebase without it answers most questions correctly and stumbles on these predictably.
In the code
| The trap | What is actually true |
|---|---|
“Debt appears on ITM” — grepping for долг/debt lands in the call branch | Debt is call-only. A put’s collateral is K·q USDC, the (K−S)·q debit comes out of that same collateral and cannot exceed it. conversion-how-it-works.md:51 |
Looking for the sixth lock by the word scheduler | BASE_SEND_LOCK_KEY lives in treasury/bridge-in.sender.ts:28, not in any *.scheduler.ts |
| “Deposit: the user sends to an HD address, we sweep it and bridge it” | Backwards. The user themselves calls depositToAppChain on the source-chain vault; the bridge mints the wrapper already on Derive Chain; the sweep happens inside Derive Chain. There is no EIP-3009 in this project at all |
“getCollaterals and getMargin are different calls” | The same private/get_subaccount, two projections of one response |
Reading maintenanceMargin as a requirement | It is a buffer in USD. Liquidation happens when it goes negative. Live measurement: initial $14.81 below maintenance $15.80 — read as a “requirement” that is inverted |
| “The fee can be computed with a formula” | Forbidden (P6). The fee and the settlement debit are taken from the exchange response. Your own arithmetic will disagree on rounding and alert on every ITM position |
Money.gt | There is no such method. “a > b” is written b.lt(a) |
dataSource.query() over a bare UPDATE returns rows | It returns the tuple [rows, affectedCount]. See Architecture |
| A posting’s currency is the token ticker | The journal’s vocabulary is asset-level: ETH, not WETH. 'WETH' does not net against a recorded +ETH, and it zeroes a user’s entire totalUsd through UnsupportedAssetError |
Looking for /subaccounts/:asset/release under SubaccountsModule | SubaccountsController and ReleaseService sit in the subaccounts/ folder but are registered by PositionsModule (positions/positions.module.ts:59) — releasing funds asks both subaccounts and position cycles, and leaving them in SubaccountsModule would have created a module cycle |
Looking for usdRateFor in the prices module | It is in strategies/index-price.source.ts. The prices module is a separate, unrelated spot-price WebSocket relay |
| “There is a WebSocket client to Derive, so the trading path must use it” | Two different things share the name. The gateway has none — orders and fills are polled (P3: the maker window is 20 minutes, a minute of lag costs nothing). But prices/derive-spot-stream.ts is a raw ws client to wss://api.lyra.finance/ws, subscribed to spot_feed. It feeds the browser’s price ticker and no money path reads it |
import type for anything injected | Erased at runtime, so design:paramtypes loses it and Nest DI fails. Bitten in auth/auth.guard.ts and again in the reconciliation module |
Declared but not wired
Check before citing any of these as working.
| What | State |
|---|---|
| Platform fee | PlatformFee is provided and exported but injected by nobody; applyTo() is never called outside its own spec. platform:fee_revenue takes part in no posting rule. The rate is 0. Its only real use is display: GET /trading-config returns platformFeeRate for the frontend |
| Reconciliation | Wired, but defaults to observe: it compares and logs divergences, writing nothing to the journal and stopping nobody. RECONCILE_MODE=enforce turns on adjustments and stops. See Reconciliation |
funding_paid, bridged, signal_received, stop_cleared | Event types declared, nobody writes them, no posting rules |
| Clearing a stop | Implemented nowhere. Not one line clears cycles.stop_reason, and no code path returns subaccounts.status to 'active'. By design it is a manual admin action, and the admin panel does not exist |
cycles.pending_withdrawal | Column created by a migration, read nowhere |
| Alerts | The Telegram channel exists but only activates when both TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are set; without them it falls back to logger.warn |
QuoteSource.estimate | Dead in production — its only caller is an integration test |
| Nonce cleanup | auth_nonces_expires_idx was created for a sweeper that does not exist. The table grows without bound |
| Refresh tokens | Not present in any form — no endpoint, no field in the JWT payload. A 7-day expiry means signing in through SIWE again |
getBestRfqQuote drops invalid_reason | Acknowledged debt: the contract was not widened because the consumer (the wheel engine) did not exist when the comment was written |
Stale specifications
The specs under docs/superpowers/specs/ are dated and were accurate when written. These had since
drifted, and the drift is the kind that reads as current fact.
Each affected spec now carries a dated «⚠️ Что изменилось» block at the top listing exactly these rows, so the correction is visible at the source rather than only here. The dated measurements inside those specs were kept — they are evidence about Derive’s calendar and remain true for their date.
| Spec | What it says | What the code does |
|---|---|---|
strategies-showcase-how-it-works.md §7, showcase-streaming-how-it-works.md §7 | The APR threshold only annotates — rejected candidates are returned with passesThreshold/rejectionReason | The threshold filters. RejectionReason is a local type that never leaves buildCandidates, and the contract has no such fields |
| Both showcase specs §8 | hasSignal is always false | It is really computed, through signals/signal-matcher.ts:28 |
| Both showcase specs | Five assets on the showcase | Two: ETH and BTC. XAUT, HYPE and XRP were removed — no ERC20 collateral on the Derive bridge means no covered call, and “half a circle is not a product” |
openapi-how-it-works.md:118 | “The client is not generated from the schema; there is no generation step in the frontend build” | It is generated, via swagger-typescript-api — pnpm --filter=@arkada/web build-api |
A contradiction inside the code itself
derive/gateway/derive-gateway.interface.ts marks createSubaccount as never having been executed
live (M4a), while the header comment of live-derive.gateway.ts states that subaccount 70021 was
created live on 2026-08-21. Both cannot be true. Nothing depends on the answer today, but do not
quote either one as settled.