本文整理自 requirements.md 的原始说明,尽量照搬原文内容,并在关键步骤补充解释与可视化示意,方便从“概念 → 链上日志 → 解码实现”全链路理解。
conditionId 或创建日志,还原该市场的链上参数(问题描述对应的标识、预言机地址、质押品、Yes/No 头寸 TokenId 等)。在 Polymarket 中,事件代表一个预测主题,例如“某次美联储利率决议”。一个事件下可以包含一个或多个市场。每个市场对应该事件下的一个具体预测问题。例如,对于事件“2024 年美国大选”可以有多个市场:“候选人 A 当选总统?”、“候选人 B 当选总统?”等,每个市场通常是一个二元预测(Yes/No)。
Market(市场):对应具体的 Yes/No 问题,是交易发生的基本单位。一些事件只有一个市场(例如简单的二元事件),而有些事件包含多个市场形成一个多结果事件。后者通常采用 Polymarket 的“负风险 (NegativeRisk)”机制来提高资金效率(见下文)。
NegativeRisk(负风险):当一个事件包含多个互斥市场(即“赢家通吃”的多选一事件)时,Polymarket 引入 NegRiskAdapter 合约,将这些市场关联起来,提高流动性利用率。具体来说,在同一事件下,一个市场的 NO 头寸可以转换为该事件中所有其他市场的 YES 头寸。这意味着持有任意一个结果的反向(NO)仓位,相当于持有对其他所有可能结果的正向(YES)头寸。通过这种机制,参与者不需要为每个可能结果都分别提供独立的资金,从而提高资金效率。NegRiskAdapter 合约提供 convert 功能,实现 NO → YES 的头寸转换。
示例:假设事件是“谁将赢得选举?”,包含 5 个候选人作为 5 个市场(每个市场问“候选人 X 会赢吗?”)。在负风险架构下:
NegRiskAdapter,可以将对某候选人的 NO 头寸随时转换为对其他所有候选人的 YES 头寸持有。这体现了所有候选人“不赢”的头寸和其他候选人“赢”的头寸是等价的,从而联通了各市场的流动性。Polymarket 使用 Gnosis 开发的条件代币框架 (Conditional Token Framework, CTF) 来实现预测市场的头寸代币化。在该框架下:
每个市场在链上的“登记身份”。创建市场时,会调用 CTF 合约的 prepareCondition 方法注册一个条件。ConditionId 是通过哈希计算得出的唯一标识:
conditionId = keccak256(oracle, questionId, outcomeSlotCount)
其中:
oracle 是预言机合约地址(Polymarket 目前使用 UMA Optimistic Oracle 作为预言机)。questionId 是问题的标识符(通常由问题内容等信息哈希得到,或 UMA Oracle 的 ancillary data 哈希)。outcomeSlotCount 是结果选项数量。对于二元市场,该值为 2。Condition 就像市场的问题在链上的“出生证明”,绑定了唯一的问题 ID 和预言机。市场需要结算时,预言机会针对这个 conditionId 发布结果。
头寸指的是用户持有的某市场某结果的份额(又称 Outcome Share)。Polymarket 将每个头寸实现为一个 ERC-1155 标准的可交易代币(又称 PositionId 或 TokenId)。每种结果对应一个不同的 TokenId,用于区分 YES 和 NO 两种头寸。
在条件代币框架中,中间引入了集合的概念,用于表示特定条件下某个结果集合。计算方法为:
collectionId = keccak256(parentCollectionId, conditionId, indexSet)
其中:
parentCollectionId 对于独立的条件通常为 bytes32(0)(Polymarket 所有市场都是独立条件,没有嵌套条件,因此 parentCollectionId 一律为 0)。indexSet 是一个二进制位掩码,表示选取哪些结果槽位。对于二元市场,有两个可能的 indexSet:
indexSet = 1 (0b01,表示选取第一个结果槽)indexSet = 2 (0b10,表示选取第二个结果槽)最后,用抵押品代币地址和集合 ID 一起计算得到 ERC-1155 的 Token ID:
tokenId = keccak256(collateralToken, collectionId)
在 Polymarket 中,对于每个条件(市场),会产生两个 TokenId——一个对应 YES 份额,一个对应 NO 份额。这两个 TokenId 是在该市场上交易的标的资产,代表了对同一预测问题的两种相反结果的头寸。
Polymarket 市场的下注资金均以稳定币 USDC(Polygon 上为 USDC.e,地址 0x2791...Aa84174)作为抵押品。每份 Outcome Token 背后对应 1 USDC 的抵押,当市场结算时兑现。
价格含义:比如价格 0.60 USDC 意味着花 0.60 USDC 可购买该市场 1 份 YES 代币。如果该结果最终发生,持有者可赎回 1 USDC(获得净盈利 0.40 USDC);如果未发生,则该代币价值归零,损失全部本金 0.60 USDC。因此,二元期权代币价格可以理解为市场对该事件发生概率的定价。
Polymarket 的市场从创建到结算,关键的链上步骤和日志事件如下:
由市场创建者调用 ConditionalTokens.prepareCondition 创建条件。
关键日志:ConditionPreparation 事件,包含 conditionId、oracle、questionId、outcomeSlotCount 等信息。这一事件在链上确认了某预言机地址与问题 ID 的绑定关系,相当于市场的建立。一旦发布,预言机(UMA OptimisticOracle)稍后将根据这个 conditionId 报告结果。
市场创建后,需要流动性提供者拆分出初始的 YES/NO 代币。通常通过调用 ConditionalTokens.splitPosition 将抵押品 USDC 拆分成等价值的 YES 和 NO 头寸。
关键日志:PositionSplit 事件,包含 conditionId、collateralToken(对应 USDC 地址)、parentCollectionId(一般为 0)、partition(拆分出的 indexSets 列表,如 [1,2]),以及 amount(拆分抵押品数量)。该事件证明抵押品被锁定,并生成了对应数量的 YES 和 NO 代币。对二元市场,拆分 1 USDC 通常会得到面值各 1 USDC 的 YES 和 NO 代币各一枚。最初的流动性提供者可能是做市商,他们将 USDC 拆分为两种头寸代币,并可以在订单簿上挂单提供买卖报价。
交易在 Polymarket 的链上撮合引擎(CLOB 合约)中进行。Polymarket 采用中心限价订单簿模型,订单撮合通过智能合约(普通二元市场是 CTF Exchange,多结果市场通过 NegRisk_CTFExchange)完成。每笔撮合成交在链上记录交易日志。
关键日志:OrderFilled 事件。每当买卖双方的订单在链上部分或全部成交时,都会触发该事件,记录交易的详情,包括:
maker 和 taker 地址:做市(挂单)方和吃单方地址。makerAssetId 和 takerAssetId:成交时双方各自支付的资产 ID(Polymarket 用一个 ID 表示资产,0 表示 USDC,非零表示特定市场的头寸 TokenId)。makerAmountFilled 和 takerAmountFilled:各方成交的数量(对应各自资产的数量,整数形式)。fee:maker 方支付的手续费数量。重要细节(避免重复计数):同一笔撮合在链上可能产生多条 OrderFilled。通常会有“每个 maker 一条”的 OrderFilled,以及一条“taker 汇总”的 OrderFilled,其中 taker 字段会显示为 Exchange 合约地址本身。若直接按 OrderFilled 条数统计成交笔数或成交量,会出现双计。实践中可选以下方式之一避免重复:
taker == exchange_address 的 OrderFilled(保留 maker 侧填单)。OrdersMatched 作为“一次撮合”的唯一汇总记录。补充说明:在 Polymarket 内部,代币铸造与销毁与交易匹配是紧密相关的。若两个相反方向的订单成交且共同投入的 USDC 满足 1:1 配比,会触发抵押品锁定和头寸代币铸造。例如:
PositionSplit 事件记录 1 USDC 拆分出一对头寸,并伴随 OrderFilled 记录双方各得到代币和支出 USDC 的情况。PositionsMerge 和 OrderFilled 等日志,表示头寸被合并并赎回。这是 Polymarket 允许无需等待事件结算就能退出仓位的一种机制。当事件结果揭晓且到达市场设定的关闭时间后,预言机合约(如 UMA OptimisticOracle)将把结果提交回 CTF 合约,调用 reportPayouts(conditionId, payouts[]) 来公布各结果的兑付率。
结果日志:调用 reportPayouts 本身通常不会有特殊事件(或有 ConditionResolution 事件),但其效果是将相应 conditionId 下的头寸标记为可赎回:胜出的头寸代币每份价值 1 USDC,失败的头寸代币价值 0。用户随后可以调用 ConditionalTokens.redeemPositions 来赎回胜出代币的抵押品。Polymarket 当前使用 UMA 的乐观预言机机制,这意味着通常在预言机确认结果后,通过 Polymarket 前端或合约即可触发结算。结算完成后,对应的 YES/NO 代币可以兑换回 USDC,市场生命周期结束。
以上链上事件共同构成了市场的证据链:从 ConditionPreparation 证明市场的存在和参数,PositionSplit 证明资金注入和代币铸造,OrderFilled 记录交易交换细节,直到 reportPayouts 确认结果以供赎回。这些事件串联起来,可以让我们基于链上数据重建出市场发生的一切。
flowchart LR
A[ConditionPreparation] --> B[PositionSplit]
B --> C[OrderFilled / OrdersMatched]
C --> D[reportPayouts]
D --> E[redeemPositions]
实现一个通用的交易日志解析器,输入交易哈希(在 Polygon 链上),输出该交易中 Polymarket 订单撮合的详情。例如给定样本交易哈希 0x916cad...9946(假设其中包含一个 OrderFilled 事件),需要解析得到如下 JSON 结构:
{
"txHash": "0x916cad...9946",
"logIndex": 123,
"exchange": "0xC5d5...f80a",
"maker": "0x....",
"taker": "0x....",
"makerAssetId": "12345...",
"takerAssetId": "67890...",
"makerAmountFilled": "1000000",
"takerAmountFilled": "500000",
"price": "0.5",
"tokenId": "67890...",
"side": "BUY"
}
字段说明:
exchange: 撮合发生的交易所合约地址maker: 挂单方地址taker: 吃单方地址makerAssetId: maker 给出的资产 IDtakerAssetId: taker 给出的资产 IDmakerAmountFilled: maker 支付的资产数量(整数,可能需要转换单位)takerAmountFilled: taker 支付的资产数量price: 成交价格(计算得到,单位 USDC,例如 0.5 表示每份头寸 0.5 USDC)tokenId: 本次交易涉及的 OutcomeToken 的 ID(非 USDC 的资产 ID)side: 表示这笔交易对该 OutcomeToken 来说是买单成交(BUY)还是卖单成交(SELL)通过 Polygon RPC(如 eth_getTransactionReceipt)获取指定交易的所有日志。过滤出 OrderFilled 事件(根据其主题 topic 或合约地址匹配 Polymarket 交易所合约)。Polymarket 有两类撮合合约:
0x4bFb41...8B8982E0xC5d563...5220f80a这两个合约的 OrderFilled 事件格式相同。
按 Polymarket 交易所合约的定义,提取 OrderFilled 日志中的字段。判断资产类型时注意:
根据 Polymarket 的定义:
makerAssetId 为 0 表示订单类型为 BUY(用 USDC 换取结果代币)。takerAssetId 为 0 表示订单类型为 SELL(得到 USDC,卖出结果代币)。将提取的信息格式化为所需 JSON。txHash 为交易哈希本身,logIndex 为日志在交易中的索引,exchange 从日志 address 字段获取,其他字段根据解析结果填入。注意 price 等数值应为可读格式(通常用字符串表示数值以避免精度问题),side 用 "BUY" 或 "SELL"。
给定链上获取的市场创建相关信息(如 ConditionPreparation 日志,或已知的 conditionId),提取并计算出该市场的核心链上参数,包括预言机、问题 ID、抵押品地址,以及 YES/NO 两种头寸的 TokenId。
输入可能:
conditionId 值,或ConditionPreparation 事件日志记录(其中包含创建时的 oracle 地址、questionId 等)。{
"conditionId": "0xabc...123",
"questionId": "0xdef...456",
"oracle": "0xOracleAddr...789",
"collateralToken": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"yesTokenId": "0xYesTokenId...",
"noTokenId": "0xNoTokenId..."
}
若输入为日志,直接从 ConditionPreparation 事件中读取:
conditionIdoraclequestionIdoutcomeSlotCount(应为 2)若输入仅为 conditionId,则需从上下文或其他渠道获得对应的 oracle 和 questionId;通常 Gamma API 的市场数据中会包含 questionId 和 oracle 信息用于参考。抵押品 collateralToken 在 Polymarket 几乎总是 USDC.e,可视作已知常量(或通过 PositionSplit 日志交叉验证)。
按 Gnosis 条件代币框架计算:
collectionId_yes = keccak256(parentCollectionId, conditionId, 1)
collectionId_no = keccak256(parentCollectionId, conditionId, 2)
yesTokenId = keccak256(collateralToken, collectionId_yes)
noTokenId = keccak256(collateralToken, collectionId_no)
其中 parentCollectionId = bytes32(0)。
将结果填入 JSON。可与 Gamma API 返回的 clobTokenIds 交叉验证,或与 Trade Decoder 解析出的 tokenId 对比一致性。
USDC 精度为 6 位(小数点后 6 位)。Polymarket 头寸代币通常也是按 USDC 的基础单位发行。链上数据(如 makerAmountFilled)为整数形式,需除以 1e6 转成人类可读数量。
Trade Decoder 中简化用 makerAssetId 是否为 0 来判断买卖方向。如需更精细,也可结合 maker/taker 是否为用户自身来决定视角,但用资产类别判断方向已足够。
负风险市场可能出现单笔交易一个 OrderFilled 同时涉及多个市场的情况(通过 NO → YES 转换)。这会伴随 PositionsConverted 事件。基础版本可暂不处理,但设计上应考虑同一交易 hash 中是否存在多条 OrderFilled 以及 PositionsConverted。
开发与测试建议保存关键数据为 JSON:
fixtures/tx_<hash>.jsonfixtures/market_<slug>.json这样可在无链上连接时进行离线单元测试,并保证解析逻辑稳定性。
if maker_asset_id == 0: # maker 出 USDC
price = Decimal(maker_amount) / Decimal(taker_amount)
token_id = taker_asset_id
side = "BUY"
else: # maker 出 Token
price = Decimal(taker_amount) / Decimal(maker_amount)
token_id = maker_asset_id
side = "SELL"
positions = derive_binary_positions(
oracle=oracle,
question_id=question_id,
condition_id=condition_id,
collateral_token=collateral_token,
)
# positions.position_yes -> YES Token ID
# positions.position_no -> NO Token ID
# 交易解码:解析指定交易的 OrderFilled 事件
python -m src.trade_decoder --tx-hash 0x916cad96dd5c219997638133512fd17fe7c1ce72b830157e4fd5323cf4f19946
# 输出到文件
python -m src.trade_decoder \
--tx-hash 0x916cad96dd5c219997638133512fd17fe7c1ce72b830157e4fd5323cf4f19946 \
--output ./data/trades.json
# 市场解码:通过 Gamma API slug 获取市场信息并计算 TokenId
python -m src.market_decoder \
--market-slug will-there-be-another-us-government-shutdown-by-january-31
# 市场解码:通过交易哈希解析 ConditionPreparation 事件
python -m src.market_decoder \
--tx-hash <condition_preparation_tx_hash> \
--log-index <log_index>
# 输出到文件
python -m src.market_decoder \
--market-slug will-there-be-another-us-government-shutdown-by-january-31 \
--output ./data/market.json