60 Commits

Author SHA1 Message Date
subochev dd4f16a930 feat(relayer): API v2 (canBet/30% от баланса) + п.8 — ничья играет заново
Тестовый TS-шлюз (relayer/), обслуживающий smart-updown.binom.pw и
testfront.binom.pw. Контракт и prod-Kotlin сервер не тронуты.

1+2. GET /state: canBet (boolean) + canBetReasonCode/canBetReason (русский
     текст) — можно ли ставить с фиксированным кошельком. Учитывает:
     захардкоженный адрес (GW_FIXED_BETTOR), наличие открытой ставки,
     хватает ли фантиков на ставку, лимиты [min,max], paused, наличие цены.
3-6. POST /bet: тело {"side":"up"|"down"} — сумма с фронта НЕ приходит,
     размер = GW_BET_PERCENT (30) % от баланса фантиков, клампится в
     [minAmount, maxAmount]. Ответ: {accepted, reasonCode, reason,
     retryAfterSeconds, betId, txSig, ...}.
7.   GET /state: openBet / lastBet (взаимоисключающие: открытая имеет
     приоритет) + поле status ("open"/"closed"), result, retryAfterSeconds.
8.   Ничья (refunded, exit==entry) игроку НЕ показывается: планировщик молча
     пересоздаёт ставку тем же размером и направлением (хук replayBet) и
     шлёт WS-событие bet_replay вместо bet_closed. Лимит GW_MAX_REPLAYS
     (дефолт 10) обрывает цепочку при «замороженной» цене.
9.   relayer/API.md — документация всех endpoint-ов и WS-событий.

Новые файлы: src/betRules.ts (правила ставки: canBetDecision,
computeBetAmountUnits), API.md.
Новые env: GW_FIXED_BETTOR, GW_BET_PERCENT (30), GW_MAX_REPLAYS (10).

Проверено: npm run typecheck + npm run test — 127 проверок, 0 провалов;
живая приёмка tools/gateway-api-v2-check.cjs — 26/26 на
https://smart-updown.binom.pw/api (включая реальную ставку → payout_done).
2026-09-17 00:11:31 +03:00
Caffeine 42dfa58582 feat(contract): возврат залога при ничьей (exit_price == entry_price)
Раньше при равенстве цен close_bet делал `return Ok(())` — ставка навсегда
оставалась в статусе open, а залог запирался в ваулте (замер: 33.6% окон).
Из-за правила «одна открытая ставка на адрес» игрок получал 409 пожизненно.

Теперь ничья возвращает залог целиком (без множителя) и финализирует ставку:
- constants.rs: STATUS_REFUNDED = 3 (0/1/2 не тронуты — зашиты в шлюз и в цепи)
- close_bet.rs: CPI-перевод amount из ваулта беттору под сидами ваулт-PDA,
  запись exit_price/status, событие BetClosed(status=3, payout=amount)
- settle.ts: STATUS_REFUNDED, statusName "refunded", статус 3 в условии финализации
- check.ts: проверка формы события refunded
- тест close_bet_equal_price_stays_open → close_bet_equal_price_refunds
  (+ повторное закрытие отклоняется)

Проверено: cargo test-sbf 10/10, npm run typecheck EXIT=0, две мутации
(снятие статуса, снятие перевода) валят именно новый тест.
Задеплоено: программа 9ALs… (хэш из цепи совпал побайтово), шлюз rev 15.
2026-09-14 02:09:08 +03:00
Caffeine a73e44de9a feat(relayer): авто-закрытие ставок, WS-события, одна открытая ставка на адрес
- settle.ts: серверный планировщик (startScheduler, тик SMART_SETTLE_INTERVAL_MS)
  находит просроченные открытые ставки, берёт цену из истории на expire_time,
  сам зовёт close_bet; рассылает bet_closed
- gateway.ts: ручка POST /close удалена (404); проверка 'одна открытая ставка
  на адрес' под withBetLock -> 409 с openBetId; bet_open по WS при создании;
  lastState прокидывается в settleOnce (без него лог спамил каждый тик)
