Prediction Market
Binary prediction market with YES/NO outcome tokens, liquidity pool trading, and vote-based settlement
Category: Token Types
Summary
Required standards: ["Prediction Market"]
Binary prediction market: "Will X happen by Y?" Users deposit USDC to mint paired YES+NO tokens. Trade YESβNO on a liquidity pool. Verifier settles by voting. Winner redeems 1:1.
Token ID 1 = YES, Token ID 2 = NO (via alias paths with 6 decimals)
mintEscrowAddress holds all deposited USDC
invariants: `noForcefulPostMintTransfers: true` β locks non-mint approvals (redeem, settlement, transferable) from using `overridesFromOutgoingApprovals` or `overridesToIncomingApprovals`. Non-mint approvals rely on `defaultBalances.autoApproveSelfInitiatedOutgoingTransfers: true` for outgoing-side auth and on the burn destination for incoming-side auth
All permissions frozen after creation
7 approvals: paired mint, freely transferable, pre-settlement redeem, yes-wins, no-wins, push-yes, push-no
Alias paths for YES (token 1) and NO (token 2) with 6 decimals
Settlement via votingChallenges with 1-of-1 multisig verifier
Liquidity pool: MsgCreateBalancerPool with badgeslp:collectionId:uyes and badgeslp:collectionId:uno, equal weights
DON'T use Smart Token standard β this uses mintEscrowAddress, not invariant paths
DON'T forget votingChallenges on settlement approvals
DON'T set maxNumTransfers on mint/redeem (should be unlimited = 0)
DON'T forget to freeze all permissions
DON'T forget predeterminedBalances with BOTH token IDs in paired mint/redeem
DON'T set overrideFromWithApproverAddress on the deposit coinTransfer (filler pays, not escrow)
Instructions
Prediction Market Configuration
Mental Model
Binary prediction market: "Will X happen by Y?" Users deposit USDC to mint paired YES+NO tokens. Trade YESβNO on a liquidity pool. Verifier settles by voting. Winner redeems 1:1.
Collection Structure
Token ID 1 = YES, Token ID 2 = NO (via alias paths with 6 decimals)
Standard: 'Prediction Market'
mintEscrowAddress holds all deposited USDC
All permissions frozen after creation
Alias Paths
Two alias paths are REQUIRED β one for YES (token 1) and one for NO (token 2):
7 Approvals
CRITICAL: All amounts in startBalances must be in BASE units (micro-units). Since YES/NO tokens have 6 decimals, 1 display token = 1,000,000 base units. Use "1000000" (not "1") for startBalance amounts when minting 1 display-YES + 1 display-NO per deposit.
1. Paired Mint (deposit 1 USDC β receive 1 YES + 1 NO)
CRITICAL: The
tofield MUST be"Mint". The chain auto-resolves"Mint"to the collection'smintEscrowAddressat execution time for collection-level approvals. This ensures deposited USDC goes to the escrow (not the creator's wallet) and is available for redemption payouts.DO NOT use the creator's address or any hardcoded address β use
"Mint"which auto-resolves to the escrow.
Note: The deposit coinTransfer uses to: "Mint" with no overrides: initiator pays USDC β escrow (auto-resolved). The payout coinTransfers use to: "" with overrideFromWithApproverAddress: true (escrow pays) + overrideToWithInitiator: true (redeemer receives).
2. Freely Transferable (allows transfers between users, pools, DEX)
This approval has NO coinTransfers, NO votingChallenges, and mustPrioritize: false. It allows auto-scanning so tokens can be freely transferred to pool addresses and between users.
3. Pre-Settlement Redeem (burn 1 YES + 1 NO β 1 USDC from escrow)
4. YES Wins (burn YES β 1 USDC)
5. NO Wins (burn NO β 1 USDC)
Same as YES Wins but with token ID 2, separate proposalId, and separate amountTrackerId:
6. Push YES (burn YES β 0.5 USDC β fallback if market is indeterminate)
7. Push NO (burn NO β 0.5 USDC β fallback if market is indeterminate)
Same as Push YES but with token ID 2 and a separate proposalId:
Settlement Flow
Verifier sends MsgCastVote with 100% yes on the proposalId of the winning approval
Once quorum reached, that approval becomes active for transfers
Holders burn winning token to receive USDC from escrow
Liquidity Pool
After creating the collection and minting initial pairs:
Create pool: MsgCreateBalancerPool with badgeslp:collectionId:uyes and badgeslp:collectionId:uno, equal weights
Market price discovery: YES_price = NO_reserve / (YES_reserve + NO_reserve)
Steps for AI Builder
Use per-field tools to initialize the collection (set_standards, set_valid_token_ids, etc.)
set_token_metadatafor YES (token 1) and NO (token 2)set_invariantswith{ "noCustomOwnershipTimes": true, "disablePoolCreation": false }β MUST set disablePoolCreation to falseAdd 7 approvals via
add_approval:paired-mint: Mint β All (deposit USDC, receive YES+NO). coinTransfertoMUST be"Mint"β the chain auto-resolves this to the collection's mintEscrowAddress at execution time. No overrides on deposit.transferable: !Mint β All (free transfers between users/pools, NO coinTransfers, NO mustPrioritize, overridesFromOutgoingApprovals: false, overridesToIncomingApprovals: false)pre-settlement-redeem: !Mint β burn (redeem pair for USDC). coinTransferto: ""withoverrideFromWithApproverAddress: true+overrideToWithInitiator: true(escrow pays redeemer).yes-wins,no-wins,push-yes,push-no: settlement (vote-gated). Same payout pattern:to: ""with both overrides true.
set_mint_escrow_coinsβ NOT needed upfront (coins come from deposits)Add alias paths via
add_alias_pathfor YES and NOset_permissionswith preset"fully-immutable"to freeze everything (NOT "locked-approvals" β that leaves some permissions neutral)After collection creation: mint initial pairs + create pool
Common Mistakes
DON'T forget both alias paths (uyes and uno)
DON'T set the alias denom/symbol the same as the denomUnit symbol β the chain rejects duplicate denom unit symbols. Use base denoms like "uyes"/"uno" with display denomUnits "YES"/"NO" (6 decimals)
DON'T use Smart Token standard β this uses mintEscrowAddress, not invariant paths
DON'T forget votingChallenges on settlement approvals
DON'T set maxNumTransfers on the paired mint approval (deposits should be unlimited = 0)
DO set maxNumTransfers.overallMaxNumTransfers to "18446744073709551615" (max uint64) on ALL approvals that use overrideFromWithApproverAddress: true (redeem + settlement). The chain REQUIRES a non-zero maxNumTransfers when overrideFromWithApproverAddress is true. Use max uint64 for effectively unlimited.
DON'T disable overrideFromWithApproverAddress to work around maxNumTransfers errors β that breaks payout routing. Always keep overrideFromWithApproverAddress: true on redemption/settlement approvals and set maxNumTransfers to max uint64.
DON'T forget to freeze all permissions
DON'T forget predeterminedBalances with BOTH token IDs in paired mint/redeem
DON'T set overrideFromWithApproverAddress on the deposit coinTransfer (filler pays, not escrow)
DON'T leave the "to" field empty on the paired mint coinTransfer β use
"Mint"which auto-resolves to the collection's mintEscrowAddress at execution time.DON'T use the creator's address or any hardcoded address as "to" on the deposit β use
"Mint"for auto-routing to escrow.DON'T hardcode the creator address as the coinTransfer "to" on redemption/settlement β use overrideToWithInitiator: true so the person redeeming receives the payout
DON'T use "1" for startBalance amounts β YES/NO tokens have 6 decimals, so 1 display token = 1,000,000 base units. Use "1000000" for each startBalance amount.
DON'T use
set_permissionspreset "locked-approvals" β use "fully-immutable" to freeze ALL permissions including validTokenIdsDON'T use lowercase "prediction-market" as the standard β the correct name is "Prediction Market" (title case with space)
DON'T set invariants.disablePoolCreation to true β prediction markets REQUIRE liquidity pools for YES/NO trading. Set it to false.
Last updated