预言机集成(Switchboard)
Astromarkets 使用 Switchboard(Surge / Pull Feed)作为结果数据源
什么是 Switchboard
Switchboard 是 Solana 生态的去中心化预言机协议,与 Pyth、Chainlink 定位类似,但有两个特点使其适合预测市场:
- 无许可自定义 feed:任何人都可以用「任务图」(task graph)定义数据源——HTTP 抓取、JSON 解析、条件判断等任务自由组合,不局限于官方维护的价格对。预测市场的结算条件五花八门("某日 BTC 是否高于 X"、"某场比赛谁赢"),正需要这种灵活性;
- 拉取模式(Pull Feed):数据不是持续推送上链,而是在需要时(如结算时)由用户/keeper 主动请求预言机节点签名报价,再连同签名一起提交上链验证。链上只为实际用到的数据付费,且每次取值都带新鲜度(slot)保证。
其中 Surge 是 Switchboard 的低延迟报价通道:预言机节点在 TEE(可信执行环境)中运行任务图并对结果做 Ed25519 签名,链上程序通过 QuoteVerifier 验证签名后方可使用取值——数据的真实性由密码学保证,而非信任某个中心化服务器。
想深入了解 Switchboard,可访问 官网 与 官方文档。
取值约定
每个市场的 feed 必须输出以下三个值之一:
| Feed 取值 | 含义 |
|---|---|
1 × 10¹⁸ | Yes — 事件发生 |
0 | No — 事件未发生 |
2 × 10¹⁸ | Invalid — 取值错误 / 暂无法判定 |
数据源锁定与验证
- 创建时锁定 :
创建市场时feed_id(Switchboard feedHash)写入市场账户,且当场验证一次取值格式合法(∈{0, 1e18, 2e18}),不合法的 feed 无法用于创建市场; - 结算时验证 :
提交预言机市场结果要求:- quote 账户地址由
(queue, feed_id)推导——只认创建时锁定的数据源; - 验证报价签名(Surge 路径);
- slot 新鲜度 ——防止用陈旧数据结算。
- quote 账户地址由
同一笔交易内需要先执行 Switchboard 的 Ed25519 验签 + verified_update 指令,再执行 提交预言机市场结果。
创建 feed 的工作流
创建一个可用于结算的 feed,分五步:
- 把市场标题翻译成判定式——数据从哪个来源取、在什么时刻取、用什么条件比较。判定式必须客观、无歧义:"BTC 年底会涨吗"不可结算,"2026-12-31 00:00 UTC Coinbase BTC-USD 现货价是否 ≥ 150,000"才可以;
- 设计任务图——用 Switchboard 的任务(task)组合出数据管道:抓取(
httpTask)→ 解析(jsonParseTask)→ 条件映射(比较/条件任务),最终输出1/0/2(链上按 18 位定点读取,即上表的1e18/0/2e18); - 模拟验证——通过 Crossbar 模拟接口试跑任务图(即透明度工具中的 Simulate Feed),确认当前输出符合预期;
- 部署 feed——得到
feed_id; - 创建市场时传入
feed_id——创建市场指令会现场验证一次取值格式,不合法直接拒绝。
前端创建向导内置 Feed Task Library,可视化配置任务图并一键部署,以上五步都在向导内完成,详见创建市场。
设计任务图的三个要点
- 输出只能是 1 / 0 / 2——不要输出原始价格或其他数值,最后一步必须把结果映射到这三个值;
- 多源聚合抗操纵——对可能被操纵的数据(尤其是价格),从多个独立来源取值后用
medianTask取中位数,单一来源异常不影响结果; - 失败要兜底为 Invalid(2)而不是报错——API 挂了、字段缺失时,让任务图输出
2。2不会进入结算(见预言机出错怎么办),修复后可重试;而直接报错的 feed 无法完成结算流程。
示例 1:价格阈值市场
市场:"2026-12-31 00:00 UTC,BTC 价格是否 ≥ 150,000 USD?"
注意:判定时刻是固定的,不能用「当前现货价」接口——结算发生在到期之后,拉到的会是结算那一刻的价格,而不是题目规定时刻的价格。正确做法是用支持按时间戳查询的历史 K 线接口,比如币安的 /api/v3/klines:传入 startTime=1798675200000(即 2026-12-31 00:00:00 UTC 的毫秒时间戳),取该分钟 K 线的开盘价,即为整点时刻的价格。无论何时拉取,返回的都是同一条历史数据——结果确定、可复现。
{
"tasks": [
{
"comparisonTask": {
"op": "OPERATION_GT",
"lhs": {
"tasks": [
{
"httpTask": {
"url": "https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&startTime=1798675200000&limit=1"
}
},
{ "jsonParseTask": { "path": "$[0][1]" } }
]
},
"rhs": { "tasks": [{ "valueTask": { "value": 150000 } }] },
"onTrue": { "tasks": [{ "valueTask": { "value": 1 } }] },
"onFalse": { "tasks": [{ "valueTask": { "value": 0 } }] },
"onFailure": { "tasks": [{ "valueTask": { "value": 2 } }] }
}
}
]
}
币安 kline 返回 [[开盘时间, 开盘价, 最高价, 最低价, 收盘价, …]],$[0][1] 取出 00:00:00 整点的开盘价 → 与 150,000 比较 → 高于输出 1(Yes),否则输出 0(No),抓取失败输出 2(Invalid)。到期时间(如 2026-12-31 00:05 UTC)只需晚于判定时刻,K 线数据生成后即可随时结算。
示例 2:多数据源中位数(抗操纵增强版)
只依赖单一交易所,意味着该所宕机或价格异常时结果会被带偏。把 lhs 换成多个独立来源取中位数,任何单一来源的异常都不会左右结果。下面以三个交易所的现货接口为例展示写法(若判定时刻固定,把每个来源换成各自的历史 K 线接口即可,思路同示例 1):
{
"lhs": {
"tasks": [
{
"medianTask": {
"jobs": [
{ "tasks": [
{ "httpTask": { "url": "https://api.coinbase.com/v2/prices/BTC-USD/spot" } },
{ "jsonParseTask": { "path": "$.data.amount" } }
]},
{ "tasks": [
{ "httpTask": { "url": "https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT" } },
{ "jsonParseTask": { "path": "$.price" } }
]},
{ "tasks": [
{ "httpTask": { "url": "https://api.kraken.com/0/public/Ticker?pair=XBTUSD" } },
{ "jsonParseTask": { "path": "$.result.XXBTZUSD.c[0]" } }
]}
]
}
}
]
}
}
示例 3:非价格事件(官方数据判定)
市场:"2026 年 7 月美国 CPI 同比是否 ≥ 3.0%?"——套路相同,换成权威统计 API,比较官方公布的数字:
{
"tasks": [
{
"comparisonTask": {
"op": "OPERATION_GT",
"lhs": {
"tasks": [
{ "httpTask": { "url": "https://api.bls.gov/publicAPI/v2/timeseries/data/CUUR0000SA0?latest=true" } },
{ "jsonParseTask": { "path": "$.Results.series[0].data[0].calculations.pct_changes.12" } }
]
},
"rhs": { "tasks": [{ "valueTask": { "value": 3.0 } }] },
"onTrue": { "tasks": [{ "valueTask": { "value": 1 } }] },
"onFalse": { "tasks": [{ "valueTask": { "value": 0 } }] },
"onFailure": { "tasks": [{ "valueTask": { "value": 2 } }] }
}
}
]
}
体育赛果、选举结果等同理:找到输出结构化结果的权威 API,解析出获胜方字段,用 OPERATION_EQ 与目标值比较即可。
示例 4:需要 API Key 的私有数据源
很多高质量数据源(体育数据、金融终端、天气服务等)要求请求携带 API key。绝对不要把 key 明文写进任务图——feed 定义通过 Crossbar 对所有人公开(见透明度工具),明文 key 等于直接泄露。
正确做法是用 Switchboard Secrets:把 key 加密托管,只有运行在 TEE 中的预言机节点能解密;任务图里用 ${变量名} 占位引用。
市场:"某场 NBA 比赛主队是否获胜?"(体育数据 API 需要 key):
{
"tasks": [
{ "secretsTask": { "authority": "<你的钱包公钥>" } },
{
"comparisonTask": {
"op": "OPERATION_EQ",
"lhs": {
"tasks": [
{
"httpTask": {
"url": "https://api.sportsdata.example/v3/nba/scores/GAME_ID",
"headers": [
{ "key": "Ocp-Apim-Subscription-Key", "value": "${SPORTSDATA_API_KEY}" }
]
}
},
{ "jsonParseTask": { "path": "$.HomeTeamWon" } }
]
},
"rhs": { "tasks": [{ "valueTask": { "value": 1 } }] },
"onTrue": { "tasks": [{ "valueTask": { "value": 1 } }] },
"onFalse": { "tasks": [{ "valueTask": { "value": 0 } }] },
"onFailure": { "tasks": [{ "valueTask": { "value": 2 } }] }
}
}
]
}
工作方式:
- 事先托管密钥——用 Switchboard Secrets 的 SDK/CLI 把
SPORTSDATA_API_KEY加密上传,绑定到你的钱包(即任务图中secretsTask.authority); - 任务图只含占位符——
${SPORTSDATA_API_KEY}公开可见,但真实 key 只在预言机节点的 TEE 内解密注入,任何人(包括节点运营者)都拿不到明文; - 审计不受影响——交易者仍能看到完整的 URL、解析路径和判定逻辑,只是看不到 key 本身。
注意:带密钥的 feed 用 Simulate 试跑时,公共模拟接口无法注入你的密钥,可能返回失败——请在部署前用 Switchboard 提供的带密钥模拟方式自测。
以上任务图展示的是设计思路,具体任务字段以 Switchboard 官方文档与前端 Feed Task Library 内置模板为准;部署前务必用 Simulate 验证输出。
透明度工具
交易页 Oracle 标签提供:
- Feed ID 链接到 Switchboard Explorer;
- Feed JSON / Task Graph:通过 Crossbar(
crossbar.switchboardlabs.xyz)拉取 feed 定义,展示完整数据源逻辑; - Simulate Feed:调用 Crossbar 模拟接口现场跑一遍任务图,预览「如果现在结算,结果是什么」。
任何交易者在下单前都能完整审计市场的结算数据源——这是中心化预言机方案做不到的。
预言机出错怎么办
预言机只是三层裁定的第一层:
- 取值格式异常 → 结果 Invalid,不进入结算,可修复后重试;
- 取值「合法但错误」(如数据源被操纵)→ 争议 + 投票纠错;
- 投票仍错误 → DAO M-of-N 复审兜底。
下单前请务必通过 Oracle 标签页自行审查数据源是否合法可靠
-
任务图逻辑是否真实对应市场标题
-
数据来源(API/网站)可靠
-
Simulate结果是否符合预期
对可疑或无法看懂的数据源,请勿参与交易!