- settle.ts: пакетный getProgramAccounts вместо поштучного перебора по id
  (замер: 3 мс против 28 мс, 9.3x, 1 запрос вместо 15); фильтр не-Bet аккаунтов
  по BET_ACCOUNT_SIZE (Global исключён из unmapped)
- check.ts: +38 проверок (decodeBet, openBetOf, dueForSettle, события, границы)
2026-09-13 18:49:26 +03:00
Caffeine 0b136c6502 feat(relayer): цена закрытия из истории на момент экспирации — окно выбора времени закрыто
Проблема: handleClose брал getPrice() (текущую цену), поэтому игрок мог
подождать выгодного момента и закрыть ставку тогда, когда ему надо
(эксплуатировалось вживую: ставка висела open >2 мин после экспирации 15с).

Решение (только шлюз, контракт не тронут):
- src/priceHistory.ts (новый) — локальная история цены на встроенном node:sqlite
  (Node 22, без новых зависимостей). Запись только при изменении цены
  (~300 строк/сутки вместо 92k тиков), ретеншен 24ч, prune одним DELETE.
- natsPrice: в PriceTick добавлено eventTime (время сделки у Binance) — источник
  истины по времени; ts не тронут.
- config: SMART_PRICE_HISTORY_PATH (/app/data/price-history.db),
  SMART_PRICE_RETENTION_HOURS (24).
- gateway: каждый тик пишется в историю; handleClose берёт
  history.priceAtOrBefore(expireTime*1000) — последняя цена НЕ ПОЗЖЕ экспирации.
  Отказ 400 'bet not expired yet' до экспирации, 400 на уже закрытую,
  503 если истории нет (не выдумываем цену).
- helm: env SMART_PRICE_HISTORY_PATH / SMART_PRICE_RETENTION_HOURS.
- check.ts: 10 проверок истории, включая ключевую «НЕ отдаёт цену позже момента».

