Prediction Market — WebSocket Push API(接入指南)
1. 字段格式规范
1.1 amount(金额)
规则:截取到小数点后 2 位(DOWN 模式,向 0 截断)
示例:
2.5678 → "2.56"
0.009 → "<0.01" ← 小于 0.01 的统一展示
null → "<0.01"
100 → "100.00"
接入建议:
- 直接用
amount字段渲染给用户 - 如需精确数值计算,请通过业务后端 REST API 获取,不要反向解析此字段
1.2 topic(市场标题)
规则:超过 12 字符则截断后追加 ".."(共最长 14 字符)
示例:
"BTC will hit 100K" → "BTC will hi.."
"Short title" → "Short title"
null → ""
1.3 outcome(结果名称)
规则:原样透传,未截断
可能值:
"Yes" / "No"(二元市场)
"Trump" / "Biden" / 其他(多元市场)
""(数据缺失时 )
1.4 pushId(幂等键)
格式见 Wallet Events WebSocket API 第 3.3 节。所有 topic 必有,强烈建议客户端用此字段做去重。
2. 接入完整流程示例
2.1 完整流程
1. 通过 REST API 在用户登录态获取 API Key + Secret Key
2. 准备 timestamp + random
3. 拼接所有 URL 参数(按字母序)
4. HMAC SHA256 计算 signature
5. 构造完整 WSS URL
6. WebSocket 连接,Header 加 X-MBX-APIKEY
7. on('open') → 连接成功,可发送 SUBSCRIBE 命令(如未在 URL 中订阅)
8. on('message') → 解析消息(先 outer JSON, 再 inner JSON)
9. on('close') → 触发重连逻辑(重新生成 timestamp + signature)
10. setInterval 30s → 发送空 PING
2.2 Node.js 示例
const WebSocket = require('ws');
const crypto = require('crypto');
const API_KEY = process.env.BINANCE_API_KEY;
const SECRET_KEY = process.env.BINANCE_SECRET_KEY;
const BASE_URL = 'wss://api.binance.com/sapi/wss';
// 订阅的所有 prediction topics
const TOPICS = [
'web3_prediction_pm_market_buy_success',
'web3_prediction_pm_market_buy_fail',
'web3_prediction_pm_market_sell_success',
'web3_prediction_pm_market_sell_fail',
'web3_prediction_pm_limit_submit_success',
'web3_prediction_pm_limit_submit_fail',
'web3_prediction_pm_limit_order_filled',
'web3_prediction_pm_limit_order_partial_fill',
'web3_prediction_pm_claim_success',
'web3_prediction_pm_claim_fail',
'web3_prediction_pm_claim_partial_success',
'web3_prediction_pm_transfer_success',
'web3_prediction_pm_transfer_fail',
'web3_prediction_pm_market_close',
];
function buildConnectionUrl() {
const params = {
random: crypto.randomBytes(16).toString('hex'),
topic: TOPICS.join('|'),
recvWindow: '30000',
timestamp: Date.now().toString(),
};
const sortedKeys = Object.keys(params).sort();
const payload = sortedKeys.map(k => `${k}=${params[k]}`).join('&');
const signature = crypto
.createHmac('sha256', SECRET_KEY)
.update(payload)
.digest('hex');
return `${BASE_URL}?${payload}&signature=${signature}`;
}
const seenPushIds = new Set(); // 简单 LRU 推荐生产替换
function connect() {
const url = buildConnectionUrl();
const ws = new WebSocket(url, [], {
headers: { 'X-MBX-APIKEY': API_KEY },
});
ws.on('open', () => {
console.log('[ws] connected');
// 心跳
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) ws.ping();
}, 30_000);
});
ws.on('message', (raw) => {
const envelope = JSON.parse(raw.toString());
// 命令响应
if (envelope.type === 'COMMAND') {
console.log('[ws] command response:', envelope);
return;
}
// 业务推送
if (envelope.type === 'TOPIC') {
const payload = JSON.parse(envelope.data); // ⚠️ data 是字符串化 JSON
handlePredictionPush(envelope.topic, payload);
}
});
ws.on('close', (code, reason) => {
console.warn('[ws] closed', code, reason.toString());
setTimeout(connect, 3_000); // 简单重连,生产建议指数退避
});
ws.on('error', (err) => {
console.error('[ws] error', err);
});
return ws;
}
function handlePredictionPush(topic, payload) {
// 1. 幂等去重
if (seenPushIds.has(payload.pushId)) return;
seenPushIds.add(payload.pushId);
// 2. 提取 scenarioCode(topic 去前缀)
const scenarioCode = topic.replace('web3_prediction_', '');
// 3. 字段兜底
const amount = payload.amount ?? '<0.01';
const marketTopic = payload.topic ?? 'the market';
const outcome = payload.outcome ?? '';
// 4. 按场景分发
switch (scenarioCode) {
case 'pm_market_buy_success':
console.log(`Bought ${amount} USDT in ${marketTopic}`);
break;
case 'pm_claim_success':
console.log(`Successfully claimed ${amount} USDT.`);
break;
case 'pm_claim_fail':
console.log(`Failed to claim ${amount} from ${outcome}.`);
break;
case 'pm_market_close':
console.log(`Market ${marketTopic} resolved.`);
break;
// ... 其他 11 个场景
default:
console.log(`[unknown scenario] ${scenarioCode}`, payload);
}
}
connect();