REST API
https://api.arb.capital is a free, public REST API operated by
Arb Capital . It serves wstGBP state as plain JSON over GET — live
on-chain reads (NAV, prices, reserves, quotes) and indexed history (snapshots, APR, events,
per-chain supply) — with no API key or authentication required.
Pick the right tool for the job: use this API when you want numbers without running any infrastructure, the subgraph for arbitrary historical queries over GraphQL, and direct contract reads when you need trust-minimized live state.
Responses are edge-cached, and history routes serve periodically collected snapshots (hourly for wrapper and pool metrics, 6-hourly for per-chain supply). Treat this API as a convenience layer — for settlement-grade numbers, read the contracts directly.
Conventions
{token}path segment — accepts the token slug (wstgbp) or the home-chain contract address0x57C3571f10767E49C9d7b60feb6c67804783B7aE, case-insensitive. Unknown tokens return404.- Amounts — wei values are serialized as strings (BigInt-safe); fields with a
Floatsuffix carry the same value as a display-precision float. Prices (navprice,mintcost,burncost) are WAD — 18-decimal fixed point, tGBP per 1 wstGBP (see the NAV mechanic). - Rate limits — per IP, per path, over a 1-minute window: 60 req/min by default,
30 req/min for routes that read the chain live (
/api/tokens/{token},/reserves,/pools,/quote). Every response carriesX-RateLimit-LimitandX-RateLimit-Remaining; exceeding the limit returns429withRetry-After: 60. - Caching — most routes are served through an edge cache; the freshness column below
is each route’s cache lifetime, so a response can be up to that many seconds old (plus a
short stale-while-revalidate grace). The cache directives are consumed at the edge and
not echoed back — to see whether a response was cached, check the
AgeandX-Vercel-Cache(HIT/MISS) response headers rather thanCache-Control./quoteis never cached. - CORS — server-side calls need no special headers. Browser (cross-origin) access is limited to allowlisted origins; email ops@wstgbp.com to request one.
Endpoints
| Endpoint | Returns | Freshness |
|---|---|---|
/api/tokens | Token list with cached metrics | 120 s |
/api/tokens/{token} | Live wrapper state | 60 s |
…/snapshots | Hourly NAV / supply / TVL history | 300 s |
…/daily | Daily rollup of the same series | 300 s |
…/apr | Trailing APR from the NAV curve | 300 s |
…/price-history | Oracle poke history | 300 s |
…/supply | Per-chain supply (home + bridged) | 300 s |
…/total-supply, …/circulating-supply | Plain-number supply (listing sites) | 300 s |
…/chains | Static bridge / deployment metadata | 3600 s |
…/reserves | Live backing / collateralization check | 60 s |
…/events | Mint / redeem / claim event feed | 120 s |
…/pools | Live Uniswap v4 pool state | 60 s |
…/pools/history | Hourly pool snapshots | 300 s |
…/quote | Mint / redeem preview | uncached |
GET /api/tokens
All tracked tokens with cached headline metrics (currently just wstGBP).
{
"tokens": [{
"slug": "wstgbp",
"name": "Wren Staked tGBP",
"symbol": "wstGBP",
"decimals": 18,
"chainId": 1,
"address": "0x57C3571f10767E49C9d7b60feb6c67804783B7aE",
"underlying": { "symbol": "tGBP", "address": "0x27f6c8289550fCE67f6B50BeD1F519966aFE5287", "decimals": 18 },
"description": "NAV-accruing GBP token wrapping tGBP. A weekly oracle poke raises the redemption price; balances never rebase.",
"deploymentCount": 11,
"poolCount": 3,
"apr30d": 3.5, // trailing 30d simple APR, percent
"apr7d": 3.4, // may be null — pokes land weekly
"aprTrend": "stable", // "up" | "down" | "stable"
"navprice": "1005372972418361109",
"navpriceFloat": 1.005372,
"tvl": "19939965230686339736621",
"tvlFloat": 19939.96523,
"holderCount": 34,
"updatedAt": "2026-07-12 03:00:45"
}]
}GET /api/tokens/{token}
Token detail with live on-chain wrapper state.
{
"slug": "wstgbp",
"name": "Wren Staked tGBP",
"symbol": "wstGBP",
"decimals": 18,
"chainId": 1,
"address": "0x57C3571f10767E49C9d7b60feb6c67804783B7aE",
"underlying": { "symbol": "tGBP", "address": "0x27f6c8289550fCE67f6B50BeD1F519966aFE5287", "decimals": 18 },
"live": {
"navprice": "1005372972418361109", // WAD: tGBP per 1 wstGBP
"navpriceFloat": 1.005372,
"mintcost": "1005372972418361109", // WAD buy price (nav + bpsin)
"mintcostFloat": 1.005372,
"burncost": "1002859539987315207", // WAD sell price (nav - bpsout)
"burncostFloat": 1.002859,
"totalSupply": "19830263038853336707255",
"totalSupplyFloat": 19830.263038,
"tvl": "19939965230686339736621", // underlying tGBP held by the wrapper
"tvlFloat": 19939.96523,
"mintable": true,
"burnable": true,
"capacity": "115792089237316195423570985008687907853269984665640564039457584007913129639935", // 2^256-1 = uncapped
"cooldown": "0", // 0 = atomic redemption
"redemptionCount": "24",
"feeBps": { "in": 0, "out": 25 }
},
"fetchedAt": "2026-07-12T03:15:43.371Z"
}GET /api/tokens/{token}/snapshots
Hourly NAV price / supply / TVL snapshots, ascending (max 1000 rows).
| Param | Type | Required | Description |
|---|---|---|---|
days | number | No | Lookback period (default 30, 1–365) |
{
"snapshots": [{
"timestamp": "2026-07-12 00:36:30",
"navprice": "1005372972418361109",
"total_supply": "19830263038853336707255",
"tvl": "19939965230686339736621"
}]
}GET /api/tokens/{token}/daily
Daily rollup — the last snapshot of each day. Same shape as /snapshots plus a date
field, under a daily key.
| Param | Type | Required | Description |
|---|---|---|---|
days | number | No | Lookback period (default 30, 1–365) |
{
"daily": [{
"date": "2026-07-12",
"timestamp": "2026-07-12 03:00:45",
"navprice": "1005372972418361109",
"total_supply": "19830263038853336707255",
"tvl": "19939965230686339736621"
}]
}GET /api/tokens/{token}/apr
Trailing 30-day NAV growth annualized over the actual elapsed seconds — simple APR, no compounding, computed from the oracle-poke history. This is the same method the subgraph page derives by hand, precomputed.
{
"apr30d": 3.5, // percent
"apr7d": 3.4, // percent; may be null (weekly pokes)
"aprWad": "34961230279451382", // WAD, 1e18 == 100%
"aprTrend": "stable",
"windowDays": 30,
"sampleCount": 5, // non-pause pokes inside the window
"fromTimestamp": 1780680287, // unix s of the start price used
"asOf": 1783698131, // unix s of the latest poke used
"updatedAt": "2026-07-12 03:00:45"
}GET /api/tokens/{token}/price-history
Oracle poke history (pauses included — price is 0 and is_pause is 1 on a pause).
| Param | Type | Required | Description |
|---|---|---|---|
days | number | No | Lookback period (default 90, 1–730) |
{
"updates": [{
"price": "1002695027107482205",
"is_pause": 0,
"block_timestamp": 1781290787,
"timestamp": "2026-06-12 18:59:47"
}]
}GET /api/tokens/{token}/supply
Per-chain supply across the home chain and all bridged deployments.
Every bridged deployment is a canonical lock-and-mint bridge, so L2 supply is backed 1:1
by tokens escrowed in a home-chain contract. The primary per-chain figure (totalSupply,
source: "escrow") is that L1 escrow balance, snapshotted every 6 hours. A weekly
cross-check reads the actual totalSupply() on each L2 (l2Supply); an l2Supply above
the escrow balance would indicate an unbacked mint on that chain. Escrow can legitimately
run slightly ahead of L2 supply while deposits or withdrawals are in flight (up to the
~7-day challenge window for optimistic-rollup exits). A chain whose snapshot is older
than 24 h is flagged stale. bridgedSupply mirrors escrowed home-chain tokens, so it is
not additive with home supply.
{
"home": {
"chainId": 1,
"address": "0x57C3571f10767E49C9d7b60feb6c67804783B7aE",
"totalSupply": "19830263038853336707255", // actual totalSupply() on Ethereum
"totalSupplyFloat": 19830.263038,
"timestamp": "2026-07-12 00:59:18",
"stale": false,
"source": "escrow"
},
"chains": [{
"chainId": 8453,
"name": "Base",
"address": "0xEa4Fcd4dE37eC038947a6834751E71Bb8FE68fff",
"totalSupply": "1000000000000000000", // primary: L1 escrow balance
"totalSupplyFloat": 1,
"timestamp": "2026-07-12 00:59:18",
"stale": false,
"source": "escrow", // "escrow" | "l2"
"l1Escrow": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
"l2Supply": "1000000000000000000", // weekly on-L2 verification (null until first check)
"l2SupplyFloat": 1,
"l2Timestamp": "2026-07-12 00:36:44"
}],
"aggregate": {
"bridgedSupply": "11000000000000000000", // sum of L1 escrow balances
"bridgedSupplyFloat": 11,
"chainsReporting": 11,
"chainsConfigured": 11,
"l2ReportedSupply": "11000000000000000000",
"l2ReportedSupplyFloat": 11,
"l2ChainsReporting": 11
}
}GET /api/tokens/{token}/total-supply / …/circulating-supply
Listing-site supply endpoints in the
format CoinGecko requires :
the response body is only the supply as a plain decimal number in token units —
text/plain, no JSON wrapper. Both read the wrapper’s live totalSupply(); for wstGBP
circulating equals total (no locked tranches, and bridged balances mirror escrowed
home-chain supply). Error responses are still JSON.
19830.263038853336707255GET /api/tokens/{token}/chains
Static chain and bridge metadata for every deployment — no data reads, just configuration.
l1Escrow is the home-chain contract that locks the token 1:1 against the bridged supply:
the L1StandardBridge (OP Stack), L1ERC20Gateway (Arbitrum Nitro), ERC20Predicate
(Polygon PoS), the OmniBridge Foreign mediator (Gnosis), or L1NativeTokenVault (zkSync
Elastic). Per-chain addresses and bridge walkthroughs live on the
Bridges & Portals page.
{
"home": { "chainId": 1, "name": "Ethereum", "address": "0x57C3571f10767E49C9d7b60feb6c67804783B7aE" },
"chains": [{
"chainId": 8453,
"name": "Base",
"address": "0xEa4Fcd4dE37eC038947a6834751E71Bb8FE68fff",
"bridgeType": "op-stack", // op-stack | arbitrum-nitro | polygon-pos | gnosis-omnibridge | zksync-elastic
"l1Escrow": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
"explorerUrl": "https://basescan.org",
"bridgeUrl": "https://bridge.base.org",
"status": "live"
}]
}GET /api/tokens/{token}/reserves
Live backing / collateralization check. expectedBacking = totalSupply × navprice — what
full redemption would owe. Solvency is judged on net, which credits the redeem-fee
buffer (diff + expectedBacking × bpsout / 10000): holders pay the exit fee, so the
wrapper only needs to cover the net amount.
{
"navprice": "1005372972418361109",
"navpriceFloat": 1.005372,
"totalSupply": "19830263038853336707255",
"totalSupplyFloat": 19830.263038,
"backing": "19939965230686339736621", // underlying tGBP actually held
"backingFloat": 19939.96523,
"expectedBacking": "19936810495209941434186", // owed at full redemption
"expectedBackingFloat": 19936.810495,
"diff": "3154735476398302435",
"diffFloat": 3.154735,
"net": "52996761714423156020", // diff + redeem-fee buffer
"netFloat": 52.996761,
"bpsout": 25,
"collateralizationPct": 100.01,
"status": "OVER-COLLATERALIZED", // OVER-COLLATERALIZED | BALANCED | SHORTFALL
"minTopUp": "0",
"minTopUpFloat": 0,
"fetchedAt": "2026-07-12T03:17:37.937Z"
}GET /api/tokens/{token}/events
Mint / redeem / claim event feed, newest first.
| Param | Type | Required | Description |
|---|---|---|---|
type | mint | redeem | claim | No | Filter by event type |
user | address | No | Filter by account |
days | number | No | Lookback period (default 30, 1–365) |
limit | number | No | Max rows (default 100, 1–500) |
Denominations: mint amounts are the wstGBP minted; redeem and claim amounts are
the underlying tGBP — the same convention as the
subgraph entities.
{
"events": [{
"action": "mint", // mint | redeem | claim
"user_address": "0x1234…abcd",
"amount": "53955718038521932143",
"price": "1005372972418361109", // mint only
"redemption_id": null, // redeem/claim only
"tx_hash": "0xe89c…a468",
"block_number": 25510188,
"chain_id": 1,
"timestamp": "2026-07-11 15:12:11"
}]
}GET /api/tokens/{token}/pools
Live Uniswap v4 state, one array entry per configured venue. The primary venue is the
tGBP/wstGBP backstop pool (Uniswap v4 & Aggregators): its hook
routes every swap through the wrapper’s atomic mint/redeem, so it has no LP curve —
fairPrice, deviationPpm, and estimatedFeePpm are null, and the wrapper’s NAV band
is reported in band instead. For venues with an LP curve, price is the pool’s spot
price and fairPrice the oracle-implied one (both WAD, pairToken per 1 wstGBP),
deviationPpm their gap in parts-per-million, and estimatedFeePpm a per-direction fee
estimate.
{
"pools": [{
"key": "backstop",
"label": "tGBP/wstGBP Backstop",
"type": "backstop",
"poolId": "0xdb21c31f461611ebeeab8af1280c77a82bb81725e1bf9d6093fbbc207a375ce5",
"hook": "0xfE36B48c9c0240991E4CEf006a2445F2ff524888",
"pairToken": { "symbol": "tGBP", "address": "0x27f6c8289550fCE67f6B50BeD1F519966aFE5287", "decimals": 18 },
"sqrtPriceX96": "79228162514264337593543950336",
"tick": 0,
"liquidity": "0", // backstop holds no LP liquidity by design
"price": "1005372972418361109", // WAD: tGBP per 1 wstGBP
"priceFloat": 1.005372,
"fairPrice": null,
"fairPriceFloat": null,
"deviationPpm": null,
"estimatedFeePpm": null,
"band": { // backstop only: the wrapper's NAV band
"navprice": "1005372972418361109",
"mintcost": "1005372972418361109",
"burncost": "1002859539987315207",
"bandBps": 24
}
}],
"fetchedAt": "2026-07-12T03:16:03.121Z"
}GET /api/tokens/{token}/pools/history
Hourly snapshots for one venue.
| Param | Type | Required | Description |
|---|---|---|---|
pool | string | Yes | Pool key (e.g. backstop) or 0x… poolId |
days | number | No | Lookback period (default 30, 1–365) |
{
"snapshots": [{
"pool_key": "backstop",
"price": "1005372972418361109",
"fair_price": null, // null for the backstop
"deviation_ppm": null,
"liquidity": "0",
"fee_mint_ppm": null,
"fee_redeem_ppm": null,
"timestamp": "2026-07-12 00:36:42"
}]
}GET /api/tokens/{token}/quote
Preview a mint or redeem at current on-chain prices. Amounts are exact-input. The response
echoes the on-chain WsgemQuoter verdict under
quoter — its previewSwap also checks cooldown and wrapper funding, and overrides the
local executability checks when configured. Never cached.
| Param | Type | Required | Description |
|---|---|---|---|
side | mint | redeem | Yes | mint pays tGBP in, redeem pays wstGBP in |
amount | string | Yes | Exact input amount in wei (BigInt as string) |
// GET /api/tokens/wstgbp/quote?side=mint&amount=250000000000000000000
{
"side": "mint",
"amountIn": "250000000000000000000", // tGBP in
"amountOut": "248663935532940386388", // wstGBP out
"amountOutFloat": 248.663935,
"price": "1005372972418361109", // mintcost used
"priceFloat": 1.005372,
"feeBps": 0,
"executable": true,
"reason": null, // e.g. "below mint dust threshold", "exceeds capacity"
"cooldown": "0",
"quoter": {
"amountIn": "250000000000000000000",
"amountOut": "248663935532940386388"
}
}Errors
Errors are JSON — { "error": "<message>" } with a matching HTTP status:
| Status | Example body | When |
|---|---|---|
400 | { "error": "side must be mint or redeem" } | Invalid or missing query parameters |
404 | { "error": "Token not found" } / { "error": "Pool not found" } | Unknown {token} segment or pool |
429 | { "error": "Too many requests" } | Rate limit exceeded — retry after Retry-After seconds |
500 | { "error": "Database error" } | History routes, transient |
502 | { "error": "On-chain read failed" } | Live routes, transient — safe to retry |
The two plain-text supply endpoints (/total-supply, /circulating-supply) also return
JSON for their error cases.