Task 1: Create Market Implementation Design
Objective
Enable the "Create Market" page to functional correctly by implementing the backend endpoint to create markets on-chain and store them in the database.
Current State
- Frontend:
web/src/app/create-market/page.tsxexists but points to a non-existent endpoint/api/markets/grouped. It also displays instructions to run a manual script. - Backend:
api/src/routes/markets.tslacks thePOST /api/markets/groupedendpoint. - Contracts:
MarketFactorycontract exists and hascreateBinaryMarketfunction. - Script:
scripts/interact/world-series-mvp/01-create-market.tsexists for manual creation.
Important Note: Multi-Choice Market Limitation
MarketFactory.createMultipleChoiceMarket does not register tokens with the CTFExchange, making them untradable on the current exchange implementation.
- Impact: We cannot create true Multi-Choice markets (A vs B vs C) that are tradable.
- Solution: We use Grouped Binary Markets (Polymarket style).
- "Who will win?" -> [Will A win? (YES/NO)], [Will B win? (YES/NO)].
- Each option is a separate Binary Market.
- This is fully supported by the current contracts and exchange.
Liquidity Provisioning
2. Frontend Implementation (web/src/app/create-market/page.tsx)
- UI Components:
- Market Details Form (Question, Description, End Date).
- Liquidity Input: Field for "Initial Liquidity" (e.g., 100 USDC).
- Outcome Configuration: For Multi-choice, allow adding outcomes.
- Interaction Flow:
- Approve USDC: User approves
ConditionalTokensto spend Liquidity Amount. - Create Market: Call
MarketFactory.createMarket(). - Mint Tokens: Call
ConditionalTokens.splitPosition()to mint outcome tokens for the Creator. - Sign Orders: User signs EIP-712 orders to sell the minted tokens (seeding the book).
- Submit to API: POST
/api/markets/groupedwith market details and signed orders.
- Approve USDC: User approves
Proposed Solution
1. Backend Implementation (api)
New Endpoint: POST /api/markets/grouped
Request Body:
{
"groupQuestion": "Who will win...?",
"category": "Sports",
"players": [
{ "name": "Player A", "description": "...", "imageUrl": "https://..." },
{ "name": "Player B", "description": "...", "imageUrl": "" }
],
"resolutionTime": 1735689600, // Unix timestamp
"endTime": 1735603200, // Unix timestamp
"marketType": "grouped-binary"
}
Logic:
- Validation: Check inputs.
- Blockchain Interaction:
- Load Admin Private Key from environment variables (
ADMIN_PRIVATE_KEY). - Initialize
BlockchainServicewith signer. - For each player:
- Generate
questionId(usingethers.id+ timestamp/salt). - Call
marketFactory.createBinaryMarket(...). - Wait for transaction confirmation.
- Parse
MarketCreatedevent to getconditionIdandtokenIds.
- Generate
- Retry Logic:
- Wrap the blockchain call in a retry loop.
MAX_RETRIES = 3.RETRY_DELAY = 2000ms.- If a transaction fails (e.g., network timeout, RPC error), wait and retry.
- If it fails after 3 attempts, mark this specific market creation as
FAILEDin the response/DB but continue processing other players if possible (or abort depending on strictness). Recommendation: Abort and return partial success details.
- Load Admin Private Key from environment variables (
- Database Storage:
- Create
MarketGrouprecord. - For each player, create
Marketrecord with the returnedconditionIdand the providedimageUrl. - Create
Outcomerecords (YES/NO) with returnedtokenIds.
- Create
- Response: Return the created market group details.
File Changes:
api/src/routes/markets.ts: AddPOST /groupedroute.api/src/services/MarketService.ts: AddcreateGroupedMarketmethod (refactoring logic out of routes).api/.env: EnsureADMIN_PRIVATE_KEYis present.
2. Frontend Implementation (web)
Updates:
- Remove the "Run the deployment script" instruction card.
- Update the success message to indicate markets are live on-chain.
- Ensure the form sends the correct data structure.
- Image Support:
- Add an optional "Image URL" input field for each player.
- Fallback UI: If
imageUrlis empty or fails to load, display a placeholder avatar using the first letter of the player's name (e.g., "S" for Shohei, "W" for Who will win).
- Navigation:
- On success, redirect the user to the Market Detail Page of the newly created market group (e.g.,
/market/[marketGroupId]).
- On success, redirect the user to the Market Detail Page of the newly created market group (e.g.,
- My Markets Tab:
- Update
web/src/app/my-positions/page.tsxto add a new tab: "Markets". - Display a list of markets created by the current user.
- Columns: Title, Volume, Status, Created At, Actions.
- Actions:
- Update Button: Enabled only before the market starts (e.g., before
startTimeor first trade). - Opens a modal to edit metadata (Description, Image URL). Note: On-chain data (Question, Outcomes) cannot be changed.
- Update Button: Enabled only before the market starts (e.g., before
- Update
File Changes:
web/src/app/create-market/page.tsx: Update UI text, error handling, and add Image URL input.web/src/app/my-positions/page.tsx: Add "Markets" tab with Update functionality.api/src/routes/users.ts: AddGET /:address/marketsendpoint to fetch created markets.api/src/routes/markets.ts: AddPUT /grouped/:id(or/markets/:id) to update metadata.
Database Schema
- Update Required: Add
imageUrlcolumn toMarkettable.
model Market {
// ... existing fields
imageUrl String? @map("image_url") // URL for market/player image
}
model Outcome {
// ... existing fields
imageUrl String? @map("image_url") // URL for outcome image (for future multi-choice support)
}
- Run
npx prisma migrate devto apply changes.
Considerations
- Gas Fees: The Admin wallet will pay for gas. Ensure it is funded.
- Latency: Creating multiple markets on-chain sequentially might take time (block times). The UI should show a loading state (e.g., "Creating market 1 of 3...").
- Error Handling: If one market fails, we should handle partial creation or retry.
Step-by-Step Plan
- Backend: Implement
MarketService.createGroupedMarket. - Backend: Add route
POST /api/markets/grouped. - Frontend: Update
create-market/page.tsx. - Test: Create a market via UI and verify it appears in DB and on-chain (via logs).
Image Upload Implementation
We use Multer, a node.js middleware for handling multipart/form-data, which is primarily used for uploading files.
Implementation Details
Backend (
api):- Middleware:
multeris configured to store uploaded files in the localapi/uploadsdirectory. - Route:
POST /api/uploadaccepts a single file, saves it with a unique name (timestamp + random), and returns the relative URL (e.g.,/uploads/12345.png). - Static Serving:
express.staticserves theuploadsdirectory at the/uploadspath. - Market Creation: The
POST /api/markets/groupedendpoint was updated to accept animageUrlfield and store it in theMarketGrouprecord.
- Middleware:
Frontend (
web):- UI: Added a file input field for "Market Image".
- Logic: When a file is selected, it is immediately uploaded to
/api/upload. The returned URL is stored in state and then sent as part of the market creation payload.