import { PublicKey } from "@solana/web3.js"; import { getMint } from "@solana/spl-token"; import { Program } from "@anchor-lang/core"; import { loadConfig, loadKeypair, SIDE_UP } from "./config"; import { getPrice } from "./priceSource"; import { nextBetRequest } from "./frontStub"; import { initialize, createBet, closeBet, readGlobal, ensureVaultFunded, ensureBettorTokenAccount, fundBettor, } from "./ops"; import { betPda } from "./pda"; import { makeConnection, makeProgram } from "./program"; import type { SmartUpdown } from "../idl/smart_updown"; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const STATUS_OPEN = 0; /** Человекочитаемо-формат: токен-юниты -> "N.NNNNNN" по decimals. */ function humanize(units: string | number | bigint, decimals: number): string { const n = BigInt(units); const sign = n < 0n ? "-" : ""; const abs = n < 0n ? -n : n; const pow = 10n ** BigInt(decimals); return `${sign}${abs / pow}.${(abs % pow).toString().padStart(decimals, "0")}`; } /** Закрывает ставку, повторяя при «equal price» (exit==entry) пока не зафиналится. */ async function settleBet( program: Program, id: any, entryPrice: number, relayer: any, bettor: PublicKey, mint: PublicKey ): Promise { for (let attempt = 1; ; attempt++) { const exitPrice = await getPrice(); if (exitPrice === entryPrice) { console.log(`attempt ${attempt}: exit==entry (${exitPrice}), жду движение...`); await sleep(2000); continue; } const sig = await closeBet(program, id, exitPrice, relayer, bettor, mint); console.log(`close_bet #${id} exit=${exitPrice} (attempt ${attempt}) tx=${sig}`); const status = await getStatus(program, id); if (status === STATUS_OPEN) { console.log(`ставка ещё open (equal price), повторяю close...`); await sleep(2000); continue; } return; } } async function getStatus(program: Program, id: any): Promise { const bet = await program.account.bet.fetchNullable(betPda(program.programId, id)); return bet ? bet.status.toNumber() : -1; } async function main(): Promise { const cfg = loadConfig(); const admin = loadKeypair(cfg.adminKeypairPath); const connection = makeConnection(cfg.rpcUrl); const program = makeProgram(connection, admin); console.log(`RPC: ${cfg.rpcUrl}`); console.log(`Admin/relayer: ${admin.publicKey.toBase58()}`); console.log(`Token mint: ${cfg.tokenMint.toBase58()}`); // Decimals фантиков (см. state.rs = 6). Только для человекочитаемых логов. let decimals = 6; try { decimals = (await getMint(connection, cfg.tokenMint)).decimals; } catch { // mint ещё не создан (плейсхолдер) — дефолт из ТЗ контракта } const global = await readGlobal(program, program.programId, cfg.tokenMint); if (!global) { if (!process.argv.includes("--init")) { throw new Error( "Контракт не инициализирован. Запусти с флагом --init (или init+run)." ); } await initialize(program, { minAmount: cfg.minAmount, maxAmount: cfg.maxAmount, multiplierBps: cfg.multiplierBps, expirySeconds: cfg.expirySeconds, }, admin); console.log("Контракт инициализирован."); } else { console.log( `Global: counter=${global.counter} vault=${humanize(global.vaultBalance, decimals)} fant ` + `expiry=${global.expirySeconds}s paused=${global.paused}` ); } if (cfg.allowAirdrop) { await ensureVaultFunded(connection, program, admin, cfg); } const loops = process.argv.includes("--once") ? 1 : Infinity; let n = 0; while (n < loops) { n++; const req = nextBetRequest(cfg.minAmount, cfg.maxAmount); await fundBettor(connection, req.bettor.publicKey); // Беттору нужен токен-аккаунт (ATA) — без него create_bet не пройдёт. // На localnet (allowAirdrop) после создания ATA наливает фантики (аналог SOL-airdrop). await ensureBettorTokenAccount( connection, admin, cfg.tokenMint, req.bettor.publicKey, cfg.allowAirdrop ? BigInt(cfg.startBalance) : undefined ); const entryPrice = await getPrice(); await createBet(program, cfg.tokenMint, req.bettor, { side: req.side, amount: req.amount, entryPrice, }); console.log( `ставка ${req.side === SIDE_UP ? "UP" : "DOWN"} на ${humanize(req.amount, decimals)} fant, жду ${cfg.expirySeconds}s...` ); await sleep((cfg.expirySeconds + 1) * 1000); const g0 = await readGlobal(program, program.programId, cfg.tokenMint); const id = g0!.counter.subn(1); await settleBet(program, id, entryPrice, admin, req.bettor.publicKey, cfg.tokenMint); const outcome = await program.account.bet.fetchNullable(betPda(program.programId, id)); console.log( outcome!.status.toNumber() === 1 ? `>> результат #${id}: ПОЛЬЗОВАТЕЛЬ ВЫИГРАЛ (payout 1.3x)` : `>> результат #${id}: ВЫИГРАЛ ДОМ (user lost)` ); } console.log("Готово."); } main().catch((e) => { console.error(e); process.exit(1); });