AlcorDocs

Market API

Prices, depth, pools and order books for bots, updated every block, with a WebSocket stream.

Prices, depth, pools and order books for bots and apps, computed from on-chain state that is updated every block, with the swap contracts' own math. Use it to know what tokens are worth, what a pair trades at, what a given amount fetches, and how much a market takes before its price gives way. To build a swap transaction, use Swap route v3.

https://<chain>.alcor.exchange/api/v3/market/, with <chain> one of wax, eos, telos, proton.

EndpointWhat it answers
GET tokensEvery token with its price in the system token and in USD
GET tokens/<id>One token
GET priceA pair's mid price, and what selling and buying an amount fetches
GET depthHow much a pair takes at each price impact level
GET pools, pools/<id>AMM pools with price, reserves and TVL
GET orderbooks, orderbooks/<id>Order book markets with best bid and ask, and orders
WSS streamAll of the above pushed after every block that changes it

Conventions

  • Every answer carries block: the last block the data reflects.
  • Prices and values are decimal strings with 8 significant digits. Amounts are decimal strings in the token's precision, e.g. 1000.0000 for a token with 4 decimals.
  • Token ids are <symbol>-<contract> in lower case, e.g. tlm-alien.worlds.
  • A field with no value is null, never left out. Only sell and buy of Pair price without amount, and bids and asks of the order book list, are left out.
  • Errors are JSON, {"error": "<message>"}:
StatusWhenMessages
400A bad parameter of price or depth, including a token id that does not existUnknown token, Invalid amount, Invalid impact, Base and quote are the same token, Input and output are the same token
404An unknown network, or an id in the path that does not existUnknown network, Unknown token, Unknown pool, Unknown order book
429Over the rate limit

Requests are rate-limited per IP address, with the same limits as the swap route. To follow prices all the time, open one stream instead of polling: a stream is one request, however long it stays open.

How token prices are made

Prices are in the chain's system token (WAX, EOS, TLOS, XPR), read off the AMM pools' spot prices. The system token is worth 1; every other token is priced through a pool whose other side is already priced. Of all the paths from the system token to a token, the price comes through the one whose weakest pool holds the most value, and a path through a pool below the liquidity threshold does not price at all, so a thin pool cannot set a token's price. The threshold is set per network, about $150 worth of the system token on the priced side of the pool. A token traded only on order books, or only in thin pools, has no price.

USD prices multiply by the system token's USD price from CoinGecko, refreshed every minute. When CoinGecko has not answered for an hour, USD prices are null until it does.

Prices are recomputed with every block that changes a pool.

Market tokens

Every token: the system token first, then the priced tokens by liquidity, then the unpriced ones by id.

GET https://wax.alcor.exchange/api/v3/market/tokens

The list is large: on WAX about 2,100 tokens and 370 KB. To follow it, subscribe to the stream's tokens channel, which sends it once and then only the tokens whose price moved.

Example

curl https://wax.alcor.exchange/api/v3/market/tokens
{
  "block": 461732168,
  "systemToken": "wax-eosio.token",
  "systemUsdPrice": "0.00456392",
  "tokens": [
    {
      "id": "wax-eosio.token",
      "symbol": "WAX",
      "contract": "eosio.token",
      "decimals": 8,
      "price": "1",
      "priceUsd": "0.00456392",
      "liquidity": null,
      "liquidityUsd": null,
      "pools": 3313,
      "orderbooks": 833
    },
    {
      "id": "tlm-alien.worlds",
      "symbol": "TLM",
      "contract": "alien.worlds",
      "decimals": 4,
      "price": "0.24765637",
      "priceUsd": "0.0011302838",
      "liquidity": "890719.54",
      "liquidityUsd": "4065.1727",
      "pools": 257,
      "orderbooks": 12
    },
    {
      "id": "a-ilbau.wam",
      "symbol": "A",
      "contract": "ilbau.wam",
      "decimals": 6,
      "price": null,
      "priceUsd": null,
      "liquidity": null,
      "liquidityUsd": null,
      "pools": 1,
      "orderbooks": 0
    }
  ]
}

Response fields

NameTypeDescription
blocknumberLast block the data reflects
systemTokenstringToken every price is quoted in
systemUsdPricestringUSD price of the system token, null when unknown
tokensarrayTokens, see below

Each token

NameTypeDescription
id, symbol, contract, decimalsThe token
pricestringSystem tokens per one token, null when unpriced
priceUsdstringUSD per one token, null when unpriced or the USD price is unknown
liquiditystringWhat backs the price: the system-token value of the priced side of the weakest pool on the path the price comes through. null for the system token and for unpriced tokens
liquidityUsdstringThe same in USD
poolsnumberPools trading the token
orderbooksnumberOrder book markets trading the token

Market token

One token, as in Market tokens. An unknown id answers HTTP 404.

