Risks, and what is done about each
Each risk below is paired with its mitigation. Where a mitigation is partial, that is stated. Where there is none, that is stated too.
3.1 Market risk — your upside is capped
The risk. On the ETH side, appreciation above the strike does not belong to you. If ETH is $3,000, you sell a $3,200 call for $80, and ETH ends at $4,000, you receive $3,280 — not $4,000. In a strong bull market a covered-call wheel underperforms simply holding the asset. This is not a defect; it is the trade being made, and it is the price of the premium.
What is done. The strike is a risk dial, and it is exposed rather than hidden. The user selects the strike from a board showing every qualifying candidate with its delta and its annualised premium. (Candidates that fail the threshold are filtered out server-side; the rejection reason is kept for diagnostics and is not part of the response.) In automated mode, the level is set by an external signal and premium only ranks candidates within that level — deliberately, because ranking purely by annualised return always selects the strike nearest the money, i.e. roughly delta 0.4 and assignment about 40% of the time. Measured on a live board: annualised return fell monotonically with distance from the money, 81% → 16% → −11% on a single expiry. Premium cannot be the risk control, because premium is the compensation for risk.
What is not done. There is no hedge against the capped upside. It is inherent.
3.2 Liquidation risk
The risk. Derive liquidates an account when maintenance margin goes negative. Anyone can call
liquidate(). The auction opens at a 5% discount to mark-to-market and widens to 30% within 15
minutes. Portfolio margin stress-tests the whole account jointly, and no position type is exempt.
What is done. The primary mitigation is structural rather than reactive: because every position is fully collateralised and unlevered, measured margin utilisation sits at 10–25%, and maintenance margin is deeply positive. A worked stress case: 0.5 ETH of collateral under a PM2 haircut against a short call, shocked −18% — the ETH loses about $170, the call gains about $5, for a max loss near $165 against roughly $100 of contingencies. Strikes are opened out of the money, and the exchange’s own documentation flags delta decay as a concern for deep-ITM covered calls specifically.
Secondarily, a margin watchdog polls every minute during the final hour before expiry — the cadence is set by the 15-minute auction, which makes hourly polling useless.
What is not done, stated plainly. The watchdog only alerts. It does not unwind. Early buyback of an option was rejected on measured grounds — asks stand 25–90% above bids, so buying back the position typically costs far more than it saves. The kill switch is first-level: it stops the automated wheel from opening new positions and lets existing ones run to expiry — it does not gate a manual position request. There is no automatic unwind, and calling the watchdog a protection would be untrue — an untruth that would eventually cause someone to rely on it. Liquidation is not claimed to be impossible.
The intended backstop is a monitored metric — utilisation above 50% treated as a signal that the model has changed and the sizing question must be reopened. It is specified, not built: nothing in the code computes utilisation or alerts on it today.
3.3 Settlement deficit risk
The risk. Because options are cash-settled in USDC, an ITM call on an ETH-side account creates a negative USDC balance at exactly 08:00 UTC, while the funds to clear it arrive minutes later from the conversion. During that window the account carries a debit and accrues borrow interest.
What is done. The window is minutes, not hours: withdrawal to Base is fast and free, the swap is immediate, and the deposit back is fast. At this scale the borrow interest is pennies, and it is booked to a real expense account rather than ignored. The pre-withdrawal step in the conversion begins moving the withdrawable fraction ahead of 08:00 precisely to shorten this window.
A deliberate omission. No standing USDC buffer is held on the subaccount. A buffer would mask exactly the cost the first run is meant to measure. The accepted consequence is that an ITM day may cost a skipped cycle — accepted knowingly, not overlooked.
3.4 Conversion and slippage risk
The risk. Converting the entire balance means selling at market, on another chain, through an aggregator, under time pressure. Slippage, a reverting swap, a bad route, or a stale quote all cost real money. And after the swap there is no way back.
What is done.
- Tolerance is discovered, not assumed: 3 → 6 → 12 → 25 → 50 → 100 bps, stopping at the first that simulates successfully.
- A price sanity guard rejects execution more than 100 bps from Derive’s own index — checked before signing and again against the actual receipt.
- The swap is never performed before settlement is final (the conversion).
- The transaction hash and the signed bytes are written to the database before broadcast, so a crash mid-send cannot produce a second transaction — rebroadcasting identical bytes yields the same hash and is a no-op. (Token approvals are not persisted this way; they are signed and broadcast in one step, and are idempotent by nature.)
- A dedicated lock guards the swap wallet, because two schedulers send from that address and both read the pending nonce — without it, one transaction would evict the other from the mempool after its hash was already recorded.
- A gas floor sized for the entire round trip guards the bridge back, because a wallet with enough gas for the swap but not the return hop would strand a user’s funds mid-conversion. The check before the swap itself is a separate, smaller floor — roughly 150 operations’ worth of autonomy — so the round-trip floor is enforced at the point where stranding becomes possible, not at the start.
Residual risk. Slippage itself is a market cost and is borne by the user. A partially-delivered bridge transfer continues the cycle with whatever arrived, plus an alert — stopping would abandon the delivered portion on Base.
3.5 Liquidity risk
The risk. Options liquidity here is intermittent, not merely thin. On the 2026-08-12 mainnet snapshot, beyond 4 days to expiry there were no bids at all — the 4-day, 9-day and 16-day expiries had zero bids across all 42–64 strikes. A follow-up measurement the next day found bids at 7 and 14 days to expiry at 93–97% of mark, so this is intermittent, not structural — “weeklies cannot be sold” is not a general truth, and the first snapshot happened to catch an empty book.
What is done. RFQ is queried alongside the order book at execution, which is the only way to trade those expiries when the book is empty. Note the asymmetry: candidate selection reads the book only, so an instrument with no bid is dropped before RFQ is ever asked — an expiry that RFQ could have priced will not appear as a candidate in the first place. Depth is not the constraint where bids exist — 60–200 ETH per level against a 0.5 ETH order — so the problem is price, and the composite threshold refuses to sell into a bad one, accepting a skipped cycle instead. The showcase deliberately presents weekly (Friday) expiries, which carry 40–64 instruments against 20–24 on dailies.
Residual risk. Some days there will be nothing worth selling. That is a revenue risk, not a capital risk, and it is measured rather than hidden.
3.6 Venue and smart-contract risk
The risk. User assets sit in Derive’s contracts. A contract vulnerability, an insolvency, or a venue failure would affect them. This is the single largest exogenous risk in the product and it is not one we control.
What is done — by Derive. Derive’s smart contracts have been audited by Sigma Prime ; the reports are public at github.com/sigp/public-audits/tree/master/reports/derive and cover an initial review, a second round, and separate reviews of the atomic, PPTSA and v2-matching components. Derive also operates a Security Module holding reserved funds used to pay off insolvent debt in the event of trader bankruptcy.
What is done — by us. We treat the exchange as the source of truth for balances and positions, and never argue with it: our ledger explains why a balance moved, the exchange decides what it is. Fee rates and instrument constraints are read from the live ticker at runtime rather than hardcoded, after finding that three separate Derive documentation sources contradicted each other and the live values. The reconciliation that compares the two now runs on a schedule — once an hour, and within a minute of any operation that moved a subaccount balance — though deliberately in observing mode until the noise floor is known (§3.8).
Stated directly: our own layer has not been audited by anyone. The custody design, key handling, bridging and conversion code have been reviewed only internally. Any serious diligence should treat that as an open item, and an external audit belongs on the path to holding third-party funds.
3.7 Custody and key risk
The risk. This is a custodial service. Between deposit and withdrawal, the platform controls the keys. Key compromise, insider action, or operational error are live risks.
What is done.
- The withdrawal recipient is always the address the user authenticated with, and there is no field in the API to specify another. Compromising the API does not redirect funds.
- Deposit addresses are derived per user and deterministic; attribution is by destination address, so it survives deposits sent from exchanges.
- Every transaction that moves money on-chain is signed in a background worker, never in an HTTP
request handler. Two request-path exceptions are worth naming precisely:
GET /deposit-addressderives an address from the master mnemonic (an address, no signature), and the release endpoint signs its exchange authorisation synchronously. - A stale session key stops the affected subaccount, and clearing that stop is manual — there is no code path that clears it (§4.4). Exhausted gas does not raise a stop at all: it is treated as a retryable condition and the gas funder tops the wallet up on the next tick.
Residual risk. Custody is custody. There is no non-custodial fallback in this design, and no multi-party or hardware-backed signing scheme is in place today. This is the most significant unmitigated risk in the product for a user, and it should be read as such.
3.8 Accounting and operational risk
The risk. Money moving across three chains, an exchange, and a swap aggregator, through processes that can crash at any point, is exactly the setting in which funds get double-sent, double-counted or silently lost.
What is done. This is where most of the engineering has gone.
| Property | How |
|---|---|
| Double-entry | Every entry is booked twice, in native currency and in USD at the rate at the moment of the event. Every event’s postings must sum to zero in USD, checked when the event is projected into balances; a violation rejects the balance entries rather than the event, which is already immutable by then. Money is decimal.js; floating point is banned. |
| Append-only journal | Database triggers reject UPDATE, DELETE and TRUNCATE on the event log — the events themselves. Balances are a projection of those events, rebuildable from scratch and deliberately rewritable, never an independent truth. |
| Idempotency | Every event that moves money carries a unique key — trade id, transaction hash plus log index, or a time-bucketed tuple. Duplicate inserts are discarded atomically at the database level, not by check-then-insert, which loses the race. (One non-monetary event type, a rejected trade, keys on a millisecond timestamp and so does record one row per attempt.) |
| No lock is held across a wait | The 20-minute maker window is a future timestamp, not a held lock. For swaps and conversions a process dying rolls back its transaction and releases the row immediately. Position cycles are the exception: they take a two-minute lease, so a dead worker’s cycle is unclaimable for up to two minutes. |
| Reconciliation (running in observe mode — see below) | Collateral balances are compared against private/get_subaccount every hour, and within a minute of any operation that moves a subaccount balance. In observe mode — today’s default — a divergence is written to the log and nothing else: no adjustment, no stop, not even an alert. Under enforce it writes an explicit adjustment event carrying the delta — never a silent overwrite — stops that subaccount and alerts. Position reconciliation is not implemented. The cumulative size of adjustments is meant to be an accounting-quality metric: if it grows, something is leaking. |
| Reserves | Funds committed to a conversion are locked against subaccount release and against opening a new position, and the reserve follows the conversion stage, tracking the outgoing asset before the swap and the incoming one after. The third path is not closed: the conversion reserve is subtracted in the balance shown on screen but not in the check that admits a withdrawal request, which reads only the withdrawal table. |
| Recovery by verification, not retry | On restart, steps interrupted after sending to an external system are verified against that system rather than repeated, because at that point we do not know whether the order arrived. This holds for fills, withdrawals, swaps and bridge hops; placing a maker order is the one path still exposed, since a crash between sending the order and recording its id would let the next tick place a second one. |
| Test coverage | 1,201 test cases across 130 suites, plus 21 web test files, plus 27 integration suites, of which 14 run against live mainnet and the rest against Postgres — without spending money. Live spending is gated behind a single explicit environment flag; without it those tests skip rather than fail. |
Failure classes are explicit, and the default matters: retryable errors are re-attempted on a fixed interval, scheduled errors wait for the next cycle, and an unrecognised error is treated as fatal. The system stops rather than guessing.
Two honest gaps in this section, both found while preparing this document. The first has since been closed; the second has not.
-
Reconciliation now runs, but deliberately in “observe” mode. Until this week nothing called it at all. It is now scheduled — hourly, plus within a minute of any operation that moves a subaccount balance — and it skips accounts with an operation in flight, because the exchange updates a balance before we record the confirmation and a naive comparison in that window would “correct” a movement that is about to be booked properly. It currently reports divergences to the log without writing to the journal, for one honest reason: we do not yet know the noise floor. The exchange accrues interest continuously while we read it periodically, and the journal is append-only — a correction written on a guess could never later be told apart from a real accounting error, which is precisely the metric this is supposed to produce. Enforcement is a configuration flag, flipped once the noise floor is measured.
-
Platform gas was under-counted, and now is not. Gas spent on sweeps and on the bridge relay was recorded as an event but never carried through into balances, so only swap gas showed up. That understated our own cost, not the user’s — but it is exactly the number the first run exists to measure, and gas is nearly all of it: the venue pays for subaccount creation, deposits and withdrawals itself, so our own spend reduces to sweeps, the relay fee and swaps. Two of those three were invisible. All three are now counted — with one source still missing: the inbound bridge hop pays a relay fee as transaction value and emits no gas event at all, so every live conversion will under-report by that amount until it does.
One modelling gap remains, and it is worth naming: the gas wallet’s balance in the journal only ever decreases, because topping it up is a manual transfer that no event records. As a running total of what we have spent it is correct — which is the number that matters here — but it will not agree with the wallet itself until top-ups are journalled too.
3.9 Bridge risk
The risk. Funds transit bridges. Deposits and withdrawals use four source chains — Ethereum, Optimism, Base and Arbitrum — while the automated conversion path crosses only between Base and Derive Chain. Bridges are a well-known category of loss in this industry.
What is done. The route table is fixed in code, and it was built by reading the live contracts rather than the documentation — a route present in the bridge’s deployment file but with a zero lock limit was excluded after an on-chain check, because a deposit would revert. That verification was a one-off performed when the table was written, not a runtime check. Bridge limits are read live, displayed to the user, and block the action at 100%. Measured headroom on the inbound path is 50,000 ETH and 100,000,000 USDC, three orders of magnitude above any conversion we would perform. Relay cost on the inbound leg measured at 0.0000012 ETH, about a third of a cent.
Residual risk. We do not control the bridge. A bridge failure is a real loss scenario, mitigated only by keeping transit windows short — 2–5 minutes on the L2 routes, 5–10 on Ethereum — and balances in transit small.
3.10 Oracle and settlement-price risk
The risk. The settlement price decides ITM versus OTM, and therefore decides real money.
What is done. Settlement uses a 30-minute TWAP of the index over 07:30–08:00 UTC, which is Derive’s mechanism, not ours, and is expensive to manipulate. All internal USD valuation uses the same Derive index price that settles the options, keeping conversion cost, settlement and gas valuation in one coordinate system — with one exception, reconciliation, which values a divergence at the exchange’s own mark price for that collateral rather than the index.
Not done: there is no clock-drift check anywhere in the system. It is a requirement in the architecture specification and has not been implemented.
A measured limitation. The spot feed used by the ITM predictor retains roughly 24 hours of history — 30 points at 23.5 hours back, zero at 24. Verifying the predictor against past expiries therefore only works for the most recent one, and only within the same day.
3.11 Signal dependency
The risk. In automated mode, the strike level comes from an external TradingView signal. The signal is a hard filter, not a tiebreaker: the automated wheel ranks by premium only within the strikes a signal has marked, and if nothing is marked it stops rather than selling something arbitrary. So the product’s automated mode depends on a third-party feed continuing to arrive.
What is done. Users can select strikes manually, and the candidate list is a product surface rather than an internal step — the product functions with no signal at all, just without automation. The webhook is authenticated with a shared secret and constant-time comparison. Signal levels are constrained at the database level, so a malformed or out-of-range signal is rejected rather than acted on. Premium deliberately cannot substitute for the signal: ranking purely by premium always selects the strike nearest the money, which on a live board means roughly delta 0.4 and assignment about 40% of the time — so the wheel uses the signal as a hard filter and ranks by annualised return only within the strikes the signal marked. The signal is the only risk dial the automated mode has.
Two properties worth knowing, both found in the code while preparing this document:
- Signals never expire. The last value received applies indefinitely. A silent TradingView does not put the wheel into “no signal” — it leaves it acting on a stale one. This was settled as an explicit decision on 2026-09-08 rather than left as an accident: the reasoning is that a signal is active because the matcher marked a strike, not because the webhook arrived recently, and the indicator’s own neutral zone stops the wheel by itself. The behaviour is still worth knowing, because a stuck feed and a neutral feed are indistinguishable from the outside.
- The interface and the automation do not see the same marked strikes. The web interface narrows each signal to the nearest expiry; the wheel does not, and will happily roll into a further-dated strike the user was never shown. Nothing is unsafe here — every candidate passed the same filters — but a user reconciling what they saw against what the wheel did would find a discrepancy.
Neither changes the capital risk of a position. Both change what the user can predict about the automation, which is why they are listed here rather than buried.
3.12 Concentration and scope
The first iteration is deliberately narrow: ETH and BTC only, USDC as put collateral, one strategy, one venue. Assets without their own ERC-20 on Derive (XAUT, HYPE, XRP) were removed from the showcase rather than half-supported — a covered call on them is impossible in principle, and half a wheel is not a product.
Production minimums are 0.5 ETH / 0.015 BTC / 1,000 USDC. Below the exchange’s own minimum lot the position simply cannot trade, and the user is told rather than left wondering.
3.13 Response shapes we have never seen
The risk. Several exchange responses have never been observed with real data, because the events that produce them have not happened yet: no option on our account has ever reached settlement, no order has ever filled, and no balance has ever gone negative. Where the venue documents the shape, we follow the documentation; where it does not, the code has to infer field names — and a wrong inference on a money path is not a crash, it is a wrong number.
What is done. Every unobserved shape is marked as unobserved in the code rather than presented as fact. Where nothing matches, the code throws loudly and prints the fields the exchange actually sent, so it fails visibly rather than proceeding on a default. Amounts and fees are always taken from the exchange’s own response rather than recomputed, so our arithmetic cannot drift from theirs.
The sharpest instance — the settlement record, which decides whether an option finished in the money and therefore whether collateral gets sold — has been closed against the venue’s published schema. The debit is now read from the single documented field, with the sign convention the venue specifies (the settled amount is negative for a short position, and this product is only ever short). A positive value would mean a long position, which the product never holds; the code now refuses such a record instead of taking its absolute value and booking a credit as a debit.
Residual risk. These resolve on first contact with real events, and each one is a cheap fix once seen. But they are the reason the first live run is deliberately small: the purpose of the first cycle is to observe shapes, not to earn.