$npx skillfedfor your agent
REPO

jev-trader's entire design bends around Monad's 300 ms block window

on: jarrodwatts/jev-trader

One AI call per block, one order per block. That is the entire design philosophy of jev-trader, a market-making bot for the Kuru MON-USDC order book on Monad. Every ~300 ms, a model inspects the live order book and answers buy or sell. The bot then posts a post-only limit order one tick inside the best price on that side, canceling whatever it had resting before. Fills come from takers hitting the resting order, so the bot collects the spread rather than paying it.

The timing constraint is real and the engineering reflects it. Each block gives roughly 300 ms, and the hot loop makes exactly two RPC calls: one eth_call to read the book (around 18 ms on the public RPC) and one eth_sendRawTransaction that returns on acceptance. Gas estimation is skipped entirely — Monad charges the gas limit regardless, so the limit is hardcoded or derived once at startup. Static type-2 fees remove the gas price lookup. Measured in dry-run with the mock model, the full loop runs at a p50 of 100 ms, with roughly 80 ms of that consumed by the mock's inference stand-in.

The model interface is cleanly separated. The default is a momentum heuristic called MockModel. Set the right environment variables and it swaps to JevModel, which uses the AI SDK's experimental_evaluate to ask a TypeSafe Jev model whether price will be up over a configurable horizon (default 100 blocks, roughly 30 seconds). The response carries explicit buy and sell probabilities, a latency reading, and a late flag — if the model missed the block window, the bot holds and posts nothing rather than acting on stale information.

The event schema is worth examining. Every block emits a structured object covering the mid price, spread in basis points, the model's probability distribution, the order that was sent, gas cost, position, and running P&L. Receipts arrive asynchronously one or two blocks later as separate SSE events, with status transitioning from sent to placed or reverted. Fills are detected through eth_getLogs polling — they arrive in someone else's transaction, not the bot's own. A dry-run mode runs the full book-reading and decision logic with simulated fills, making it genuinely useful for testing without a funded wallet.

Position accounting handles the edge cases honestly. When a position cap or insufficient margin blocks the preferred side, the quote flips to the other side with a capped flag, while the model's original probability call is still recorded. That separation between what the model wanted and what the bot actually did is a small but meaningful design choice for anyone trying to evaluate model quality separately from execution constraints.

The codebase is compact and well-decomposed across eight source files. A bench script compares the hand-encoded book reader against the SDK for both correctness and latency. A dry-encode script signs orders offline and asserts the calldata matches. These are not afterthoughts — they reflect that at 300 ms per decision, correctness of the encoding path matters as much as the model.

A tight, honest market-making loop that treats the 300 ms block window as a hard constraint and engineers every component around it.

Install it

Sources & links