23 KiB
JEV‑Trader vs FLIGHT13 SOA — Execution‑Flow Comparison
Read‑only study. No files in the production trees, CH, or HZ were altered. New doc only
(/mnt/dolphinng5_predict/prod/docs/).
Sources (all read‑only): jev‑trader src/{trader,market,trades,book,chain,model,server}.ts
(b587759e main); FLIGHT13 r29 formal spec
/root/uv-wt/f13-r29/prod/docs/formal/F13_ExecLifecycle_SOA_v5.tla (Next + all transitions);
/root/uv-wt/f13-r29/prod/docs/F13_EXECUTION_FLOW_DIAGRAM.md;
/root/uv-wt/f13-r29/prod/docs/VIOLET_PASS2.4_LIVE_EXEC_WIRING.md;
/root/uv-wt/f13-r29/prod/docs/EXEC_SOA_SUPERSESSION_AUDIT_20260808.md;
/root/uv-wt/f13-r29/prod/clean_arch/dita_v2/{hl_venue,exec_router}.py; and
/mnt/dolphinng5_predict/prod/docs/DITA_V2_KERNEL_REFERENCE.md.
Operator premise (accepted): “Our state machine is tad more complete… the .ts execs
against the book is worth taking in/studying.” This doc agrees the FLIGHT13 FSM is strictly
more complete (it must be — it is built for Hyperliquid, where ack‑or‑not / partial / network
hell is the baseline), and isolates exactly what jev‑trader does against the book that is
worth porting: the post‑only‑inside‑the‑touch, replace‑every‑block maker‑capture policy and its
serialized one‑send‑per‑cadence gate.
0. TL;DR
| dimension | jev‑trader | FLIGHT13 DITAv3.00 / ASEx | verdict for an HL port |
|---|---|---|---|
| order model | 1 resting order / asset; serialized replaces | multi‑slot (MaxSlots); maker chase ladder + standing TP |
jev’s policy is a special case FLIGHT already hosts |
| exec against the book | post‑only, 1 tick inside the touch, cancel‑prev + place‑new every block (atomic from the bot’s view) | post‑only at the touch (TOUCH_ALO); atomic modify to touch (same cloid, fee‑free, ≤1/bar) |
jev ≈ FLIGHT’s maker chase w/ offset_ticks=1; drop‑in not viable — needs the hl_venue adapter |
| ack model | tx‑receipt = placed/reverted; lost after 10 blocks; assumes receipt == truth |
VenueAccept / VenueReject / VenueIndeterminate; submit exception → INDETERMINATE unless provenance proves NOT_ATTEMPTED; venue‑truth polls the OBJECT; UNKNOWN is never FLAT |
FLIGHT strictly stronger — jev’s 10‑block timeout is the only recovery |
| partial fills | taker hits resting order; size shrinks; re‑quoted next block (no remainder re‑quote within a bar) | PartialFill → pendingFill; IocExpire residual handled by Add.13 TakeResidualAbandon; IOC residual inherits the live TAKE obligation |
FLIGHT keeps the residual; jev just re‑quotes |
| exit | none — side flip → new opposite quote naturally closes | TP_FLOOR → TP → SL → MAX_HOLD → V7(retract ½) → ADVSL(shadow); reduce‑only, side‑flipped, MUST_FILL; MAX_HOLD monotonic latch |
jev has no exit authority — the port must add FLIGHT’s exit policy layer |
| venue | Kuru AMM on Monad (eth_call book, eth_sendRaw batchUpdate, Trade‑log fills) | Hyperliquid VST (off‑chain OB, REST/WS, batchOrders, postOnly/reduceOnly, cloid nonce) | plumbing differs; policy transfers |
| hot cadence | ~300 ms block; 2 RPC round trips (1 eth_call book + 1 send) | scan bar ~5–6 s decision; BLUE bar 11 s; chase 33 s; rest ceiling 132 s | jev’s 300 ms cadence is not portable to HL‑VST timing — map the policy, not the clock |
1. Two state machines, side by side
1.1 jev‑trader — single‑slot, replace‑every‑block (one in‑flight)
IDLE(busy=false, block t)
│ confirmPending(t): poll receipts for prior inflight → placed|reverted|lost
│ (placed → this.orders[orderId] = {side,price,size,block} ← next cancel list)
│ (fills: trades.poll(t).then(harvest) → applyFill → position FIFO realized)
│
├─ IF busy (prev read+decide+send still running) ──► emit(lateness, decision.hold) ──► IDLE (no order)
│
│ readBook() — 1 eth_call getL2Book (+vault batched); throws on empty side
│ decision = model.decide(state) // the question: ↑/↓ after horizonBlocks; {buy,sell,hold}+upIn10
│ wanted = (decision.action==sell)? sell : buy // buy wins ties
│ side = allowed(wanted)? wanted : allowed(other)? other : null // cap(5×) + margin(live)
│
├─ IF side==null ──► emit(block, book, decision, null, false, capped=true) ──► IDLE (no order)
│
│ cancel = [...this.orders.keys].filter(id>0) // confirmed resting ids ONLY
│ market.send(t, side, 200 MON, book, cancel, capped) // ONE batchUpdate: cancel[] + place 1 post‑only
│ → sign → eth_sendRawTransaction → hash (status=SENT, inflight[hash]=quote, gasMon charged)
│ → DARK : status=sim, orders.clear()+orders.set(-simId)
└─ emit(BlockEvent{snapshot,history/events,block,quote}) ──► IDLE(t+1)
There is no EXIT state. A model side‑flip (long→sell) cancels the bid and posts a one‑tick‑inside ask; the next taker print against that ask closes the position. The FSM’s only “memory” across blocks is orders (confirmed resting), inflight (unacked txs), and position (FIFO cost basis).
State inventory (trader.ts): busy, orders:Map(id,Resting), inflight:Map(hash,Quote),
position:{mon,costUsd}, totals, lastBook, plus per‑block RPC feeWei/gasLimit.
1.2 FLIGHT13 DITAv3.00/ASEx — multi‑slot FSM with reconciliation (formal TLA+)
Order‑lifecycle FSM (TLA OrderStates):
NONE → RESERVED → QUEUED → {INDETERMINATE | LIVE} → {PARTIAL | CANCEL_PENDING | FILLED | CANCELLED | REJECTED}
(plus pendingFill, linkedOwner, venueOwner per object).
Per‑trade FSM (entryPhase):
NONE → SELECT_ENTRY_ARM(REST|CHASE) → ReserveInitialMaker(ENTRY_MAKER, TOUCH_ALO, RESERVED)
→ QueueSubmit (QUEUED, parityHeld) → VenueAccept (LIVE, venueOwner) / VenueReject (REJECTED) / VenueIndeterminate (INDETERMINATE)
→ on LIVE: EntryHold | EntryReprice (atomic modify‑to‑touch, CHASE only) | EntryTerminalLive/Dark
→ EntryEconomicAbandon (explicit policy verdict, book usable) OR ReserveEntryTake (BOUNDED IOC, MUST_FILL) → Add.13 TakeResidualAbandon
→ EntryFillOne/DeliverOne (kernelPos++, undeliveredEntry++, entry COMPLETE or ABORT_REQUIRED)
→ POSITION_OPEN → [ArmProtection → STANDING_TP] / [exit]
→ RaiseExitSignal → SelectExitIntent (same‑tick KILL>TP_FLOOR>FIXED_TP>STOP_LOSS>ADVSL>MAX_HOLD; urgency KILL/SL>TP_FLOOR/ADVSL>FIXED_TP>MAX_HOLD) → ReserveExitTake (EXIT_TAKE, MUST_FILL, reduce‑only, side‑flipped) → ExitFillOne (kernelPos--, realized) → ClearExitWhenFlat → FLAT.
→ recovery at any tick: STALE_STATE_RECONCILING blocks progression until venue‑truth reconciled;
VenueSnapshot/VenueTruthStale/BookTruthStale + VenueFillOne/DeliverOne + RetireOrder.
Exit is terminal & monotonic: MAX_HOLD deadline latches (maxHoldFired, never cleared); ExitCostRefuse (cost‑ceiling veto) is legal only before the deadline — at terminal it is forced (TerminalCeilingVetoBug is a defect): cross with full telemetry. Invariant LIV_ExitEventuallyFlat (with the cost‑veto + MAX_HOLD latch) is the liveness tooth.
Foreign‑exposure path (the orphan class jev cannot express): ForeignFill(s,side) → foreignExposure++ (a fill for our intent‑id that the kernel did not originate — i.e. a stale cloid collision or a foreign place). This arms ReserveFlatten (FLATTEN, MUST_FILL) whose quantity = foreignExposure, side‑flipped, reduce‑only — a forced flatten whose obligation persists through reject/partial/unknown until flat. (NoFlattenerBug would drop this to unownedSeen — a defect.)
Invariant teeth (TLA+ Invs): UNKNOWN is never FLAT; cancel REQUEST is never cancel TRUTH;
one trade may own several simultaneous physical orders; pending/self acquisition consumes parity before venue truth catches up; the Bug-prefixed switches are mutation‑litmus defect flags
(FillBeforeLinkDropsBug, CancelRequestTerminalBug, ExitNotReduceOnlyBug, WrongSideExitBug,
OptimisticResizeBug, …) — TLC proves each fires only under its bug gate.
1.3 The structural gap in one line
jev: one block ≈ one send ≈ one resting order ≈ book is replaced, not reconciled.** FLIGHT: **the venue owns the truth; the kernel owns the book; the gap between them is where every
defect lives and is exhaustively modeled. jev’s lost (no receipt in 10 blocks) is the only
reconciliation jev has. FLIGHT has STALE_STATE_RECONCILING, venue‑truth polls, foreign‑recovery,
standing TPs, MAX_HOLD latches, and a 30‑item defect‑flag lattice over exactly that gap.
2. How each execs against the book (the user’s focus)
2.1 jev‑trader — "replace‑every‑block, one tick inside, post‑only by construction"
Against the book, per block (trader.onBlock):
readBook()→ oneeth_call getL2Book; the entire resting‑order price is recomputed from the current book each block. There is no "keep the old level"; the old level is cancelled and a new one is placed.quotePrice(market.ts:121) →bid + 1 tickfor buy,ask − 1 tickfor sell, clamped to the touch when the spread is thinner than a tick. The clamp is the never‑cross guarantee: the order always rests at or inside the touch on the model’s side.batchUpdate(buyPrices,buySizes,sellPrices,sellSizes,cancelIds, **postOnly=true**)(market.ts:185) — the cancel list (last block’s confirmed restingorderIds) and the new post‑only quote are sent in one Kuru tx. From the book’s perspective the quote hops every block with no gap: the moment the new order is placed, the old one is cancelled in the same atomic batch. (It is not a true on‑chain modify; it is cancel‑prev + place‑new. FLIGHT does a same‑cloid atomic modify instead — see §3.)- The resting order therefore sits one tick inside the touch and waits for the next taker
print that crosses the spread. A taker
sellfills ourbuyatbid+1tick; a takerbuyfills oursellatask−1tick. We are, by construction, the counterparty the taker hits, earning the tick plus the spread‑capture edge — exactly “be the one whose order flow gets picked up.” - Because
tradeSizeMonis fixed at 200 MON andquoteInsideTicks=1is fixed, the exec is size‑ and distance‑blind to depth. The model seesdepthBps{10/25/50}andimbalancebut the placement ignores them — jev earns edge only so long as it (a) is first to the touch each block and (b) the book is wide enough that one tick is inside it.
Net: jev’s book‑exec is a deterministic, stateless, post‑only hopping of a single order one tick inside the touch, every ~300 ms. It wins or loses the spread every block; there is no reprice‑to‑touch ladder, no venue‑truth, no partial remainder. The policy is attractive because it is obviously correct about not crossing.
2.2 FLIGHT13 — "maker chase ladder + atomic modify + venue‑truth"
Against the book, per the timeline + TLA (F13_EXECUTION_FLOW_DIAGRAM.md):
- t=0 ExecCtl urgency stamp →
high(do‑or‑die)=MARKET Ioc(taker, 4.5 bp measured);low(maker‑first)=LIMIT Alo + TTL(maker, 1.5 bp). Order type is chosen once, at submit, by the control plane — not by a per‑bar re‑quote. QueueSubmit→VenueAccept(LIVE,venueOwner=o,cloid=sha256(intent_id)journaled as a(cloid, intent_id)pair — seehl_venue.py_cloid_for/_remember_cloid). The kernel does not re‑quote every bar by default.- Chase ladder (
EntryReprice, bars =wall/11s): bar 0–1 → atomic modify to the touch (same cloid, ≤1 bar, fee‑free, owner‑side sizing remainder,join bid — can never cross); measured edge starts ~6 bp, concave decays; bar 2, edge ≤ 2 bp floor →ABANDON→expire_now→ same‑sweepCANCEL. So FLIGHT does re‑price toward the touch — but only for the CHASE arm, and it does it with a same‑cloid atomic modify (fee‑free), not a cancel‑+‑place. - Modify‑error taxonomy (
hl_venue._events_from_modify→_modify_error_class): a failed reprice is resolved, never guessed:old_order_intact→ quote untouched, retry the reprice;old_order_consumed→ cancel half succeeded / place failed → quote gone unfilled, slot freed (no double‑fill: atomicity holds; presence does not);selector_gone→ order already gone before the modify (filled/cancelled) → decide via venue‑truth, never by assumption.
- Venue‑truth on stuck (
VENUE‑TRUTH read, 60 s cadence, query the object not the action):VenueSnapshot— GONE → slot freed; FILLED → feed‑gap alarm +fill path owns position;RESTING < ceiling→ re‑cancel;RESTING ≥ 132 s→ HALT new ENTERs (loud alarm);UNKNOWN→ ask again. Submit exception →_submit_exc_proves_no_order→NOT_ATTEMPTED(ConnectError/NameResolution/SSLError/timeout‑before‑send) ⇒ safe REJECTED; anything else ⇒VenueIndeterminateError(INDETERMINATE) ⇒ the order MAY exist and is never rolled back to flat at the book layer (UNKNOWN is never FLAT). - Fills (
FillOne/DeliverOne,VenueFillOne): taker lifts the maker →actualPos+++pendingFill++; self‑acquisition consumes parity before venue truth catches up; the fill is delivered into the book (delivered < filled→ pending).STANDING_TPis registered whenprotection == ARMED. - Exit (
ReserveExitTake, MUST_FILL, reduce‑only, side‑flipped;SMART_EXITmaker LIMIT- TTL →
sweep CANCEL → confirm/retry → MARKET cross EXACTLY ONCE), arbitratedKILL>TP_FLOOR>FIXED_TP>STOP_LOSS>ADVSL>MAX_HOLDsame‑tick, MAX_HOLD a monotonic latch.
- TTL →
- Single‑writer (VIOLET_PASS2.4):
ShadowDecision → ExecIntent → ExecDeadlineDriver@100ms → VST adapter; fills fold back into the ASEx‑guarded working‑order + position/capital ledger (single writer — no race with the decision loop’s reads). The race test (“decision‑loop reads vs fill‑apply writes through ASEx”) is a named self‑test.
Net: FLIGHT’s book‑exec is a chase ladder that modifies‑to‑the‑touch for cheap, keeps the quote alive across bars, resolves every failure by asking the venue about the object, and never rolls back a possibly‑live order. jev does the opposite: it re‑places the quote every bar and only ever recovers via a 10‑block receipt timeout.
3. Comparison matrix (exec‑against‑book focus)
| axis | jev‑trader | FLIGHT13 DITAv3.00 / ASEx (VST/HL) | delta |
|---|---|---|---|
| quote distance from touch | fixed 1 tick inside (QUOTE_INSIDE_TICKS) |
TOUCH / touch + chase‑modify; maker offset configurable (offset_ticks) |
jev’s “1 tick in” = FLIGHT maker offset=1 |
| how the quote moves | cancel‑prev + place‑new every block (fresh oid each block) | atomic modify to touch, same cloid, fee‑free (EntryReprice) |
jev pays 2 txs/round (cancel+place); FLIGHT 1 modify — FLIGHT cheaper |
| post‑only guarantee | quotePrice clamp × batchUpdate(postOnly=true) |
postOnly flag + TOUCH_ALO (Kuru/HL) |
FLIGHT also rejects‑and‑reprice on cross |
| order‑state knowledge | receipt OrderCreated/OrdersCanceled only |
VenueStatus{NEW/ACKED/PARTIAL_FILLED/FILLED/CANCELED/REJECTED/RATE_LIMITED/CANCELED_REJECTED} + VenueIndeterminate |
FLIGHT has partial + rate‑limit states jev lacks |
| ack‑or‑not | receipt mined ⇒ placed; timeout 10 blocks ⇒ lost; else assumed |
submit exception ⇒ VenueIndeterminate unless NOT_ATTEMPTED proven; venue‑truth polls the OBJECT |
FLIGHT strictly stronger; jev’s lost is the only backstop |
| partial fills | taker fills resting → size shrinks → re‑quoted next block | PartialFill → held as pendingFill; IocExpire residual handled by Add.13 (TakeResidualAbandon) |
FLIGHT preserves the residual obligation; jev just re‑quotes |
| orphan / foreign fill | none modelable | ForeignFill (our cloid, not our intent) → FLATTEN MUST_FILL forced‑close |
jev has no defense; this is the HL “shared‑key contamination” risk |
| reconciliation cadence | receipts every block off‑path; Trade‑log every block | venue‑truth 60 s; max 8 cancel retries; rest ceiling 132 s; feed‑gap alarm | FLIGHT survives minutes‑of‑darkness; jev survives ~3 s |
| stuck‑order policy | timeout 10 blocks ⇒ lost, resync nonce |
venue‑truth GONE ⇒ free; RESTING ≥132 s ⇒ HALT ENTERs; UNKNOWN ⇒ ask again | FLIGHT escalates to HALT; jev silently gives up |
| exit (close) | none — side flip posts opposite‑touch quote | TP_FLOOR>TP>SL>MAX_HOLD>V7½>ADVSL arbitrated same‑tick; MAX_HOLD monotonic; cost‑ceiling advisory at terminal |
jev has no exit authority at all |
| concurrency | 1 asset (MON), 1 order | multi‑asset, MaxSlots, one trade may own several simultaneous physical orders |
jev is the degenerate single‑slot case |
| single‑writer | single event‑loop process | ASEx single‑writer lane (P0‑P4 queue); fill‑apply vs read race‑free by design | FLIGHT is concurrency‑safe by construction |
4. Mapping jev’s exec onto FLIGHT/HL (concrete port‑under‑HL recommendation)
jev‑trader is not a drop‑in under nautilius (the exec driver) — it is Kuru/Monad‑coupled.
But its book‑placement policy maps onto an existing FLIGHT surface:
| jev concept | FLIGHT/HL realization | action |
|---|---|---|
| post‑only 1‑tick‑inside the touch | exec_router.plan_entry maker mode + maker_price(asset,order_side,ref) + post_only=True + offset_ticks=1 + max_spread_bps gate |
already exists — set DOLPHIN_PINK_EXEC_STYLE=maker_entry, DOLPHIN_PINK_POST_ONLY=1, DOLPHIN_PINK_MAKER_OFFSET_TICKS=1 |
| cancel‑prev + place‑new every block | EntryReprice atomic modify to touch (CHASE arm) |
replace jev’s cancel+place with FLIGHT’s atomic modify — cheaper, fee‑free; jev’s replace‑every‑block cadence must be expressed as the chase ladder’s reprice ticks |
| one send / block cadence (busy gate) | ASEx single‑writer lane P0 + scan‑bar cadence | do not drive 300 ms; map to scan‑bar (5–6 s) so the kernel’s book truth and the HL rate governor stay coherent |
| model.side ∈ {buy,sell} + upIn10 | Intent.action + ExecControl.urgency stamp + FLIGHT brain upIn10 field |
wire jev‑as‑oracle behind the existing Intent producer; do not let jev touch the book |
| position cap (maxPositionMon 1000) | ExecCtl governor / MaxSlots + capital‑fresh gate |
reuse the governor; jev’s 5× cap is a special case of FLIGHT’s slot + capital gate |
| fills = taker hits our resting order | HL userFills WS (maker) + HlFillAdmission dedup on tid; venue size wins (L10) |
hl_venue already owns this — the fill path is the part that does NOT transfer from jev |
| no TP/SL/exit | FLIGHT EXIT_* policy (ReserveExitTake, MUST_FILL, reduce‑only, side‑flipped) + MAX_HOLD latch |
add FLIGHT’s exit authority — this is jev’s single biggest missing piece for an HL port. jev’s “side flip closes naturally” is unsafe on VST (no forced flatten; orphan/foreign fills exist). |
| gas = static ceiling | HL: gas free (off‑chain); HL nonce per‑order, USD fee | drop the whole jev gas model — HL is gasless, but HL nonce + budget (1 req/$1 USDC traded) must be honored by the adapter |
Recommendation (ranked)
-
Port the policy, not the plumbing. Jev’s attractive thing is “post one‑tick‑inside, replace toward the touch, post‑only always, one order per cadence.” That is FLIGHT maker_entry maker_exit with chase — already present in
exec_router.py+hl_venue.py. Do not ship jev’sbatchUpdate/Trade‑log/gasLimit/nonce code. -
Graft jev‑as‑oracle onto the Intent producer.
model.tsexposes a clean oracle contract ({buy,sell} + upIn10). Wire it behind the existingExecIntent → ExecDeadlineDriver@100ms → VST adapter(VIOLET_PASS2.4 wiring). The oracle must not know it posts orders. -
Replace jev’s “cancel+place every block” with FLIGHT’s atomic modify‑to‑touch. jev pays a cancel gas‑ish on Kuru every 300 ms; on HL the modify‑to‑touch is fee‑free and is the whole point of the chase ladder. Map jev’s cadence to scan‑bar so the book truth / rate governor /
repriceSafeleash stay coherent. -
Add FLIGHT’s exit authority — jev has none. A pure side‑flip exit is not safe on HL/VST (no orphan/foreign recovery, no MAX_HOLD latch, no reduce‑only enforce). Use
ReserveExitTake(MUST_FILL, reduce‑only, side‑flipped) + the same‑tick arbitrationKILL>TP_FLOOR>FIXED_TP>STOP_LOSS>ADVSL>MAX_HOLD. This is the non‑negotiable delta. -
Inherit the ack/partial/venue‑truth stack from hl_venue. jev’s 10‑block receipt timeout is the only recovery and it assumes “receipt == truth.” Under HL,
_submit_exc_proves_no_order⇒ INDETERMINATE‑unless‑NOT‑ATTEMPTED, and venue‑truth polls the OBJECT — port the policy but keep FLIGHT’s ack/partial/foreign/FLATTEN recovery verbatim. This is the “tad more complete” part the operator flagged. -
Stay gasless, honor the HL nonce + budget. jev’s
MAX_FEE_GWEI/gasLimitFallbackare pure noise for HL. HL charges per signed action against a 1‑request/$1 USDC‑traded bucket —HlVenueAdapter.__init__already maintains the_refill_seen/_refill_orderledger with the #59 fix (dedup on venuetid, refill viarecord_trade_volume). jev’s fixed‑size 200‑MON orders map cleanly onto that bucket.
Bottom line: jev‑trader’s book‑exec policy (post‑only 1‑tick‑inside, replace‑every‑cadence,
single‑in‑flight gate) is a special case of the FLIGHT13 maker chase ladder already wired through
hl_venue.py + exec_router.py. The port is an adapter‑layer concern (map jev’s two RPC
round trips onto HL REST/WS batchOrders + userOrders/fills + cloid), not a drop‑in, and it
must import FLIGHT’s exit authority (jev has none) and ack/partial/venue‑truth recovery
(jev’s 10‑block timeout is insufficient on VST). The .ts is attractive because it is obvious;
FLIGHT is more complete precisely because the obvious version does not survive HL’s
ack‑or‑not / partial / orphan / network‑partition regime.