Bifrost Phase 5 — Handoff Document
PR
- PR #148: https://github.com/ForSyt-io/Hannibal/pull/148
- Branch:
fix/bifrost-phase5(fromdev) - Worktree:
.claude/worktrees/bifrost-phase5/ - Commits: 2 (initial fixes + review/simplification pass)
What Was Done
7 Issues Resolved (6 code fixes + 1 no-code-change)
| # | Priority | Issue | Status |
|---|---|---|---|
| 1 | CRITICAL | settlementVersion INT4 overflow — Bifrost sends versions >2.1B, all settlements failed | Fixed: Int→BigInt + migration |
| 2 | CRITICAL | Batch order lay liability used stake*(odds-1) for ALL bets including Bifrost Indian odds | Fixed: calcLayLiability() helper, DRY across 3 call sites |
| 3 | CRITICAL | Settlement lay loss/amountDeducted used decimal formula for Bifrost orders | Fixed: uses order.bookmaker === 'bifrost' check |
| 4 | HIGH | Lay payout display showed only stake (profit), not total return | Fixed: shows liability + stake |
| 5 | MEDIUM | Max button used uncapped market limit, ignored user balance | Fixed: Math.min(maxMarket, maxFromBalance) with lay liability math |
| 6 | MEDIUM | Betslip didn't auto-update odds when live price improved | Fixed: syncMarketData now updates odds when price is better for user |
| 7 | LOW | Green/red highlighting explanation | No code change — communicate to Bifrost team |
Review Pass Findings (Fixed)
| Finding | Fix |
|---|---|
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 throws | All BigInt log values use .toString() |
| Max button ignored lay liability → user gets "Insufficient balance" on submit | Calculates maxFromBalance = balance / divisor for lay bets |
Simplification Pass
| Change | Detail |
|---|---|
New frontend/src/utils/odds.ts | Shared calcLayLiability(stake, odds, isFancy) utility |
Settlement isBifrost + layLiabilityD | Computed once, reused for P&L and amountDeducted |
| BifrostBetConsumer BigInt storage | Simplified to BigInt(outcome.version ?? 0) |
| Removed 5 redundant comments | Comments that restated the code |
Files Modified
Backend
| File | Changes |
|---|---|
backend/prisma/schema.prisma | settlementVersion Int → BigInt |
backend/prisma/migrations/20260305120000_fix_settlement_version_bigint/migration.sql | ALTER COLUMN settlement_version SET DATA TYPE BIGINT |
backend/src/exchanges/adapters/bifrost/BifrostBetConsumer.ts | BigInt handling for version comparison/storage, .toString() in logger |
backend/src/services/orderService.ts | New calcLayLiability() helper, batch order Bifrost fix |
backend/src/services/settlement.ts | order.bookmaker === 'bifrost' check, bookmaker added to type + settleOrderFromBetsApi call |
Frontend
| File | Changes |
|---|---|
frontend/src/utils/odds.ts | NEW — shared calcLayLiability() |
frontend/src/components/betting/BetSlipFooter.tsx | Uses calcLayLiability(), lay payout = liability + stake |
frontend/src/store/betSlipStore.ts | Uses calcLayLiability(), syncMarketData price improvement |
frontend/src/components/betting/BetSlipCard.tsx | Max 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):
- Green/Red highlighting: Green = price increased since last update, Red = price decreased. For Fancy markets, this follows the same convention as Match Odds.
- 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
| Item | Priority | Detail |
|---|---|---|
| Price improvement overwrites user-typed odds | Medium | syncMarketData can silently overwrite manually entered odds. Needs oddsOverridden flag on BetSlipItem. Acceptable v1 since Bifrost users primarily click prices. |
| Sync bhdev after merge | Required | After merging to dev: cd bhdev && git merge origin/dev && git push |
Run /test-hannibal | Recommended | Run test suite after merge to verify no regressions across the full platform |
Reference Documents
| Document | Location |
|---|---|
| Fix plan (root cause analysis) | docs/bifrost/BIFROST_PHASE5_FIX_PLAN.md |
| This handoff | docs/bifrost/BIFROST_PHASE5_HANDOFF.md |
| Bifrost integration history | docs/bifrost/BIFROST_INTEGRATION_HISTORY.md |
| Bifrost troubleshooting | docs/bifrost/BIFROST_TROUBLESHOOTING.md |
| Bifrost testing plan | docs/bifrost/BIFROST_TESTING_PLAN.md |
Skills for Next Task
| Skill | Purpose |
|---|---|
/bhdev-server-inspect | Verify deployment on bhdev server |
/commit-push-pr-hannibal | If additional changes needed |
/test-hannibal | Run tests after merge |
/ops-hannibal | Deployment and incident response |
/bifrost-api-inspector | Deep Bifrost market/price/settlement debugging |
/review-hannibal | Code review before merge |
Key Architectural Decisions
-
order.bookmakeroverisBifrostBettableMarket(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 withisBookmaker=true).isBifrostBettableMarket()only checks marketId prefix, missing the bookmaker match odds case. -
Shared
calcLayLiability()on both backend (Decimal.js) and frontend (number) — Backend usescalcLayLiability(Decimal, Decimal, boolean): DecimalinorderService.tsfor financial precision. Frontend usescalcLayLiability(number, number, boolean): numberinutils/odds.tsfor display. Same formula, different types — intentionally not shared cross-stack. -
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.