Add Democratic fundraising platform.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
97
src/app/api/games/slots/route.ts
Normal file
97
src/app/api/games/slots/route.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { generateServerSeed, deriveInt } from "@/lib/provably-fair";
|
||||
import { creditWalletCredits, debitWalletCredits, INSUFFICIENT_CREDITS } from "@/lib/wallet-safety";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const SYMBOLS = ["🍒", "🍋", "🍊", "🍇", "💎", "7️⃣"];
|
||||
const SYMBOL_WEIGHTS = [30, 25, 20, 15, 7, 3]; // out of 100
|
||||
|
||||
// Paytable: 3 of a kind multipliers
|
||||
const PAYTABLE: Record<string, number> = {
|
||||
"🍒": 2,
|
||||
"🍋": 3,
|
||||
"🍊": 5,
|
||||
"🍇": 10,
|
||||
"💎": 25,
|
||||
"7️⃣": 50,
|
||||
};
|
||||
|
||||
function weightedSymbol(roll: number): string {
|
||||
let cumulative = 0;
|
||||
for (let i = 0; i < SYMBOLS.length; i++) {
|
||||
cumulative += SYMBOL_WEIGHTS[i];
|
||||
if (roll < cumulative) return SYMBOLS[i];
|
||||
}
|
||||
return SYMBOLS[SYMBOLS.length - 1];
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
const body = await req.json();
|
||||
const { wageBLW, clientSeed } = body as { wageBLW: number; clientSeed?: string };
|
||||
|
||||
if (!Number.isInteger(wageBLW) || wageBLW < 1) return NextResponse.json({ error: "Invalid wager" }, { status: 400 });
|
||||
|
||||
const serverSeed = generateServerSeed();
|
||||
const cs = clientSeed ?? "default";
|
||||
|
||||
const reels = [
|
||||
weightedSymbol(deriveInt(serverSeed, cs, 0, 0, 99)),
|
||||
weightedSymbol(deriveInt(serverSeed, cs, 1, 0, 99)),
|
||||
weightedSymbol(deriveInt(serverSeed, cs, 2, 0, 99)),
|
||||
];
|
||||
|
||||
let multiplier = 0;
|
||||
let outcome = "loss";
|
||||
|
||||
if (reels[0] === reels[1] && reels[1] === reels[2]) {
|
||||
multiplier = PAYTABLE[reels[0]] ?? 2;
|
||||
outcome = "win";
|
||||
} else if (reels[0] === reels[1] || reels[1] === reels[2] || reels[0] === reels[2]) {
|
||||
multiplier = 1.5;
|
||||
outcome = "win";
|
||||
}
|
||||
|
||||
const payout = outcome === "win" ? Math.floor(wageBLW * multiplier) : 0;
|
||||
|
||||
try {
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await debitWalletCredits(tx, session.user.id, wageBLW);
|
||||
await tx.ledgerEntry.create({
|
||||
data: { userId: session.user.id, delta: -wageBLW, type: "DEBIT_GAME_BET", memo: "SLOTS bet" },
|
||||
});
|
||||
if (payout > 0) {
|
||||
await creditWalletCredits(tx, session.user.id, payout);
|
||||
await tx.ledgerEntry.create({
|
||||
data: { userId: session.user.id, delta: payout, type: "CREDIT_GAME_WIN", memo: "SLOTS payout" },
|
||||
});
|
||||
}
|
||||
await tx.gameSession.create({
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
gameType: "SLOTS",
|
||||
wageredBLW: wageBLW,
|
||||
payoutBLW: payout,
|
||||
multiplier,
|
||||
outcome,
|
||||
serverSeed,
|
||||
clientSeed: cs,
|
||||
resultData: { reels, multiplier },
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message === INSUFFICIENT_CREDITS) {
|
||||
return NextResponse.json({ error: "Insufficient balance" }, { status: 402 });
|
||||
}
|
||||
console.error(e);
|
||||
return NextResponse.json({ error: "Could not settle slots game" }, { status: 500 });
|
||||
}
|
||||
|
||||
return NextResponse.json({ reels, multiplier, payout, outcome, serverSeed });
|
||||
}
|
||||
Reference in New Issue
Block a user