Matcher API#
The matcher is the order-matching service behind Decentral.Exchange. You send it signed orders; it keeps the order books, matches a buy against a sell, and settles each match on the chain as an Exchange transaction. It never holds your funds: they stay in your account until a trade settles, and the matcher only reserves the amount your open orders need. For the structure of an order, see Order.
Network |
Matcher |
Public key |
|---|---|---|
Testnet |
|
|
Mainnet |
No public matcher yet |
The matcher serves its own OpenAPI reference at /api-docs/index.html. This page covers the endpoints a trading app or bot needs.
Matcher Settings#
Endpoint |
Returns |
|---|---|
|
The matcher’s public key, as a JSON string. Every order names it. |
|
|
|
How much of each asset pays a fee worth 1 DCC, for example |
The Testnet matcher accepts order versions 1, 2 and 3 and charges a fee of 0.003 DCC per order ("orderFee": {"dynamic": {"baseFee": 300000}}). The fee is taken in proportion to how much of the order fills.
Markets and Order Books#
Endpoint |
Returns |
|---|---|
|
Every market: amount and price asset, their names and decimals, and the |
|
The order book: |
|
The last trade’s price, amount and side |
|
The pair’s matching rules ( |
Assets are given by ID, with DCC for DCC. A pair has a fixed order: on the Testnet, DCCCANARY/DCC is Hs5y6vFumpwVp6my7rVEmJEQQD3wiQ29qJhnixNqPit5/DCC. The reversed pair is not a market.
Prices and Amounts#
amount is always in the amount asset’s smallest unit. price is how much of the price asset one whole amount-asset unit costs, and for order versions 1 to 3 it is scaled by 10^(8 + price asset decimals − amount asset decimals). For DCCCANARY/DCC, both with 8 decimals, the factor is 10^8: a price of 0.001 DCC is 100000. For a token with 2 decimals priced in DCC, the factor is 10^14.
Place and Cancel an Order#
POST /matcher/orderbook takes a signed limit order; POST /matcher/orderbook/market takes a market order. @decentralchain/transactions builds and signs orders with order(), sends them with submitOrder(), and cancels them with cancelOrder() and cancelSubmittedOrder():
npm install @decentralchain/transactions@5.0.1
import { order, submitOrder, cancelOrder, cancelSubmittedOrder, type WithId } from '@decentralchain/transactions';
// order()'s declared return type leaves out `id` and `proofs`, though the order it returns has both
type SignedOrder = Parameters<typeof submitOrder>[0] & WithId;
const matcherUrl = 'https://testnet-matcher.decentralchain.io';
const seed = 'your secret seed phrase here';
// From GET /matcher (the matcher's public key) and GET /matcher/orderbook (its markets)
const matcherPublicKey = 'BEtB5b2kX8Yge4sb5RRbTUqd5Va8Ah8PhphowcW31z6Y';
const amountAsset = 'Hs5y6vFumpwVp6my7rVEmJEQQD3wiQ29qJhnixNqPit5'; // DCCCANARY, 8 decimals
const priceAsset = null; // null means DCC
// Buy 1 DCCCANARY at 0.001 DCC. For order versions 1 to 3 the price is scaled by
// 10^(8 + priceAssetDecimals - amountAssetDecimals): here 10^8, so 0.001 DCC is 100_000.
const buy = order(
{
matcherPublicKey,
amountAsset,
priceAsset,
orderType: 'buy',
amount: 100_000_000, // 1 DCCCANARY
price: 100_000, // 0.001 DCC per DCCCANARY
matcherFee: 300_000, // 0.003 DCC, the matcher's base fee (GET /matcher/settings)
version: 3, // the matcher accepts order versions 1, 2 and 3
},
seed,
) as SignedOrder;
console.log('Placed:', await submitOrder(buy, { matcherUrl }));
const status = await fetch(
`${matcherUrl}/matcher/orderbook/${amountAsset}/DCC/${buy.id}`,
).then((r) => r.json());
console.log('Status:', status.status); // Accepted, PartiallyFilled, Filled or Cancelled
// Cancel it: sign a cancel request with the same seed
const cancel = cancelOrder({ orderId: buy.id }, seed);
console.log('Cancelled:', await cancelSubmittedOrder(cancel, amountAsset, priceAsset, matcherUrl));
Run on the Testnet, the matcher answered OrderAccepted, the status was Accepted, and the cancel answered OrderCanceled. An order’s expiration can be at most 30 days after its timestamp; order() sets 29 days by default.
The matcher checks an order before it accepts it: the signature, the matcher public key, the pair, the version, the fee, the price against the tick size, and that the account’s tradable balance covers the order and the fee.
Order Status#
GET /matcher/orderbook/{amountAsset}/{priceAsset}/{orderId} is public:
{"status": "Cancelled", "filledAmount": 0, "filledFee": 0}
Status |
Meaning |
|---|---|
|
In the order book, nothing filled yet |
|
Part of it has traded; the rest is still in the book |
|
Fully traded |
|
Cancelled by its owner, or by the matcher (expired, or the balance no longer covers it) |
|
The matcher doesn’t know the order |
GET /matcher/transactions/{orderId} lists the Exchange transactions that filled an order.
Requests Signed with Your Key#
Your order history and reserved balance are private. These endpoints need two headers: Timestamp, the current time in milliseconds, and Signature, the base58 signature of your public key’s bytes followed by the timestamp as an 8-byte big-endian number.
Endpoint |
Returns |
|---|---|
|
Your orders in every pair. |
|
Your orders in one pair |
|
What your open orders reserve, per asset |
import { libs } from '@decentralchain/transactions';
const matcherUrl = 'https://testnet-matcher.decentralchain.io';
const seed = 'your secret seed phrase here';
const publicKey = libs.crypto.publicKey(seed);
// Sign your public key's bytes followed by the current time as an 8-byte big-endian number
const timestamp = Date.now();
const timestampBytes = new Uint8Array(8);
new DataView(timestampBytes.buffer).setBigUint64(0, BigInt(timestamp));
const signature = libs.crypto.signBytes(
seed,
libs.crypto.concat(libs.crypto.base58Decode(publicKey), timestampBytes),
);
const headers = { Timestamp: String(timestamp), Signature: signature };
const orders = await fetch(`${matcherUrl}/matcher/orderbook/${publicKey}?activeOnly=false`, { headers })
.then((r) => r.json());
console.log('Orders:', orders);
const reserved = await fetch(`${matcherUrl}/matcher/balance/reserved/${publicKey}`, { headers })
.then((r) => r.json());
console.log('Reserved by open orders:', reserved);
A wrong signature returns 400 with "status": "InvalidSignature".
To cancel every open order at once, POST /matcher/orderbook/cancel takes {"sender": <public key>, "timestamp": <ms>, "signature": <signature of the public key bytes and the big-endian timestamp>}.
GET /matcher/orderbook/{amountAsset}/{priceAsset}/tradableBalance/{address} is public: it returns how much of each asset of the pair the address can still put into new orders.
Errors#
A rejected request returns a JSON object with "success": false, a status, a numeric error code and a message, for example:
{"success": false, "status": "InvalidSignature", "error": 1051904, "message": "The request has an invalid signature"}
Endpoints under /matcher/debug, GET /matcher/orders/{address}, the fee-rate updates and order book deletion are for the matcher’s operator: they need the operator’s X-Api-Key.