Frontend CTF1155 Integration Guide¶
Overview¶
This guide explains how to integrate CTF1155 (Conditional Token Framework) support into the Prediction DAO frontend. The backend ConditionalMarketFactory now exclusively uses CTF1155 for all markets.
Key Changes from ERC20 to CTF1155¶
Before (ERC20 Tokens)¶
- Each market had separate PASS and FAIL ERC20 tokens
- Users could trade tokens directly
- Simple token balance queries
After (CTF1155 Tokens)¶
- All markets use a single CTF1155 contract
- Each market has a unique condition with position IDs
- Users must split collateral into positions before trading
- Positions are ERC1155 tokens
Required Contract ABIs¶
Add these ABIs to frontend/src/abis/:
- CTF1155.js - The Conditional Token Framework contract
- ConditionalMarketFactory.js - Updated factory with CTF support
Generate these ABIs after compiling contracts:
cd /home/runner/work/prediction-dao-research/prediction-dao-research
npm run compile
# Export ABIs from artifacts/contracts/
Frontend Components to Update¶
1. MarketCreation Component¶
File: frontend/src/components/MarketCreation.jsx
Changes Needed: - Add note that markets now use CTF1155 - Update market creation flow to show CTF token information - Display condition ID and position IDs after market creation
Example Addition:
<div className="ctf-info">
<h3>Market uses Conditional Tokens (CTF1155)</h3>
<p>This market creates positions using the Gnosis CTF standard for gas-efficient trading.</p>
</div>
2. MarketTrading Component¶
File: frontend/src/components/MarketTrading.jsx
Major Changes Required:
A. Display CTF Position Information¶
const MarketInfo = ({ market }) => {
return (
<div className="market-ctf-info">
<h3>Market Tokens</h3>
<div className="token-info">
<div>CTF Contract: {market.passToken}</div>
<div>Condition ID: {market.conditionId}</div>
<div>Pass Position ID: {market.passPositionId}</div>
<div>Fail Position ID: {market.failPositionId}</div>
</div>
</div>
)
}
B. Add Position Splitting UI¶
Users need to split collateral into CTF positions before trading:
const PositionSplitter = ({ market }) => {
const [amount, setAmount] = useState('')
const handleSplit = async () => {
// 1. Approve collateral to CTF1155
await collateralToken.approve(ctf1155Address, amount)
// 2. Split position
await ctf1155.splitPosition(
market.collateralToken,
ethers.ZeroHash, // parentCollectionId (0x0 for base positions)
market.conditionId,
[1, 2], // partition: [PASS index, FAIL index]
amount
)
}
return (
<div className="position-splitter">
<h3>Get Position Tokens</h3>
<p>Split collateral to receive both PASS and FAIL position tokens</p>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount to split"
/>
<button onClick={handleSplit}>Split Position</button>
</div>
)
}
C. Display CTF Token Balances¶
const PositionBalances = ({ market, userAddress }) => {
const [passBalance, setPassBalance] = useState('0')
const [failBalance, setFailBalance] = useState('0')
useEffect(() => {
const fetchBalances = async () => {
// CTF1155 uses ERC1155 balanceOf
const passBal = await ctf1155.balanceOf(userAddress, market.passPositionId)
const failBal = await ctf1155.balanceOf(userAddress, market.failPositionId)
setPassBalance(ethers.formatEther(passBal))
setFailBalance(ethers.formatEther(failBal))
}
if (userAddress) {
fetchBalances()
}
}, [userAddress, market])
return (
<div className="position-balances">
<h3>Your Positions</h3>
<div>PASS: {passBalance}</div>
<div>FAIL: {failBalance}</div>
</div>
)
}
D. Position Merging UI¶
Allow users to merge positions back to collateral:
const PositionMerger = ({ market }) => {
const [amount, setAmount] = useState('')
const handleMerge = async () => {
// Merge PASS and FAIL positions back to collateral
await ctf1155.mergePositions(
market.collateralToken,
ethers.ZeroHash,
market.conditionId,
[1, 2], // same partition used in split
amount
)
}
return (
<div className="position-merger">
<h3>Redeem Collateral</h3>
<p>Merge PASS and FAIL tokens to get collateral back</p>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Amount to merge"
/>
<button onClick={handleMerge}>Merge Positions</button>
</div>
)
}
3. MarketList Component¶
File: frontend/src/components/MarketList.jsx (or similar)
Changes Needed: - Update market queries to handle CTF fields - Display CTF badge/indicator - Show condition status (prepared, resolved)
const MarketCard = ({ market }) => {
return (
<div className="market-card">
<div className="market-header">
<h3>{market.question}</h3>
<span className="ctf-badge">CTF1155</span>
</div>
<div className="market-details">
<div>Status: {market.resolved ? 'Resolved' : 'Active'}</div>
<div>Condition: {market.conditionId.slice(0, 10)}...</div>
</div>
</div>
)
}
4. Position Redemption Component¶
New Component Needed: frontend/src/components/PositionRedemption.jsx
For resolved markets, users need to redeem winning positions:
const PositionRedemption = ({ market }) => {
const handleRedeem = async () => {
// Check which outcome won
const isPassWinner = market.passValue > market.failValue
const winningIndex = isPassWinner ? 1 : 2
// Redeem winning positions
await ctf1155.redeemPositions(
market.collateralToken,
ethers.ZeroHash,
market.conditionId,
[winningIndex]
)
}
return (
<div className="position-redemption">
<h3>Redeem Winning Positions</h3>
<p>Market resolved: {market.passValue > market.failValue ? 'PASS Won' : 'FAIL Won'}</p>
<button onClick={handleRedeem}>Claim Winnings</button>
</div>
)
}
Contract Integration Hooks¶
useCTF1155 Hook¶
Create frontend/src/hooks/useCTF1155.js:
import { useState, useEffect } from 'react'
import { ethers } from 'ethers'
import CTF1155ABI from '../abis/CTF1155.js'
export const useCTF1155 = (address) => {
const [contract, setContract] = useState(null)
useEffect(() => {
const initContract = async () => {
if (!address) return
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const ctf = new ethers.Contract(address, CTF1155ABI, signer)
setContract(ctf)
}
initContract()
}, [address])
const splitPosition = async (collateral, conditionId, amount) => {
if (!contract) throw new Error('Contract not initialized')
const tx = await contract.splitPosition(
collateral,
ethers.ZeroHash,
conditionId,
[1, 2],
amount
)
await tx.wait()
return tx
}
const mergePositions = async (collateral, conditionId, amount) => {
if (!contract) throw new Error('Contract not initialized')
const tx = await contract.mergePositions(
collateral,
ethers.ZeroHash,
conditionId,
[1, 2],
amount
)
await tx.wait()
return tx
}
const redeemPositions = async (collateral, conditionId, indexSet) => {
if (!contract) throw new Error('Contract not initialized')
const tx = await contract.redeemPositions(
collateral,
ethers.ZeroHash,
conditionId,
[indexSet]
)
await tx.wait()
return tx
}
const getBalance = async (userAddress, positionId) => {
if (!contract) throw new Error('Contract not initialized')
return await contract.balanceOf(userAddress, positionId)
}
return {
contract,
splitPosition,
mergePositions,
redeemPositions,
getBalance
}
}
useMarketFactory Hook¶
Update frontend/src/hooks/useMarketFactory.js:
import { useState, useEffect } from 'react'
import { ethers } from 'ethers'
import MarketFactoryABI from '../abis/ConditionalMarketFactory.js'
export const useMarketFactory = (address) => {
const [contract, setContract] = useState(null)
useEffect(() => {
const initContract = async () => {
if (!address) return
const provider = new ethers.BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const factory = new ethers.Contract(address, MarketFactoryABI, signer)
setContract(factory)
}
initContract()
}, [address])
const getMarket = async (marketId) => {
if (!contract) throw new Error('Contract not initialized')
return await contract.getMarket(marketId)
}
const deployMarket = async (params) => {
if (!contract) throw new Error('Contract not initialized')
const tx = await contract.deployMarketPair(
params.proposalId,
params.collateralToken,
params.liquidityAmount,
params.liquidityParameter,
params.tradingPeriod,
params.betType
)
const receipt = await tx.wait()
// Extract MarketCreated and CTFMarketCreated events
const marketCreatedEvent = receipt.logs.find(log =>
log.topics[0] === ethers.id("MarketCreated(uint256,uint256,address,address,address,uint256,uint256,uint256,address,uint8)")
)
const ctfMarketEvent = receipt.logs.find(log =>
log.topics[0] === ethers.id("CTFMarketCreated(uint256,bytes32,uint256,uint256)")
)
return {
tx,
receipt,
marketCreatedEvent,
ctfMarketEvent
}
}
return {
contract,
getMarket,
deployMarket
}
}
Styling Updates¶
CTF Badge¶
Add to MarketList.css:
.ctf-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
Position Cards¶
Add to MarketTrading.css:
.position-card {
background: white;
border: 2px solid #e5e7eb;
border-radius: 12px;
padding: 24px;
margin: 16px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.position-card h3 {
font-size: 1.25rem;
margin-bottom: 16px;
color: #1f2937;
}
.position-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin: 16px 0;
}
.position-info-item {
padding: 12px;
background: #f9fafb;
border-radius: 8px;
}
.position-info-label {
font-size: 0.875rem;
color: #6b7280;
margin-bottom: 4px;
}
.position-info-value {
font-size: 1.125rem;
font-weight: 600;
color: #111827;
word-break: break-all;
}
Configuration¶
Add contract addresses to frontend/src/config/contracts.js:
export const CONTRACTS = {
CTF1155: {
address: process.env.VITE_CTF1155_ADDRESS || '0x...',
abi: CTF1155ABI
},
ConditionalMarketFactory: {
address: process.env.VITE_MARKET_FACTORY_ADDRESS || '0x...',
abi: MarketFactoryABI
}
}
Testing Checklist¶
- Market creation displays CTF information
- Users can split collateral into positions
- Position balances display correctly (ERC1155)
- Users can merge positions back to collateral
- Resolved markets show redemption option
- Users can redeem winning positions
- CTF badge displays on market cards
- All CTF-related transactions have proper error handling
- Loading states during blockchain transactions
- Event listeners for CTF1155 events
Migration Notes¶
For Existing Markets¶
- Old ERC20 markets (if any exist) will continue to work
- New markets automatically use CTF1155
- Frontend should handle both gracefully during transition
User Experience¶
- Educate users about position splitting/merging
- Provide tooltips explaining CTF concepts
- Show gas estimates for CTF operations
- Display transaction confirmations
Resources¶
Next Steps¶
- Generate and add contract ABIs
- Implement CTF1155 integration hooks
- Update market creation flow
- Add position splitting/merging UI
- Implement position redemption
- Test on testnet
- Deploy to production
Support¶
For questions or issues with CTF integration, refer to:
- Integration tests: test/ConditionalMarketFactory.CTF.test.js
- Contract documentation: contracts/CTF1155.sol
- Implementation summary: CTF_IMPLEMENTATION_SUMMARY.md