/** * ЖИВАЯ проверка: закрыто ли окно выбора времени закрытия. * Запускается ВНУТРИ пода шлюза (обращается к 127.0.0.1:8895 и читает ставку из RPC). * * Логика доказательства: * - ставим ставку, запоминаем betTime/expireTime (сырые байты аккаунта Bet) * - ЖДЁМ существенно дольше экспирации * - считаем: price_at_expiry (история, ts <= expireTime) и price_now (текущая) * - закрываем ставку и смотрим, какая цена ушла в транзакцию * * ОКНО ЗАКРЫТО, если exitPrice == price_at_expiry и при этом price_now != price_at_expiry * (т.е. цена взята из прошлого, а не с момента клика). */ const { Connection, PublicKey } = require("@solana/web3.js"); const { DatabaseSync } = require("node:sqlite"); const GW = "http://127.0.0.1:8895"; const RPC = "http://192.168.76.181:8899"; const PROGRAM_ID = new PublicKey("9ALsnxXNzDBv3mokngCHbruRTWUZiWR7pf7vswS1fCpf"); const DB = "/app/data/price-history.db"; const WAIT_MS = Number(process.argv[2] || 75) * 1000; const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function api(path, body) { const opts = body === undefined ? { method: "GET" } : { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }; const r = await fetch(GW + path, opts); let j = null; try { j = await r.json(); } catch { /* ignore */ } return { status: r.status, json: j }; } function priceAtExpiry(expireMs) { const db = new DatabaseSync(DB); const row = db.prepare("SELECT price FROM price_history WHERE ts <= ? ORDER BY ts DESC LIMIT 1").get(expireMs); const cnt = db.prepare("SELECT COUNT(*) c FROM price_history").get().c; const first = db.prepare("SELECT ts FROM price_history ORDER BY ts ASC LIMIT 1").get(); db.close(); return { price: row ? row.price : null, count: cnt, firstTs: first ? first.ts : null }; } async function readBet(id) { const conn = new Connection(RPC, "confirmed"); const seed = Buffer.alloc(8); seed.writeBigUInt64LE(BigInt(id)); const [pda] = PublicKey.findProgramAddressSync([Buffer.from("bet"), seed], PROGRAM_ID); const info = await conn.getAccountInfo(pda, "confirmed"); if (!info) return null; const d = info.data; return { pda: pda.toBase58(), betTime: Number(d.readBigInt64LE(16)), expireTime: Number(d.readBigInt64LE(24)), entryPrice: d.readBigUInt64LE(32).toString(), exitPrice: d.readBigUInt64LE(40).toString(), status: d.readBigUInt64LE(56).toString(), }; } (async () => { console.log("=== 1. кошелёк ==="); const w = await api("/wallet", {}); if (!w.json || !w.json.address) throw new Error("wallet failed: " + JSON.stringify(w)); const addr = w.json.address; console.log("address:", addr); console.log("=== 2. пополнение ==="); const f = await api("/faucet/" + addr); console.log("faucet:", f.status, JSON.stringify(f.json).slice(0, 140)); console.log("=== 3. ставка ==="); const bet = await api("/bet", { side: "UP", amountUnits: "100000000", address: addr }); console.log("bet:", bet.status, JSON.stringify(bet.json).slice(0, 200)); if (!bet.json || !bet.json.id) throw new Error("bet failed"); const id = bet.json.id; const entryResp = String(bet.json.entryPrice); await sleep(1500); const b0 = await readBet(id); console.log("\n--- ставка on-chain ---"); console.log("betTime :", b0.betTime, new Date(b0.betTime * 1000).toISOString()); console.log("expireTime:", b0.expireTime, new Date(b0.expireTime * 1000).toISOString(), "(+" + (b0.expireTime - b0.betTime) + "с)"); console.log("entry(on-chain):", b0.entryPrice, "| entry(/bet ответ):", entryResp); console.log("\n=== 4. ПРОБА: закрыть ДО экспирации (должен быть 400) ==="); const early = await api("/close", { id }); const stillOpen = await readBet(id); if (stillOpen.status === "0") { console.log("ответ:", early.status, JSON.stringify(early.json)); console.log(early.status === 400 ? " -> ок, отказ до экспирации" : " -> ВНИМАНИЕ: ожидался 400"); } else { console.log(" (ставка уже закрылась сама/успела — пропускаем пробу)"); } console.log("\n=== 5. ЖДЁМ " + (WAIT_MS / 1000) + "с (экспирация давно прошла) ==="); await sleep(WAIT_MS); const b1 = await readBet(id); const expireMs = b1.expireTime * 1000; const hist = priceAtExpiry(expireMs); const nowResp = await api("/price"); const priceNowStr = nowResp.json ? String(nowResp.json.price) : null; const priceNowInt = priceNowStr ? Number(priceNowStr.replace(".", "").padEnd(0) === "" ? "0" : priceNowStr.replace(".", "")) : null; const toInt = (s) => { const [i, fr = ""] = String(s).split("."); return Number(i + fr.padEnd(8, "0").slice(0, 8)); }; const nowInt = priceNowStr ? toInt(priceNowStr) : null; console.log("цена на момент экспирации (из истории):", hist.price, "(строк в истории:", hist.count + ")"); console.log("текущая цена (момент клика) :", nowInt, "(" + priceNowStr + ")"); console.log("entry :", b1.entryPrice); console.log("\n=== 6. ЗАКРЫТИЕ ==="); const cl = await api("/close", { id }); console.log("ответ:", cl.status, JSON.stringify(cl.json)); const exitPrice = cl.json && cl.json.exitPrice != null ? String(cl.json.exitPrice) : null; const b2 = await readBet(id); console.log("после закрытия: status=" + b2.status, "exitPrice=" + b2.exitPrice); console.log("\n================ ВЕРДИКТ ================"); console.log("price_at_expiry (история) :", hist.price); console.log("exitPrice (ушло в tx) :", exitPrice); console.log("price_now (момент клика) :", nowInt); console.log(); if (exitPrice === null) { console.log("ЗАКРЫТИЕ НЕ ВЫПОЛНИЛОСЬ — см. ответ выше."); } else if (hist.price === null) { console.log("НЕТ ДАННЫХ В ИСТОРИИ — проверить запись."); } else if (String(hist.price) === String(exitPrice)) { if (String(nowInt) !== String(hist.price)) { console.log("*** ОКНО ЗАКРЫТО ***"); console.log("exitPrice взят ИЗ ИСТОРИИ на момент экспирации и НЕ равен цене момента клика."); console.log("Игрок физически не может выбрать выгодный момент: цена уже зафиксирована прошлым."); } else { console.log("СОВПАЛО (но цена не двигалась) — тест слабый, цена сейчас та же. Повторить позже."); } } else { console.log("!!! ОКНО ОТКРЫТО !!! exitPrice (" + exitPrice + ") != цена на момент экспирации (" + hist.price + ")"); } if (b2.status === "0") console.log("\n(ставка осталась open — exit==entry: равные цены, отдельная задача)"); })().catch((e) => { console.error("ERR:", e.message); process.exit(1); });