Liquidity API
Liquidity API
Section titled “Liquidity API”The /v1/liquidity/* endpoints abstract the Snowside subnet (Avalanche L1)
behind https://ecashfarm.com/v1 so that front-ends — our own /liquidity page
and 3rd-party partners such as alpha.pecx.shop — never talk to Snowside
directly. The server is a read + broadcast relay only (AGENTS.md rule 5):
it holds no funds, no keys, and never signs. Transaction building and signing
stay 100% client-side.
Base URL
Section titled “Base URL”https://ecashfarm.com/v1/liquidityAll responses are JSON, CORS-enabled (Access-Control-Allow-Origin: *), and
require no authentication on reads.
Modes: preview vs live
Section titled “Modes: preview vs live”Every response carries a mode field:
preview— Snowside Uniswap is not yet deployed. Contract addresses are placeholders;stats/pools/quotereturn deterministic mocked values so integrators can build the full flow now.live— Snowside Uniswap is deployed.configreturns real addresses; stats/pools/quote relay real on-chain reads from Snowside.
Detect mode from GET /v1/liquidity/config → contractsDeployed (or mode).
Do not hardcode addresses — always read them from /config.
Rule 5 boundary (important for integrators)
Section titled “Rule 5 boundary (important for integrators)”| Operation | Where it happens |
|---|---|
| Reads (config, stats, pools, quote) | Server relays from Snowside / D1 |
| Transaction building (calldata) | Client-side |
| Signing | Client-side (browser wallet) |
| Broadcast | Relay of pre-signed raw txs only (server never signs) |
The server never sees private keys or holds funds. This is the same read/broadcast-relay shape the API uses for Solana today, applied to Snowside.
GET /v1/liquidity/config
Section titled “GET /v1/liquidity/config”Chain, token, and Uniswap contract metadata. D1-backed (singleton row,
updateable without a worker redeploy). This is the single source of truth —
both ecashfarm.com/liquidity and 3rd-party front-ends consume it.
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "preview", "chain": { "chainId": 0, "name": "Snowside", "rpcUrl": "" }, "tokens": { "PECX": { "symbol": "pECX", "decimals": 8, "address": "" }, "USDC": { "symbol": "USDC", "decimals": 6, "address": "" }, "BTCB": { "symbol": "BTC.b", "decimals": 8, "address": "" } }, "v2": { "factory": "", "router": "", "feeBps": 30 }, "v3": { "factory": "", "router": "", "nonfungiblePositionManager": "", "quoter": "", "feeTiers": [ { "fee": 500, "bps": 5, "label": "0.05%", "hint": "Stable pairs (USDC↔BTC.b)" }, { "fee": 3000, "bps": 30, "label": "0.30%", "hint": "Moderate volatility (USDC↔pECX)" }, { "fee": 10000,"bps": 100, "label": "1%", "hint": "Exotic / volatile pairs" } ] }, "pairs": [ { "id": "PECX-USDC", "a": "PECX", "b": "USDC", "label": "pECX / USDC" }, { "id": "BTCB-PECX", "a": "BTCB", "b": "PECX", "label": "BTC.b / pECX" } ], "contractsDeployed": false, "updatedAt": 1717600000}Token decimals (do not assume 18)
Section titled “Token decimals (do not assume 18)”- pECX: 8 decimals (follows Bitcoin — 1 pECX = 10⁸ base units)
- USDC: 6 decimals (Circle standard)
- BTC.b: 8 decimals (Avalanche wrapped BTC)
Two pairs only — the platform focuses on pECX:
- pECX / USDC — primary pair (trade pECX against the stablecoin)
- BTC.b / pECX — BTC bridge pair (move BTC into/out of the pECX ecosystem)
Volatile-left convention (base / quote): price reads as “1 base = ? quote”.
Integrator usage
Section titled “Integrator usage”- Fetch
/configon load. - If
mode === 'preview', render the UI in preview mode (no real wallet reads needed — use the mocked stats/pools/quote endpoints). - If
mode === 'live', use the returned addresses +chainIdto build Uniswap router calls client-side; the user signs in their browser wallet.
Cache-Control: public, max-age=60 — cache for up to 60s.
GET /v1/liquidity/stats
Section titled “GET /v1/liquidity/stats”Network-wide aggregates across all pECX liquidity pools. Mocked
(deterministic placeholders) while mode=preview; backed by real Snowside
reads aggregated into D1 when mode=live. Responses carry mocked: true
so integrators can detect placeholder data.
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "preview", "mocked": true, "network": { "tvlUsd": 402000, "volume24hUsd": 20700, "volume7dUsd": 129700, "fees24hUsd": 62.1, "fees7dUsd": 389.1, "aprPct": 5.05, "liquidityProviders": 60, "poolCount": 2, "activePairs": ["PECX-USDC", "BTCB-PECX"] }, "updatedAt": null}aprPct is the blended network APR annualized from fees7dUsd / tvlUsd.
GET /v1/liquidity/stats/{pair}
Section titled “GET /v1/liquidity/stats/{pair}”Per-pair aggregates. Accepts both orderings — PECX-USDC and USDC-PECX
resolve to the same pair (Uniswap orders token0/token1 by address
on-chain; the id is a display convention only).
Parameters
Section titled “Parameters”| Name | In | Required | Values |
|---|---|---|---|
pair |
path | yes | PECX-USDC, BTCB-PECX (reverse orderings accepted) |
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "preview", "mocked": true, "pair": "PECX-USDC", "label": "pECX / USDC", "version": "v2", "feeBps": 30, "price": 0.9, "priceChange24hPct": 2.4, "reserves": { "a": "50000.00000000", "b": "45000.000000" }, "reservesUsd": { "a": 45000, "b": 45000 }, "tvlUsd": 90000, "volume24hUsd": 12500, "volume7dUsd": 78400, "fees24hUsd": 37.5, "fees7dUsd": 235.2, "aprPct": 13.6, "liquidityProviders": 42, "updatedAt": null}version: which Uniswap version the pool uses (v2= Basic Provider,v3= Advanced Provider).reserves.a/reserves.b: raw reserves as decimal strings respecting token decimals (pECX 8dp, USDC 6dp, BTC.b 8dp). Parse as strings; do not assume integer base units.price: quote per base (bpera), e.g.0.9USDC per pECX.aprPct: annualized fromfees7dUsd / tvlUsd.
Errors
Section titled “Errors”404— unknown pair (lists available pairs).
GET /v1/liquidity/pools
Section titled “GET /v1/liquidity/pools”Raw Uniswap pool fields for every pair — what an integrator needs to render
positions, chart ticks, and compute swap math client-side. Mocked
(deterministic placeholders) while mode=preview; backed by real Snowside
reads when mode=live.
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "preview", "mocked": true, "pools": [ { "pair": "PECX-USDC", "label": "pECX / USDC", "version": "v2", "feeBps": 30, "fee": 3000, "token0": "PECX", "token1": "USDC", "reserves": { "token0": "50000.00000000", "token1": "45000.000000" }, "lpTotalSupply": "47434.165012496", "price": 0.9, "kLast": "2250000000.000000000000000000" }, { "pair": "BTCB-PECX", "label": "BTC.b / pECX", "version": "v3", "feeBps": 30, "fee": 3000, "tickSpacing": 60, "token0": "BTCB", "token1": "PECX", "slot0": { "sqrtPriceX96": "79228162514264337593543950336", "tick": 0, "observationCardinality": 1, "unlocked": true }, "liquidity": "17333333333333", "price": 86666.67, "tickCurrent": 0 } ], "count": 2, "updatedAt": null}- v2 pools expose
reserves(token0/token1as decimal strings respecting token decimals),lpTotalSupply(LP token total supply),kLast(reserve0 * reserve1). LP tokens are fungible ERC-20s. - v3 pools expose
slot0(sqrtPriceX96as a Q64.96 big-int string,tickas an integer, plus the observation cardinals),liquidity(active in-range uint128 as a string),fee(tier),tickSpacing. Positions are ERC-721 NFTs. token0/token1are display symbols; on-chain Uniswap orders by raw address (whichever address is numerically smaller becomestoken0). When live, the response will also carrytoken0Address/token1Address; map them viaGET /v1/liquidity/config.
GET /v1/liquidity/pools/{pair}
Section titled “GET /v1/liquidity/pools/{pair}”Single pool state — same fields as the matching entry in /pools, for one pair.
Accepts both orderings (PECX-USDC and USDC-PECX resolve to the same pool).
Parameters
Section titled “Parameters”| Name | In | Required | Values |
|---|---|---|---|
pair |
path | yes | PECX-USDC, BTCB-PECX (reverse orderings accepted) |
Errors
Section titled “Errors”404— unknown pair (lists available pairs).
GET /v1/liquidity/quote
Section titled “GET /v1/liquidity/quote”Expected swap output (like the Uniswap Quoter). Mocked (deterministic) in
preview mode; when mode=live the v2 path relays the real getAmountOut
computation and the v3 path relays the Snowside Quoter contract.
Query parameters
Section titled “Query parameters”| Name | In | Required | Default | Values / format |
|---|---|---|---|---|
pair |
query | yes | — | PECX-USDC, BTCB-PECX (reverse orderings accepted) |
tokenIn |
query | yes | — | PECX, USDC, BTCB (must be in the pair) |
amountIn |
query | yes | — | decimal string, e.g. 1000 or 0.1 |
slippageBps |
query | no | 50 (0.50%) |
integer basis points |
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "preview", "mocked": true, "pair": "PECX-USDC", "label": "pECX / USDC", "version": "v2", "tokenIn": "PECX", "tokenOut": "USDC", "amountIn": "1000", "amountOut": "879.757633", "minimumAmountOut": "875.358845", "feeBps": 30, "priceImpactPct": 1.9608, "priceImpactModeled": true, "slippageBps": 50, "updatedAt": null}- v2 uses the exact Uniswap v2
getAmountOutconstant-product formula (fee on input).priceImpactPctexcludes the protocol fee (Uniswap UI convention).priceImpactModeled: true. - v3 uses a linear quote at the current price in preview mode — the
concentrated-liquidity tick walk is not modeled (no real tick structure).
priceImpactPct: 0andpriceImpactModeled: falseuntil live (then it relays the Snowside Quoter, which walks ticks for real impact). amountOut/minimumAmountOutare decimal strings respecting the output token’s decimals (pECX 8dp, USDC 6dp, BTC.b 8dp).minimumAmountOut=amountOut × (1 - slippageBps/10000).- This endpoint is not cached (
Cache-Control: no-store) because quotes are trade-sized and time-sensitive.
Errors
Section titled “Errors”400— missingpair/tokenIn/amountIn,tokenInnot in pair, oramountInnot a positive finite number.404— unknown pair.
POST /v1/liquidity/broadcast
Section titled “POST /v1/liquidity/broadcast”Relay a pre-signed raw EVM transaction to Snowside RPC via
eth_sendRawTransaction. This is the only write endpoint in the liquidity
API, and it is rule-5-safe: the server holds no keys, builds no
calldata, and signs nothing. Calldata construction + signing stay 100%
client-side (project decision). The server is a pure broadcast relay — same
trust model as POST /v1/rpc, for the EVM/Snowside side.
In preview mode this endpoint returns 503 — broadcast is unavailable until
Snowside RPC is configured. Detect the mode via GET /v1/liquidity/config
first (mode: 'live' + a non-empty chain.rpcUrl).
Request body
Section titled “Request body”{ "rawTx": "0x02f87201048459682f00850746a5288282520894..." }| Field | Type | Required | Notes |
|---|---|---|---|
rawTx |
string | yes | 0x-prefixed hex of the signed raw tx (built + signed client-side). Max 10,000 chars. |
Response (200)
Section titled “Response (200)”{ "ok": true, "mode": "live", "txHash": "0xabcdef1234567890...", "method": "eth_sendRawTransaction"}Errors
Section titled “Errors”400— badrawTx(not a 0x hex string), or Snowside RPC rejected the transaction (insufficient funds, bad nonce, already-known, etc.). The upstream JSON-RPC error is forwarded inrpcError.405— non-POST method.413—rawTxexceeds 10,000 chars.429— per-IP rate limit exceeded (same guard as/v1/rpc+ intake).503— preview mode (Snowside RPC not configured).502— relay fetch to Snowside failed.
Coming next
Section titled “Coming next”No further liquidity endpoints are planned — the surface is complete
(config, stats, pools, quote, broadcast). The remaining work is
operational: seed the D1 liquidity_config row with real Snowside Uniswap
addresses (flipping mode from preview to live) once the Snowside
engineers deploy the contracts.