Верификация: typecheck 0 ошибок; npm test 34/34; мутационная приёмка — подмена
<= на >=/ASC роняет 4 проверки, снятие дедупа и смена ретеншена роняют свои.
Живой стенд: exitPrice = цена на момент экспирации (9977000000), тогда как
текущая цена в момент клика была 9976000000 — взята история, не 'сейчас'.
2026-09-13 14:46:47 +03:00
Caffeine 5ab6c9def5 feat(relayer): вынести начисление из /bet в отдельную тестовую ручку + гейт test/release
- /bet больше НЕ наливает SOL и фантики; address обязателен (400 с подсказкой)
- пред-проверка балансов перед ставкой (SOL >= 0.01, фантиков >= amount), 400
- новый POST /wallet — создать custodial-кошелёк, ничего не наливает
- src/faucetGate.ts: parseMode/faucetDenyReason, fail-closed
- SMART_MODE (test|release), дефолт release; /faucet: 403 если не test
- genesis-проверка: mainnet/devnet/testnet/genesis недоступен -> 403
- helm: SMART_MODE из .Values.smart.mode
- check.ts: 6 проверок гейта (мутационная приёмка пройдена)
2026-09-13 13:38:38 +03:00
Caffeine a3178cfa6e fix(helm): права на keystore — fsGroup + chmod 0644 в init
Шлюз в кубе падал с EACCES при записи .gateway-bettors.json: том от
local-path принадлежит root, контейнер бежит под node (uid 1000), а
seed-файл копировался в режиме 0444. Добавлены securityContext.fsGroup=1000
и chmod 0644 в init-контейнере. Проверено: ставка и закрытие проходят
через живой домен (entryPrice/exitPrice — реальные цены из NATS).
2026-09-13 06:05:24 +03:00
Caffeine 92cf2f61b4 feat(relayer): цена SOL из NATS + WS-раздача + деплой-артефакты
deploy-contract / deploy-contract (release) Failing after 3m34s
priceSource больше не рандомит: цена берётся из NATS (market.price.solusdt),
конверсия строка->целое x1e8 без float. Шлюз раздаёт цену по WS (/ws,
нормализация /api/... -> /...) — фронт просто выводит её. Добавлены
Dockerfile и helm-чарт для деплоя в k3s.
7
2026-09-13 05:53:48 +03:00
Caffeine bf9d05f2f2 feat: /faucet/{address} в шлюз — налив SOL+фантиков на произвольный адрес (localnet) 2026-09-10 05:23:50 +03:00
Caffeine f426f58778 feat: HTTP-шлюз поверх релейера (POST /bet,/close; GET /balance,/state) для пульта без Phantom 2026-09-10 04:33:34 +03:00
Caffeine f937c8993d fix: vault как ATA + фандинг беттора; E2E на localnet ok 2026-09-10 02:12:07 +03:00
Caffeine fb9a7f4e8b feat(relayer): migrate to SPL-token (фантики)
- ops: readGlobal читает vaultBalance (getTokenAccountBalance), createBet/closeBet передают bettorToken/vaultToken/tokenProgram, ensureVaultFunded наливает токен (mintTo, allowOwnerOffCurve)
- pda: vaultPda([global,vault]), config:.env.new SMART_TOKEN_MINT, суммы в токен-единицах
- check.ts: 14 проверок сигнатур создания create/close под новым токеновым IDL
- @solana/spl-token dependency; .gitignore + node_modules/dist
2026-09-10 00:19:21 +03:00
Caffeine 45d64d45a7 feat: migrate smart-updown contract to SPL-token (фантики)
- create_bet: escrow фантиков с bettor на vault (PDA[global,vault]) через anchor_spl::token
- close_bet: выплата фантиков из vault на bettor (CPI с signer_seeds vault)
- state/constants: comments про единицы (token units decimals=6), + VAULT_SEED
- IDL регенерирован (idl/ + relayer/idl/) с аккаунтами bettor_token/vault_token/token_program
- тесты обновлены под токен (10/10 + doctest)
2026-09-09 22:00:41 +03:00
subochev ff2289e68d fix: вернуть declare_id на 9ALs (согласовать с Anchor.toml/CI/нашим keypair); контракт живёт на нашем адресе 2026-09-05 17:31:10 +03:00
subochev a51fb3ada2 chore: игнорировать игровые ключи в deploy/ (имеют SOL-баланс) 2026-09-03 23:48:16 +03:00
subochev 4cf583f497 fix: declare_id = FMMD (фактический адрес развёрнутой программы, совпадает с program-id бэка) 2026-09-03 21:08:42 +03:00
subochev 32faec7515 docs: README под актуальную SBPFv3-сборку (anchor 0.31.1, --tools-version v1.57, two-arch тесты) 2026-09-03 13:57:31 +03:00
subochev 515adb25c9 chore: ignore .cortexkit (opencode working dir) 2026-09-03 05:28:17 +03:00
subochev 9f100ee122 feat: миграция smart-updown на anchor 0.31.1 + solana 2.x (SBPFv3)
- anchor-lang 0.31.1 вместо 2.0-rc/anchor-next (git)
- Address -> Pubkey (state/events/instructions)
- убран #[discrim], добавлен #[derive(InitSpace)] + space=8+INIT_SPACE
- тесты переписаны на solana-program-test 2.x (10 passed)
- сборка SBPFv3 через cargo build-sbf --tools-version v1.57
- deploy успешен в нашу сеть (agave 4.2.2, SBPFv3)
2026-09-03 05:27:34 +03:00
subochev 2133f65e21 ci: пробуем доустановку sbpfv3 target + build --arch v3 для нашего прод-блокчейна 2026-09-03 01:27:04 +03:00
subochev c8a95b1f0e ci: собand контракт--arch v3 (SBPFv3, как прод mainnet) на builder agave 4.2.2 2026-09-02 23:31:23 +03:00
subochev 2081c6cb0b ci: builder на agave 4.2.2 (arch v0..v3) — как прод, целевой SBPFv3 2026-09-02 23:24:36 +03:00
subochev 5fdd6902bd dbg: замер возможностей cargo-build-sbf (arch/sbpf) 2026-09-02 23:16:44 +03:00
subochev 015df5949d docs: ссылка на фронтенд-репо fun-game-front2 2026-09-02 22:52:03 +03:00
subochev f74942bbcb dbg: вывести sbpf-версию артефакта перед деплоем 2026-09-02 22:27:15 +03:00
subochev be0bf0a2bb ci: деплой только в нашу сеть solana-net (192.168.76.181:8899), без devnet и временного валидатора 2026-09-02 21:41:34 +03:00
subochev b976cd0629 anchor: [provider] cluster=localnet — и anchor test, и деплой идут на localhost:8899, а не в devnet 2026-09-02 20:56:58 +03:00
subochev c08b0dcbc6 ci: переименовать workflow deploy-devnet->deploy-contract (сломать кэш Gitea, деплой в наш test-validator) 2026-09-02 20:46:37 +03:00
subochev 3cffb000c3 deploy: --provider.cluster localnet + кластер в Anchor.toml (URL не принимается, таблица localnet=>localhost:8899) 2026-09-02 19:51:23 +03:00
subochev 59f425d680 deploy: жёстко в наш test-validator (Gitea-кэш подсовывал devnet, снял input-зависимость) 2026-09-02 19:43:58 +03:00
subochev bd5ace0b76 deploy: единый шаг, наш test-validator по умолчанию (devnet только при явном target=devnet) 2026-09-02 19:36:52 +03:00
subochev 026216a73c workflow: default target=our (наш test-validator) 2026-09-02 19:29:24 +03:00
subochev da48e278ae deploy: наш test-validator по умолчанию (devnet — по input) 2026-09-02 19:23:52 +03:00
subochev 54492a1f27 fix: anchor через git clone+--path (обход libgit2 github-auth) + PATH .cargo/bin в install.sh 2026-09-02 19:11:56 +03:00
subochev 79d93335a0 fix: предустановка SBF platform-tools (LLVM) через install.sh с anza-xyz + rustup link solana 2026-09-02 18:57:04 +03:00
subochev 4d90eba107 fix: anchor build --ignore-keys (обход keypair-mismatch на build) 2026-09-02 18:47:26 +03:00
subochev 63f8a2eaa2 fix: keypair в .anchor под точным crate-именем smart-updown-keypair.json 2026-09-02 18:41:45 +03:00
subochev 57067d920d fix: cargo-build-sbf в PATH + наш keypair в .anchor (без keys sync, перезаписывающего ID) 2026-09-02 18:36:34 +03:00
subochev 3e4b8a3bfe fix: anchor keys sync перед build (синк keypair в .anchor из provider wallet) 2026-09-02 18:25:51 +03:00
subochev 6502b236dc fix: Rust установка с fallback через podman-контейнер (host-executor с сетью DNS к static.rust-lang) 2026-09-02 18:18:20 +03:00
subochev 655cebe466 dbg: HOME+keypair диагностика в build-шаге 2026-09-02 18:09:14 +03:00
subochev b5b791608f fix: копировать наш keypair в id.json прямо в build-шаге 2026-09-02 18:05:44 +03:00
subochev 8b9dceec1b fix: наш keypair как дефолтный wallet с самого начала (до build/test/deploy) 2026-09-02 17:59:34 +03:00
subochev baa3c80d4f fix: принудительно наш keypair в ~/.config/solana/id.json (валидатор создавал свой) 2026-09-02 17:52:30 +03:00
subochev aed2c77b05 sync: Program ID E7Hg->9ALs во всех исходниках, IDL, README, deploy-localnet 2026-09-02 17:47:09 +03:00
subochev 123015a4a7 feat: выбор цели деплоя devnet|our (свой test-validator) через workflow_dispatch 2026-09-02 17:38:14 +03:00
subochev f42de51ad2 fix: export PATH в шаге Solana до solana --version 2026-09-02 17:26:14 +03:00
subochev 4f08c356f0 fix: копировать только solana-* бинари (не deps/sdk) 2026-09-02 17:24:29 +03:00
subochev 09ed6fd15c fix: solana с GitHub releases (release.solana.com недоступен) + единый PATH 2026-09-02 17:22:39 +03:00
subochev ec88bfdd6d fix: reTRY solana install + явный PATH на всех шагах 2026-09-02 17:20:20 +03:00
subochev bad35bcf97 fix: rustc 1.91 (cargo-platform требует 1.91 для anchor-cli) 2026-09-02 17:14:37 +03:00