Skip to content

Implement prediction market event pipelines for slices 5-8 (Merge, Resolution, Redemption, Cancellation) #13392

@Rotorsoft

Description

@Rotorsoft

Description

Implement the remaining four core domain slices (5-8) for the Futarchy prediction market integration, completing the on-chain event processing pipeline and market lifecycle commands. This continues the pattern established in slices 1-4 (PRs #13369, #13372, #13388, #13390).

  • Slice 5 — Merge Tokens: Process TokensMerged EVM events to record merge trades, update positions, and decrement total_collateral
  • Slice 6 — Market Resolution: Process ProposalResolved and MarketResolved EVM events to transition market status to resolved with winner; add ResolvePredictionMarket command for manual resolution
  • Slice 7 — Token Redemption: Process TokensRedeemed EVM events to record redemption trades and update positions
  • Slice 8 — Market Cancellation: Add CancelPredictionMarket command with auth checks (thread author or admin), validate status transitions (draft→cancelled, active→cancelled)

Technical Context

Type: feature
Category: api | workers | database
Priority: high
Risk: low

Architecture Notes

  • Follow the exact same patterns from slices 3-4 (PRs Implement prediction market Slice 3: Mint Tokens projection and EVM event pipeline #13388, Implement prediction market SwapExecuted event pipeline (Slice 4) #13390): EVM event → mapper function in chain-event-utils.ts → event signature + registry in evm-protocolsPredictionMarketProjection handler → direct DB mutation
  • Projections, not policies: All EVM event handlers go in PredictionMarket.projection.ts with direct Sequelize mutations — no command() indirection
  • Commands for user-initiated actions only: ResolvePredictionMarket (Slice 6) and CancelPredictionMarket (Slice 8) are commands because they are initiated by users, not EVM events
  • All multi-record mutations must be wrapped in a single Sequelize transaction for consistency
  • Idempotency enforced via composite PK on PredictionMarketTrade (eth_chain_id, transaction_hash)
  • Event schemas for all slice 5-8 events already exist in libs/schemas/src/events/events.schemas.ts
  • Feature-flag gated behind FLAG_FUTARCHY (already configured in slice 4)
  • Use DECIMAL(78,0) with Sequelize.literal() for BigInt increment/decrement operations to preserve precision

Related Systems

  • libs/evm-protocols/src/event-registry/ — event signatures and contract source registry
  • libs/model/src/services/evmChainEvents/chain-event-utils.ts — EVM log → domain event mappers
  • libs/model/src/aggregates/prediction_market/PredictionMarket.projection.ts — projection handlers
  • libs/model/src/aggregates/prediction_market/ — aggregate commands and queries
  • libs/model/src/policies/PredictionMarket.policy.ts — existing policy (do not modify for projections)
  • packages/commonwealth/server/api/predictionMarket.ts — tRPC router (needs new endpoints)

Implementation Steps

Slice 5: Merge Tokens (TokensMerged event pipeline)

  • Register TokensMerged event signature hash in eventSignatures.ts (BinaryVault contract)
  • Add TokensMerged to the BinaryVault contract source in eventRegistry.ts
  • Implement predictionMarketTokensMergedMapper in chain-event-utils.ts to decode raw EVM log → PredictionMarketTokensMerged event payload (fields: marketId, user, amount)
  • Add PredictionMarketTokensMerged handler to PredictionMarket.projection.ts:
    • INSERT PredictionMarketTrade with action: 'merge', p_amount, f_amount, collateral_returned
    • UPSERT PredictionMarketPosition (decrement both p_token_balance and f_token_balance by amount)
    • UPDATE PredictionMarket.total_collateral (decrement by collateral returned)
    • All in single Sequelize transaction
  • Write tests in prediction-market-merge.spec.ts following prediction-market-mint.spec.ts pattern:
    • Mapper decodes raw log correctly
    • Projection creates trade record
    • Position balances decremented for both tokens
    • total_collateral decremented on market
    • Idempotency: duplicate tx_hash does not create duplicate records
    • Multi-user merge scenario

Slice 6: Market Resolution (ProposalResolved + MarketResolved + ResolvePredictionMarket command)

  • Register ProposalResolved event signature hash in eventSignatures.ts (FutarchyGovernor contract)
  • Register MarketResolved event signature hash in eventSignatures.ts (BinaryVault contract)
  • Add both events to their respective contract sources in eventRegistry.ts
  • Implement predictionMarketProposalResolvedMapper in chain-event-utils.ts (fields: proposalId, marketId, winner)
  • Implement predictionMarketMarketResolvedMapper in chain-event-utils.ts (fields: marketId, winner)
  • Add PredictionMarketProposalResolved handler to projection:
    • UPDATE PredictionMarket set status='resolved', winner (1=PASS, 2=FAIL), resolved_at=now()
    • Idempotent: skip if already resolved
  • Add PredictionMarketMarketResolved handler to projection (same logic, redundant confirmation from BinaryVault — idempotent)
  • Create ResolvePredictionMarket.command.ts in aggregate directory:
    • Auth: thread author or community admin only
    • Validates market is in active status
    • Sets status='resolved', winner, resolved_at
    • Emits PredictionMarketResolved event
  • Add resolve endpoint to tRPC router in predictionMarket.ts
  • Write tests in prediction-market-resolution.spec.ts:
    • ProposalResolved mapper decodes correctly
    • MarketResolved mapper decodes correctly
    • Projection sets status=resolved, winner=1 (PASS) and winner=2 (FAIL)
    • Projection sets resolved_at timestamp
    • Idempotent: already-resolved market not modified
    • ResolvePredictionMarket command: success for author, success for admin, rejected for non-author
    • Invalid state transitions: draft→resolved rejected, already resolved rejected

Slice 7: Token Redemption (TokensRedeemed event pipeline)

  • Register TokensRedeemed event signature hash in eventSignatures.ts (BinaryVault contract)
  • Add TokensRedeemed to the BinaryVault contract source in eventRegistry.ts
  • Implement predictionMarketTokensRedeemedMapper in chain-event-utils.ts (fields: marketId, user, amount, outcome)
  • Add PredictionMarketTokensRedeemed handler to projection:
    • INSERT PredictionMarketTrade with action: 'redeem', winning_token_amount, collateral_returned
    • UPSERT PredictionMarketPosition (decrement winning token balance based on outcome)
    • All in single Sequelize transaction
  • Write tests in prediction-market-redeem.spec.ts:
    • Mapper decodes raw log correctly (including outcome field)
    • Projection creates trade record
    • Position winning token balance decremented
    • Idempotency: duplicate tx_hash handled
    • Redemption after PASS wins vs FAIL wins scenarios

Slice 8: Market Cancellation (CancelPredictionMarket command)

  • Create CancelPredictionMarket.command.ts in aggregate directory:
    • Auth: thread author or community admin only
    • Validates market is in draft or active status (not resolved or already cancelled)
    • Sets status='cancelled'
    • Emits PredictionMarketCancelled event
  • Add cancel endpoint to tRPC router in predictionMarket.ts
  • Export new commands and queries from aggregate index.ts
  • Write tests in prediction-market-cancel.spec.ts:
    • Cancel from draft status succeeds
    • Cancel from active status succeeds
    • Cancel from resolved status rejected (invalid state transition)
    • Auth: non-author non-admin rejected
    • Auth: admin can cancel

Cross-cutting

  • Add remaining query endpoints if not yet present: GetPredictionMarketTrades, GetPredictionMarketPositions
  • Verify pnpm -r check-types passes
  • Verify pnpm lint-branch-warnings passes
  • Verify all new and existing prediction market tests pass

Testing Requirements

  • Unit tests: One test file per slice following existing prediction-market-mint.spec.ts and prediction-market-swap.spec.ts patterns. Each file should test: mapper decoding, projection handler DB mutations, position balance arithmetic, idempotency, and multi-user scenarios. Command tests should cover auth checks and state machine transitions.
  • Integration tests: Extend prediction-market-lifecycle.spec.ts to cover full lifecycle through resolution and redemption
  • Manual verification: pnpm -r check-types and pnpm lint-branch-warnings clean

Acceptance Criteria

  • PredictionMarketProjection handles all 8 domain events (4 existing from slices 1-4 + 4 new: TokensMerged, ProposalResolved, MarketResolved, TokensRedeemed)
  • No command() calls inside the projection — all handlers use direct DB mutations
  • All mapper functions decode raw EVM logs using ABIs → typed event payloads
  • ResolvePredictionMarket command enforces auth (thread author or admin) and validates active status
  • CancelPredictionMarket command enforces auth and validates draft or active status
  • Both commands and new query endpoints are exposed via tRPC router behind FLAG_FUTARCHY
  • All DB mutations within each projection handler wrapped in a single Sequelize transaction
  • Idempotent event processing: duplicate EVM events (same tx_hash) do not create duplicate records
  • Position balances correctly updated: merge decrements both tokens, redeem decrements winning token only
  • total_collateral decremented on merge operations
  • Resolution sets status='resolved', winner (1 or 2), and resolved_at timestamp
  • pnpm -r check-types passes
  • pnpm lint-branch-warnings passes
  • All prediction market tests pass (new + existing)


Created with Claude by gent

Metadata

Metadata

Assignees

Labels

ai-completedAI finished, needs human reviewai-readyReady for AI implementationarea:apiAPI/backend changespriority:highHigh priorityrisk:lowLow risk - safe for AItype:featureNew feature (creates feature/* branch)

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions