API Reference
API reference
Base URL: https://api.lane.markets/v1 (testnet instance today: https://api-production-7070.up.railway.app/v1). Machine-readable: openapi.yaml (served at GET /v1/openapi.yaml).
Conventions (amounts, native HYPE, errors, request ids): API at a glance.
Auth and limits: Access.
- Swap:
GET /quote - Tokens and prices:
GET /price,GET /tokens,GET /tokens/{address},WS /ws/prices - Orders:
GET /orders,GET /orders/create,GET /orders/cancel - Account:
GET /portfolio,GET /history,GET /portfolio/pnl,GET /portfolio/transfers,GET /notifications,WS /ws/account - Pools and charts:
GET /pools,GET /pools/stats,GET /pools/{id},GET /pools/{id}/chart,GET /pools/{id}/activity,GET /activity,WS /ws/activity,GET /candles - Liquidity:
GET /liquidity/managers,GET /liquidity/positions,GET /liquidity/quote,GET /liquidity/add,GET /liquidity/remove,GET /liquidity/collect - Analytics:
GET /stats - API keys:
GET /keys/challenge,POST /keys,GET /keys/me,DELETE /keys/me - Share cards:
GET /share/swap/{tx}.png,GET /share/pnl/{address}.png - System:
GET /health,GET /config,GET /openapi.yaml
Swap
GET /quote
Best route, exact quote, and a simulated transaction for an exact-input swap.
Query parameters
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
in | token id | ✅ | ERC20 address, or HYPE for native | |
out | token id | ✅ | Must differ from in | |
amount | uint string | ✅ | Input in base units, > 0 | |
recipient | address | ✅ | Receives the output | |
sender | address | recipient | Signs the tx; used for balance, allowance and gas simulation | |
slippageBps | integer | auto | 50 | 0–5000, or auto to use slippage.recommendedBps. minOut = amountOut × (10000 − slippageBps) / 10000 | |
permit2 | "true" | ERC20 input: use Permit2 instead of an approve to the Executor. Flow | ||
signature | hex (65 bytes) | Permit2 signature (2nd call) | ||
permit2Nonce | uint string | random | Nonce of the signed permit (2nd call) | |
permit2Deadline | uint string | tx deadline | Deadline of the signed permit (2nd call) |
Response 200
| Field | Type | Description |
|---|---|---|
amountIn | uint string | Echo of amount |
amountOut | uint string | Expected output to the recipient, net of the router fee and measured token taxes |
minOut | uint string | Floor written into the tx |
feeBps | integer | Router fee (0 on the launchpad path) |
priceImpactBps | integer | null | Output vs. the no-impact spot output of the same route |
taxBps | integer | Measured sell tax of in + buy tax of out, already deducted from amountOut |
routerAmountOut | uint string | Output before token taxes |
venue | "launchpad" | Only present when the bonding curve beat the aggregator |
warnings | string[] | Token warnings prefixed in: / out:, launchpad notice |
amountOutUsd | number | null | amountOut in USD |
slippage | object | { bps, auto, recommendedBps }: the tolerance used, whether it was auto, and the recommendation (see below) |
savings | object | null | { vsBestSinglePool, usd, bestSinglePool: { amountOut, protocol, pool } }: our output minus the exact quote of the best route through one pool (both after the router fee). null when no single pool connects the pair |
scanned | object | { pools, routes, quoted }: distinct pools on the candidate paths, paths enumerated, routes quoted on-chain ("37 pools scanned") |
gas.estimate | uint string | Gas units of the route (model) |
gas.costInTokenOut | uint string | That gas, priced in tokenOut (used to rank routes) |
splits[] | array | Legs of the route |
splits[].amountIn | uint string | Part of amountIn sent down this leg |
splits[].route[] | array | Hops of the leg, in order |
…route[].protocol | string | v2, v3, v4, wrap, launchpad solidly |
…route[].pool | address | bytes32 | Pool address; for v4, the 32-byte pool id |
…route[].factory | address | null | Factory, or the PoolManager for v4 (null for wrap) |
…route[].tokenIn / tokenOut | address | Tokens of this hop (WHYPE stands for native) |
…route[].fee | integer | LP fee, hundredths of a bp (3000 = 0.3%) |
wrappedNative | address | null | WHYPE used for native in/out; null when neither side is native |
tx | object | { from, to, data, value, chainId }; adds warning when the simulation failed |
approval | object | null | { to, data } approve to send first |
permit2 | object | null | { typedData } to sign (Permit2 path) |
simulation | object | { ok, gas? , error? } |
block | uint string | Indexer block the quote is based on |
Auto slippage (slippage.recommendedBps): 30 bps, plus half the price impact, plus 20 bps per extra swap, plus 100 bps if a token is taxed, plus 50 bps through a hooked v4 pool; between 30 and 1500 bps. 300 bps on the launchpad path.
Errors: 400 bad input · 404 no route · 422 honeypot output · 429 · 503 backfilling / not configured.
Example
curl "$API/quote?in=HYPE&out=0x7D29d8047B905000459c0E80c34a26CeedcB47b2&amount=1000000000000000000&recipient=0xYou&slippageBps=50"Tokens and prices
GET /price
| Name | Type | Required | Description |
|---|---|---|---|
ids | string | ✅ | Comma-separated token ids, max 100 |
{ "quote": "USDC", "data": { "HYPE": 24.81, "0x7D29…": 1, "0xdead…": null } }Spot price of one whole token in USDC (net of LP fees, no size impact), computed in memory. null = no route to USDC.
GET /tokens
| Name | Type | Default | Description |
|---|---|---|---|
q | string | Substring of symbol or name, or an exact address | |
limit | integer | 50 | Max 500 |
strict | "true" | Verified tokens only | |
sort | pools | volume | pools | volume = popular tokens (24h volume across all pools) |
Returns { tokens: Token[] }: native HYPE first (when it matches q), then verified tokens, then by live pool count. Only tokens with at least one live pool are listed.
Token
| Field | Type | Description |
|---|---|---|
address | string | Checksummed address, or HYPE |
symbol, name | string | From the contract |
decimals | integer | |
pools | integer | Live pools containing it |
verified | boolean | On the curated list |
logoURI | string | null | Verified tokens only |
tags | string[] | Verified tokens only |
warnings | string[] | See Token safety |
check | object | null | Probe result: status (ok/taxed/honeypot/unknown), buyTaxBps, sellTaxBps, transferTaxBps, transferBlocked, checkedAt |
price | number | null | USD |
change24h | number | null | Percent vs. the price 24 h ago (null until 24 h of samples exist) |
volume24hUsd | number | 24h volume of all pools holding the token |
GET /tokens/{address}
One token, as above plus price (USDC). Runs the probe now if this token was never checked (the call can then take a second or two). 404 if the token isn't in any indexed pool.
WS /ws/prices
Send {"subscribe": [ids…]} (max 100). Receive {"prices": {id: price}} at once, then only changed prices after each block. See Live prices.
Orders
All three return 503 if the server has no ORDERBOOK_ADDRESS.
GET /orders
| Name | Type | Description |
|---|---|---|
owner | address | Optional filter |
Each order also has status (the keeper's last check, null if no keeper runs): { state, bestOut, minOut, checkedAt } with state = scheduled (next slice not due), waiting_price (due, market below the floor: "waiting for price"), no_route or filling.
{ "orders": [ { "id": "3", "owner": "0x…", "tokenIn": "HYPE", "tokenOut": "0x7D29…", "remaining": "500000000000000000",
"perSlice": "250000000000000000", "minOutPerSlice": "5600000", "interval": "86400",
"nextFillAt": "1762500000", "expiry": "1762900000" } ] }GET /orders/create
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
in, out | token id | ✅ | ||
amount | uint string | ✅ | Total input | |
owner | address | ✅ | Signs, receives output and refunds | |
minOut | uint string | ✅ | > 0, for the whole amount; pro rata per slice | |
slices | integer | 1 | 1–1000, ≤ amount | |
interval | integer (s) | if slices > 1 | 0 | |
expiresIn | integer (s) | 604800 |
Response: { order: { perSlice, minOutPerSlice, slices, interval, expiry }, tx, approval, simulation }. The approval spender is the OrderBook.
GET /orders/cancel
| Name | Type | Required |
|---|---|---|
id | uint string | ✅ |
Response: { tx: { to, data, value: "0", chainId } }. The owner can send it any time; anyone after expiry.
Account
GET /portfolio
owner (address, required). Returns { owner, totalUsd, tokens: [{ address, symbol, name, decimals, balance, price, usd }] }. Non-zero balances only. Details.
GET /history
owner (address, required), limit (default 50, max 200). Returns { owner, items: [...] }, newest first; item type is Swapped, OrderCreated, OrderFilled or OrderCancelled. Details.
GET /portfolio/pnl
owner (address, required). Average-cost PnL in USD at trade time. Sources: swaps routed through Lane (complete) and swaps on other venues that the indexer attributed to the wallet while live (tx.from). WHYPE variants and native HYPE count as one asset, HYPE. Cached 30 s per wallet.
| Field | Description |
|---|---|
summary | { realizedUsd, unrealizedUsd, totalUsd, trades, laneTrades } |
positions[] | Per token: bought {qty, usd}, sold {qty, usd}, remaining {qty, valueUsd}, avgCostUsd, realizedUsd, unrealizedUsd, firstBuy, ageSeconds, trades |
calendar[] | Per day (UTC): { t, realizedUsd, trades, volumeUsd } |
distribution[] | Realized return of each sell, in buckets: < -50% … > 100% |
equity[] | Daily { t, valueUsd, realizedCumUsd } over the last 180 days: tracked holdings at each day's close |
notes[] | Method and caveats (e.g. tokens held before the history are valued at their sale price: no gain or loss) |
GET /portfolio/transfers
owner (required), limit (1–200, default 50). Token transfers in and out, from the explorer's Etherscan-compatible API (explorerApi in the network file): { tx, block, time, token, symbol, decimals, amount, direction, counterparty, partOfSwap }. partOfSwap marks transfers that are legs of a known swap. 502 if the explorer is down.
GET /notifications
owner (required), since (last id seen, default 0), limit (1–200). Newest first: { id, ts, type, orderId, tx, data }.
type | When |
|---|---|
order_created | OrderCreated on-chain |
order_filled | A slice filled; data: amountIn, amountOut, remaining |
order_completed | The last slice filled |
order_cancelled | Cancelled; data.refunded |
order_expired | Expired and refunded by the keeper |
dca_skipped | A DCA slice is due but the market is below its floor: the buy is held (not lost) until the price is back; once per slice |
WS /ws/account
Send {"owner": "0x…"}; receive {"notification": {...}} for that wallet as it happens.
Pools and charts
All values in USD come from the event store: every swap and liquidity change of every indexed pool, valued when it was ingested on its most trusted side (USDC, then WHYPE, then verified tokens), so a memecoin with an absurd price can't inflate volumes. History starts at deployment, plus a one-time backfill (EVENTS_BACKFILL_HOURS, default 7 days, valued at backfill-time prices).
GET /pools
| Name | Default | Description |
|---|---|---|
venue | Substring of the venue name: uniswap, aero, nest, solidly, or a launchpad label | |
token | Only pools holding this token (HYPE = any WHYPE) | |
sort | tvl | tvl, volume24h, fees24h, newest |
limit, offset | 50, 0 | Paging (limit ≤ 200) |
Returns { total, pools: Pool[] } (live pools with a TVL).
Pool
| Field | Description |
|---|---|
address | Pool address, or the 32-byte id for v4 |
venue, protocol | Uniswap V3, Aero CL, Nest CL, Solidly, a launchpad label…; v2 v3 slipstream algebra v4 solidly |
factory, hooks | Factory or PoolManager; v4 hook |
feeBps | Current LP fee |
token0, token1, base, quote | { address, symbol, decimals }. base is the token being priced; quote is USDC/WHYPE when the pool has one |
priceUsd, change24h, marketCapUsd | Of base (market cap = total supply × price) |
tvlUsd | Reserves (V2/Solidly), pool balances (V3 family), in-range virtual reserves (v4, approximate); refreshed every 5 min |
volumeUsd | { "1h", "4h", "12h", "24h" } |
fees24hUsd, swaps24h | |
createdAt | Creation time (from the creation block) |
poolsInPair | Live pools for the same two tokens |
GET /pools/stats
{ tvlUsd, pools, volume24hUsd, fees24hUsd, swaps24h, openPositions }. openPositions is null: it needs indexing of every LP position (a wallet's own are in /liquidity/positions).
GET /pools/{id}
A Pool plus tvlChange7d (%), aprPct (24h fees × 365 / TVL), liquidityProviders (distinct wallets that added liquidity, live-ingested), firstSeen, key (v4 PoolKey).
GET /pools/{id}/chart
interval 1h (default, 7 days) or 1d (90 days), days (1–365). Returns { volume: [{ t, volume, fees, swaps }], tvl: [{ t, tvl }] }.
GET /pools/{id}/activity
limit (1–200), before (block, for paging). Swaps, adds and removes, newest first: { tx, block, time, pool, type, wallet, valueUsd, amounts[], base, quote }. type is buy / sell (of base), add or remove. wallet is the transaction sender (live events; null for backfilled ones).
GET /activity
The same, across all pools.
WS /ws/activity
Send {"pools": ["0x…", …]} (max 50) or {"all": true}; receive {"items": [...]} as blocks are indexed.
GET /candles
| Name | Default | Description |
|---|---|---|
token | Token id (HYPE or an indexed address; unknown → 404) | |
interval | 1h | 1m 5m 15m 1h 4h 1d |
from, to | last 100 candles | Unix seconds; at most 1000 candles |
{ token, interval, priceUsd, change24h, candles: [{ t, o, h, l, c }] }. Built from per-minute price samples; a bucket without a sample repeats the previous close. For the Trade chart: 1H = 1m, 1D = 15m, 1W = 1h, 1M = 4h.
Liquidity
Uniswap V3 and V4 positions through the venue's own position manager (guide). Every builder returns { approvals[], tx, simulation }: send the approvals in order, call again, then send tx.
Range parameters (quote, add): tickLower + tickUpper, or priceLower + priceUpper (token1 per token0, human units), or fullRange=true; default ±10% around the price. Rounded outward to the tick spacing.
GET /liquidity/managers
{ managers: [{ kind: "v3", address, factory, weth9, trusted } | { kind: "v4", address, poolManager, trusted }] }. Only trusted managers (configured in the network file) are used; others are discovered candidates.
GET /liquidity/positions
owner (required). Newest 50 positions per manager, by value: { manager, protocol, tokenId, pool, feeBps, token0, token1, tickLower, tickUpper, priceLower, priceUpper, currentPrice, liquidity, amounts { token0, token1 }, valueUsd, fees { token0, token1, usd }, inRange, inRangePct30d, aprEstimatePct, key }. key is the v4 PoolKey (native: true when currency0 is HYPE).
GET /liquidity/quote
pool (V3 address or v4 id), a range, amount0 and/or amount1 (base units). Returns { pool, manager, tickLower, tickUpper, tickSpacing, currentTick, priceLower, priceUpper, currentPrice, token0, token1, liquidity, amount0, amount1, valueUsd, inRangePct30d }. No wallet needed.
GET /liquidity/add
| Name | Required | Description |
|---|---|---|
pool | ✅ | V3 pool address or v4 pool id |
owner | ✅ | Pays, and receives the position NFT |
amount0 / amount1 | one or both | Base units; the other is derived from the price and range |
| range | See above (ignored with tokenId: give the position's ticks) | |
tokenId | Add to this existing position instead of minting | |
native | true: pay the pool's WHYPE side in native HYPE (V3). v4 pools with native HYPE always take HYPE | |
slippageBps | Default 50: minimums (V3) or maximums (V4) around the computed amounts |
Returns the range, liquidity, amount0, amount1, valueUsd, approvals, tx, simulation.
GET /liquidity/remove
manager, tokenId, owner (required), percent (1–100, default 100), slippageBps. Removes that share of the liquidity and sends the tokens plus all uncollected fees to the owner; 100% also burns the NFT. Returns liquidityRemoved, expected { amount0, amount1 }, tx, simulation.
GET /liquidity/collect
manager, tokenId, owner (required). Collects the fees only.
Analytics
GET /stats
| Field | Description |
|---|---|
totals | { volumeUsd, swaps, avgSwapUsd, savedUsd, feesUsd } over every swap routed through Lane |
today | { volumeUsd, swaps, projectedVolumeUsd } (projection = today's pace over 24 h) |
daily[] | 90 days of { day, swaps, volume, fees, saved } |
revenue | { convertedUsd, conversions }: fees converted by the FeeCollector |
topRoutes[] | 30 days, by volume: { path, swaps, volume } |
volumeByVenue30d | USD volume per venue kind |
splitShare30d | Share of swaps split across several routes |
noRoute24h | { quotes, pairs } asked for with no route ("No lane") |
savedUsd counts a swap's savings when the transaction sent is exactly the one /quote returned.
API keys
Self-serve, by wallet signature (no email). A self-serve key identifies you and tracks your usage. It starts at the public tier and shares your IP's limit; the operator can raise it (pnpm keys:limit). See Access.
GET /keys/challenge
address (required). Returns { message, expiresAt }: sign message with that wallet (personal_sign). Valid 10 minutes, single use.
POST /keys
JSON body { address, signature, label? }. Returns { key, prefix, owner, label, perMinute, createdAt }. The key is shown once; only its hash is stored. Errors: 401 bad signature, 409 the wallet already has 3 active keys, 429 more than 5 keys from this IP today.
GET /keys/me
With Authorization: Bearer <key>: { type, prefix, owner, label, perMinute, createdAt, usage: { last30Days, daily[] } }.
DELETE /keys/me
Revokes the calling key: 401 from the next request on.
Share cards
GET /share/swap/{tx}.png
A 1200×630 PNG for a swap routed through Lane: pair, amounts, USD value, savings vs. one pool, venues. 404 if the tx isn't a Lane swap.
GET /share/pnl/{address}.png
A 1200×630 PNG of a wallet's PnL (total, realized, unrealized, trades, best token).
GET /share/swap/{tx}, GET /share/pnl/{address}
HTML pages carrying the card as their og:image / twitter:card preview, which redirect to the app (APP_URL). Post these links ("Share on X"): X shows the card.
System
GET /health
Not rate limited. Always 200; with ?strict=1, 503 unless fresh (for uptime monitors).
{ "ready": true, "lastBlock": "24263641", "fresh": true, "reasons": [], "chainHead": "24263642", "lagBlocks": 1,
"lastSyncSecondsAgo": 1, "keeper": { "address": "0x…", "balanceHype": "0.42" },
"pools": { "v3": 2263, "v2": 1456, "v4": 767, "solidly": 0 }, "executor": "0x…", "orderBook": "0x…", "keeperEnabled": true }reasons lists what makes the instance not fresh: still loading, lagging behind the chain, no recent successful sync, keeper balance low.
ready turns true once the initial backfill or snapshot replay is done; /quote returns 503 until then.
GET /config
{ "name": "Elysium Testnet", "chainId": 99801, "rpcUrl": "https://testnet-rpc.elysium.kinetiq.xyz", "explorer": "https://testnet.elysium.kinetiq.xyz" }The public RPC from the network file (never the server's private RPC_URL). Used by wallets to add or switch the chain.
GET /openapi.yaml
The OpenAPI 3.1 spec of this API.
Changelog
| Version | Change |
|---|---|
| v1 | Versioned paths (/v1/...), partner API keys, x-request-id, requestId in errors, OpenAPI spec |
| v1.3 | Liquidity: /liquidity/managers, /positions, /quote, /add, /remove, /collect (Uniswap V3/V4) |
| v1.2 | /health: fresh, reasons, chainHead, lagBlocks, lastSyncSecondsAgo, keeper {address, balanceHype}, ?strict=1; keeper boolean renamed keeperEnabled |
| v1.1 | Quote savings, scanned, slippage (slippageBps=auto), amountOutUsd; tokens price/change24h/volume24hUsd, sort=volume; pools, pool detail, charts, activity (+WS), candles, analytics; PnL, transfers, notifications (+WS), order status; self-serve API keys; share cards |