NAV Navbar

General

Overview

Counter API is a RESTful web service. JSON is the primary data format.

Server responds with 200 OK HTTP status for every request that gets processed successfully, and with 400 Bad Request upon failure. Failed requests return the cause of failure in a JSON object in the response body - in most cases it's just a string with an explanation to aid debugging.

Smart contracts

With time, the Exchange address may change, and with support for new asset types additional Treasury-like contracts may appear.

Principle of operation

Counter requires traders to deposit funds into Treasury smart contract prior to trading. This allows Counter to manage all trades within its smart contracts and thus be able to guarantee trade settlement order. This eliminates most delays associated with decentralized token trading by committing to trade result while performing on-chain settlement in background.

Deposits into Treasury are free for the trader (sans the gas cost of the transaction). Once funds are in the smart contract, trades and withdrawals can only be initiated by the Counter arbiter accounts (but still can only be authorized with traders' ECDSA signatures). In the unlikely case Counter ceases operation or becomes malevolent, there is a possibility to invoke the emergency release protocol - the trader can start a 48-hour cooldown on Treasury contract, and if Counter is unable to provide any transaction with the valid signature of that trader within that period, funds can be released to the corresponding wallet in their entirety.

Core definitions

Everywhere in this doc an emphasis means that the definition of a term may be found in this section.

Exchange is organized into markets. Each market is characterized by its stock and cash tokens. The former is always written before the latter, e.g. OMG/ETH means that the stock being traded is OMG and the trades are paid in ETH.

Each market consists of two books - bids (buyers, paying cash for stock) and asks (sellers, paying stock for cash). Each book is made of orders which are characterized by:

  1. stock amount - the amount of stock being traded
  2. cash price - price (in cash) of a single unit of stock
  3. maker & taker fee - proportional fees charged by Counter for its services, explained below
  4. expiry time - timestamp by which the order is valid

Two orders can be matched if they are from different books (ask and bid), and bid cash price is not less that ask cash price. When this holds, a trade happens - stock gets transferred from seller to buyer, and corresponding amount of cash is transferred back from buyer to seller. The cash price of the trade is always the maker order cash price (terms maker and taker explained below). After a trade an order may be either fulfilled (when no stock amount is left) or partially fulfilled (when there is leftover stock amount which can be matched against other orders). The stock amount to be traded is always the minimum of remaining stock amounts of the matched orders, which means that one of them is always fulfilled.

When a new order gets posted into either one of the books, it gets matched with applicable existing orders immediately. In this series of trades, the newly posted order is called a taker order, and existing ones are called maker orders. After matching the taker order may be partially fulfilled, and in that case the remaining stock amount gets posted as a new maker order into the corresponding book, where it can get matched in the future. Hence one order may be tagged both maker and taker in the course of its lifetime.

Orders may cease to exist in one of three ways:

  1. by fulfillment
  2. by cancellation, which is digital signature confirmed operation which removes the order from the book and unlocks the alloted funds
  3. by expiration

Orders and withdrawals are signed with ECDSA signature. We denote the address corresponding to signing private key the signer address.

Fees

Counter charges a fee for its services.

For trades, the fee is charged from both counterparties and is proportional to the traded volume in the cash and stock of the trade pair. There are maker and taker fees, which are equal to 0.1% and 0.2% by default.

For withdrawals, the fee is fixed. Its main purpose is to cover the gas cost of processing the withdrawal, and thus it is expected to be roughly proportional to the transaction cost denominated in the token being withdrawn.

Fees are not strictly enforced by the smart contract - they are specified in the orders & withdrawals and can be arbitrary. It's up to the arbiter to reject the orders with the fees that do not adhere to the current policy. In that regard Counter is similar to 0x relayers.

Fixed precision arithmetic

A lot of the quantities used by Counter - stock & cash amounts, prices, fees, are fractional by nature. Yet Ethereum does not possess the ability to manipulate floating point numbers, and all the computations are performed in integer arithmetics instead. For each quantity we specify decimals - the number of digits to the right of fractional point, and process that quantity by multiplying it by 10decimals and rounding to the nearest integer. For example, for ETH amounts decimals = 18 and thus 1.158 ETH is represented by an integer 1 158 000 000 000 000 000.

For ETH the minimum denomination has a name of wei. For other tokens there is no agreed upon term, so we will generally call the lowest denomination a pennie.

The value of decimals is separate for each token (though for majority of them it is 18). For other quantities the precision is fixed - cash prices are multiplied by 108, and order fees are multiplied by 105. This enables specifying cash prices with the precision of up to 8 digits after a point, and fees to the precision of 0.001%

Fractional value representations

All endpoints that return fractional values have an optional query parameter format which can be hex, dec or float (default). Format of float returns strings with rational numbers in decimal form, the former two formats return the raw underlying integer value (multiplied by corresponding 10decimal) in either hexadecimal or decimal form.

Data model

Primitive types

Counter services are implemented in JavaScript, Rust and Solidity. Due to strongly typed nature of smart contracts some quantities need to be specified more rigorously than possible in JavaScript data model, thus we define the following helper types:

Type JSON Description
u8 number Unsigned 8-bit integer (0 - 255)
u16 number Unsigned 16-bit integer (0 - 65535)
u32 number Unsigned 32-bit integer (0 - 4294967295)
u64 number Unsigned 64-bit integer (0 - ~1.8 * 1019). Note that JavaScript is unable to represent all such values exactly.
float string Fractional quantity (string is used because JS number loses precision in lower digits). Assumed to be lossless, thus cannot have decimal digits beyond fixed precision.
Example: "13.11577"
address string Ethereum address (160-bit) - '0x' prefix followed by 40 hex digits.
Example: "0x1234567896326230a28ee368825D11fE6571Be4a"
u256 string 256-bit unsigned integer (up to ~1077), either in hexadecimal (0x2dfdc1c3e) or decimal ("123456781110000")
uniqueId string 224-bit value that uniquely identifies an order - it is composed of globally unique u64 tradeNonce occupying bits 160..223 and order signer address residing in bits 0..160. Representation is '0x' prefix followed by 56 hex digits.
hashId string 128-bit unique id of ledger event - '0x' prefix followed by 32 hex digits. Example: ""
timestamp string RFC 3339 timestamp.
Example: "2019-03-07T09:37:52.008661945Z"
epoch number Unix timestamp in seconds.
Example: 1552475590

Additional agreements

The notation type? denotes nullable values, whereas attributeName? means that attribute is optional.

There are meta-types to describe values which may change representation. These are either float or u256 in accordance with format query parameter.

Meta-type Description
amount stock/cash amount (decimals specified by the token)
cashPrice cashPrice (decimals are always 8)

Compound types

Token

Token tradeable on Counter (currently must be a ERC-20 token or ETH)

{
  "code": 0,
  "symbol": "ETH",
  "name": "Ethereum",
  "decimalPlaces": 18,
  "contractAddress": null
}
Attribute Type Description
code u16 Token code
symbol string Token ticker symbol
name string Token name
decimalPlaces u8 fractional precision for amounts (e.g. decimalPlaces = 18 means up to 18 digits after the point)
contractAddress address? token contract address (null for ETH)

Market

Active market of an exchange

{
  "symbol": "OMG/ETH",
  "stockTokenCode": 1,
  "cashTokenCode": 0
}
Attribute Type Description
symbol string Ticker symbol
stockTokenCode u16 Stock token code
cashTokenCode u16 Cash token code

Ticker

Ticker on a specific market

{
  "high": "0.12",
  "low": "0.09",

  "openPrice":"0.11"
  "lastPrice": "0.1",
  "last24hPrice": "0.1",

  "stockVolume":"1400.1",
  "cashVolume": "123.45",
}
Attribute Type Description
high cashPrice Highest price
low cashPrice Lowest price
openPrice cashPrice Open price
lastPrice cashPrice Last (close) price
last24hPrice cashPrice Last price of previous day
stockVolume amount Traded volume in stock
cashVolume amount Traded volume in cash

Order state

Current state of an order (possibly partially fulfilled)

{
  "id": 1337,

  "uniqueId": "0x00000000000000000000051c1337b12a0535d6b7a97b33a46664fc01b3e140c4",

  "type": "buy",

  "stockTokenCode": 1,
  "cashTokenCode": 0,

  "cashPrice": "0.00525449",
  "stockAmount": "18.778071467088694",
  "fulfilledStockAmount": "0.77",

  "expiryTime": "2019-03-07T09:37:52.008661945Z",

  "createdAt": "2019-03-07T09:37:52.008661945Z",
  "updatedAt": "2019-03-07T09:37:52.008661945Z",

  "maker": "0x1337b12a0535d6b7a97b33a46664fc01b3e140c4"
}
Attribute Type Description
id u64 pagination id within request
uniqueId uniqueId globally unique order id
type string order book type, either buy or sell
stockTokenCode u16 Stock token code
cashTokenCode u16 Cash token code
cashPrice cashPrice single unit of stock price in cash
stockAmount amount total amount of stock to be traded
fulfilledStockAmount amount amount of stock already fulfilled (0 for new orders)
expiryTime timestamp self explanatory
createdAt timestamp moment in time when the order was registered
updatedAt timestamp last moment of time the order was matched

Order book entry

Market book entry - one or more orders with first N digits of their cash price equal are grouped together.

{
  cashPrice: "1.278",
  stockAmount: "1.5",
  cashAmount: "1.917"
}
Attribute Type Description
cashPrice cashPrice Boundary cash price of the group, which is minimum for bids, maximum for asks. Motivation is that placing an order with the boundary cash price should be able to match all orders in the group.
stockAmount amount Sum of stock amounts of all orders in the group.
cashAmount amount Sum of cash amounts of all orders in the group, which is effectively weighted both by constituent cash prices and stock amounts.

Trade

A trade resulting from two compatible orders being matched

{
  "id": 63,

  "hashId": "0xf51ac74ee0e308bff8f21a5c5383886e",

  "type": "buy",

  "stockTokenCode": 1,
  "cashTokenCode": 0,

  "stockAmount": "1.229",
  "cashPrice": "1.137",

  "maker": "0x1337b12a0535d6b7a97b33a46664fc01b3e140c4",
  "taker": "0x380c7433e4a8640aeafc1260c2c4d676d0aa7b27",

  "timestamp":"2019-03-07T09:37:52.008661945Z"
}
Attribute Type Description
id u64 pagination id within request
hashId hashId Ledger event unique id
type string maker action, either buy or sell
stockTokenCode u16 stock token code
cashTokenCode u16 cash token code
cashPrice cashPrice HEX of cash price * 108
stockAmount amount HEX of stock amount in pennies: coins count * 10token decimal count
maker address maker signer address
taker address taker signer address
timestamp timestamp order matching timestamp

OHLCV

OHLCV (open-high-low-close-volume) datapoint, usually depicted with a "candle" on price graph.

{
  time: 1543219658,
  open: "2.16",
  high: "2.16",
  low: "2.13",
  close: "2.13",
  volume: "2600"
}
Attribute Type Description
time epoch Timestamp
open cashPrice Open price
high cashPrice High price
low cashPrice Low price
close cashPrice Close price
volume amount Trade volume

Endpoints

Pagination

Endpoints that potentially return very big datasets support pagination. This means that they take two optional query parameters - startId (u64 type, zero by default) or since (epoch type), and limit (u64 defaulting to something sensible like 100). These together specify how many objects to fetch in a single request, and their return value is an object with two keys: items storing a list of returned values, and nextId or nextSince which is either null (if there are no more values to return) or a value that should be passed to a subsequent request.

Please note that some requests return entire dataset in paginated format regardless of query parameters.

Listed tokens

cURL

curl "https://counter.market/api/tokens"
  -X GET
  -H "Content-Type: application/json"

Returns the description of all listed tokens

Request

GET /api/tokens

Response body

Entire list of Tokens.

Active markets

cURL

curl "https://counter.market/api/markets"
  -X GET
  -H "Content-Type: application/json"

Returns the description of all active markets

Request

GET /api/markets

Response body

Entire list of Markets.

List all orders for a market

cURL

curl "https://counter.market/api/markets/STOCK/CASH/orders"
  -X GET
  -H "Content-Type: application/json"

Request

GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/orders

Response body

Paginated list of order states.

Get order books for a market

cURL

curl "https://counter.market/api/markets/STOCK/CASH/orderbook"
  -X GET
  -H "Content-Type: application/json"

Request

GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/orderbook
GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/orderbook/buy
GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/orderbook/sell

Response body

When requesting both books - an object with two keys buy and sell, each containing a list of order book entries. When requesting a single book, only a list is returned.

Get OHLCV data for a market

cURL

curl "https://counter.market/api/markets/STOCK/CASH/ohlcv"
  -X GET
  -H "Content-Type: application/json"

Request

GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/ohlcv

Query parameters

Name Type Description
since? epoch timestamp of the start of the reporting period
till? epoch timestamp of the end of reporting period
step u32 aggregation window width in seconds

Response body

Paginated list of OHLCV datapoints.

Get ticker for a market

cURL

curl "https://counter.market/api/markets/STOCK/CASH/ticker"
  -X GET
  -H "Content-Type: application/json"

Request

GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/ticker

Response body

A Ticker.

Get trades for a market

cURL

curl "https://counter.market/api/markets/STOCK/CASH/trades"
  -X GET
  -H "Content-Type: application/json"

Request

GET /api/markets/{stockTokenSymbol}/{cashTokenSymbol}/trades

Query parameters

Name Type Description
since? epoch timestamp of the start of the reporting period

Response body

Paginated list of trades.