GET https://wax.alcor.exchange/api/v3/market/tokens/<token_id>

Example

curl https://wax.alcor.exchange/api/v3/market/tokens/tlm-alien.worlds
{
  "block": 461732168,
  "token": {
    "id": "tlm-alien.worlds",
    "symbol": "TLM",
    "contract": "alien.worlds",
    "decimals": 4,
    "price": "0.24765637",
    "priceUsd": "0.0011302838",
    "liquidity": "890719.54",
    "liquidityUsd": "4065.1727",
    "pools": 257,
    "orderbooks": 12
  }
}

Pair price

The price of a pair, and with amount, the price of trading that much of it.

mid is the ratio of the two token prices. With amount (in base), sell sells it for quote, and buy spends what the sell receives to buy base back. Both go through the AMM and the order books, split across routes, exactly as a swap would, so the gap between their prices is what a round trip of that size costs.

GET https://wax.alcor.exchange/api/v3/market/price

Parameters

NameTypeRequiredDescription
basestringyesBase token id
quotestringyesQuote token id
amountstringnoAmount of base to sell, e.g. 1000, at most the token's decimals

Example

curl "https://wax.alcor.exchange/api/v3/market/price?base=tlm-alien.worlds&quote=wax-eosio.token&amount=1000"
{
  "block": 461732115,
  "base": { "id": "tlm-alien.worlds", "symbol": "TLM", "contract": "alien.worlds", "decimals": 4 },
  "quote": { "id": "wax-eosio.token", "symbol": "WAX", "contract": "eosio.token", "decimals": 8 },
  "mid": "0.24765637",
  "sell": {
    "input": "1000.0000",
    "output": "247.36208632",
    "price": "0.24736209",
    "priceImpact": "0.64"
  },
  "buy": {
    "input": "247.36208632",
    "output": "995.7290",
    "price": "0.2484231",
    "priceImpact": "0.31"
  }
}

Response fields

NameTypeDescription
blocknumberLast block the data reflects
base, quoteobjectThe tokens: id, symbol, contract, decimals
midstringQuote per one base, from the token prices. null when either token is unpriced
sellobjectOnly with amount: selling it. null when no route can
buyobjectOnly with amount: buying base back with what the sell receives. null when no route can

sell and buy

NameTypeDescription
inputstringAmount spent: base for sell, quote for buy
outputstringAmount received: quote for sell, base for buy
pricestringQuote per one base, as executed, fees included
priceImpactstringPercent below the spot prices of the routes taken, as in the route API

Depth

How much input the market takes before its price gives way.

For each impact level, in percent, about the largest trade of input whose average price, fees included, is at most that far below reference. The reference is the pair's mid price; for a pair without one, it is the price of a small trade. Trades are routed through the AMM and the order books, split across routes, as a swap would.

A level's amounts are null when not even a small trade stays within it: a level below the cheapest route's fee never does. Amounts are accurate to about 1%.

A depth search takes about a second, so answers are cached for 10 seconds per pair and levels. Asking again within that time answers the same block.

GET https://wax.alcor.exchange/api/v3/market/depth

Parameters

NameTypeRequiredDescription
inputstringyesToken id to sell
outputstringyesToken id to receive
impactstringnoComma-separated levels in percent, up to 10, each above 0 and below 100. Default 0.5,1,2,5

Example

curl "https://wax.alcor.exchange/api/v3/market/depth?input=wax-eosio.token&output=tlm-alien.worlds&impact=0.5,1,2,5"
{
  "block": 461732117,
  "input": { "id": "wax-eosio.token", "symbol": "WAX", "contract": "eosio.token", "decimals": 8 },
  "output": { "id": "tlm-alien.worlds", "symbol": "TLM", "contract": "alien.worlds", "decimals": 4 },
  "reference": "4.037853",
  "referenceKind": "mid",
  "levels": [
    { "impact": "0.5", "input": "6654.20397186", "output": "26734.3509" },
    { "impact": "1", "input": "24250.57497775", "output": "96941.0229" },
    { "impact": "2", "input": "81250.75297357", "output": "321516.8950" },
    { "impact": "5", "input": "318725.96867490", "output": "1222617.5391" }
  ]
}

Response fields

