@opinion-labs/opinion-clob-sdk
v0.5.3
Published
TypeScript SDK for Opinion Labs Prediction Market CLOB operations
Readme
@opinion-labs/opinion-clob-sdk
TypeScript SDK for Opinion Labs Prediction Market. Trade on blockchain-based prediction markets with EIP-712 signed orders, Gnosis Safe multi-sig support, and real-time WebSocket updates.
Installation
npm install @opinion-labs/opinion-clob-sdkQuick Start
import { Client, CHAIN_ID_BNB_MAINNET, DEFAULT_API_HOST, OrderSide, OrderType } from '@opinion-labs/opinion-clob-sdk';
const client = new Client({
host: DEFAULT_API_HOST,
apiKey: 'your_api_key',
chainId: CHAIN_ID_BNB_MAINNET,
rpcUrl: 'https://bsc-dataseed.binance.org',
privateKey: '0x...',
multiSigAddress: '0x...',
});
// Get markets
const markets = await client.getMarkets({ limit: 10 });
console.log(markets.result.list);
// Place a limit buy order
await client.placeOrder({
marketId: 1234,
tokenId: '0x...',
side: OrderSide.BUY,
orderType: OrderType.LIMIT_ORDER,
price: '0.5',
makerAmountInQuoteToken: '10',
});Prerequisites
| Requirement | Description | |-------------|-------------| | API Key | Obtained from the Opinion Labs team | | Private Key | EOA wallet private key for signing orders | | Multi-sig Address | Your Gnosis Safe wallet address (visible in "My Portfolio") | | RPC URL | BNB Chain RPC endpoint |
Note: The signer (private key) and multi-sig address may be different wallets. The signer is an owner of the Safe that authorizes trades.
Configuration
const client = new Client({
// Required
host: 'https://openapi.opinion.trade/openapi',
apiKey: 'your_api_key',
chainId: 56, // BNB Chain (only supported chain)
rpcUrl: 'https://bsc-dataseed.binance.org',
privateKey: '0x...',
multiSigAddress: '0x...',
// Optional
proxyUrl: 'http://127.0.0.1:7890', // HTTP proxy
quoteTokensCacheTtl: 3600, // Cache TTL in seconds (default: 3600)
marketCacheTtl: 300, // Cache TTL in seconds (default: 300)
});API Reference
Market Data
// List markets with filters
const markets = await client.getMarkets({
topicType: TopicType.BINARY, // BINARY, CATEGORICAL, or ALL
status: TopicStatusFilter.ACTIVATED, // ACTIVATED, RESOLVED, or ALL
sortBy: TopicSortType.BY_VOLUME_DESC, // Sort order
page: 1,
limit: 20,
});
// Get market details
const market = await client.getMarket(marketId);
// Get categorical market details
const catMarket = await client.getCategoricalMarket(marketId);
// Get orderbook
const orderbook = await client.getOrderbook(tokenId);
// Get latest price
const price = await client.getLatestPrice(tokenId);
// Get price history
const history = await client.getPriceHistory(tokenId, {
interval: '1h', // '1m' | '1h' | '1d' | '1w' | 'max'
startAt: 1700000000,
endAt: 1700100000,
});
// Get quote tokens
const tokens = await client.getQuoteTokens();
// Get on-chain fee rates
const fees = await client.getFeeRates(tokenId);Trading
All order operations are gas-free (signed off-chain, submitted via API).
// Enable trading (one-time approval, requires gas)
await client.enableTrading();
// Place a limit buy order
await client.placeOrder({
marketId: 1234,
tokenId: '0x...',
side: OrderSide.BUY,
orderType: OrderType.LIMIT_ORDER,
price: '0.5',
makerAmountInQuoteToken: '10', // 10 USDC
});
// Place a limit sell order
await client.placeOrder({
marketId: 1234,
tokenId: '0x...',
side: OrderSide.SELL,
orderType: OrderType.LIMIT_ORDER,
price: '0.6',
makerAmountInBaseToken: '10', // 10 outcome tokens
});
// Place a market buy order
await client.placeOrder({
marketId: 1234,
tokenId: '0x...',
side: OrderSide.BUY,
orderType: OrderType.MARKET_ORDER,
price: '0',
makerAmountInQuoteToken: '10',
});
// Cancel an order
await client.cancelOrder(orderId);
// Batch operations
await client.placeOrdersBatch([order1, order2, order3]);
await client.cancelOrdersBatch([id1, id2, id3]);
// Cancel all open orders (with optional filters)
await client.cancelAllOrders({ marketId: 1234, side: OrderSide.BUY });User Data
const positions = await client.getMyPositions({ marketId: 1234 });
const trades = await client.getMyTrades({ page: 1, limit: 20 });
const orders = await client.getMyOrders({ status: '1' }); // 1 = open
const order = await client.getOrderById(orderId);
const balances = await client.getMyBalances();
const auth = await client.getUserAuth();Token Operations
These operations interact directly with the blockchain and require gas (BNB).
// Split: convert collateral (USDC) into Yes + No tokens
await client.split(marketId, 10000000n); // amount in wei
// Merge: convert Yes + No tokens back to collateral
await client.merge(marketId, 10000000n);
// Redeem: claim winnings from a resolved market
await client.redeem(marketId);WebSocket
Subscribe to real-time market updates.
const ws = client.createWebSocketClient({
heartbeatInterval: 30,
onOpen: () => console.log('Connected'),
onClose: () => console.log('Disconnected'),
onError: (err) => console.error(err),
});
await ws.connect();
// Subscribe to orderbook depth updates
ws.subscribeMarketDepthDiff(marketId, (msg) => {
console.log(`Depth: ${msg.side} ${msg.price} @ ${msg.size}`);
});
// Subscribe to price updates
ws.subscribeMarketLastPrice(marketId, (msg) => {
console.log(`Price: ${msg.price}`);
});
// Subscribe to trade updates
ws.subscribeMarketLastTrade(marketId, (msg) => {
console.log(`Trade: ${msg.price} x ${msg.size}`);
});
// Subscribe to your order status updates
ws.subscribeTradeOrderUpdate((msg) => {
console.log(`Order ${msg.orderId}: ${msg.status}`);
});
// Subscribe to your new trade records
ws.subscribeTradeRecordNew((msg) => {
console.log(`New trade: ${msg.tradeId}`);
});
// Disconnect when done
await ws.disconnect();Builder Mode
For platforms that place orders on behalf of users without holding their private keys.
import { BuilderClient, UserClient } from '@opinion-labs/opinion-clob-sdk';
const builder = new BuilderClient({
host: 'https://openapi.opinion.trade/openapi',
builderApiKey: 'your_builder_api_key',
chainId: 56,
rpcUrl: 'https://bsc-dataseed.binance.org',
});
// Create a sub-account for a user
const userInfo = await builder.createUser('0xUserWallet...');
// IMPORTANT: userInfo.apikey is returned only once — store it securely
// Build an order for the user to sign
const orderResult = await builder.buildOrderForSigning({
marketId: 1234,
tokenId: '0x...',
userWalletAddress: '0xUserSafeWallet...',
side: OrderSide.BUY,
orderType: OrderType.LIMIT_ORDER,
amount: 10,
price: '0.5',
});
// User signs the order (client-side)
const user = new UserClient('0xUserPrivateKey...');
const signature = await user.signTypedData(orderResult.typedData);
// Submit the signed order
await builder.placeOrderForUser({
...orderResult,
signature,
userWalletAddress: '0xUserSafeWallet...',
});Error Handling
import {
SdkError,
InvalidParamError,
OpenApiError,
BalanceNotEnough,
InsufficientGasBalance,
} from '@opinion-labs/opinion-clob-sdk';
try {
await client.placeOrder(orderData);
} catch (err) {
if (err instanceof InvalidParamError) {
// Invalid order parameters
} else if (err instanceof BalanceNotEnough) {
// Insufficient token balance
} else if (err instanceof InsufficientGasBalance) {
// Not enough BNB for gas
} else if (err instanceof OpenApiError) {
// API call failed
}
}Response Format
All API methods return:
{
errno: number; // 0 = success
errmsg: string; // Error message (empty on success)
result: T; // Response payload
}Workflow
1. enableTrading() One-time approval (requires gas)
|
2. split() (optional) Convert USDC -> Yes/No tokens (requires gas)
|
3. placeOrder() Place limit/market orders (gas-free)
|
4. cancelOrder() Cancel open orders (gas-free)
|
5. merge() / redeem() Convert back or claim winnings (requires gas)Requirements
- Node.js >= 18
- TypeScript >= 5.3
License
MIT
