# Jev Trader: Execution Flow and Decision Semantics
**Subject:**`https://github.com/jarrodwatts/jev-trader` (tree at commit `b587759e`, `main`).
## Scope and source identity
**Scope:** this document studies the **execution logic outside Jev itself** — i.e. the order‑placement / book‑interaction state machine in `src/trader.ts`, `src/market.ts`, `src/trades.ts`, `src/chain.ts`, `src/server.ts`. Jev (the LLM decision model) is only the **decision point**; it is treated as an opaque oracle below. **Read‑only study: no files in the working tree were altered.**
**TL;DR:** Jev‑trader is a **single‑in‑flight‑order, post‑only, replace‑every‑block maker‑capture bot**. One order rests on the book at a time; every ~300 ms block it cancels the resting order and posts a fresh one **one tick inside the touch on the model’s side, never crossing**. It earns the spread by being the counterparty taker flow hits; it does **not** chase, does **not** do TP/SL, and has **no venue‑truth reconciliation** — it relies on `eth_getTransactionReceipt` + a 10‑block `lost` timeout. The parts that are portable to Hyperliquid are the *book‑placement policy* and the *serialized one‑send‑per‑block cadence*, **not** the Kuru/Monad‑specific plumbing.
This is a read-only study of the public Jev Trader repository at commit b587759e459ea049590102e54a0b07800864cdc3 (main, cloned 2026-09-22), the full tracked file inventory, and its backend implementation. The Jev clone is at /tmp/jev-exec-study-b587759. No Jev process, wallet, provider call, or order was run. No execution test was added or run.
---
The relevant upstream files are src/index.ts, src/config.ts, src/chain.ts, src/book.ts, src/market.ts, src/trader.ts, src/trades.ts, src/model.ts, src/server.ts; scripts/*; package.json, SPEC.md, README.md; and web/src/app/*, web/src/components/*, web/src/lib/*. Source citations below link to the pinned commit.
## 0. Repository surface (what exists, what is dead‑simple)
## Executive finding
```
Jev is a compact asynchronous single-market strategy loop. On each newly observed Monad block, it reads a Kuru book, asks Jev for a buy/sell classification, applies local inventory and margin limits, then submits a signed post-only batchUpdate containing a new limit order and cancellations for previously confirmed order IDs. It later polls transaction receipts and Kuru trade logs.
jev-trader/
├── package.json # bun runtime; deps: ethers@5, @kuru-labs/kuru-sdk, ai + @ai-sdk/typesafe-ai
`src/index.ts` is the **only** program entry. There are **no tests, no services, no worker pool** — one process, one loop, one order. The `scripts/` dir (`probe‑*.ts`, `dry‑encode.ts`, `bench‑read.ts`, `trace‑rpc.ts`) are offline probes/dry‑run signers, **not** on the live path.
---
The critical correction to the preliminary draft is that the busy flag serializes the book-read/decision/send routine, not venue transactions or outstanding orders. Receipt polling runs concurrently; send returns after the RPC returns a hash; a later block can send another transaction while earlier transactions remain pending. Unknown/late receipts, overlapping polls, fills racing receipt handling and slow model responses can therefore produce more than one live or uncertain order. “Exactly one resting order” is not an invariant in this source.
## 1. Configuration (the 23 levers that bound the exec)
Jev is a decision provider, not an order-management agent here. It returns an action and provider-reported choice distribution from one structured question. The code does not validate that distribution as a calibrated forecast, derive size from it, choose execution style, or decide an exit. The actual prompt describes IOC execution while the code submits post-only orders. Jev's confidence must not be interpreted as fill probability, maker edge or realized expectancy.
From `config.ts` (the only place behavior is tunated at runtime):
## Boot and feed
| knob | value | exec meaning |
src/index.ts performs this sequence:
1. Instantiate Market and await market.init().
2. Construct the selected Model.
3. Start the HTTP/SSE server.
4. Construct Trader with event callbacks and attach its trade feed.
5. Start the block feed, whose callback invokes Trader.onBlock.
The package entry is src/index.ts. The repository-root index.ts only prints “Hello World.” MODEL selects Jev only for the exact value “jev”; otherwise createModel returns MockModel. Dry run is enabled when DRY_RUN is exactly “true” or no private key is configured. Numeric environment values are converted without comprehensive range validation. There is no durable trader-state restore or startup reconciliation of open orders.
chain.startBlockFeed combines HTTP eth_blockNumber polling (default 150 ms) with optional WebSocket newHeads and a newest-block coalescer. The callback is invoked without awaiting its promise. Async interval polls can overlap; WS JSON parsing is not guarded; older block numbers are ignored with no reorg rollback. RPC has no explicit timeout, retry policy, HTTP status check or result schema validation. Book and log reads are independent latest/range requests, not guaranteed snapshots of the triggering block.
## Per-block decision and order path
Trader.onBlock increments counters and launches receipt polling for previous sends. Periodic market.refresh is also off-path. If busy is already true, the block is counted late and may be emitted with lastBook; no new model call is started. Otherwise busy is set and the routine:
1. Awaits Market.readBook, stores the latest book and appends its mid to a process-local 400-value history.
2. Starts TradeFeed.poll(block) without awaiting it.
3. Builds TradeState and awaits model.decide.
4. Uses the model's sell action as sell; other values default to buy.
5. Calls allowed() for the desired side. If disallowed, it tries the opposite side as a risk-reducing alternative. If neither is allowed, no order is sent.
6. Overwrites decision.action with the actually selected side, then calls Market.send with cancellations for currently known positive order IDs.
7. Stores the returned hash as in-flight, emits the block, and clears busy in finally.
An exception is logged; it does not cancel existing orders. There is no decision-age deadline before send, so a slow model may submit using a stale captured book.
Book decoding in src/book.ts parses Kuru L2/vault data, applies tick rounding and derives mid, spread, imbalance, depth and top levels. Empty sides throw. buildState passes current book and process-local price/trade histories, returns and allowed-side flags. Coalesced/missed blocks mean its history is not guaranteed to be N consecutive canonical observations.
## Jev's decision and its implications
### What the live adapter asks
src/model.ts constructs a TypeSafe evaluation model using JEV_MODEL_ID (default jev-latest). JevModel.decide calls experimental_evaluate with the current TradeState, one choice question named direction and maxRetries=0.
The instructions ask whether MON will be higher or lower than the current mid after horizonBlocks, say the move should exceed spread, and describe an “immediate-or-cancel market order in the next block.” Inputs named include taker flow, book depth and imbalance, returns, recent mids, and allowed buy/sell flags. Jev is told that a disallowed side will be traded in the other direction.
The response becomes Action plus buy/sell/hold values. If probabilities are absent, the adapter makes a one-hot fallback for the selected choice. It sets hold to zero and upIn10 equal to buy probability. Despite the name, upIn10 is not a separate 10-block forecast in this source. The code does not normalize or range-check probabilities, calibrate them, set an abstain threshold or record a stable model version in each decision.
### What Jev controls
The model's choice is an input to side selection, not the final execution instruction. Quantity is fixed at TRADE_SIZE_MON (default 200); code selects price from the book and post-only execution. Jev does not choose quantity, limit price, IOC/taker use, cancellation timing, stop, take-profit, exit reason or post-entry holding period.
When allowed() rejects Jev's side but permits the other, Trader changes decision.action while leaving probability values untouched. A displayed sell can therefore coexist with a high buy probability because risk constraints selected the reducing side. These are distinct facts, and must not be collapsed into a single calibrated Jev output.
The prompt's “by more than the spread” language does not create a numerical expected-value calculation. A provider choice distribution is not by itself a calibrated probability that a trade profits after fees, fill selection, queue position, adverse selection and inventory costs.
### The prompt/execution mismatch
The prompt describes IOC market execution crossing the spread. The implementation encodes batchUpdate with postOnly=true and chooses a price one tick better than the same-side best quote when the spread permits; if that reaches the opposite touch, it falls back to the same-side touch. Post-only guards against crossing at acceptance, but this is a different economic event from immediate taker execution.
Maker fills are conditional: the counterparty chooses to trade against a resting quote, often while price is moving through it. That creates adverse-selection risk. “One tick better than the same-side touch” is a price concession and queue-priority reset on every replacement, not a guaranteed captured tick or profit. Net expectancy requires actual fill-conditional markouts, fees/gas, queue age, cancellation latency and inventory costs.
The official TypeSafe documentation describes Jev as a structured decision/classification layer and stresses measuring classification quality on the deployer's labelled data. Tool routing is a separate capability; this repository's Jev call has one choice output and no tools. Jev does not send a transaction in this code: Market signs and submits only after Trader applies local constraints. See [Pydantic TypeSafe / Jev documentation](https://pydantic.dev/docs/ai/models/typesafe/), especially decision measurement and tool routing.
## Price and transaction lifecycle
### Quote construction
Market.quotePrice converts best bid and ask to integer tick units. A buy is best bid plus quoteInsideTicks; a sell is best ask minus quoteInsideTicks. If the candidate reaches/crosses the opposite touch, it reverts to the same-side touch. Market.encode builds one batchUpdate with one side's price/size arrays, known IDs to cancel, and postOnly=true.
A single batchUpdate can atomically apply its listed cancellations and new order within one EVM transaction. This does not give the bot a one-order invariant: it cancels only IDs already learned from confirmed receipts, cannot include pending or unknown IDs, and does not wait for earlier transactions. A reverted transaction leaves the book unchanged.
### Send, nonce and uncertainty
Market.send signs locally and calls eth_sendRawTransaction. It increments its local nonce and adds the transaction to Market.pending only after the RPC returns a hash. If the call throws, it attempts latest-nonce resynchronization and rethrows. If a node accepted a transaction but its response is lost, the source does not first preserve the signed hash in its pending map. There is no explicit RPC deadline or transaction replacement strategy.
After the send RPC returns, busy clears. A later block can send another transaction while one or more earlier hashes remain pending. Thus busy is a decision-routine gate, not a transaction mutex.
### Receipt polling
confirmPending launches Market.pollPending without awaiting it. Pending hashes are polled concurrently on each block; calls from successive blocks can overlap. A map-presence check prevents duplicate application after another poll removed the same hash, but one slow RPC within Promise.all can delay delivery of other results from that invocation.
A non-null receipt is removed and parsed. Status 0x0 is reverted; any other value, including an absent or unexpected status, is treated as placed. Parsed OrderCreated and OrdersCanceled logs for the local wallet update known IDs. There is no open-order query to verify the resulting state.
After pendingBlocks (default 10) without a receipt, the pending record is removed and emitted as lost; gas is shown as zero for that event and nonce resync is attempted. “Lost” means local polling gave up, not venue proof the transaction cannot still land. The path has no subsequent reconciliation of that uncertain order.
### Fill logs and accounting races
TradeFeed polls Kuru Trade logs in chunks of up to 100 blocks, maintains a process-local cursor and ring, and suppresses fills on its first catch-up poll. It does not persist its cursor, deduplicate by stable transaction/log identity or roll back removed logs after a reorg.
Live harvest applies fills to position even if Trader.orders does not know the order ID. It deletes/resizes a known local order, but has no lifecycle record for an unknown ID. A fill can precede the order-creation receipt. Later applyQuoteResult can insert the original full order size, even if already filled or partially filled. A late receipt can similarly re-add an order after a subsequent replacement. These are concrete ordering gaps.
The dry-run simulator matches prints against local resting quotes by price and print size. It does not model queue position, latency, cancellation races, conditional maker fills, fees or adverse selection; it cannot establish live maker profitability.
Inventory uses signed position and weighted-average cost basis, not FIFO. Opposite fills realize against the average basis and excess quantity flips the position. Gas estimates are attached to quotes, while totals charge gas when receipt results are applied. Displayed trading PnL includes realized/unrealized USD value minus tracked gas; inference spend is separate and not deducted. Venue maker fees are not included.
### Local exposure checks
allowed() uses signed position plus known resting orders and full-size in-flight quotes on the side being considered, then checks absolute exposure against MAX_POSITION_MON. This is side-specific projected signed exposure, not a universal gross-risk guarantee across uncertain transactions. A funded wallet also uses cached margin balances refreshed periodically; source does not reserve/decrement local margin for each unconfirmed order. These are local guards, not exchange-authoritative reconciliation.
## Source map
| Full local path | Role | Limitation relevant to execution |
|---|---|---|
|---|---|---|
| `tradeSizeMon` | 200 (min Kuru order) | **fixed** order size every block — not notional‑scaled |
| /tmp/jev-exec-study-b587759/src/index.ts | Runtime composition | no restore/open-order reconciliation |
| `maxPositionMon` | 1000 | position cap = **5× trade size**; hard stop‑gap |
| /tmp/jev-exec-study-b587759/scripts/ | ABI/RPC probes and smoke scripts | not all scripts are offline/read-only |
Key structural facts: **size is constant** (200 MON), **quote distance is fixed at 1 tick**, and **gas is a static ceiling** — nothing adapts to book depth or volatility at runtime. The exec is deliberately stateless across blocks except for `orders`/`inflight`/`position`.
The UI consumes in-memory snapshots and SSE updates; it has no order command endpoint. A live SSE connection only proves UI-to-server reachability, not that model calls, book data, orders or venue truth are current.
---
## Failure and recovery summary
## 2. The 300‑ms hot path (why it is “exactly two round trips”)
| Condition | Source behavior | Recovery in source |
Per README §“The 300ms budget” and `trader.onBlock`:
1.`confirmPending(block)` → off‑hot‑path `pollPending` (receipts for prior sends) — overlaps the read.
2.`market.readBook()` → **one `eth_call` `getL2Book`** on `READ_RPC_URL` (~18 ms p50 on the public RPC). Optional vault read batched into the same HTTP request.
3.`model.decide(state)` → the Jev call (≈ real inference; `MockModel` sleeps 80 ms to emulate).
4.`market.send(...)` → `signTransaction` + **`eth_sendRawTransaction`** on `RPC_URL` (returns the hash as soon as the pool accepts it).
That is it on the hot path: **one read + one write per block**. Fees, margin, vault, estimateGas, receipts, and fills are **never** on the hot path (they run on later blocks or once at `init()` — see `market.init` / `refresh` / `pollPending`). `gasLimit` is estimated **once at startup** (`initGasLimit`: 1 `estimateGas` + 90k headroom × 1.15) and then **hardcoded** — no per‑block `eth_estimateGas`. Effective gas price = `MAX_FEE_GWEI` cap + `PRIORITY_FEE_GWEI`=2gwei; the cap is “free” so the real cost is base+priority. `gasMon = gasLimit × effectiveFeeWei`.
---
## 3. The decision point (how Jev is *asked*, not what it answers)
`model.ts``QUESTIONS.direction.type = "choice"` with `instructions`:
> **“Will MON be higher or lower than the current mid after `horizonBlocks` more blocks?”**
> goal: trade MON‑USDC on Kuru. Blocks ~300 ms; horizon ≈30 s is the move window. A decision is made every few blocks and held until the next one. The trade crosses the spread (`spreadBps`), so the move must beat that cost.
> criteria: buy = “mid more likely HIGHER after horizon, by > spread”; sell = “LOWER”.
> timing: “immediate‑or‑cancel market order in the next block” (note: this is the mock’s doc; the live path is post‑only maker rest).
The model returns `Action ∈ {buy,sell}` (hold only when late) + `probabilities:{buy,sell,hold}` + `upIn10` (= buy prob, the “will price go up” number). `trader.ts:105` maps it: `wanted = action==="sell" ? "sell" : "buy"` (buy wins ties / default). **Jev never says “how big” or “when to exit”** — size is fixed at 200 MON and there is no TP/SL; the model is asked the same binary every block and a fresh order is placed. This is the crux of how Jev is used: a **stream of independent per‑block binary direction calls**, each consumed by one post‑only maker order.
---
## 4. HOW IT EXECs AGAINST THE BOOK (the portable core)
### 4.1 Book reading — one eth_call, exact‑match decode (`book.ts`)
`readBook()` issues a **single `eth_call getL2Book`** (`0x46fdfbb1`) tagged `latest`; if the Kuru AMM vault is active (`readVaultParams``getVaultParams``0x88bb4f60`), **both calls go in one HTTP batch** — no second round trip. This exists only to beat the SDK path (SDK does 2 sequential eth_calls; jev does 1, or 1 batched). Decoding (`decodeL2Book`, `buildBook`) replicates the Kuru SDK `getFormattedL2OrderBook`**bit‑for‑bit** (parseFloat, floor bids / ceil asks to tick, group by price, same ordering) so `bid/ask/mid/imbalance` match exactly. `buildBook` then emits `levels` (top 5 each side), `depthBps` (10/25/50 bps), `spreadBps`, `imbalance` — the full state fed to `buildState`. A book with an empty side **throws** (`empty book side at block …`), i.e. jev would rather skip a block than guess.
### 4.2 The placement policy — post‑only, 1 tick inside, never crosses (`market.quotePrice`)
```ts
quotePrice(side,book):
bidU=round(book.bid*scale);askU=round(book.ask*scale)// tick units
if(buy&&p>=askU)p=bidU// clamp to touch (spread too tight)
if(sell&&p<=bidU)p=askU// never cross
returnp/scale
```
Two independent guarantees the order **cannot cross the spread**: (a) the price math clamps to the touch when the spread is <thetickstep,and(b)theon‑chaincallisencodedwith`postOnly=true`(the5thargof`batchUpdate`—see`encode`).Thequotesits**one tick inside the touch**wheneverthebookiswideenough—thisisthe“capture”edge:atakerprintingatthetouchfillsourrestingorderonetickearlyandweearnthetick.
### 4.3 The replace‑every‑block mechanic — atomic cancel‑all + one place (`trader.onBlock` + `market.send`)
Everyblock(whennotbusyandwhen`side`isallowed):
```ts
cancel=[...this.orders.keys()].filter(id=>id>0)// confirmed RESTING order ids only
// (inflight txs awaiting receipt are NOT cancelled — they live in this.inflight, keyed by txHash)
Ablockarrivingwhiletheprior`readBook→decide→send`isstillrunningis**not**anewopportunity:itisemittedasa**late block**(`decision.hold {buy:0,sell:0,hold:1}`,noquote,`late:true`),and**no order is placed**.Thisisjev’sserializationprimitive:atmost**one send per block cadence**.Italsomeansjevneverracesitself—thereisexactlyonerestingorder(thenewestconfirmed),andthecancellistalwaysnamestheonepriorconfirmedrestingorder.
Receiptsarepolled**off the hot path**:`confirmPending`runsatthetopofthenext`onBlock`,`Promise.all`over`pending`,one`eth_getTransactionReceipt`perin‑flighthash.
```ts
parseReceipt(r,p):
if(r.effectiveGasPrice)this.feeWei=BN.from(r.effectiveGasPrice)// refresh fee estimate for real
gasMon=this.gasMon(p.gasLimit,effectiveGasPrice??feeWei)// charged on reverts too
Lifecycleofaquote:`sent`→(receipt)`placed`(orderIdrecorded,**only then**insertedinto`this.orders`fornextblock’scancellist)**|**`reverted`(bookmovedthroughthepriceoracancelledidhadalreadyfilled;`reverted`counter++;gasstillcharged)**|**`lost`(noreceiptwithin`pendingBlocks=10`blocks;`status:"lost"`,`gasMon:0`,nonceresynced).`applyQuoteResult`thenmutates`orders`(deletecanceled,setplacedorderId),deletestheinflightentry,adds`gasMon`tototals,and**patches the historical BlockEvent’s `quote`**andfires`onQuote`(SSE`quote`event).
Criticalproperty:**jev never assumes**—anorderisonly“resting”onceitsreceiptproves`OrderCreated`.Untilthenitis`sent`(countstowardthepositioncapvia`restingMon`/`inflight`butisnotinthecancellist).
### 4.6 Fill observation — taker hits our resting order (`trades.ts` + `harvest`)
Jev's structured directional output and feature prompt are research inputs worth testing. Keep order identity, fill economics, unknown-state handling, cancellation truth, exposure reservation, exit authority and reconciliation under FLIGHT's existing execution owner. Treat Jev output as policy input. A separate bounded execution policy should decide price, post-only versus IOC, size and deadline.
## 10. What is actually attractive / portable from Jev’s exec
For model evaluation, record input, raw output, model ID and latency. Score the stated directional horizon on every signal, whether or not it filled; separately score fill-conditional markout, fees/gas, queue age, cancel latency and inventory cost. Calibrate out of sample by regime and side before using probabilities for sizing or risk.
Official Jev documentation: https://pydantic.dev/docs/ai/models/typesafe/
---
The full tracked inventory is available with git -C /tmp/jev-exec-study-b587759 ls-files. Besides runtime code it includes package/lock/config metadata, docs, authoring instructions, RPC/ABI scripts and the Next.js dashboard. The lockfiles pin dependencies, not execution behavior. This static audit does not establish provider reliability, live Jev accuracy, Kuru economics, profitable fills or a deployed runtime configuration.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.