NameTypeDescription
blocknumberLast block the data reflects
input, outputobjectThe tokens
referencestringOutput per one input that impact is measured against. null when there is no route
referenceKindstringmid (the pair's mid price) or smallTrade (the price of a small trade); null when there is no route
levelsarrayOne per requested level, in the order asked: impact, and the input sold and output received at that level

Market pools

Every AMM pool, or the pools trading token, by id.

GET https://wax.alcor.exchange/api/v3/market/pools answers {"block", "pools"}.

GET https://wax.alcor.exchange/api/v3/market/pools/<pool_id> answers one pool as {"block", "pool"}, or HTTP 404.

Parameters of the list

NameTypeRequiredDescription
tokenstringnoOnly pools trading this token id

Example

curl https://wax.alcor.exchange/api/v3/market/pools/0
{
  "block": 461732115,
  "pool": {
    "id": 0,
    "tokenA": { "id": "tlm-alien.worlds", "symbol": "TLM", "contract": "alien.worlds", "decimals": 4 },
    "tokenB": { "id": "wax-eosio.token", "symbol": "WAX", "contract": "eosio.token", "decimals": 8 },
    "fee": "0.3",
    "tickSpacing": 60,
    "active": true,
    "price": "0.24765637",
    "sqrtPriceX64": "918003784128374099714",
    "tick": 78150,
    "liquidity": "5723638121470",
    "reserveA": "3974114.3644",
    "reserveB": "890719.54153255",
    "tvl": "1874934.3",
    "tvlUsd": "8557.05"
  }
}

Response fields

NameTypeDescription
idnumberPool id
tokenA, tokenBobjectThe tokens, in the pool's order
feestringFee in percent
tickSpacingnumberTick spacing
activebooleanWhether the pool is active
pricestringToken B per one token A, the pool's own spot price
sqrtPriceX64stringCurrent sqrt price, Q64.64, in raw units
ticknumberCurrent tick
liquiditystringIn-range liquidity
reserveA, reserveBstringToken balances the pool holds
tvlstringBoth reserves in system tokens; null unless both tokens are priced
tvlUsdstringThe same in USD

Market order books

Order book markets. The list comes without orders; a single market comes with its best 20 orders on each side.

GET https://wax.alcor.exchange/api/v3/market/orderbooks answers {"block", "orderbooks"}.

GET https://wax.alcor.exchange/api/v3/market/orderbooks/<market_id> answers {"block", "orderbook"}, or HTTP 404.

Parameters of the list

NameTypeRequiredDescription
tokenstringnoOnly markets trading this token id

Example

curl https://wax.alcor.exchange/api/v3/market/orderbooks/26
{
  "block": 461732115,
  "orderbook": {
    "id": 26,
    "base": { "id": "wax-eosio.token", "symbol": "WAX", "contract": "eosio.token", "decimals": 8 },
    "quote": { "id": "tlm-alien.worlds", "symbol": "TLM", "contract": "alien.worlds", "decimals": 4 },
    "fee": "0.2",
    "frozen": false,
    "bestBid": "0.24725901",
    "bestAsk": "0.24950131",
    "bids": [
      { "price": "0.24725901", "amount": "470.8472" },
      { "price": "0.247012", "amount": "18000.0000" }
    ],
    "asks": [
      { "price": "0.24950131", "amount": "500.0000" },
      { "price": "0.24950131", "amount": "500.0000" }
    ]
  }
}

Response fields

NameTypeDescription
idnumberMarket id
base, quoteobjectThe tokens, named as the DEX contract names them
feestringFee in percent
frozenbooleanWhether the market is frozen
bestBid, bestAskstringBest buy and sell prices, in base per one quote; null when the side is empty
bids, asksarraySingle market only: {price, amount}, best first, one entry per order (two orders at one price are two entries). Price in base per one quote, amount in quote

Here base and quote follow the DEX contract, which is the reverse of the Market Data tickers: the contract's base (WAX in market 26) is the ticker's target.

Market stream

A plain WebSocket (not Socket.IO) that pushes the market API as the chain moves: subscribe to a pair's price, a pool, an order book or the token list, get its current state at once, then a message after every block that changes it.

wss://<chain>.alcor.exchange/api/v3/market/stream, e.g. wss://wax.alcor.exchange/api/v3/market/stream.

An unknown network answers the handshake with HTTP 404. Requests and messages are JSON text frames; binary frames are ignored.

Requests

{"op": "subscribe", "channel": "price", "base": "tlm-alien.worlds", "quote": "wax-eosio.token"}
{"op": "subscribe", "channel": "pool", "id": 0}
{"op": "subscribe", "channel": "orderbook", "id": 26}
{"op": "subscribe", "channel": "tokens"}
{"op": "unsubscribe", "channel": "pool", "id": 0}
NameTypeDescription
opstringsubscribe or unsubscribe
channelstringprice, pool, orderbook or tokens
base, quotestringprice only: the pair's token ids
idnumberpool and orderbook only: the pool id or the market id
  • A subscription is answered at once with the channel's current state (its snapshot).
  • Subscribing again to a channel already subscribed sends the snapshot again and does not count as a new subscription. Use it to resynchronise one channel.
  • An unsubscription is not answered. Unsubscribing from a channel not subscribed does nothing.
  • Answers come in the order of the requests.

Channels

ChannelSubscribe withSnapshot and updates carrySent after a block when
pricebase, quotemid: quote per one base, as in Pair price; null when either token is unpricedEither token's price moved and the mid differs from the last one sent
poolidpool, as in Market pools; null once the pool is goneThe pool changed in the block and differs from the last one sent
orderbookidorderbook with its orders, as in Market order books; null once the market is goneThe market changed in the block and differs from the last one sent
tokenstokens: every token on subscribe, as in Market tokens; afterwards the tokens whose price movedSome token's price moved

A block that leaves a channel as it was sends nothing on it, so a quiet pool or pair can go minutes without a message.

Messages

Every message names its channel, the fields it was subscribed with, and the block it reflects. The channel's data is in the field named in the table above.

{"channel": "price", "base": "tlm-alien.worlds", "quote": "wax-eosio.token", "block": 461732254, "mid": "0.25123696"}
{"channel": "pool", "id": 0, "block": 461732254, "pool": {"id": 0, "price": "0.24765637", "...": "as in Market pools"}}
{"channel": "orderbook", "id": 26, "block": 461732254, "orderbook": {"id": 26, "bestBid": "0.24725901", "...": "as in Market order books"}}
{"channel": "tokens", "block": 461732254, "tokens": [{"id": "tlm-alien.worlds", "price": "0.24765637", "...": "as in Market tokens"}]}

Each message carries the whole state of what it names, not a diff: replace the pool, the order book or the mid you hold with what arrives. A tokens message lists whole token entries; update the tokens it lists by id and keep the others.

When the server falls behind a connection, or reloads a chain's state, it checks every channel of the connection again and sends what changed. Then tokens sends every token once more, as on subscribe.

The tokens snapshot is the whole Market tokens list: on WAX about 370 KB in one message. Later messages are small, usually a few tokens.

Errors

A bad request is answered with {"error": "<message>"}, and the connection stays open. The error does not repeat the request; match it by order, as answers come in the order of the requests.

MessageWhen
Invalid requestNot JSON, an unknown op or channel, or a missing or wrong-typed field
Unknown tokenprice names a token id that does not exist
Unknown poolpool names a pool id that does not exist
Unknown order bookorderbook names a market id that does not exist
Too many subscriptionsThe connection already holds 100 subscriptions

Connection

  • One connection holds up to 100 subscriptions. For more, open more connections.
  • The server sends a WebSocket ping every 30 seconds, which keeps a quiet connection open through proxies; WebSocket clients answer it on their own.
  • The server keeps no subscriptions across connections. On a disconnect, reconnect with a backoff and subscribe again: the snapshots bring the state up to date.

Example

const SUBSCRIPTIONS = [
  { op: 'subscribe', channel: 'price', base: 'tlm-alien.worlds', quote: 'wax-eosio.token' },
  { op: 'subscribe', channel: 'pool', id: 0 },
  { op: 'subscribe', channel: 'orderbook', id: 26 },
]

function connect(delay = 1000) {
  const ws = new WebSocket('wss://wax.alcor.exchange/api/v3/market/stream')

  ws.onopen = () => {
    delay = 1000
    for (const subscription of SUBSCRIPTIONS) ws.send(JSON.stringify(subscription))
  }

  ws.onmessage = ({ data }) => {
    const message = JSON.parse(data)
    if (message.error) return console.error(message.error)
    if (message.channel === 'price') console.log(message.block, 'TLM/WAX', message.mid)
    if (message.channel === 'pool') console.log(message.block, 'pool', message.id, message.pool?.price)
    if (message.channel === 'orderbook') console.log(message.block, 'book', message.id, message.orderbook?.bestBid, message.orderbook?.bestAsk)
  }

  ws.onclose = () => setTimeout(() => connect(Math.min(delay * 2, 30000)), delay)
}

connect()
import asyncio
import json

import websockets  # pip install websockets

URL = "wss://wax.alcor.exchange/api/v3/market/stream"
SUBSCRIPTIONS = [
    {"op": "subscribe", "channel": "price", "base": "tlm-alien.worlds", "quote": "wax-eosio.token"},
    {"op": "subscribe", "channel": "tokens"},
]


async def main():
    prices = {}  # token id -> price in WAX
    async for ws in websockets.connect(URL, max_size=None):  # reconnects with a backoff; no limit on the tokens snapshot
        try:
            for subscription in SUBSCRIPTIONS:
                await ws.send(json.dumps(subscription))
            async for data in ws:
                message = json.loads(data)
                if "error" in message:
                    print("error:", message["error"])
                elif message["channel"] == "tokens":
                    prices.update((token["id"], token["price"]) for token in message["tokens"])
                elif message["channel"] == "price":
                    print(message["block"], "TLM/WAX", message["mid"])
        except websockets.ConnectionClosed:
            continue


asyncio.run(main())

On this page