跳到主要内容

预言机集成(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 — 事件发生
0No — 事件未发生
2 × 10¹⁸Invalid — 取值错误 / 暂无法判定

数据源锁定与验证

  • 创建时锁定 : 创建市场feed_id(Switchboard feedHash)写入市场账户,且当场验证一次取值格式合法(∈ {0, 1e18, 2e18}),不合法的 feed 无法用于创建市场;
  • 结算时验证 : 提交预言机市场结果 要求:
    1. quote 账户地址由 (queue, feed_id) 推导——只认创建时锁定的数据源;
    2. 验证报价签名(Surge 路径);
    3. slot 新鲜度 ——防止用陈旧数据结算。

同一笔交易内需要先执行 Switchboard 的 Ed25519 验签 + verified_update 指令,再执行 提交预言机市场结果

创建 feed 的工作流

创建一个可用于结算的 feed,分五步:

  1. 把市场标题翻译成判定式——数据从哪个来源取、在什么时刻取、用什么条件比较。判定式必须客观、无歧义:"BTC 年底会涨吗"不可结算,"2026-12-31 00:00 UTC Coinbase BTC-USD 现货价是否 ≥ 150,000"才可以;
  2. 设计任务图——用 Switchboard 的任务(task)组合出数据管道:抓取(httpTask)→ 解析(jsonParseTask)→ 条件映射(比较/条件任务),最终输出 1 / 0 / 2(链上按 18 位定点读取,即上表的 1e18 / 0 / 2e18);
  3. 模拟验证——通过 Crossbar 模拟接口试跑任务图(即透明度工具中的 Simulate Feed),确认当前输出符合预期;
  4. 部署 feed——得到 feed_id;
  5. 创建市场时传入 feed_id——创建市场 指令会现场验证一次取值格式,不合法直接拒绝。

前端创建向导内置 Feed Task Library,可视化配置任务图并一键部署,以上五步都在向导内完成,详见创建市场

设计任务图的三个要点

  • 输出只能是 1 / 0 / 2——不要输出原始价格或其他数值,最后一步必须把结果映射到这三个值;
  • 多源聚合抗操纵——对可能被操纵的数据(尤其是价格),从多个独立来源取值后用 medianTask 取中位数,单一来源异常不影响结果;
  • 失败要兜底为 Invalid(2)而不是报错——API 挂了、字段缺失时,让任务图输出 22 不会进入结算(见预言机出错怎么办),修复后可重试;而直接报错的 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 } }] }
}
}
]
}

工作方式:

  1. 事先托管密钥——用 Switchboard Secrets 的 SDK/CLI 把 SPORTSDATA_API_KEY 加密上传,绑定到你的钱包(即任务图中 secretsTask.authority);
  2. 任务图只含占位符——${SPORTSDATA_API_KEY} 公开可见,但真实 key 只在预言机节点的 TEE 内解密注入,任何人(包括节点运营者)都拿不到明文;
  3. 审计不受影响——交易者仍能看到完整的 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 模拟接口现场跑一遍任务图,预览「如果现在结算,结果是什么」。

任何交易者在下单前都能完整审计市场的结算数据源——这是中心化预言机方案做不到的。

预言机出错怎么办

预言机只是三层裁定的第一层:

  1. 取值格式异常 → 结果 Invalid,不进入结算,可修复后重试;
  2. 取值「合法但错误」(如数据源被操纵)→ 争议 + 投票纠错;
  3. 投票仍错误 → DAO M-of-N 复审兜底。
市场的结算数据源由创建者自行配置,平台不对 feed 的正确性做背书

下单前请务必通过 Oracle 标签页自行审查数据源是否合法可靠

  • 任务图逻辑是否真实对应市场标题

  • 数据来源(API/网站)可靠

  • Simulate 结果是否符合预期

对可疑或无法看懂的数据源,请勿参与交易!