在智能合约里接入 OOv3
标准模式:你的合约持有资金,市场到期时调 assertTruth(),通过回调拿到
结果。这与主流预测市场平台的做法一致。
先在沙盒里跑通:把你的集成合约部到 X Layer 测试网(chainId 1952), 指向同一份 OOv3 地址(合约地址),用
app-dev.xtruth.xyz做断言/争议/结算 的 UI 调试。准备好了再切到app.xtruth.xyz。 详见 环境。
最小预测市场合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { OptimisticOracleV3Interface } from
"./interfaces/OptimisticOracleV3Interface.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract YesNoMarket {
OptimisticOracleV3Interface public immutable oo;
IERC20 public immutable bondCurrency;
bytes32 public constant identifier = "ASSERT_TRUTH";
struct Market {
bool resolved;
bool outcome; // true = YES
bytes32 assertionId;
}
mapping(bytes32 => Market) public markets;
constructor(address oov3, address currency) {
oo = OptimisticOracleV3Interface(oov3);
bondCurrency = IERC20(currency);
}
/// 任何人可以通过质押 + 断言来解析市场结果。
function resolve(bytes32 marketId, string calldata claim, uint64 liveness) external {
require(!markets[marketId].resolved, "already resolved");
uint256 bond = oo.getMinimumBond(address(bondCurrency));
bondCurrency.transferFrom(msg.sender, address(this), bond);
bondCurrency.approve(address(oo), bond);
bytes32 assertionId = oo.assertTruth(
bytes(claim),
msg.sender, // 断言者
address(this), // 回调目标
address(0), // 自定义升级管理器
liveness,
bondCurrency,
bond,
identifier,
bytes32(0)
);
markets[marketId].assertionId = assertionId;
}
/// OOv3 在结算时回调(无论真假)。
function assertionResolvedCallback(
bytes32 assertionId,
bool assertedTruthfully
) external {
require(msg.sender == address(oo), "only OO");
// 找到对应市场,记录结果,给胜方发钱。(查表细节略。)
}
/// 可选:被争议时的钩子(升级到 DVM)。
function assertionDisputedCallback(bytes32 assertionId) external {
require(msg.sender == address(oo), "only OO");
// 在 UI 上显示"争议中";冻结待发的支付。
}
}
关键参数选择
保证金
getMinimumBond(currency) 返回该代币的最小保证金;你也可以设更高。
保证金越高,争议成本越高,适合高价值市场。
挑战期 liveness
权衡:
- 短(如 1 小时)—— 结算快,但争议者可能错过窗口
- 长(如 7 天)—— 结算慢但极安全;适合大型市场或治理类问题
OOv3 默认 2 小时。Polymarket 根据市场大小用 2 小时到几天不等。
Identifier
OOv3 最常用 ASSERT_TRUTH,意为"声明 claim 文本为真,无人争议则判真"。
也可以用 YES_OR_NO_QUERY、NUMERICAL、MULTIPLE_CHOICE_QUERY,
但只在你的升级管理器对其做特殊解释时才有意义。
自定义升级管理器
默认走 address(0) 即原生 DVM 流程。如果想自定义(白名单争议者、
强制多签后才能升级等),实现 EscalationManagerInterface 并传它的地址。
回调注意事项
assertionResolvedCallback在settleAssertion()内被调用。保持精简, 必须在 settle 调用者的 gas budget 内执行完。- 如果你的回调 revert,不会 撤销 settle —— OOv3 用 try/catch 包了它。 但你的合约可能不知道这次结算。准备一个"按 id 主动 claim"的兜底函数。
- 幂等性:别假设回调只执行一次。用
markets[marketId].resolved标志位 防一手。
从合约自己授权
当合约(而非用户)押保证金时,通常做法是合约预先存入 bond 币,
然后在 resolve() 内 approve OOv3。如果不想每次烧 gas approve,
部署时一次性 approve type(uint256).max。
ABI
OptimisticOracleV3 的接口和读写 ABI 打包在 xtruth-app/src/lib/contracts/oov3-abi.ts。 我们用到的核心函数:
function assertTruth(
bytes memory claim,
address asserter,
address callbackRecipient,
address escalationManager,
uint64 liveness,
IERC20 currency,
uint256 bond,
bytes32 identifier,
bytes32 domainId
) external returns (bytes32 assertionId);