Futarchy market
In futarchy markets, participants trade on how a specific decision (e.g. a DAO proposal) will affect the price of two tokens. The market asks a single yes/no question (e.g. βWill proposal GIP-120 be accepted by February 1 2025?β). There are four outcome tokens: Yes/Token1, No/Token1, Yes/Token2, No/Token2. After resolution, either the two βYesβ outcomes or the two βNoβ outcomes are redeemable for the two collateral tokens.
Creation uses FutarchyFactory.createProposal. Split, merge and redeem use FutarchyRouter, which is like the standard Router but works with two collateral tokens and a FutarchyProposal (proposal address) instead of a Market. Resolution uses FutarchyRealityProxy.resolve(proposal) (or proposal.resolve()).
Create a futarchy proposal
Call FutarchyFactory.createProposal with a CreateProposalParams struct. Outcomes and token names are derived from the two collateral token symbols (e.g. Yes-GNO, No-GNO, Yes-wstETH, No-wstETH).
struct CreateProposalParams {
string marketName;
IERC20 collateralToken1;
IERC20 collateralToken2;
string category;
string lang;
uint256 minBond;
uint32 openingTime;
}marketName: The Reality.eth question (e.g. βWill proposal X be accepted by date Y?β).
collateralToken1 / collateralToken2: The two ERC20 tokens used as collateral (e.g. GNO and wstETH).
category / lang: Reality question category and language.
minBond: Minimum bond for answering on Reality.eth.
openingTime: Unix timestamp when the question can be answered.
On success, the factory returns the new proposal contract address (you can also read it from the NewProposal event).
Split, merge and redeem
Use FutarchyRouter for all three. The argument order differs from the standard Router: the proposal (FutarchyProposal address) comes first, then the collateral token, then the amount.
Split
You choose one of the two collateral tokens and split that token into the two outcome tokens for that collateral (Yes-Token and No-Token). So one split gives you only the pair for token1 or the pair for token2. To get all four outcome tokens you split twice (once per collateral).
Approve collateralToken (token1 or token2) to the FutarchyRouter.
Call
futarchyRouter.splitPosition(proposal, collateralToken, amount).You receive the two ERC20 outcome tokens for that collateral (e.g. Yes-GNO and No-GNO).
Merge
Hold one full set of outcome tokens for one collateral (i.e. equal amounts of Yes-Token and No-Token for that collateral). Merge returns that collateral to you.
Approve both outcome tokens (for that collateral) to the router, then call mergePositions(proposal, collateralToken, amount). You receive collateralToken back.
Redeem (after resolution)
After the proposal is resolved (FutarchyRealityProxy has reported payouts), only the winning side (Yes or No) is redeemable. You can redeem per collateral or both in one call.
Option A β Redeem one collateral
Approve the winning outcome token for one collateral (e.g. Yes-GNO if the proposal was accepted). Call redeemPositions(proposal, collateralToken, amount). You receive collateralToken.
Option B β Redeem both collaterals in one tx
Approve the winning outcome tokens for both collaterals (e.g. Yes-GNO and Yes-wstETH). Call redeemProposal(proposal, amount1, amount2). You receive collateralToken1 and collateralToken2 (amount1 and amount2 of each).
Resolve a futarchy proposal
Same idea as Resolve a market: the question must be answered and finalized on Reality.eth. Then call either:
FutarchyRealityProxy.resolve(proposal), orproposal.resolve()(the proposal forwards to the reality proxy).
After resolution, YES (outcome 0) or NO (outcome 1) is the winning side; only the corresponding outcome tokens are redeemable.
Viem examples
We use the Viem setup: getPublicClient(chain) and getWalletClient(chain, process.env.PRIVATE_KEY). Addresses come from SEER_CONTRACTS[chain.id]. Futarchy contracts are deployed on Gnosis (chain 100); on that chain use addresses.FutarchyFactory and addresses.FutarchyRouter.
Shared: addresses
1. Create a futarchy proposal
Build the params tuple and call createProposal. The factory derives outcomes and token names from the collateral symbols.
2. Split (one collateral)
Choose one of the two collateral tokens, approve it, then call splitPosition(proposal, collateralToken, amount). Argument order: proposal first, then collateral, then amount.
To get outcome tokens for the second collateral as well, repeat with wstETH_ADDRESS (and approve wstETH to the router).
3. Merge (one collateral)
Hold equal amounts of Yes-Token and No-Token for one collateral. Approve both outcome tokens (Yes and No for that collateral) to the FutarchyRouter, then call mergePositions(proposal, collateralToken, amount).
4. Redeem β one collateral (redeemPositions)
redeemPositions)After the proposal is resolved, approve the winning outcome token (e.g. Yes-GNO if accepted) to the FutarchyRouter. Then call redeemPositions(proposal, collateralToken, amount) to receive that collateral.
5. Redeem β both collaterals (redeemProposal)
redeemProposal)Approve the winning outcome tokens for both collaterals. Then call redeemProposal(proposal, amount1, amount2) to receive both collateral tokens in one tx.
6. Resolve the proposal
Once the Reality.eth question is answered and finalized, call proposal.resolve() (or FutarchyRealityProxy.resolve(proposal)).
7. Simulate before sending
To avoid reverts (e.g. insufficient allowance or balance):
Summary
Create
FutarchyFactory
createProposal(params)
params: marketName, collateralToken1, collateralToken2, category, lang, minBond, openingTime
Split
FutarchyRouter
splitPosition(proposal, collateralToken, amount)
Approve collateral first; do once per collateral to get all four outcome tokens
Merge
FutarchyRouter
mergePositions(proposal, collateralToken, amount)
Need equal amounts of Yes + No for that collateral
Redeem (one collateral)
FutarchyRouter
redeemPositions(proposal, collateralToken, amount)
Approve winning outcome token; after resolve
Redeem (both)
FutarchyRouter
redeemProposal(proposal, amount1, amount2)
Approve both winning outcome tokens
Resolve
FutarchyProposal
resolve()
Or FutarchyRealityProxy.resolve(proposal); question must be settled on Reality
Last updated