Skip to main content

Bifrost Phase 5 — Handoff Document

PR


What Was Done

7 Issues Resolved (6 code fixes + 1 no-code-change)

#PriorityIssueStatus
1CRITICALsettlementVersion INT4 overflow — Bifrost sends versions >2.1B, all settlements failedFixed: Int→BigInt + migration
2CRITICALBatch order lay liability used stake*(odds-1) for ALL bets including Bifrost Indian oddsFixed: calcLayLiability() helper, DRY across 3 call sites
3CRITICALSettlement lay loss/amountDeducted used decimal formula for Bifrost ordersFixed: uses order.bookmaker === 'bifrost' check
4HIGHLay payout display showed only stake (profit), not total returnFixed: shows liability + stake
5MEDIUMMax button used uncapped market limit, ignored user balanceFixed: Math.min(maxMarket, maxFromBalance) with lay liability math
6MEDIUMBetslip didn't auto-update odds when live price improvedFixed: syncMarketData now updates odds when price is better for user
7LOWGreen/red highlighting explanationNo code change — communicate to Bifrost team

Review Pass Findings (Fixed)

FindingFix
Settlement used isBifrostBettableMarket(marketId) — misses bookmaker match odds (1.x + isBookmaker=true)Changed to order.bookmaker === 'bifrost'
BigInt values passed to structured logger → JSON.stringify throwsAll BigInt log values use .toString()
Max button ignored lay liability → user gets "Insufficient balance" on submitCalculates maxFromBalance = balance / divisor for lay bets

Simplification Pass

ChangeDetail
New frontend/src/utils/odds.tsShared calcLayLiability(stake, odds, isFancy) utility
Settlement isBifrost + layLiabilityDComputed once, reused for P&L and amountDeducted
BifrostBetConsumer BigInt storageSimplified to BigInt(outcome.version ?? 0)
Removed 5 redundant commentsComments that restated the code

Files Modified

Backend

FileChanges
backend/prisma/schema.prismasettlementVersion IntBigInt
backend/prisma/migrations/20260305120000_fix_settlement_version_bigint/migration.sqlALTER COLUMN settlement_version SET DATA TYPE BIGINT
backend/src/exchanges/adapters/bifrost/BifrostBetConsumer.tsBigInt handling for version comparison/storage, .toString() in logger
backend/src/services/orderService.tsNew calcLayLiability() helper, batch order Bifrost fix
backend/src/services/settlement.tsorder.bookmaker === 'bifrost' check, bookmaker added to type + settleOrderFromBetsApi call

Frontend

FileChanges
frontend/src/utils/odds.tsNEW — shared calcLayLiability()
frontend/src/components/betting/BetSlipFooter.tsxUses calcLayLiability(), lay payout = liability + stake
frontend/src/store/betSlipStore.tsUses calcLayLiability(), syncMarketData price improvement
frontend/src/components/betting/BetSlipCard.tsxMax button accounts for lay liability, capped to balance

Deployment Steps (for next task / ops)

1. Merge PR #148 to dev

gh pr merge 148 --merge

2. Deploy to bhdev

# SSH into bhdev server
ssh bhdev

# Pull latest dev
cd /root/bhdev && git pull origin dev

# Run Prisma migration (CRITICAL — must happen before backend starts)
cd backend && ./node_modules/.bin/prisma migrate deploy

# Restart services
docker compose restart backend frontend

3. Verify on bhdev

Use /bhdev-server-inspect skill to verify:

  • Backend is running, no startup errors
  • Settlement queue consuming messages without INT4 overflow
  • Place a NO/lay bet on a Fancy market with Indian odds — check balance deduction is correct (stake × odds / 100, NOT stake × (odds - 1))
  • Verify betslip shows correct potential payout for lay bets
  • Max button doesn't exceed user balance for lay bets

4. Communicate to Bifrost team (Michael)

Two items that need communication (no code change):

  1. Green/Red highlighting: Green = price increased since last update, Red = price decreased. For Fancy markets, this follows the same convention as Match Odds.
  2. Commission on Fancy markets: We do NOT apply margin on Fancy/Session markets (Indian odds pass through unmodified). However, we charge 5% platform commission on NET MARKET WINNINGS after settlement (Betfair commission model — per-market, only on net profit).

Use /slack:slack-messaging skill or direct Slack message to Michael.


Known Follow-up Items

ItemPriorityDetail
Price improvement overwrites user-typed oddsMediumsyncMarketData can silently overwrite manually entered odds. Needs oddsOverridden flag on BetSlipItem. Acceptable v1 since Bifrost users primarily click prices.
Sync bhdev after mergeRequiredAfter merging to dev: cd bhdev && git merge origin/dev && git push
Run /test-hannibalRecommendedRun test suite after merge to verify no regressions across the full platform

Reference Documents

DocumentLocation
Fix plan (root cause analysis)docs/bifrost/BIFROST_PHASE5_FIX_PLAN.md
This handoffdocs/bifrost/BIFROST_PHASE5_HANDOFF.md
Bifrost integration historydocs/bifrost/BIFROST_INTEGRATION_HISTORY.md
Bifrost troubleshootingdocs/bifrost/BIFROST_TROUBLESHOOTING.md
Bifrost testing plandocs/bifrost/BIFROST_TESTING_PLAN.md

Skills for Next Task

SkillPurpose
/bhdev-server-inspectVerify deployment on bhdev server
/commit-push-pr-hannibalIf additional changes needed
/test-hannibalRun tests after merge
/ops-hannibalDeployment and incident response
/bifrost-api-inspectorDeep Bifrost market/price/settlement debugging
/review-hannibalCode review before merge

Key Architectural Decisions

  1. order.bookmaker over isBifrostBettableMarket(marketId) in settlement — The bookmaker field is the single source of truth set at placement time. It covers both sportsbook markets (14.x/9.x) AND bookmaker match odds (1.x with isBookmaker=true). isBifrostBettableMarket() only checks marketId prefix, missing the bookmaker match odds case.

  2. Shared calcLayLiability() on both backend (Decimal.js) and frontend (number) — Backend uses calcLayLiability(Decimal, Decimal, boolean): Decimal in orderService.ts for financial precision. Frontend uses calcLayLiability(number, number, boolean): number in utils/odds.ts for display. Same formula, different types — intentionally not shared cross-stack.

  3. BigInt for settlementVersion — Bifrost version numbers are timestamps (e.g., 1772641624109). BigInt is the correct Prisma/PostgreSQL type. All comparisons use native BigInt operators (<=, >). Logger values must be .toString()-ed before structured logging.