feat: gambling (coinflip & slots)

This commit is contained in:
Face 2025-05-29 14:12:27 +03:00
parent 543a5a951c
commit 1cae171748
38 changed files with 5631 additions and 1 deletions

View file

@ -0,0 +1,84 @@
import { auth } from '$lib/auth';
import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { randomBytes } from 'crypto';
import type { RequestHandler } from './$types';
interface CoinflipRequest {
side: 'heads' | 'tails';
amount: number;
}
export const POST: RequestHandler = async ({ request }) => {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session?.user) {
throw error(401, 'Not authenticated');
}
try {
const { side, amount }: CoinflipRequest = await request.json();
if (!['heads', 'tails'].includes(side)) {
return json({ error: 'Invalid side' }, { status: 400 });
}
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
return json({ error: 'Invalid bet amount' }, { status: 400 });
}
if (amount > 1000000) {
return json({ error: 'Bet amount too large' }, { status: 400 });
}
const userId = Number(session.user.id);
const result = await db.transaction(async (tx) => {
const [userData] = await tx
.select({ baseCurrencyBalance: user.baseCurrencyBalance })
.from(user)
.where(eq(user.id, userId))
.for('update')
.limit(1);
const currentBalance = Number(userData.baseCurrencyBalance);
if (amount > currentBalance) {
throw new Error(`Insufficient funds. You need *${amount.toFixed(2)} but only have *${currentBalance.toFixed(2)}`);
}
const gameResult: 'heads' | 'tails' = randomBytes(1)[0] < 128 ? 'heads' : 'tails';
const won = gameResult === side;
const multiplier = 2;
const payout = won ? amount * multiplier : 0;
const newBalance = currentBalance - amount + payout;
await tx
.update(user)
.set({
baseCurrencyBalance: newBalance.toFixed(8),
updatedAt: new Date()
})
.where(eq(user.id, userId));
return {
won,
result: gameResult,
newBalance,
payout,
amountWagered: amount
};
});
return json(result);
} catch (e) {
console.error('Coinflip API error:', e);
const errorMessage = e instanceof Error ? e.message : 'Internal server error';
return json({ error: errorMessage }, { status: 400 });
}
};

View file

@ -0,0 +1,105 @@
import { auth } from '$lib/auth';
import { error, json } from '@sveltejs/kit';
import { db } from '$lib/server/db';
import { user } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
import { randomBytes } from 'crypto';
import type { RequestHandler } from './$types';
interface SlotsRequest {
amount: number;
}
function getRandomSymbol(symbols: string[]): string {
const randomValue = randomBytes(1)[0];
const index = Math.floor((randomValue / 256) * symbols.length);
return symbols[index];
}
export const POST: RequestHandler = async ({ request }) => {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session?.user) {
throw error(401, 'Not authenticated');
}
try {
const { amount }: SlotsRequest = await request.json();
if (!amount || amount <= 0 || !Number.isFinite(amount)) {
return json({ error: 'Invalid bet amount' }, { status: 400 });
}
if (amount > 1000000) {
return json({ error: 'Bet amount too large' }, { status: 400 });
}
const userId = Number(session.user.id);
const symbols = ['bliptext', 'bussin', 'griddycode', 'lyntr', 'subterfuge', 'twoblade', 'wattesigma', 'webx'];
const result = await db.transaction(async (tx) => {
const [userData] = await tx
.select({ baseCurrencyBalance: user.baseCurrencyBalance })
.from(user)
.where(eq(user.id, userId))
.for('update')
.limit(1);
const currentBalance = Number(userData.baseCurrencyBalance);
if (amount > currentBalance) {
throw new Error(`Insufficient funds. You need *${amount.toFixed(2)} but only have *${currentBalance.toFixed(2)}`);
}
// Generate random symbols
const gameResult = [
getRandomSymbol(symbols),
getRandomSymbol(symbols),
getRandomSymbol(symbols)
];
let multiplier = 0;
let winType = '';
if (gameResult[0] === gameResult[1] && gameResult[1] === gameResult[2]) {
multiplier = 5;
winType = '3 OF A KIND';
}
else if (gameResult[0] === gameResult[1] || gameResult[1] === gameResult[2] || gameResult[0] === gameResult[2]) {
multiplier = 2;
winType = '2 OF A KIND';
}
const won = multiplier > 0;
const payout = won ? amount * multiplier : 0;
const newBalance = currentBalance - amount + payout;
await tx
.update(user)
.set({
baseCurrencyBalance: newBalance.toFixed(8),
updatedAt: new Date()
})
.where(eq(user.id, userId));
return {
won,
symbols: gameResult,
newBalance,
payout,
amountWagered: amount,
winType: won ? winType : undefined
};
});
return json(result);
} catch (e) {
console.error('Slots API error:', e);
const errorMessage = e instanceof Error ? e.message : 'Internal server error';
return json({ error: errorMessage }, { status: 400 });
}
};