Ship log - order book on-chain, position lines on chart, unified admin
A roundup of the April 28 release - five highlights worth more than a one-liner. Order book reads pool ticks directly via RPC. Position lines overlay on the chart. The admin panel collapses three pages into one. Plus a story about banners that didn't survive contact with production.
Order book straight from pool ticks
The Market page now shows an order book panel beside the price chart. The data isn't from a subgraph - it's pulled live from the Uniswap V3 pool via RPC. The hook calls tickBitmap() to find which ticks are initialized, then ticks() for each one, all through the standard IUniswapV3Pool ABI.
// useOrderBook.ts (excerpt)
const { current: tickSpacing } = poolImmutables.tickSpacing
const range = tickSpacing * 20 * 3 // ~20 levels per side, scales with fee tier
const initialized = await pool.tickBitmap(/* word range */)
const levels = await Promise.all(
initialized.map(t => pool.ticks(t))
)Why this matters: subgraphs lag, sometimes by 10-30 seconds. For a depth view next to a price feed that updates every block, that drift is unacceptable. RPC is heavier per request, but the request budget on Arbitrum is generous and we hit it once every 20 seconds, not per tick.
The visualization is a cumulative-depth gradient instead of a bar chart - asks fade red into the centre, bids fade teal. Spread is computed live and shown above the centre line.
Position lines on the price chart
Open positions for the active pair now render as horizontal lines on the chart - entry, liquidation, take-profit, and stop-loss. Each line carries a PnL label in basis points and a leverage tag. Long positions are green, shorts are red, with dashed style for the liquidation/TP/SL lines.
The data comes from contract state, not user input. You can't drag a line to move a stop - you have to use the order panel and sign a transaction, and the line redraws when the contract acknowledges the change. The polling cycle is 15 seconds.
This required swapping the default chart from the iframe widget to TradingView's TVChartContainer (Charting Library), which exposes the line-drawing API. The iframe is still the fallback for localhost and for the Aurora chain test environment, where the Charting Library isn't whitelisted.
Position table - PnL in grouped rows, fixed
If you've held multiple positions on the same pair, the table groups them by symbol with a summary row above each group. The summary row used to show an aggregate PnL across the legs, which was confusing in two cases:
- When you had a long and a short on the same pair (a hedge) - the aggregate netted out and read like neither position was making money.
- When the legs had different leverage - aggregate PnL didn't reflect the risk distribution.
Fix: the summary row now shows a dash (—) for PnL, just like other non-applicable summary columns. Per-leg PnL in the expanded view is unchanged, with the same green/red colour logic.
Unified /admin page
Three previously separate admin views - Positions, Referral Payouts, Banners - collapsed into one page at /admin with tab navigation. The wrapper enforces a single wallet check at the top, with multi-wallet support: any address in ADMIN_WALLETS (lowercase-normalized) can access all three tabs. We added a second admin wallet alongside the primary so the team has signing redundancy.
The tabs share their loading and admin-guard logic, so adding a fourth tab in the future is a one-component change.
The banner story
We wanted a way to surface promo content (boosted APY, new pair listings) above the trading panel without redeploying the frontend each time. Initial design: a server-side API at /api/banners with file uploads to a uploads/banners/ directory and JSON metadata, gated by ADMIN_SECRET.
It built. It worked locally. It deployed to one server. Then the production host returned 405 Method Not Allowed on the upload endpoint - the production-facing app on app.atomic.green is fronted by static Nginx with no /api/* route handler. The API existed on a different host (newinfo.atomic.green) and we'd been testing there.
We rolled back to static frontend assets - bannerlend.png and bannerref.png, hardcoded targets, two slots rotating every 5 seconds. The deploy script was updated to provision the API path so we can flip back without manual SSH the day the production host gains a Node.js layer. For now: simpler, safer, ships in a build.
Dependency audit
Quietly, but worth flagging: a full npm audit produced 77 findings. We classified them into three buckets and landed Stage 1 - the safe semver fixes (axios, express chain, lodash, tar, undici, follow-redirects, picomatch, serialize-javascript). Stage 2 covers breaking-but-clean upgrades (drop iziswap-sdk in favour of a local ABI copy). Stage 3 is the migration from ethers@5 to ethers@6, tracked separately because the API surface differs enough that we want to do it as a single dedicated PR.
The triage notes are in DEPENDENCY_UPDATE_PLAN.md at the repo root.
Coming up
The next release window is the routing rewrite - a deeper post on that is in draft. Short version: position-open latency is down 78% on average, and we want to explain how before the comparison stops being interesting.