Configurable Crypto Price Prediction Market Skill
1. Protocol and contract overview
- - Market contract: INLINECODE0
- Factory contract: INLINECODE1
- Model: Polymarket-style CPMM YES/NO prediction market. Each round has its own YES/NO outcome tokens and uses a constant product market maker.
- Key features:
- Uses Uniswap V3 pool prices as the oracle and computes time-windowed average prices.
- Configurable to predict
token0/token1 or the reverse (
reverseOrder).
- Supports native coin or arbitrary ERC20 as the liquidity token.
- Automatically starts a new round on schedule, and supports expiry handling and abnormal price handling (surfaced via events).
This skill only describes the contract APIs and calling patterns. The actual deployment network and addresses should be provided by the product layer (for example via dApp config files or OpenClow workflow parameters).
2. Factory contract GouGouBiMarketConfigurableFactory
2.1 Core roles and state
- -
owner: factory admin, manages the creator whitelist. - INLINECODE6 : implementation contract being cloned (
GouGouBiMarketConfigurable). - INLINECODE8 : whether an address is allowed to call
createMarket. - INLINECODE10 : list of all created market addresses.
- INLINECODE11 : index of a market address (starting from 1, 0 means not created by this factory).
- INLINECODE12 : creation records (see below).
2.2 MarketRecord structure (read-only)
For each market created by the factory:
- -
market: market contract address - INLINECODE15 : creator address
- INLINECODE16 : market name
- INLINECODE17 : Uniswap V3 pool address
- INLINECODE18 : liquidity token address,
address(0) means native coin - INLINECODE20 : fee recipient address
- INLINECODE21 : fee rate (denominator 10000)
- INLINECODE22 : creation timestamp
- The following fields align with
GouGouBiMarketConfigurable.MarketConfig (see section 3):
-
tokenDec
-
reverseOrder
-
settlementInterval
-
expiredSeconds
-
initialReserve
-
priceLookbackSeconds
-
imageUrl
-
rules
-
timezone
-
language
-
groupUrl
-
tags
-
predictionToken
-
anchorToken
- INLINECODE38
2.3 Managing creator whitelist
CODEBLOCK0
- - Only the factory
owner can call these. - Must ensure
account != address(0).
2.4 Creating a market: createMarket
CODEBLOCK1
Constraints:
- - Only
owner or an address with isWhitelistedCreator[msg.sender] == true can create:
-
require(msg.sender == owner || isWhitelistedCreator[msg.sender], "NOT_WHITELISTED");
1. Use
Clones.clone(marketImplementation) to create a minimal proxy.
2. Call the new market’s
initialize(_config, msg.sender) to set config and owner.
3. Push the market address into
markets[], populate
marketRecords, and update
marketIndex.
4. Emit
MarketCreated event with key configuration info.
Read-only helpers:
CODEBLOCK2
3. Market configuration MarketConfig (passed when creating)
The _config used by factory createMarket is identical to the MarketConfig struct inside the market contract:
CODEBLOCK3
Key constraints (validated in initialize):
- -
marketName must be non-empty: INLINECODE57 - INLINECODE58
- INLINECODE59
- INLINECODE60
- INLINECODE61
- INLINECODE62
- If
feeRate > 0, then feeRecipient != address(0); otherwise it will default to _owner. - If
liquidityToken == address(0): tokenDec is automatically set to 18. - If
liquidityToken != address(0): tokenDec = IERC20(liquidityToken).decimals(), requiring tokenDec <= 18. - If
priceLookbackSeconds == 0: it is automatically set to 300 seconds.
After initialization, the contract immediately calls _startNewRound() to open round 1.
4. Market rounds RoundInfo and price settlement
4.1 RoundInfo structure
CODEBLOCK4
- - Accessed via the read-only mapping
rounds[round]. - Current round index:
currentRound.
4.2 Price fetching: getAveragePriceFromUniswapV3
CODEBLOCK5
- -
startTime < endTime, and endTime <= block.timestamp. - Internally uses Uniswap V3
observe + TickMath to compute the average price over the interval, represented with 1e18 precision. - If
reverseOrder == true, takes the reciprocal of the price (still with 1e18 precision).
4.3 Settlement and round transitions
- -
_checkAndExecuteSettlement(): called before every trade/redeem/resolve to:
- If
block.timestamp > expiredTime: set
winning = 3,
endAveragePrice = 0, emit
AutoResolve event, and start a new round.
- If the current round has reached
endTime and not yet expired: call
_calculateSettlementByPrice().
- -
_calculateSettlementByPrice():
- Uses
getAveragePriceFromUniswapV3(priceLookbackSeconds, 0) to get the settlement-time average price.
- Compares with
startAveragePrice:
- equal →
winning = 3 (draw/special situation)
- settlement price > start price →
winning = 1 (YES wins)
- settlement price < start price →
winning = 2 (NO wins)
- Updates
endTime and
endAveragePrice, emits
AutoResolve, then starts a new round.
External callers can trigger the check manually:
CODEBLOCK6
5. Core user interaction methods
5.1 Buying YES / NO
5.1.1 buyYes
CODEBLOCK7
Semantics:
- - For the current
currentRound:
- If the market uses the native coin (
liquidityToken == address(0)):
- Ignore
tokenIn, use
msg.value as the input amount, requiring
msg.value > 0.
- If using an ERC20:
- Require
tokenIn > 0 && msg.value == 0.
- Must first call
IERC20(liquidityToken).approve(market, tokenIn); the contract uses
safeTransferFrom internally.
- Add
tokenIn to
rounds[currentRound].poolAmount.
- Mint equal virtual YES/NO positions (
mintedYes = mintedNo = tokenIn), then use the constant product formula to compute slippage and obtain additional swapped amount.
- Final YES amount is
totalYesOut = mintedYes + swappedYes, which must be
>= minYesOut or the transaction reverts (slippage protection).
- Use
OutcomeToken(yesToken).mint(msg.sender, totalYesOut) to issue YES tokens.
5.1.2 buyNo
CODEBLOCK8
- - Symmetric to
buyYes but operates on the NO pool, yielding totalNoOut, which must be >= minNoOut.
5.2 Position swaps: swapYesForNo / swapNoForYes
CODEBLOCK9
Common logic:
- - Only allowed while
winning == 0 (round not yet settled). - Caller must already hold the corresponding YES or NO token balance.
- Computes
amountOut based on the current reserves x, y and amountIn, then checks slippage:
-
swapYesForNo:
amountOut = (y * amountIn) / (x + amountIn)
-
swapNoForYes:
amountOut = (x * amountIn) / (y + amountIn)
- - Burns the input tokens via
burn, mints output tokens via mint, and updates x, y.
5.3 Settlement redemption: redeem
CODEBLOCK10
Constraints and flow:
- - First calls
_checkAndExecuteSettlement(), which may change the current round state. - Requires:
-
round > 0 && round <= currentRound
-
rounds[round].winning != 0 (round already settled: YES wins, NO wins, or draw)
-
rounds[round].poolAmount > 0
- Caller holds at least one of YES/NO tokens for that round.
- If
winning == 1 (YES wins):
- Distribute pool amount in proportion to YES token supply shares.
- If
winning == 2 (NO wins):
- Distribute in proportion to NO token supply shares.
- If
winning == 3 (draw/exception):
- Sum the caller’s YES and NO balances and distribute according to their share of total YES+NO supply.
- Every payout deducts
tokenOut from the pool and applies fees by
feeRate:
-
fee = tokenOut * feeRate / FEE_DENOMINATOR
- User receives
userAmount = tokenOut - fee
- Fee is transferred to
config.feeRecipient
- Supports both native coin and ERC20 transfers.
5.4 Price queries and pool info
CODEBLOCK11
- -
priceYes/priceNo return prices as fractions (num/den) for UI display or estimation. - INLINECODE151 returns current round pool and price information in one call.
- INLINECODE152 returns detailed data for any round, including YES/NO token addresses and timing info.
6. Usage examples in scripts / frontends (ethers.js)
These examples only demonstrate call patterns. The actual FACTORY_ADDRESS and market addresses depend on deployment and should be passed via your project config.
CODEBLOCK12
Recommended integration practices in frontends or automation scripts:
- - Read operations (
getPoolInfo, getRoundInfo, priceYes, priceNo, etc.) should use a read-only provider. - Write operations (
createMarket, buyYes, buyNo, swapYesForNo, swapNoForYes, redeem, resolve) must use a signer with signing capability. - For ERC20 markets, call
approve on the market contract before buyYes / buyNo; for native-coin markets, attach funds via value. - Frontends should set
minYesOut / minNoOut with reasonable slippage protection (e.g. 95%–99% of expected output).
7. Relationship with other skills
- - This
market-configurable-skills focuses on the concrete implementation and calling patterns of the GouGouBi configurable price prediction market, including factory creation, market configuration fields, and trading/settlement logic. - If your product also uses the giveaway protocol or other contracts, you can read the corresponding skills (such as
giveaway-skills / giveaway-protocol) together. There is no direct contract-level dependency; they only cooperate at the product level.
8. ABI information and minimal examples
For the full ABI, it is recommended to use the compiled artifacts directly (for example, the abi fields in artifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.json and ...Factory.sol/GouGouBiMarketConfigurableFactory.json). This section only provides minimal fragments for common events and functions, for quick debugging or for constructing temporary contract instances when build artifacts are not accessible.
8.1 Factory contract GouGouBiMarketConfigurableFactory ABI fragments
CODEBLOCK13
8.2 Market contract GouGouBiMarketConfigurable ABI fragments
CODEBLOCK14
The ABI fragments above can be combined with the TypeScript examples in section 6, for example:
CODEBLOCK15
name: market-configurable-skills
display_name: GouGouBi 可配置价格预测市场 Skill
description: >
基于 contracts/contracts/GouGouBiMarketConfigurable.sol 与 GouGouBiMarketConfigurableFactory.sol 的加密价格预测市场合约调用说明与最佳实践,包含工厂创建参数、市场配置字段、核心交易/结算方法以及在脚本、前端或 OpenClow 工作流中通过 ethers/web3 调用该合约的规范。当需要创建新预测市场、进行 YES/NO 买入、头寸互换或结算赎回时使用本 skill。
author: frank
version: 0.1.0
language: zh-CN
tags:
- prediction-market
- evm
- contract-call
可配置加密价格预测市场 Skill
1. 协议与合约概览
- - 市场合约: INLINECODE181
- 工厂合约: INLINECODE182
- 模式:Polymarket 风格 CPMM YES/NO 预测市场,每一轮(Round)都有独立的 YES/NO 结果代币,通过常数乘积做市。
- 特点:
- 使用 Uniswap V3 池的价格作为预言机,按时间区间计算平均价格。
- 可配置是否预测
token0/token1 还是反向(
reverseOrder)。
- 支持原生币或任意 ERC20 作为流动性代币。
- 自动按周期开启新一轮市场,支持结算过期、价格异常处理(通过事件体现)。
本 skill 只描述 合约 API 与调用模式,具体部署网络与地址由业务方在上层配置(例如在 dApp 配置文件或 OpenClow workflow 的参数中传入)。
2. 工厂合约 GouGouBiMarketConfigurableFactory
2.1 核心角色与状态
- -
owner:工厂管理员,可管理创建白名单。 - INLINECODE186 :被克隆的逻辑合约实现(
GouGouBiMarketConfigurable)。 - INLINECODE188 :是否允许该地址调用
createMarket。 - INLINECODE190 :所有已创建市场地址列表。
- INLINECODE191 :市场地址对应的索引(从 1 开始,0 表示不是本工厂创建)。
- INLINECODE192 :创建记录(见下)。
2.2 MarketRecord 结构(只读)
对应工厂中每个已创建市场:
- -
market:市场合约地址 - INLINECODE194 :创建者地址
- INLINECODE195 :市场名称
- INLINECODE196 :Uniswap V3 池地址
- INLINECODE197 :流动性代币地址,
address(0) 表示原生币 - INLINECODE199 :手续费接收地址
- INLINECODE200 :手续费率(分母 10000)
- INLINECODE201 :创建时间戳
- 以下字段与
GouGouBiMarketConfigurable.MarketConfig 对齐(见第 3 节):
-
tokenDec
-
reverseOrder
-
settlementInterval
-
expiredSeconds
-
initialReserve
-
priceLookbackSeconds
-
imageUrl
-
rules
-
timezone
-
language
-
groupUrl
-
tags
-
predictionToken
-
anchorToken
- INLINECODE217
2.3 管理白名单
CODEBLOCK16
- - 只有工厂
owner 可调用。 - 需保证
account != address(0)。
2.4 创建市场 createMarket
CODEBLOCK17
约束:
- - 仅
owner 或 isWhitelistedCreator[msg.sender] == true 的地址可创建:
-
require(msg.sender == owner || isWhitelistedCreator[msg.sender], "NOT_WHITELISTED");
1. 使用
Clones.clone(marketImplementation) 创建最小代理。
2. 调用新市场的
initialize(_config, msg.sender),设置配置与 owner。
3. 将市场地址加入
markets[],填充
marketRecords,更新
marketIndex。
4. 触发
MarketCreated 事件,记录关键配置信息。
读接口:
CODEBLOCK18
3. 市场配置 MarketConfig(创建时传入)
工厂 createMarket 的 _config 与市场合约中的 MarketConfig 完全一致:
CODEBLOCK19
关键约束(在 initialize 中校验):
- -
marketName 非空: INLINECODE234 - INLINECODE235
- INLINECODE236
- INLINECODE237
- INLINECODE238
- INLINECODE239
- 若
feeRate > 0,feeRecipient != address(0),否则会默认设置为 _owner。 - 若
liquidityToken == address(0):tokenDec 自动设为 18。 - 若
liquidityToken != address(0):tokenDec = IERC20(liquidityToken).decimals(),要求 tokenDec <= 18。 - 若
priceLookbackSeconds == 0:自动设为 300 秒。
初始化完成后,合约立即调用 _startNewRound() 开启第 1 轮。
4. 市场轮次 RoundInfo 与价格结算
4.1 RoundInfo 结构
CODEBLOCK20
- - 通过
rounds[round] 只读映射访问。 - 当前轮次索引:
currentRound。
4.2 价格获取:getAveragePriceFromUniswapV3
CODEBLOCK21
- -
startTime > endTime,且 endTime <= block.timestamp。 - 内部使用 Uniswap V3
observe + TickMath 计算对应区间的平均价格,并用 1e18 精度表示。 - 若
reverseOrder == true,会对价格取倒数(同样保持 1e18 精度)。
4.3 结算与轮次切换
- -
_checkAndExecuteSettlement():在每次交易/赎回/resolve 前调用,用于:
- 若
block.timestamp > expiredTime:将本轮
winning = 3,
endAveragePrice = 0,触发
AutoResolve 事件,并开启新一轮。
- 若当前轮已到达
endTime 且未过期:调用
_calculateSettlementByPrice()。
- -
_calculateSettlementByPrice():
- 通过
getAveragePriceFromUniswapV3(priceLookbackSeconds, 0) 获取结算时平均价。
- 与
startAveragePrice 比较:
- 相等 →
winning = 3(平局/特殊情况)
- 结算价 > 起始价 →
winning = 1(YES 赢)
- 结算价 < 起始价 →
winning = 2(NO 赢)
- 更新
endTime、
endAveragePrice,触发
AutoResolve,然后开启新一轮。
外部可主动触发检查:
CODEBLOCK22
5. 用户交互核心方法
5.1 买入 YES / NO
5.1.1 buyYes
CODEBLOCK23
语义:
- 若市场为原生币(
liquidityToken == address(0)):
- 忽略
tokenIn 参数,使用
msg.value 作为投入金额,要求
msg.value > 0。
- 若为 ERC20:
- 要求
tokenIn > 0 && msg.value == 0。
- 需提前
IERC20(liquidityToken).approve(market, tokenIn),合约内部使用
safeTransferFrom。
- 将
tokenIn 记入
rounds[currentRound].poolAmount。
- 铸造等量 YES/NO 虚拟头寸(
mintedYes = mintedNo = tokenIn),通过常数乘积公式计算价格滑点,得到额外的 swapped 数量。
- 最终得到的 YES 数量
totalYesOut = mintedYes + swappedYes,必须
>= minYesOut,否则交易回滚(滑点保护)。
- 使用
OutcomeToken(yesToken).mint(msg.sender, totalYesOut) 发放 YES 代币。
5.1.2 buyNo
CODEBLOCK24
- - 与
buyYes 对称,只是对 NO 池进行操作,最终得到 totalNoOut,需要满足 >= minNoOut。
5.2 头寸互换:swapYesForNo / swapNoForYes
CODEBLOCK25
通用逻辑要点:
- - 仅当本轮
winning == 0(未结算)时允许互换。 - 需先持有对应的 YES 或 NO 代币余额。
- 根据当前池子
x、y 以及输入数量 amountIn 计算 amountOut,并进行滑点检查:
-
swapYesForNo:
amountOut = (y * amountIn) / (x + amountIn)
-
swapNoForYes:
amountOut = (x * amountIn) / (y + amountIn)
- - 使用
burn 销毁输入代币,mint 发放输出代币,并更新 x、y。
5.3 赎回结算:redeem
CODEBLOCK26
约束与流程:
- - 先调用
_checkAndExecuteSettlement(),可能会改变当前轮状态。 - 要求:
-
round > 0 && round <= currentRound
-
rounds[round].winning != 0(本轮已结算:YES 赢、NO 赢或平局)
-
rounds[round].poolAmount > 0
- 调用者在该轮 YES/NO 中至少持有一种代币。
- 若
winning == 1(YES 赢):
- 按 YES 代币在总 YES 供应中的占比分配池中金额。
- 若
winning == 2(NO 赢):
- 按 NO 代币在总 NO 供应中的占比分配。
- 若
winning == 3(平局/异常):
- 先将调用者持有的 YES/NO 数量相加,按其在总供应量中的占比分配池中金额。
- 所有分配都会从池中扣除
tokenOut,并按
feeRate 抽取手续费:
-
fee = tokenOut * feeRate / FEE_DENOMINATOR
- 用户实收
userAmount = tokenOut - fee
- 手续费转给
config.feeRecipient
- 支持原生币或 ERC20 方式转账。
5.4 价格查询与池信息
CODEBLOCK27
- -
priceYes/priceNo 返回分数形式的价格(num/den),用于前端展示或估算。 - INLINECODE318 一次性获取当前轮的池子与价格信息。
- INLINECODE319 可查询任意轮的详细数据,包括 YES/NO 代币地址与时间信息。
6. 在脚本 / 前端中调用示例(ethers.js)
示例仅展示调用模式,实际 FACTORY_ADDRESS 与市场地址由部署环境决定,请在项目配置中传入。
CODEBLOCK28
在前端或自动化脚本中集成时建议:
- - 读操作(
getPoolInfo、getRoundInfo、priceYes、priceNo 等)使用只读 provider。 - 写操作(
createMarket、buyYes、buyNo、swapYesForNo、swapNoForYes、redeem、resolve)必须使用具有签名能力的 signer。 - ERC20 市场在
buyYes / buyNo 前要先对市场合约执行一次 approve,原生币市场则通过 value 附带资金。 - 前端应对
minYesOut / minNoOut 做合理设置以防滑点过大(例如按预期输出的 95%~99% 设定)。
7. 与其他 skill 的关系
- - 本
market-configurable-skills 专注于 GouGouBi 可配置价格预测市场的具体实现与调用方式,包括工厂创建、市场配置字段、交易与结算逻辑。 - 若在同一产品中同时使用红包协议或其他合约,可结合对应 skill(如
giveaway-skills / giveaway-protocol)一起阅读,但它们之间没有直接合约依赖关系,仅在产品层面协同使用。
8. ABI 信息与精简示例
完整 ABI 推荐直接使用编译产物(例如 artifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.json 和 ...Factory.sol/GouGouBiMarketConfigurableFactory.json 中的 abi 字段)。本节只提供常用事件与函数的精简片段,用于快速调试或在无法访问编译产物时临时构造合约实例。
8.1 工厂合约 GouGouBiMarketConfigurableFactory ABI 片段
CODEBLOCK29
8.2 市场合约 GouGouBiMarketConfigurable ABI 片段
CODEBLOCK30
上述 ABI 片段可以与第 6 节中的 TypeScript 示例直接组合使用,例如:
CODEBLOCK31
可配置加密价格预测市场 Skill
1. 协议与合约概览
- - 市场合约:contracts/contracts/GouGouBiMarketConfigurable.sol
- 工厂合约:contracts/contracts/GouGouBiMarketConfigurableFactory.sol
- 模式:Polymarket 风格 CPMM YES/NO 预测市场。每一轮都有独立的 YES/NO 结果代币,并使用恒定乘积做市商。
- 关键特性:
- 使用 Uniswap V3 池价格作为预言机,计算时间窗口平均价格。
- 可配置预测 token0/token1 或反向(reverseOrder)。
- 支持原生币或任意 ERC20 作为流动性代币。
- 按计划自动开始新一轮,支持到期处理和异常价格处理(通过事件体现)。
本技能仅描述合约 API 和调用模式。实际部署网络和地址应由产品层提供(例如通过 dApp 配置文件或 OpenClow 工作流参数)。
2. 工厂合约 GouGouBiMarketConfigurableFactory
2.1 核心角色和状态
- - owner:工厂管理员,管理创建者白名单。
- marketImplementation:被克隆的实现合约(GouGouBiMarketConfigurable)。
- isWhitelistedCreator[address]:地址是否被允许调用 createMarket。
- markets[]:所有已创建市场地址的列表。
- marketIndex[market]:市场地址的索引(从 1 开始,0 表示非此工厂创建)。
- marketRecords[index]:创建记录(见下文)。
2.2 MarketRecord 结构(只读)
对于工厂创建的每个市场:
- - market:市场合约地址
- creator:创建者地址
- marketName:市场名称
- uniswapV3Pool:Uniswap V3 池地址
- liquidityToken:流动性代币地址,address(0) 表示原生币
- feeRecipient:费用接收地址
- feeRate:费率(分母 10000)
- createdAt:创建时间戳
- 以下字段与 GouGouBiMarketConfigurable.MarketConfig 对齐(见第 3 节):
- tokenDec
- reverseOrder
- settlementInterval
- expiredSeconds
- initialReserve
- priceLookbackSeconds
- imageUrl
- rules
- timezone
- language
- groupUrl
- tags
- predictionToken
- anchorToken
- currencyUnit
2.3 管理创建者白名单
solidity
function setCreatorWhitelist(address account, bool allowed) external onlyOwner
function setCreatorWhitelistBatch(address[] calldata accounts, bool allowed) external onlyOwner
- - 仅工厂 owner 可调用。
- 必须确保 account != address(0)。
2.4 创建市场:createMarket
solidity
function createMarket(GouGouBiMarketConfigurable.MarketConfig memory _config)
external
returns (address market)
约束条件:
- - 仅 owner 或 isWhitelistedCreator[msg.sender] == true 的地址可创建:
- require(msg.sender == owner || isWhitelistedCreator[msg.sender], NOT_WHITELISTED);
1. 使用 Clones.clone(marketImplementation) 创建最小代理。
2. 调用新市场的 initialize(_config, msg.sender) 设置配置和所有者。
3. 将市场地址推入 markets[],填充 marketRecords,并更新 marketIndex。
4. 使用关键配置信息触发 MarketCreated 事件。
只读辅助函数:
solidity
function getMarkets() external view returns (address[] memory)
function marketCount() external view returns (uint256)
function getMarketRecord(uint256 index) external view returns (MarketRecord memory)
function getMarketRecordsPaginated(uint256 offset, uint256 limit) external view returns (MarketRecord[] memory)
3. 市场配置 MarketConfig(创建时传入)
工厂 createMarket 使用的 _config 与市场合约内部的 MarketConfig 结构相同:
solidity
struct MarketConfig {
string marketName;
address uniswapV3Pool;
address liquidityToken; // address(0) 表示原生币
uint8 tokenDec; // 如果 liquidityToken==0,在 initialize 中自动设置为 18
bool reverseOrder; // 价格方向:false=Token0/Token1, true=Token1/Token0
uint256 settlementInterval; // 结算间隔(秒)
uint256 expiredSeconds; // 轮次到期时间,若未在此时间前解决则作废
uint256 initialReserve; // 每轮初始虚拟储备(将乘以 10^decimals)
uint32 priceLookbackSeconds;// 平均价格的回溯窗口
address feeRecipient; // 费用接收地址
uint256 feeRate; // 费用分子,分母 10000
string imageUrl;
string rules;
string timezone;
string language;
string groupUrl;
string[] tags;
address predictionToken; // 被预测价格的代币
address anchorToken; // 报价/锚定代币,例如 USDT, BNB
string currencyUnit; // 显示单位,例如 USD, BNB, DOGE
}
关键约束(在 initialize 中验证):
- - marketName 不能为空:bytes(config.marketName).length > 0
- uniswapV3Pool != address(0)
- settlementInterval > 0
- expiredSeconds > 0
- 0 < initialReserve <= 1000000000
- feeRate <= FEEDENOMINATOR (10000)
- 如果 feeRate > 0,则 feeRecipient != address(0);否则默认为 owner。
- 如果 liquidityToken == address(0):tokenDec 自动设置为 18。
- 如果 liquidityToken != address(0):tokenDec = IERC20(liquidityToken).decimals(),要求 tokenDec <= 18。
- 如果 priceLookbackSeconds == 0:自动设置为 300 秒。
初始化后,合约立即调用 _startNewRound() 开启第 1 轮。
4. 市场轮次 RoundInfo 和价格结算
4.1 RoundInfo 结构
solidity
struct RoundInfo {
uint8 winning; // 0=未结算, 1=YES 赢, 2=NO 赢, 3=平局/异常
uint256 poolAmount; // 该轮池中的总流动性代币数量
uint256 x; // CPMM x 储备(YES 池)
uint256 y; // CPMM y 储备(NO 池)
address yesToken; // 该轮的 YES 结果代币
address noToken; // 该轮的 NO 结果代币
uint256 startTime; // 轮次开始时间
uint256 endTime; // 预定结算时间
uint256 expiredTime; // 到期时间(如果超过,视为 winning=3)
uint256 startAveragePrice; // 轮次开始时的平均价格
uint256 endAveragePrice; // 轮次结束/结算时的平均价格
}
- - 通过只读映射 rounds[round] 访问。
- 当前轮次索引:currentRound。
4.2 价格获取:getAveragePriceFromUniswapV3
solidity
function getAveragePriceFromUniswapV3(uint32 startTime, uint32 endTime)
public
view
returns (uint256 averagePrice)
- - startTime < endTime,且 endTime <= block.timestamp。
- 内部使用 Uniswap V3 observe + TickMath 计算区间内的平均价格,以 1e18 精度表示。
- 如果 reverseOrder == true,则取价格的倒数(仍以 1e18 精度表示)。
4.3 结算和轮次转换
- - _checkAndExecuteSettlement():在每次交易/赎回/解决前调用,用于:
- 如果 block.timestamp > expiredTime:设置 winning = 3,endAveragePrice = 0,触发 Auto