Myriad Order Book Reference
This guide walks through the end-to-end flow for trading on Myriad's Order Book. It covers authentication, market discovery, order placement, position management, and redemption.
For detailed reference, see:
Full REST API documentation (orders, orderbook, trades, positions, events, WebSockets)
JavaScript SDK for on-chain interactions (split, merge, redeem, approvals)
Overview
The Myriad Order Book is a hybrid off-chain/on-chain order book for prediction markets. Traders sign orders off-chain using EIP-712 and submit them to the REST API. A matcher service finds compatible orders and settles them atomically on-chain via the
MyriadCTFExchange contract.Each market has two outcomes. Example: YES (0) and NO (1). Outcome shares are ERC1155 tokens. Collateral is an ERC20 token (e.g. USDC, USD1).
plain textβββββββββββββ POST /orders ββββββββββββββ matchOrders() ββββββββββββββββ β Trader β βββββββββββββββββββββββΊ β API β ββββββββββββββββββββΊ β Exchange β β (off-chainβ signed EIP-712 order β validates β settles on-chain β (on-chain) β β signing) β βββββββββββββββββββββββ β + stores β ββββββββββββββββββββ β transfers β β β { orderHash, status } β β OrdersMatched β shares + β βββββββββββββ ββββββββββββββ event β collateral β ββββββββββββββββ
1. Authentication & Order Signing
API Authentication
Most read endpoints are public, with higher rate limits if an API key is provided. Order endpoints require authentication tied to your account's connected wallet:
GET /orders returns only your own orders, and placing or cancelling requires the order's trader to match your connected wallet.Every credential starts with a wallet sign-in (SIWE): logging in creates your account on first use and permanently ties it to your wallet. From that session you mint an API key + secret, and agents HMAC-sign every request from then on. The two credential types:
- Session JWT (SIWE) β for first-party apps. Sign a server-issued message with your wallet (
personal_sign) and exchange it atPOST /auth/loginfor short-lived access + refresh tokens. Smart-contract wallets are supported: the login signature is verified via EIP-1271 when standard EOA recovery fails β no extra parameter needed.
- API key + secret (HMAC) β for agents / market makers. Long-lived; created from a session (
POST /auth/api-keys) or via Myriad Account settings. Each request carriesx-api-key,x-api-timestamp, and anx-api-signatureHMAC over the timestamp, method, path, and body.
For the full specs β message formats, the HMAC signing recipe, key management, and WebSocket subscription tokens β see the Authentication section of the Myriad API Reference.
Rate Limiting
- Requests with API key - 200 requests/second per IP and/or API key.
- Requests w/o API key - 30 requests/10 seconds per IP
- Order placement: 200 orders per 10-second window per trader address (shared across single and bulk order endpoints).
- Headers included on responses:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset
Order Signing (EIP-712)
Orders are signed off-chain using the EIP-712 typed data standard. You can sign with either polkamarkets-js or ethers.js.
Order Structure
plain textconst order = { trader: '0xYourWallet', marketId: '42', outcomeId: 0, // 0 = YES, 1 = NO side: 0, // 0 = BUY, 1 = SELL amount: '1000000000000000000', // shares in wei price: '500000000000000000', // 0.50 in 1e18 minFillAmount: '0', nonce: '1', expiration: '0', // 0 = GTC };
Price scale: prices are integers in
[1, 1e18] where 1e18 = 1.00 (one full collateral token per share).Price tick size: new orders must be priced on a 0.01 (1-cent) grid β
price must be a multiple of 1e16 (e.g. 570000000000000000 = 0.57 is valid; 575000000000000000 = 0.575 is rejected with 400). The tick rule applies to placement only; cancellation reuses the original signed values.Example A: Using the SDK
plain textimport * as polkamarketsjs from 'polkamarkets-js'; const polkamarkets = new polkamarketsjs.Application({ web3Provider: '<https://bsc-dataseed.binance.org>', web3PrivateKey: '<your_private_key>', }); const exchange = polkamarkets.getMyriadCTFExchangeContract({ contractAddress: '0x<ExchangeAddress>', }); // Compute the EIP-712 order hash const orderHash = await exchange.hashOrder({ order }); // Sign using the web3 provider (eth_signTypedData_v4) const traderAddress = await polkamarkets.getAddress(); const web3 = polkamarkets.web3; const typedData = JSON.stringify({ types: { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], Order: [ { name: 'trader', type: 'address' }, { name: 'marketId', type: 'uint256' }, { name: 'outcomeId', type: 'uint8' }, { name: 'side', type: 'uint8' }, { name: 'amount', type: 'uint256' }, { name: 'price', type: 'uint256' }, { name: 'minFillAmount', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'expiration', type: 'uint256' }, ], }, primaryType: 'Order', domain: { name: 'MyriadCTFExchange', version: '1', chainId: 56, verifyingContract: '0x<ExchangeAddress>', }, message: order, }); const signature = await web3.currentProvider.request({ method: 'eth_signTypedData_v4', params: [traderAddress, typedData], });
Example B: Using ethers.js
plain textimport { ethers } from 'ethers'; const domain = { name: 'MyriadCTFExchange', version: '1', chainId: 56, verifyingContract: '0x<ExchangeAddress>', }; const types = { Order: [ { name: 'trader', type: 'address' }, { name: 'marketId', type: 'uint256' }, { name: 'outcomeId', type: 'uint8' }, { name: 'side', type: 'uint8' }, { name: 'amount', type: 'uint256' }, { name: 'price', type: 'uint256' }, { name: 'minFillAmount', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'expiration', type: 'uint256' }, ], }; const signer = new ethers.Wallet('<private_key>', provider); const signature = await signer.signTypedData(domain, types, order);
Smart-Contract Wallets (signatureType)
Orders are signed by an EOA by default. If your funds live in a smart-contract wallet (SCW), have the wallet produce an EIP-1271 signature over the same EIP-712 order digest and pass
signatureType: 3 alongside signature in order and cancel requests β the API then verifies the signature on-chain via the wallet's isValidSignature instead of ECDSA recovery. signatureType is transport metadata only (not part of the signed struct, doesn't affect the order hash) and defaults to 0 (EOA); values 1β2 are reserved and rejected. SCW login is supported too (see Authentication).2. Fetching Markets
List Order Book Markets
Use
trading_model=ob to filter for Order Book markets:bashGET /markets?trading_model=ob&state=open&sort=volume_24h&limit=20
plain textconst res = await fetch( '<https://api-v2.myriadprotocol.com/markets?trading_model=ob&state=open&sort=volume_24h&limit=20>', { headers: { 'x-api-key': 'YOUR_API_KEY' } } ); const { data, pagination } = await res.json();
Each market includes
id (on-chain market ID), networkId, title, outcomes, fees, and more.Get Market Orderbook
bashGET /markets/42/orderbook?network_id=56&outcome=0
Returns aggregated bids and asks as
[price, remaining_amount] tuples sorted by best price.List NegRisk Events
For multi-outcome markets (e.g. "Who wins the election?"):
bashGET /events?network_id=56&state=open
Each event contains an
outcomes array linking to individual binary markets.3. Placing Orders
Prerequisites
Before placing orders, ensure the correct approvals are in place:
Order side | What to approve | Spender |
BUY | ERC20 collateral ( approve) | Exchange contract |
SELL | ERC1155 outcome shares ( setApprovalForAll) | Exchange contract |
plain text// For BUY orders: approve collateral await erc20.approve({ address: '0x<ExchangeAddress>', amount: '1000000000', }); // For SELL orders: approve outcome shares await ct.getContract().methods .setApprovalForAll('0x<ExchangeAddress>', true) .send({ from: traderAddress });
Submit an Order
plain textconst res = await fetch('<https://api-v2.myriadprotocol.com/orders>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY', }, body: JSON.stringify({ order, signature, network_id: 56, time_in_force: 'GTC', // GTC | GTD | FOK | FAK | PO // signatureType: 3, // only for smart-contract wallets (EIP-1271) }), }); const { orderHash, status } = await res.json();
The API validates the signature, checks on-chain balance/allowance, and stores the order. The matcher picks it up automatically.
Time-in-Force Options
TIF | Behaviour |
GTC | Good-til-cancelled. expiration must be 0. |
GTD | Good-til-date. expiration is a unix timestamp. |
FOK | Fill-or-kill. Must fill fully or the order is cancelled. |
FAK | Fill-and-kill. Partial fill allowed; remainder cancelled. |
PO | Post-only. Never fills as a taker β cancelled entirely if it would cross an older counterparty. expiration=0 rests GTC-style, >0 GTD-style. |
Matching & Priority
Makers fill in price-time priority (best price first, oldest first on ties). The on-chain matcher settles direct matches (buy vs sell on the same outcome), mint/merge matches (opposite-outcome orders whose prices sum to 1), and cross-market NegRisk fills. See the Myriad API Reference for the full concept definitions, batch endpoints, and per-endpoint validation rules.
Cancel an Order
bashDELETE /orders/0x<orderHash>
With the original order + signature in the request body for ownership verification.
4. Splitting & Merging Positions
Splitting converts collateral into equal amounts of YES + NO shares. Merging is the inverse.
Split (Collateral β YES + NO)
plain text// Via SDK (direct on-chain call) await erc20.approve({ address: '0x<ConditionalTokens>', amount: '1000000' }); await ct.splitPosition({ marketId: 42, amount: '1000000' }); // Via API (returns calldata to sign) const res = await fetch('<https://api-v2.myriadprotocol.com/positions/split>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ market_id: 42, amount: '1000000', network_id: 56 }), }); const { to, calldata, value } = await res.json();
Merge (YES + NO β Collateral)
plain text// Via SDK await ct.mergePositions({ marketId: 42, amount: '1000000' }); // Via API const res = await fetch('<https://api-v2.myriadprotocol.com/positions/merge>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ market_id: 42, amount: '1000000', network_id: 56 }), });
NegRisk Split & Merge
For multi-outcome events, use the NegRisk endpoints which handle collateral wrapping automatically:
plain text// Split for a specific outcome within an event const res = await fetch('<https://api-v2.myriadprotocol.com/positions/neg-risk/split>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ event_id: '0x<bytes32>', outcome_index: 0, amount: '1000000', network_id: 56, }), });
5. Redeeming Positions
After a market resolves, winning shareholders can redeem their shares for collateral.
Redeem Winning Shares
plain text// Via SDK await ct.redeemPositions({ marketId: 42 }); // Via API (returns calldata) const res = await fetch('<https://api-v2.myriadprotocol.com/positions/redeem>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ market_id: 42, network_id: 56 }), }); const { to, calldata, value } = await res.json();
Redeem Voided Market
If a market is voided (cancelled), shares are redeemable at the market's voided payout ratios:
plain text// Via API const res = await fetch('<https://api-v2.myriadprotocol.com/positions/redeem-voided>', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': 'YOUR_API_KEY' }, body: JSON.stringify({ market_id: 42, network_id: 56 }), });
Check Portfolio
To see which positions are redeemable:
bashGET /users/0xYourWallet/portfolio?trading_model=ob GET /users/0xYourWallet/markets?state=resolved
The response includes
winningsToClaim and winningsClaimed flags per position.Quick Reference
Step | Method | Endpoint / SDK |
List ob markets | GET | /markets?trading_model=ob |
View orderbook | GET | /markets/:id/orderbook |
View trades | GET | /markets/:id/trades |
Place order | POST | /orders |
Place orders in bulk | POST | /orders/batch |
Replace orders atomically | POST | /orders/batch-modify |
Check order | GET | /orders/:hash |
Cancel order | DELETE | /orders/:hash |
Cancel orders in bulk | POST | /orders/cancel-batch |
Cancel everything | POST | /orders/cancel-all |
Split position | POST | /positions/split or ct.splitPosition() |
Merge position | POST | /positions/merge or ct.mergePositions() |
Redeem winnings | POST | /positions/redeem or ct.redeemPositions() |
Redeem voided | POST | /positions/redeem-voided |
NegRisk split | POST | /positions/neg-risk/split |
NegRisk merge | POST | /positions/neg-risk/merge |
User portfolio | GET | /users/:address/portfolio |