Skip to content

API ReferenceAPI reference

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

Best route, exact quote, and a simulated transaction for an exact-input swap.

Query parameters

NameTypeRequiredDefaultDescription
intoken id✅ERC20 address, or HYPE for native
outtoken id✅Must differ from in
amountuint string✅Input in base units, > 0
recipientaddress✅Receives the output
senderaddressrecipientSigns the tx; used for balance, allowance and gas simulation
slippageBpsinteger | auto500–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
signaturehex (65 bytes)Permit2 signature (2nd call)
permit2Nonceuint stringrandomNonce of the signed permit (2nd call)
permit2Deadlineuint stringtx deadlineDeadline of the signed permit (2nd call)

Response 200

FieldTypeDescription
amountInuint stringEcho of amount
amountOutuint stringExpected output to the recipient, net of the router fee and measured token taxes
minOutuint stringFloor written into the tx
feeBpsintegerRouter fee (0 on the launchpad path)
priceImpactBpsinteger | nullOutput vs. the no-impact spot output of the same route
taxBpsintegerMeasured sell tax of in + buy tax of out, already deducted from amountOut
routerAmountOutuint stringOutput before token taxes
venue"launchpad"Only present when the bonding curve beat the aggregator
warningsstring[]Token warnings prefixed in: / out:, launchpad notice
amountOutUsdnumber | nullamountOut in USD
slippageobject{ bps, auto, recommendedBps }: the tolerance used, whether it was auto, and the recommendation (see below)
savingsobject | 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
scannedobject{ pools, routes, quoted }: distinct pools on the candidate paths, paths enumerated, routes quoted on-chain ("37 pools scanned")
gas.estimateuint stringGas units of the route (model)
gas.costInTokenOutuint stringThat gas, priced in tokenOut (used to rank routes)
splits[]arrayLegs of the route
splits[].amountInuint stringPart of amountIn sent down this leg
splits[].route[]arrayHops of the leg, in order
…route[].protocolstringv2, v3, v4, wrap, launchpad solidly
…route[].pooladdress | bytes32Pool address; for v4, the 32-byte pool id
…route[].factoryaddress | nullFactory, or the PoolManager for v4 (null for wrap)
…route[].tokenIn / tokenOutaddressTokens of this hop (WHYPE stands for native)
…route[].feeintegerLP fee, hundredths of a bp (3000 = 0.3%)
wrappedNativeaddress | nullWHYPE used for native in/out; null when neither side is native
txobject{ from, to, data, value, chainId }; adds warning when the simulation failed
approvalobject | null{ to, data } approve to send first
permit2object | null{ typedData } to sign (Permit2 path)
simulationobject{ ok, gas? , error? }
blockuint stringIndexer 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

NameTypeRequiredDescription
idsstring✅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

NameTypeDefaultDescription
qstringSubstring of symbol or name, or an exact address
limitinteger50Max 500
strict"true"Verified tokens only
sortpools | volumepoolsvolume = 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

FieldTypeDescription
addressstringChecksummed address, or HYPE
symbol, namestringFrom the contract
decimalsinteger
poolsintegerLive pools containing it
verifiedbooleanOn the curated list
logoURIstring | nullVerified tokens only
tagsstring[]Verified tokens only
warningsstring[]See Token safety
checkobject | nullProbe result: status (ok/taxed/honeypot/unknown), buyTaxBps, sellTaxBps, transferTaxBps, transferBlocked, checkedAt
pricenumber | nullUSD
change24hnumber | nullPercent vs. the price 24 h ago (null until 24 h of samples exist)
volume24hUsdnumber24h 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

NameTypeDescription
owneraddressOptional 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

NameTypeRequiredDefaultDescription
in, outtoken id✅
amountuint string✅Total input
owneraddress✅Signs, receives output and refunds
minOutuint string✅> 0, for the whole amount; pro rata per slice
slicesinteger11–1000, ≤ amount
intervalinteger (s)if slices > 10
expiresIninteger (s)604800

Response: { order: { perSlice, minOutPerSlice, slices, interval, expiry }, tx, approval, simulation }. The approval spender is the OrderBook.

GET /orders/cancel

NameTypeRequired
iduint 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.

FieldDescription
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 }.

typeWhen
order_createdOrderCreated on-chain
order_filledA slice filled; data: amountIn, amountOut, remaining
order_completedThe last slice filled
order_cancelledCancelled; data.refunded
order_expiredExpired and refunded by the keeper
dca_skippedA 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

NameDefaultDescription
venueSubstring of the venue name: uniswap, aero, nest, solidly, or a launchpad label
tokenOnly pools holding this token (HYPE = any WHYPE)
sorttvltvl, volume24h, fees24h, newest
limit, offset50, 0Paging (limit ≤ 200)

Returns { total, pools: Pool[] } (live pools with a TVL).

Pool

FieldDescription
addressPool address, or the 32-byte id for v4
venue, protocolUniswap V3, Aero CL, Nest CL, Solidly, a launchpad label…; v2 v3 slipstream algebra v4 solidly
factory, hooksFactory or PoolManager; v4 hook
feeBpsCurrent 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, marketCapUsdOf base (market cap = total supply × price)
tvlUsdReserves (V2/Solidly), pool balances (V3 family), in-range virtual reserves (v4, approximate); refreshed every 5 min
volumeUsd{ "1h", "4h", "12h", "24h" }
fees24hUsd, swaps24h
createdAtCreation time (from the creation block)
poolsInPairLive 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

NameDefaultDescription
tokenToken id (HYPE or an indexed address; unknown → 404)
interval1h1m 5m 15m 1h 4h 1d
from, tolast 100 candlesUnix 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

NameRequiredDescription
pool✅V3 pool address or v4 pool id
owner✅Pays, and receives the position NFT
amount0 / amount1one or bothBase units; the other is derived from the price and range
rangeSee above (ignored with tokenId: give the position's ticks)
tokenIdAdd to this existing position instead of minting
nativetrue: pay the pool's WHYPE side in native HYPE (V3). v4 pools with native HYPE always take HYPE
slippageBpsDefault 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

FieldDescription
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 }
volumeByVenue30dUSD volume per venue kind
splitShare30dShare 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

VersionChange
v1Versioned paths (/v1/...), partner API keys, x-request-id, requestId in errors, OpenAPI spec
v1.3Liquidity: /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.1Quote 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
lane.markets · Make the most of every move.