Install
openclaw skills install market-configurable-skillsCall guide and best practices for the configurable crypto price prediction market contracts GouGouBiMarketConfigurable.sol and GouGouBiMarketConfigurableFact...
openclaw skills install market-configurable-skillscontracts/contracts/GouGouBiMarketConfigurable.solcontracts/contracts/GouGouBiMarketConfigurableFactory.soltoken0/token1 or the reverse (reverseOrder).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).
GouGouBiMarketConfigurableFactoryowner: factory admin, manages the creator whitelist.marketImplementation: implementation contract being cloned (GouGouBiMarketConfigurable).isWhitelistedCreator[address]: whether an address is allowed to call createMarket.markets[]: list of all created market addresses.marketIndex[market]: index of a market address (starting from 1, 0 means not created by this factory).marketRecords[index]: creation records (see below).MarketRecord structure (read-only)For each market created by the factory:
market: market contract addresscreator: creator addressmarketName: market nameuniswapV3Pool: Uniswap V3 pool addressliquidityToken: liquidity token address, address(0) means native coinfeeRecipient: fee recipient addressfeeRate: fee rate (denominator 10000)createdAt: creation timestampGouGouBiMarketConfigurable.MarketConfig (see section 3):
tokenDecreverseOrdersettlementIntervalexpiredSecondsinitialReservepriceLookbackSecondsimageUrlrulestimezonelanguagegroupUrltagspredictionTokenanchorTokencurrencyUnitfunction setCreatorWhitelist(address account, bool allowed) external onlyOwner
function setCreatorWhitelistBatch(address[] calldata accounts, bool allowed) external onlyOwner
owner can call these.account != address(0).createMarketfunction createMarket(GouGouBiMarketConfigurable.MarketConfig memory _config)
external
returns (address market)
Constraints:
owner or an address with isWhitelistedCreator[msg.sender] == true can create:
require(msg.sender == owner || isWhitelistedCreator[msg.sender], "NOT_WHITELISTED");Clones.clone(marketImplementation) to create a minimal proxy.initialize(_config, msg.sender) to set config and owner.markets[], populate marketRecords, and update marketIndex.MarketCreated event with key configuration info.Read-only helpers:
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)
MarketConfig (passed when creating)The _config used by factory createMarket is identical to the MarketConfig struct inside the market contract:
struct MarketConfig {
string marketName;
address uniswapV3Pool;
address liquidityToken; // address(0) means native coin
uint8 tokenDec; // if liquidityToken==0, automatically set to 18 in initialize
bool reverseOrder; // price direction: false=Token0/Token1, true=Token1/Token0
uint256 settlementInterval; // settlement interval in seconds
uint256 expiredSeconds; // round expires and is void if not resolved by this time
uint256 initialReserve; // initial virtual reserve per round (will be multiplied by 10^decimals)
uint32 priceLookbackSeconds;// lookback window for average price
address feeRecipient; // fee recipient address
uint256 feeRate; // fee numerator, denominator 10000
string imageUrl;
string rules;
string timezone;
string language;
string groupUrl;
string[] tags;
address predictionToken; // token whose price is being predicted
address anchorToken; // quote/anchor token, e.g. USDT, BNB
string currencyUnit; // display unit, e.g. "USD", "BNB", "DOGE"
}
Key constraints (validated in initialize):
marketName must be non-empty: bytes(_config.marketName).length > 0uniswapV3Pool != address(0)settlementInterval > 0expiredSeconds > 00 < initialReserve <= 1_000_000_000feeRate <= FEE_DENOMINATOR (10000)feeRate > 0, then feeRecipient != address(0); otherwise it will default to _owner.liquidityToken == address(0): tokenDec is automatically set to 18.liquidityToken != address(0): tokenDec = IERC20(liquidityToken).decimals(), requiring tokenDec <= 18.priceLookbackSeconds == 0: it is automatically set to 300 seconds.After initialization, the contract immediately calls _startNewRound() to open round 1.
RoundInfo and price settlementRoundInfo structurestruct RoundInfo {
uint8 winning; // 0=not settled, 1=YES wins, 2=NO wins, 3=draw/exception
uint256 poolAmount; // total liquidity token amount in the pool for this round
uint256 x; // CPMM x reserve (YES pool)
uint256 y; // CPMM y reserve (NO pool)
address yesToken; // YES outcome token for this round
address noToken; // NO outcome token for this round
uint256 startTime; // round start time
uint256 endTime; // scheduled settlement time
uint256 expiredTime; // expiration time (if exceeded, treated as winning=3)
uint256 startAveragePrice; // average price at round start
uint256 endAveragePrice; // average price at round end / settlement
}
rounds[round].currentRound.getAveragePriceFromUniswapV3function getAveragePriceFromUniswapV3(uint32 startTime, uint32 endTime)
public
view
returns (uint256 averagePrice)
startTime < endTime, and endTime <= block.timestamp.observe + TickMath to compute the average price over the interval, represented with 1e18 precision.reverseOrder == true, takes the reciprocal of the price (still with 1e18 precision)._checkAndExecuteSettlement(): called before every trade/redeem/resolve to:
block.timestamp > expiredTime: set winning = 3, endAveragePrice = 0, emit AutoResolve event, and start a new round.endTime and not yet expired: call _calculateSettlementByPrice()._calculateSettlementByPrice():
getAveragePriceFromUniswapV3(priceLookbackSeconds, 0) to get the settlement-time average price.startAveragePrice:
winning = 3 (draw/special situation)winning = 1 (YES wins)winning = 2 (NO wins)endTime and endAveragePrice, emits AutoResolve, then starts a new round.External callers can trigger the check manually:
function resolve() external nonReentrant {
_checkAndExecuteSettlement();
}
buyYesfunction buyYes(uint256 tokenIn, uint256 minYesOut) external payable nonReentrant
Semantics:
currentRound:
liquidityToken == address(0)):
tokenIn, use msg.value as the input amount, requiring msg.value > 0.tokenIn > 0 && msg.value == 0.IERC20(liquidityToken).approve(market, tokenIn); the contract uses safeTransferFrom internally.tokenIn to rounds[currentRound].poolAmount.mintedYes = mintedNo = tokenIn), then use the constant product formula to compute slippage and obtain additional swapped amount.totalYesOut = mintedYes + swappedYes, which must be >= minYesOut or the transaction reverts (slippage protection).OutcomeToken(yesToken).mint(msg.sender, totalYesOut) to issue YES tokens.buyNofunction buyNo(uint256 tokenIn, uint256 minNoOut) external payable nonReentrant
buyYes but operates on the NO pool, yielding totalNoOut, which must be >= minNoOut.swapYesForNo / swapNoForYesfunction swapYesForNo(uint256 amountIn, uint256 minAmountOut) external nonReentrant
function swapNoForYes(uint256 amountIn, uint256 minAmountOut) external nonReentrant
Common logic:
winning == 0 (round not yet settled).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)burn, mints output tokens via mint, and updates x, y.redeemfunction redeem(uint256 round) external nonReentrant
Constraints and flow:
_checkAndExecuteSettlement(), which may change the current round state.round > 0 && round <= currentRoundrounds[round].winning != 0 (round already settled: YES wins, NO wins, or draw)rounds[round].poolAmount > 0winning == 1 (YES wins):
winning == 2 (NO wins):
winning == 3 (draw/exception):
tokenOut from the pool and applies fees by feeRate:
fee = tokenOut * feeRate / FEE_DENOMINATORuserAmount = tokenOut - feeconfig.feeRecipientfunction priceYes() external view returns (uint256 num, uint256 den)
function priceNo() external view returns (uint256 num, uint256 den)
function getPoolInfo() external view returns (
uint256 _x, uint256 _y, uint256 _poolAmount,
uint256 _priceYes, uint256 _priceNo,
uint256 _currentRound, uint8 _winning
)
function getRoundInfo(uint256 round) external view returns (
uint8 _winning, uint256 _poolAmount, uint256 _x, uint256 _y,
address _yesToken, address _noToken,
uint256 _startTime, uint256 _endTime, uint256 _expiredTime,
uint256 _startAveragePrice, uint256 _endAveragePrice
)
priceYes/priceNo return prices as fractions (num/den) for UI display or estimation.getPoolInfo returns current round pool and price information in one call.getRoundInfo returns detailed data for any round, including YES/NO token addresses and timing info.These examples only demonstrate call patterns. The actual
FACTORY_ADDRESSand market addresses depend on deployment and should be passed via your project config.
import { ethers } from "ethers";
import MarketAbi from "../artifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.json";
import FactoryAbi from "../artifacts/contracts/GouGouBiMarketConfigurableFactory.sol/GouGouBiMarketConfigurableFactory.json";
// Provided by the product side
const FACTORY_ADDRESS = "<REPLACE_WITH_FACTORY_ADDRESS>";
export function getFactory(providerOrSigner: ethers.Signer | ethers.providers.Provider) {
return new ethers.Contract(FACTORY_ADDRESS, FactoryAbi.abi, providerOrSigner);
}
export function getMarket(
marketAddress: string,
providerOrSigner: ethers.Signer | ethers.providers.Provider
) {
return new ethers.Contract(marketAddress, MarketAbi.abi, providerOrSigner);
}
// Create a new market (requires factory owner or whitelisted address)
export async function createMarket(
signer: ethers.Signer,
config: {
marketName: string;
uniswapV3Pool: string;
liquidityToken: string; // for native-coin markets, pass ethers.constants.AddressZero
reverseOrder: boolean;
settlementInterval: number;
expiredSeconds: number;
initialReserve: ethers.BigNumberish;
priceLookbackSeconds?: number;
feeRecipient?: string;
feeRate: number;
imageUrl?: string;
rules?: string;
timezone?: string;
language?: string;
groupUrl?: string;
tags?: string[];
predictionToken: string;
anchorToken: string;
currencyUnit: string;
}
) {
const factory = getFactory(signer);
const _config = {
...config,
// Let the contract infer defaults such as tokenDec / priceLookbackSeconds / feeRecipient
tokenDec: 0,
priceLookbackSeconds: config.priceLookbackSeconds ?? 0,
feeRecipient: config.feeRecipient ?? ethers.constants.AddressZero,
tags: config.tags ?? [],
};
const tx = await factory.createMarket(_config);
const receipt = await tx.wait();
// You can parse the new market address from the MarketCreated event
return receipt;
}
// Buy YES in the current round (ERC20 market example)
export async function buyYes(
signer: ethers.Signer,
marketAddress: string,
amountIn: ethers.BigNumberish,
minYesOut: ethers.BigNumberish
) {
const market = getMarket(marketAddress, signer);
const tx = await market.buyYes(amountIn, minYesOut);
return tx.wait();
}
// Buy NO in the current round (native-coin market example)
export async function buyNoNative(
signer: ethers.Signer,
marketAddress: string,
amountWei: ethers.BigNumberish,
minNoOut: ethers.BigNumberish
) {
const market = getMarket(marketAddress, signer);
const tx = await market.buyNo(0, minNoOut, { value: amountWei });
return tx.wait();
}
// Redeem settlement for a specific round
export async function redeem(
signer: ethers.Signer,
marketAddress: string,
round: number
) {
const market = getMarket(marketAddress, signer);
const tx = await market.redeem(round);
return tx.wait();
}
Recommended integration practices in frontends or automation scripts:
getPoolInfo, getRoundInfo, priceYes, priceNo, etc.) should use a read-only provider.createMarket, buyYes, buyNo, swapYesForNo, swapNoForYes, redeem, resolve) must use a signer with signing capability.approve on the market contract before buyYes / buyNo; for native-coin markets, attach funds via value.minYesOut / minNoOut with reasonable slippage protection (e.g. 95%–99% of expected output).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.giveaway-skills / giveaway-protocol) together. There is no direct contract-level dependency; they only cooperate at the product level.For the full ABI, it is recommended to use the compiled artifacts directly (for example, the
abifields inartifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.jsonand...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.
GouGouBiMarketConfigurableFactory ABI fragments[
{
"type": "event",
"name": "CreatorWhitelistUpdated",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "account", "type": "address" },
{ "indexed": false, "name": "allowed", "type": "bool" }
]
},
{
"type": "event",
"name": "MarketCreated",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "market", "type": "address" },
{ "indexed": false, "name": "marketName", "type": "string" },
{ "indexed": true, "name": "uniswapV3Pool", "type": "address" },
{ "indexed": true, "name": "creator", "type": "address" },
{ "indexed": false, "name": "liquidityToken", "type": "address" },
{ "indexed": false, "name": "createdAt", "type": "uint256" },
{ "indexed": false, "name": "feeRecipient", "type": "address" },
{ "indexed": false, "name": "feeRate", "type": "uint256" },
{ "indexed": false, "name": "tokenDec", "type": "uint8" },
{ "indexed": false, "name": "reverseOrder", "type": "bool" },
{ "indexed": false, "name": "settlementInterval", "type": "uint256" },
{ "indexed": false, "name": "expiredSeconds", "type": "uint256" },
{ "indexed": false, "name": "initialReserve", "type": "uint256" },
{ "indexed": false, "name": "priceLookbackSeconds", "type": "uint32" },
{ "indexed": false, "name": "imageUrl", "type": "string" },
{ "indexed": false, "name": "rules", "type": "string" },
{ "indexed": false, "name": "timezone", "type": "string" },
{ "indexed": false, "name": "language", "type": "string" },
{ "indexed": false, "name": "groupUrl", "type": "string" },
{ "indexed": false, "name": "tags", "type": "string[]" },
{ "indexed": false, "name": "predictionToken", "type": "address" },
{ "indexed": false, "name": "anchorToken", "type": "address" },
{ "indexed": false, "name": "currencyUnit", "type": "string" }
]
},
{
"type": "function",
"stateMutability": "view",
"name": "owner",
"inputs": [],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "marketImplementation",
"inputs": [],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "isWhitelistedCreator",
"inputs": [{ "name": "account", "type": "address" }],
"outputs": [{ "type": "bool" }]
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "setCreatorWhitelist",
"inputs": [
{ "name": "account", "type": "address" },
{ "name": "allowed", "type": "bool" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "setCreatorWhitelistBatch",
"inputs": [
{ "name": "accounts", "type": "address[]" },
{ "name": "allowed", "type": "bool" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "createMarket",
"inputs": [
{
"name": "_config",
"type": "tuple",
"components": [
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getMarkets",
"inputs": [],
"outputs": [{ "type": "address[]" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "marketCount",
"inputs": [],
"outputs": [{ "type": "uint256" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getMarketRecord",
"inputs": [{ "name": "index", "type": "uint256" }],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "market", "type": "address" },
{ "name": "creator", "type": "address" },
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "createdAt", "type": "uint256" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
]
}
]
GouGouBiMarketConfigurable ABI fragments[
{
"type": "event",
"name": "StartRound",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "x", "type": "uint256" },
{ "indexed": false, "name": "y", "type": "uint256" },
{ "indexed": false, "name": "startTime", "type": "uint256" },
{ "indexed": false, "name": "endTime", "type": "uint256" },
{ "indexed": false, "name": "expiredTime", "type": "uint256" },
{ "indexed": false, "name": "startAveragePrice", "type": "uint256" },
{ "indexed": false, "name": "endAveragePrice", "type": "uint256" },
{ "indexed": false, "name": "yesToken", "type": "address" },
{ "indexed": false, "name": "noToken", "type": "address" }
]
},
{
"type": "event",
"name": "AutoResolve",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "winning", "type": "uint8" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" },
{ "indexed": false, "name": "x", "type": "uint256" },
{ "indexed": false, "name": "y", "type": "uint256" },
{ "indexed": false, "name": "startTime", "type": "uint256" },
{ "indexed": false, "name": "endTime", "type": "uint256" },
{ "indexed": false, "name": "endAveragePrice", "type": "uint256" }
]
},
{
"type": "event",
"name": "BuyYes",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "tokenIn", "type": "uint256" },
{ "indexed": false, "name": "yesOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" }
]
},
{
"type": "event",
"name": "BuyNo",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "tokenIn", "type": "uint256" },
{ "indexed": false, "name": "noOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" }
]
},
{
"type": "event",
"name": "Redeem",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "winning", "type": "uint8" },
{ "indexed": false, "name": "tokensYes", "type": "uint256" },
{ "indexed": false, "name": "tokensNo", "type": "uint256" },
{ "indexed": false, "name": "tokensReceived", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" },
{ "indexed": false, "name": "timestamp", "type": "uint256" }
]
},
{
"type": "event",
"name": "SwapYesForNo",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "yesIn", "type": "uint256" },
{ "indexed": false, "name": "noOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" }
]
},
{
"type": "event",
"name": "SwapNoForYes",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "noIn", "type": "uint256" },
{ "indexed": false, "name": "yesOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" }
]
},
{
"type": "event",
"name": "FeeCollected",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "feeAmount", "type": "uint256" },
{ "indexed": false, "name": "feeRecipient", "type": "address" }
]
},
{
"type": "function",
"stateMutability": "view",
"name": "config",
"inputs": [],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
]
},
{
"type": "function",
"stateMutability": "view",
"name": "currentRound",
"inputs": [],
"outputs": [{ "type": "uint256" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getRoundInfo",
"inputs": [{ "name": "round", "type": "uint256" }],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "winning", "type": "uint8" },
{ "name": "poolAmount", "type": "uint256" },
{ "name": "x", "type": "uint256" },
{ "name": "y", "type": "uint256" },
{ "name": "yesToken", "type": "address" },
{ "name": "noToken", "type": "address" },
{ "name": "startTime", "type": "uint256" },
{ "name": "endTime", "type": "uint256" },
{ "name": "expiredTime", "type": "uint256" },
{ "name": "startAveragePrice", "type": "uint256" },
{ "name": "endAveragePrice", "type": "uint256" }
]
}
]
},
{
"type": "function",
"stateMutability": "view",
"name": "getPoolInfo",
"inputs": [],
"outputs": [
{ "name": "_x", "type": "uint256" },
{ "name": "_y", "type": "uint256" },
{ "name": "_poolAmount", "type": "uint256" },
{ "name": "_priceYes", "type": "uint256" },
{ "name": "_priceNo", "type": "uint256" },
{ "name": "_currentRound", "type": "uint256" },
{ "name": "_winning", "type": "uint8" }
]
},
{
"type": "function",
"stateMutability": "payable",
"name": "buyYes",
"inputs": [
{ "name": "tokenIn", "type": "uint256" },
{ "name": "minYesOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "payable",
"name": "buyNo",
"inputs": [
{ "name": "tokenIn", "type": "uint256" },
{ "name": "minNoOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "swapYesForNo",
"inputs": [
{ "name": "amountIn", "type": "uint256" },
{ "name": "minAmountOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "swapNoForYes",
"inputs": [
{ "name": "amountIn", "type": "uint256" },
{ "name": "minAmountOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "redeem",
"inputs": [{ "name": "round", "type": "uint256" }],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "resolve",
"inputs": [],
"outputs": []
}
]
The ABI fragments above can be combined with the TypeScript examples in section 6, for example:
import { ethers } from "ethers";
import { factoryFragment, marketFragment } from "./fragments"; // export the JSON fragments above as constants
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryFragment, signerOrProvider);
const market = new ethers.Contract(marketAddress, marketFragment, signerOrProvider);
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:
contracts/contracts/GouGouBiMarketConfigurable.solcontracts/contracts/GouGouBiMarketConfigurableFactory.soltoken0/token1 还是反向(reverseOrder)。本 skill 只描述 合约 API 与调用模式,具体部署网络与地址由业务方在上层配置(例如在 dApp 配置文件或 OpenClow workflow 的参数中传入)。
owner:工厂管理员,可管理创建白名单。marketImplementation:被克隆的逻辑合约实现(GouGouBiMarketConfigurable)。isWhitelistedCreator[address]:是否允许该地址调用 createMarket。markets[]:所有已创建市场地址列表。marketIndex[market]:市场地址对应的索引(从 1 开始,0 表示不是本工厂创建)。marketRecords[index]:创建记录(见下)。对应工厂中每个已创建市场:
market:市场合约地址creator:创建者地址marketName:市场名称uniswapV3Pool:Uniswap V3 池地址liquidityToken:流动性代币地址,address(0) 表示原生币feeRecipient:手续费接收地址feeRate:手续费率(分母 10000)createdAt:创建时间戳GouGouBiMarketConfigurable.MarketConfig 对齐(见第 3 节):
tokenDecreverseOrdersettlementIntervalexpiredSecondsinitialReservepriceLookbackSecondsimageUrlrulestimezonelanguagegroupUrltagspredictionTokenanchorTokencurrencyUnitfunction setCreatorWhitelist(address account, bool allowed) external onlyOwner
function setCreatorWhitelistBatch(address[] calldata accounts, bool allowed) external onlyOwner
owner 可调用。account != address(0)。function createMarket(GouGouBiMarketConfigurable.MarketConfig memory _config)
external
returns (address market)
约束:
owner 或 isWhitelistedCreator[msg.sender] == true 的地址可创建:
require(msg.sender == owner || isWhitelistedCreator[msg.sender], "NOT_WHITELISTED");Clones.clone(marketImplementation) 创建最小代理。initialize(_config, msg.sender),设置配置与 owner。markets[],填充 marketRecords,更新 marketIndex。MarketCreated 事件,记录关键配置信息。读接口:
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)
工厂 createMarket 的 _config 与市场合约中的 MarketConfig 完全一致:
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 > 0uniswapV3Pool != address(0)settlementInterval > 0expiredSeconds > 00 < initialReserve <= 1_000_000_000feeRate <= FEE_DENOMINATOR (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 轮。
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 OutcomeToken
address noToken; // 本轮 NO OutcomeToken
uint256 startTime; // 本轮开始时间
uint256 endTime; // 预定结算时间
uint256 expiredTime; // 过期时间(超时自动视为 winning=3)
uint256 startAveragePrice; // 轮次开始时平均价格
uint256 endAveragePrice; // 轮次结束/结算时平均价格
}
rounds[round] 只读映射访问。currentRound。function getAveragePriceFromUniswapV3(uint32 startTime, uint32 endTime)
public
view
returns (uint256 averagePrice)
startTime > endTime,且 endTime <= block.timestamp。observe + TickMath 计算对应区间的平均价格,并用 1e18 精度表示。reverseOrder == true,会对价格取倒数(同样保持 1e18 精度)。_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,然后开启新一轮。外部可主动触发检查:
function resolve() external nonReentrant {
_checkAndExecuteSettlement();
}
function buyYes(uint256 tokenIn, uint256 minYesOut) external payable nonReentrant
语义:
currentRound:
liquidityToken == address(0)):
tokenIn 参数,使用 msg.value 作为投入金额,要求 msg.value > 0。tokenIn > 0 && msg.value == 0。IERC20(liquidityToken).approve(market, tokenIn),合约内部使用 safeTransferFrom。tokenIn 记入 rounds[currentRound].poolAmount。mintedYes = mintedNo = tokenIn),通过常数乘积公式计算价格滑点,得到额外的 swapped 数量。totalYesOut = mintedYes + swappedYes,必须 >= minYesOut,否则交易回滚(滑点保护)。OutcomeToken(yesToken).mint(msg.sender, totalYesOut) 发放 YES 代币。function buyNo(uint256 tokenIn, uint256 minNoOut) external payable nonReentrant
buyYes 对称,只是对 NO 池进行操作,最终得到 totalNoOut,需要满足 >= minNoOut。function swapYesForNo(uint256 amountIn, uint256 minAmountOut) external nonReentrant
function swapNoForYes(uint256 amountIn, uint256 minAmountOut) external nonReentrant
通用逻辑要点:
winning == 0(未结算)时允许互换。x、y 以及输入数量 amountIn 计算 amountOut,并进行滑点检查:
swapYesForNo:amountOut = (y * amountIn) / (x + amountIn)swapNoForYes:amountOut = (x * amountIn) / (y + amountIn)burn 销毁输入代币,mint 发放输出代币,并更新 x、y。function redeem(uint256 round) external nonReentrant
约束与流程:
_checkAndExecuteSettlement(),可能会改变当前轮状态。round > 0 && round <= currentRoundrounds[round].winning != 0(本轮已结算:YES 赢、NO 赢或平局)rounds[round].poolAmount > 0winning == 1(YES 赢):
winning == 2(NO 赢):
winning == 3(平局/异常):
tokenOut,并按 feeRate 抽取手续费:
fee = tokenOut * feeRate / FEE_DENOMINATORuserAmount = tokenOut - feeconfig.feeRecipientfunction priceYes() external view returns (uint256 num, uint256 den)
function priceNo() external view returns (uint256 num, uint256 den)
function getPoolInfo() external view returns (
uint256 _x, uint256 _y, uint256 _poolAmount,
uint256 _priceYes, uint256 _priceNo,
uint256 _currentRound, uint8 _winning
)
function getRoundInfo(uint256 round) external view returns (
uint8 _winning, uint256 _poolAmount, uint256 _x, uint256 _y,
address _yesToken, address _noToken,
uint256 _startTime, uint256 _endTime, uint256 _expiredTime,
uint256 _startAveragePrice, uint256 _endAveragePrice
)
priceYes/priceNo 返回分数形式的价格(num/den),用于前端展示或估算。getPoolInfo 一次性获取当前轮的池子与价格信息。getRoundInfo 可查询任意轮的详细数据,包括 YES/NO 代币地址与时间信息。示例仅展示调用模式,实际
FACTORY_ADDRESS与市场地址由部署环境决定,请在项目配置中传入。
import { ethers } from "ethers";
import MarketAbi from "../artifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.json";
import FactoryAbi from "../artifacts/contracts/GouGouBiMarketConfigurableFactory.sol/GouGouBiMarketConfigurableFactory.json";
// 由业务方配置
const FACTORY_ADDRESS = "<REPLACE_WITH_FACTORY_ADDRESS>";
export function getFactory(providerOrSigner: ethers.Signer | ethers.providers.Provider) {
return new ethers.Contract(FACTORY_ADDRESS, FactoryAbi.abi, providerOrSigner);
}
export function getMarket(
marketAddress: string,
providerOrSigner: ethers.Signer | ethers.providers.Provider
) {
return new ethers.Contract(marketAddress, MarketAbi.abi, providerOrSigner);
}
// 创建新市场(需要工厂 owner 或白名单地址)
export async function createMarket(
signer: ethers.Signer,
config: {
marketName: string;
uniswapV3Pool: string;
liquidityToken: string; // 原生币市场可传 ethers.constants.AddressZero
reverseOrder: boolean;
settlementInterval: number;
expiredSeconds: number;
initialReserve: ethers.BigNumberish;
priceLookbackSeconds?: number;
feeRecipient?: string;
feeRate: number;
imageUrl?: string;
rules?: string;
timezone?: string;
language?: string;
groupUrl?: string;
tags?: string[];
predictionToken: string;
anchorToken: string;
currencyUnit: string;
}
) {
const factory = getFactory(signer);
const _config = {
...config,
// 让合约自动推断 tokenDec / priceLookbackSeconds / feeRecipient 等默认值
tokenDec: 0,
priceLookbackSeconds: config.priceLookbackSeconds ?? 0,
feeRecipient: config.feeRecipient ?? ethers.constants.AddressZero,
tags: config.tags ?? [],
};
const tx = await factory.createMarket(_config);
const receipt = await tx.wait();
// 可以从 MarketCreated 事件中解析新市场地址
return receipt;
}
// 在当前轮买入 YES(ERC20 市场为例)
export async function buyYes(
signer: ethers.Signer,
marketAddress: string,
amountIn: ethers.BigNumberish,
minYesOut: ethers.BigNumberish
) {
const market = getMarket(marketAddress, signer);
const tx = await market.buyYes(amountIn, minYesOut);
return tx.wait();
}
// 在当前轮买入 NO(原生币市场为例)
export async function buyNoNative(
signer: ethers.Signer,
marketAddress: string,
amountWei: ethers.BigNumberish,
minNoOut: ethers.BigNumberish
) {
const market = getMarket(marketAddress, signer);
const tx = await market.buyNo(0, minNoOut, { value: amountWei });
return tx.wait();
}
// 赎回某一轮的结算结果
export async function redeem(
signer: ethers.Signer,
marketAddress: string,
round: number
) {
const market = getMarket(marketAddress, signer);
const tx = await market.redeem(round);
return tx.wait();
}
在前端或自动化脚本中集成时建议:
getPoolInfo、getRoundInfo、priceYes、priceNo 等)使用只读 provider。createMarket、buyYes、buyNo、swapYesForNo、swapNoForYes、redeem、resolve)必须使用具有签名能力的 signer。buyYes / buyNo 前要先对市场合约执行一次 approve,原生币市场则通过 value 附带资金。minYesOut / minNoOut 做合理设置以防滑点过大(例如按预期输出的 95%~99% 设定)。market-configurable-skills 专注于 GouGouBi 可配置价格预测市场的具体实现与调用方式,包括工厂创建、市场配置字段、交易与结算逻辑。giveaway-skills / giveaway-protocol)一起阅读,但它们之间没有直接合约依赖关系,仅在产品层面协同使用。完整 ABI 推荐直接使用编译产物(例如
artifacts/contracts/GouGouBiMarketConfigurable.sol/GouGouBiMarketConfigurable.json和...Factory.sol/GouGouBiMarketConfigurableFactory.json中的abi字段)。本节只提供常用事件与函数的精简片段,用于快速调试或在无法访问编译产物时临时构造合约实例。
[
{
"type": "event",
"name": "CreatorWhitelistUpdated",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "account", "type": "address" },
{ "indexed": false, "name": "allowed", "type": "bool" }
]
},
{
"type": "event",
"name": "MarketCreated",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "market", "type": "address" },
{ "indexed": false, "name": "marketName", "type": "string" },
{ "indexed": true, "name": "uniswapV3Pool", "type": "address" },
{ "indexed": true, "name": "creator", "type": "address" },
{ "indexed": false, "name": "liquidityToken", "type": "address" },
{ "indexed": false, "name": "createdAt", "type": "uint256" },
{ "indexed": false, "name": "feeRecipient", "type": "address" },
{ "indexed": false, "name": "feeRate", "type": "uint256" },
{ "indexed": false, "name": "tokenDec", "type": "uint8" },
{ "indexed": false, "name": "reverseOrder", "type": "bool" },
{ "indexed": false, "name": "settlementInterval", "type": "uint256" },
{ "indexed": false, "name": "expiredSeconds", "type": "uint256" },
{ "indexed": false, "name": "initialReserve", "type": "uint256" },
{ "indexed": false, "name": "priceLookbackSeconds", "type": "uint32" },
{ "indexed": false, "name": "imageUrl", "type": "string" },
{ "indexed": false, "name": "rules", "type": "string" },
{ "indexed": false, "name": "timezone", "type": "string" },
{ "indexed": false, "name": "language", "type": "string" },
{ "indexed": false, "name": "groupUrl", "type": "string" },
{ "indexed": false, "name": "tags", "type": "string[]" },
{ "indexed": false, "name": "predictionToken", "type": "address" },
{ "indexed": false, "name": "anchorToken", "type": "address" },
{ "indexed": false, "name": "currencyUnit", "type": "string" }
]
},
{
"type": "function",
"stateMutability": "view",
"name": "owner",
"inputs": [],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "marketImplementation",
"inputs": [],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "isWhitelistedCreator",
"inputs": [{ "name": "account", "type": "address" }],
"outputs": [{ "type": "bool" }]
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "setCreatorWhitelist",
"inputs": [
{ "name": "account", "type": "address" },
{ "name": "allowed", "type": "bool" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "setCreatorWhitelistBatch",
"inputs": [
{ "name": "accounts", "type": "address[]" },
{ "name": "allowed", "type": "bool" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "createMarket",
"inputs": [
{
"name": "_config",
"type": "tuple",
"components": [
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
],
"outputs": [{ "type": "address" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getMarkets",
"inputs": [],
"outputs": [{ "type": "address[]" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "marketCount",
"inputs": [],
"outputs": [{ "type": "uint256" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getMarketRecord",
"inputs": [{ "name": "index", "type": "uint256" }],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "market", "type": "address" },
{ "name": "creator", "type": "address" },
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "createdAt", "type": "uint256" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
]
}
]
[
{
"type": "event",
"name": "StartRound",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "x", "type": "uint256" },
{ "indexed": false, "name": "y", "type": "uint256" },
{ "indexed": false, "name": "startTime", "type": "uint256" },
{ "indexed": false, "name": "endTime", "type": "uint256" },
{ "indexed": false, "name": "expiredTime", "type": "uint256" },
{ "indexed": false, "name": "startAveragePrice", "type": "uint256" },
{ "indexed": false, "name": "endAveragePrice", "type": "uint256" },
{ "indexed": false, "name": "yesToken", "type": "address" },
{ "indexed": false, "name": "noToken", "type": "address" }
]
},
{
"type": "event",
"name": "AutoResolve",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "winning", "type": "uint8" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" },
{ "indexed": false, "name": "x", "type": "uint256" },
{ "indexed": false, "name": "y", "type": "uint256" },
{ "indexed": false, "name": "startTime", "type": "uint256" },
{ "indexed": false, "name": "endTime", "type": "uint256" },
{ "indexed": false, "name": "endAveragePrice", "type": "uint256" }
]
},
{
"type": "event",
"name": "BuyYes",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "tokenIn", "type": "uint256" },
{ "indexed": false, "name": "yesOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" }
]
},
{
"type": "event",
"name": "BuyNo",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "tokenIn", "type": "uint256" },
{ "indexed": false, "name": "noOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" }
]
},
{
"type": "event",
"name": "Redeem",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "winning", "type": "uint8" },
{ "indexed": false, "name": "tokensYes", "type": "uint256" },
{ "indexed": false, "name": "tokensNo", "type": "uint256" },
{ "indexed": false, "name": "tokensReceived", "type": "uint256" },
{ "indexed": false, "name": "poolAmount", "type": "uint256" },
{ "indexed": false, "name": "timestamp", "type": "uint256" }
]
},
{
"type": "event",
"name": "SwapYesForNo",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "yesIn", "type": "uint256" },
{ "indexed": false, "name": "noOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" }
]
},
{
"type": "event",
"name": "SwapNoForYes",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "noIn", "type": "uint256" },
{ "indexed": false, "name": "yesOut", "type": "uint256" },
{ "indexed": false, "name": "newX", "type": "uint256" },
{ "indexed": false, "name": "newY", "type": "uint256" }
]
},
{
"type": "event",
"name": "FeeCollected",
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "user", "type": "address" },
{ "indexed": true, "name": "round", "type": "uint256" },
{ "indexed": false, "name": "feeAmount", "type": "uint256" },
{ "indexed": false, "name": "feeRecipient", "type": "address" }
]
},
{
"type": "function",
"stateMutability": "view",
"name": "config",
"inputs": [],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "marketName", "type": "string" },
{ "name": "uniswapV3Pool", "type": "address" },
{ "name": "liquidityToken", "type": "address" },
{ "name": "tokenDec", "type": "uint8" },
{ "name": "reverseOrder", "type": "bool" },
{ "name": "settlementInterval", "type": "uint256" },
{ "name": "expiredSeconds", "type": "uint256" },
{ "name": "initialReserve", "type": "uint256" },
{ "name": "priceLookbackSeconds", "type": "uint32" },
{ "name": "feeRecipient", "type": "address" },
{ "name": "feeRate", "type": "uint256" },
{ "name": "imageUrl", "type": "string" },
{ "name": "rules", "type": "string" },
{ "name": "timezone", "type": "string" },
{ "name": "language", "type": "string" },
{ "name": "groupUrl", "type": "string" },
{ "name": "tags", "type": "string[]" },
{ "name": "predictionToken", "type": "address" },
{ "name": "anchorToken", "type": "address" },
{ "name": "currencyUnit", "type": "string" }
]
}
]
},
{
"type": "function",
"stateMutability": "view",
"name": "currentRound",
"inputs": [],
"outputs": [{ "type": "uint256" }]
},
{
"type": "function",
"stateMutability": "view",
"name": "getRoundInfo",
"inputs": [{ "name": "round", "type": "uint256" }],
"outputs": [
{
"type": "tuple",
"components": [
{ "name": "winning", "type": "uint8" },
{ "name": "poolAmount", "type": "uint256" },
{ "name": "x", "type": "uint256" },
{ "name": "y", "type": "uint256" },
{ "name": "yesToken", "type": "address" },
{ "name": "noToken", "type": "address" },
{ "name": "startTime", "type": "uint256" },
{ "name": "endTime", "type": "uint256" },
{ "name": "expiredTime", "type": "uint256" },
{ "name": "startAveragePrice", "type": "uint256" },
{ "name": "endAveragePrice", "type": "uint256" }
]
}
]
},
{
"type": "function",
"stateMutability": "view",
"name": "getPoolInfo",
"inputs": [],
"outputs": [
{ "name": "_x", "type": "uint256" },
{ "name": "_y", "type": "uint256" },
{ "name": "_poolAmount", "type": "uint256" },
{ "name": "_priceYes", "type": "uint256" },
{ "name": "_priceNo", "type": "uint256" },
{ "name": "_currentRound", "type": "uint256" },
{ "name": "_winning", "type": "uint8" }
]
},
{
"type": "function",
"stateMutability": "payable",
"name": "buyYes",
"inputs": [
{ "name": "tokenIn", "type": "uint256" },
{ "name": "minYesOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "payable",
"name": "buyNo",
"inputs": [
{ "name": "tokenIn", "type": "uint256" },
{ "name": "minNoOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "swapYesForNo",
"inputs": [
{ "name": "amountIn", "type": "uint256" },
{ "name": "minAmountOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "swapNoForYes",
"inputs": [
{ "name": "amountIn", "type": "uint256" },
{ "name": "minAmountOut", "type": "uint256" }
],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "redeem",
"inputs": [{ "name": "round", "type": "uint256" }],
"outputs": []
},
{
"type": "function",
"stateMutability": "nonpayable",
"name": "resolve",
"inputs": [],
"outputs": []
}
]
上述 ABI 片段可以与第 6 节中的 TypeScript 示例直接组合使用,例如:
import { ethers } from "ethers";
import { factoryFragment, marketFragment } from "./fragments"; // 将上面的 JSON 片段拆分成常量
const factory = new ethers.Contract(FACTORY_ADDRESS, factoryFragment, signerOrProvider);
const market = new ethers.Contract(marketAddress, marketFragment, signerOrProvider);