Bifrost Phase 5 — Fix Resolution Plan
Summary
6 issues reported by Bifrost team (Michael) + 1 critical blocker found during investigation. All issues traced to root causes with specific file:line references.
Worktree
- Branch:
fix/bifrost-phase5fromdev(ee856d0b) - Path:
.claude/worktrees/bifrost-phase5/
Fix 1: Settlement Version INT4 Overflow (CRITICAL BLOCKER)
Problem: settlementVersion is Int (INT4, max ~2.1B) but Bifrost sends version numbers like 1772641624109. ALL settlement processing fails with "Unable to fit integer value into INT4".
Evidence: Server logs show repeated errors:
FINANCIAL_INTEGRITY: error processing forsyt.bets.outcomes.queue message (attempt 1/3)
Error: Unable to fit integer value '1772641624109' into an INT4
Fix:
- File:
backend/prisma/schema.prisma:182 - Change:
settlementVersion Int @default(0)→settlementVersion BigInt @default(0) - Run migration:
prisma migrate dev --name fix-settlement-version-bigint - File:
backend/src/exchanges/adapters/bifrost/BifrostBetConsumer.ts:424 - Update
outcome.version ?? 0usages to handle BigInt properly
Cross-system impact: Schema change requires migration on bhdev DB.
Fix 2: Batch Order Lay Liability — Wrong Formula for Bifrost (CRITICAL)
Problem: placeBatchOrders() uses stake × (odds - 1) for ALL lay bets without checking if it's a Bifrost market. For Indian odds 100, this calculates 9900 instead of 100.
Evidence: Error screenshots: "Insufficient balance for batch: need 9900.00, have 3995.00" and "need 19800.00, have 3995.00"
Root cause: orderService.ts:966-969
// CURRENT (BROKEN):
totalRequired = totalRequired.plus(
new Decimal(o.stake).times(new Decimal(o.odds).minus(1))
);
// FIX:
const isBifrost = isBifrostBettableMarket(String(o.marketId)) || o.isBookmaker === true;
totalRequired = totalRequired.plus(
isBifrost
? new Decimal(o.stake).times(new Decimal(o.odds)).div(100)
: new Decimal(o.stake).times(new Decimal(o.odds).minus(1))
);
Fix 3: Settlement Lay Liability — Wrong Formula for Bifrost (CRITICAL)
Problem: settleOrder() uses decimal formula for lay bet loss/amountDeducted without checking Bifrost.
Root cause: settlement.ts:465,476
// Line 465 - lay loss calculation:
profitLossD = stakeD.times(oddsD.minus(1)).negated(); // WRONG for Bifrost
// Line 476 - amount deducted at placement:
const amountDeducted = order.betType === 'lay' ? stake * (odds - 1) : stake; // WRONG for Bifrost
Fix: Check if order.bookmaker === 'bifrost' and use Indian formula:
- Lay loss:
stakeD.times(oddsD).div(100).negated() - Amount deducted:
stake * odds / 100
Fix 4: Frontend NO/Lay Potential Payout Display
Problem: Lay bet shows Potential Payout = stake (just profit), while back shows total return. Inconsistent.
Root cause: BetSlipFooter.tsx:39-47
// CURRENT (lay branch):
return sum + Math.round(item.stake * 100); // Only profit
// FIX (lay branch):
const liability = item.isFancy
? item.stake * item.odds / 100
: item.stake * (item.odds - 1);
return sum + Math.round((liability + item.stake) * 100); // liability refund + profit
Also fix betSlipStore.ts:269-282 getTotalPotentialReturn for consistency.
Fix 5: Max Button Exceeds User Balance (MINOR)
Problem: Max button sets stake to item.maxMarket (total market limit) without capping to user's balance.
Root cause: BetSlipCard.tsx:75-78
Fix: Cap maxMarket to user's available balance:
const handleMaxStake = useCallback(() => {
const maxAvailable = Math.min(
item.maxMarket ?? Infinity,
userBalance ?? Infinity
);
if (maxAvailable > 0 && maxAvailable < Infinity) {
updateStake(item.id, Math.floor(maxAvailable));
}
}, [item.id, item.maxMarket, userBalance, updateStake]);
Fix 6: Better Price Not Passed to Customer (MEDIUM)
Problem: WebSocket odds updates refresh maxStake, priceIndex, line in betslip but NOT the actual odds/price.
Root cause: betSlipStore.ts:230+ — syncBetSlipWithLiveData skips price updates.
Fix: When live price improves (better for the user), auto-update the betslip:
- Back bet: if new price > current price → update (user gets better odds)
- Lay bet: if new price < current price → update (user gets better odds)
- If price worsens → keep stale price (user already locked in, must re-click to accept worse)
Fix 7: Green/Red Highlighting Explanation (NO CODE CHANGE)
Current behavior: Green = price increased since last update, Red = price decreased. For Match Odds: green = drifting (better for back), red = shortening (worse for back). For Fancy: the same logic applies but may appear counterintuitive since Indian odds direction differs.
Action: Communicate to Bifrost team what the colors mean. No code change needed unless they request different behavior.
Commission Clarification (Issue 1 — NO CODE CHANGE)
Finding: We do NOT apply margin on Fancy/Session markets (skipMargin = market.oddsType === 'INDIAN'). Bifrost's prices pass through unmodified.
However: We charge platform commission (currently 5%) on NET MARKET WINNINGS after settlement. This is the Betfair commission model — charged per-market when all orders in a market are settled, only on net profit. This is separate from the margin Bifrost builds into their prices.
Action: Communicate this clearly to Bifrost team.
Implementation Order
- Fix 1 (settlementVersion BigInt) — unblocks ALL settlements
- Fix 2 (batch liability) — unblocks NO/lay bet placement
- Fix 3 (settlement liability) — ensures correct lay settlement
- Fix 4 (payout display) — fixes UI inconsistency
- Fix 5 (max button) — minor UX fix
- Fix 6 (better price) — medium complexity, lower priority
Testing
- Typecheck:
cd backend && npx tsc --noEmit+cd frontend && npx tsc --noEmit - DB migration on bhdev after deploy
- Manual test: place YES and NO bets on Fancy market, verify payout display and balance deduction
- Verify settlement processes without INT4 overflow