Files
pump-game-ops/TASKS.md
T

24 KiB
Raw Blame History

TASKS.md — список задач (трекер проекта)

Здесь живёт весь список задач — не только мои. Трекаем проект целиком. Бинарные составляющие:

  • фаркул/pivotable-задачи (что можно отложить или развернуть иначе) помечаются [pivotable];
  • общие/инфра (деплой, стабильность, мок) — [pump.meta].

Статусы: TODO / IN_PROGRESS / DONE / BLOCKED.


⭐ Актуальное состояние (2026-09-13)

Где мы: цена SOL — реальная, из NATS, шлюз и тестовый фронт крутятся в k3s на testfront.binom.pw. Первый щуп экономики закрыт: ставка → раунд → результат считается по живой рыночной цене.

Новое (2026-09-13):

  • Цена SOL берётся из NATS (market.price.solusdt), заглушка удалена — ADR-007, детали в docs/PRICE-NATS.md.
  • Шлюз раздаёт цену фронту по WebSocket: wss://testfront.binom.pw/api/ws.
  • test-front показывает живую цену SOL.
  • Оба контейнера (статика + шлюз) — в k3s, ns game, один домен через ингресс.
  • Solana localnet пересобрана с нуля, контракт задеплоен (9ALs…).

⭐ Предыдущее состояние (2026-09-09)

Где мы: Фаза 1 (контракт переведён на фантики) — DONE. Сейчас вход в Фазу 2 (релейер под токен).

Готово:

  • Контракт smart-updown-token — переведён на SPL-токен (фантики), IDL+тесты обновлены. Коммит 45d64d4.
  • Ktor swap-клиент в бэкенде (ввод/вывод) — заготовка.
  • Тестовый фронт test-front — каркас (Phantom, балансы, пополнение/вывод).

В работе / вперёд:

  • Фаза 2 (активная): релейер перевести с SOL на токен (ops/pda/config). После неё + E2E на localnet.
  • Веха «первый щуп»: 👉 как только релейер на фантиках + E2E на localnet прогнаны — через test-front вы самостоятельно щупаете: пополнить фантики → ставка → результат. Это первый реальный user-тест (без Unity).

Параллельные TODO (не блокируют Фазу 2): реальная цена (вместо RandomPriceSource — источник выбран: binance-market, ADR-007, остаётся интеграция), эмиссия, ликвидность/комиссии swap.


Фаза 0 — Экономика и спека (кросс)

  • TODO ADR-002 зафиксирован, документировать ECONOMY.md: токен, курс (Способ 1), decimals=6, mint-адреса — [pump.meta]
  • DONE Решить «вверх/вниз» — предмет ставки = цена SOL (ADR-006), валюта = фантики
  • DONE Цена SOL: реальный источник (ADR-007) — subochev/binance-market через NATS market.price.solusdt; интеграция PriceSource в релейере сделана (ADR-008, 2026-09-13)
  • TODO Кто держит ликвидность swap и комиссии — [pump.meta]
  • TODO Эмиссия: разовая vs постоянная — [pump.meta]

Фаза 1 — Контракт на токене (ядро) — репо smart-updown-token (копия Smart-game)

  • DONE Создана копия контракта smart-updown-token (ветка main, чистый) — работаем тут
  • DONE Перевести create_bet с SOL (lamports) на SPL-токен: приём фантиков с ATA игрока на ваулт
  • DONE Выплата в close_bet фантиками из ATA-ваулта на ATA игрока
  • DONE state/constants: единицы = token units (decimals=6), + VAULT_SEED
  • DONE Обновить IDL (idl/ + relayer/idl/) + Anchor-тесты (10/10) — коммит 45d64d4
  • DONE Localnet стенд поднят заново (ledger пересобран), контракт задеплоен, mint создан, init прогнан (2026-09-13)
  • DONE E2E живьём: ставка → close по реальной цене из NATS (entryPrice=10195000000 при курсе 101.95)

Фаза 2 — Релейер и цена (АКТИВНАЯ — следующий шаг)

  • IN_PROGRESS SolanaClient/ops: подписывать/отправлять токеновую ставку (ATA + ваулт) — перевести релейер с SOL на токен
  • TODO readGlobal: баланс vault_token вместо SOL; ensureVaultFunded — наливать токен в vault
  • DONE PriceSource: реальная цена (вместо RandomPriceSource) — интеграция с binance-market (ADR-007), NATS-консьюмер market.price.solusdt в src/natsPrice.ts, цена ×1e8 (ADR-008)
  • TODO Интеграционный тест релейера на токеновом флоу

Фаза 4 — Бэкенд: задействовать swap

  • DONE Ktor PumpApiClient + POST /agents/swap + DTO + заготовка (в smart-updown-server)
  • TODO Проверить реальный swap на devnet (не мок)
  • TODO Обвязать GameService: ввод/вывод фантиков через swap

Фаза 5 — Фронт (Unity)

  • TODO Кнопки «Пополнить»/«Вывести», баланс в фантиках
  • TODO SolanaBridge.jslib: подпись токеновой create_bet (ATA игрока в ключах)
  • TODO Wallet.cs: баланс в фантиках (ATA + getTokenAccountBalance), CanAfford по токену

Фаза 5b — Тестовый фронт (test-front, для ручной гонки экономики без Unity)

  • DONE Каркас: Phantom, балансы (фантики+SOL), swap пополнение/вывод через /api/pump/swap
  • DONE Ставка вверх/вниз через контракт с поллингом до SETTLED
  • TODO Перевести подпись под токеновую версию контракта (после Фазы 1)
  • DONE Реальный провайдер цены вместо RandomPriceSource (за нами) → binance-market через NATS (ADR-007/ADR-008)
  • DONE Живая цена SOL по WebSocket (wss://testfront.binom.pw/api/ws), вывод на странице — релиз 2
  • DONE Развернуть / дать постоянный URL — https://testfront.binom.pw (k3s, ns game, ингресс; релиз 2)

Фаза 6 — E2E и деплой

  • TODO Полный круг на devnet: создать токен → swap ввод → ставка токеном → раунд → вывод
  • TODO Обновить API.md/INTEGRATION.md, развернуть на mainnet
  • TODO Стабильность: версионировать конфиги, mint-адрес в конфиге

Ссылки

  • Решения/экономика: docs/DECISIONS.md, docs/ECONOMY.md
  • Карта репозиториев: README.md

⭐ Добавлено 2026-09-15 — боевой домен и находки приёмки

Где мы: боевой домен smart-updown.binom.pw переведён на тот же TS-шлюз (:8895), что и testfront.binom.pw (некастодиальный Kotlin-бэк выключен, обратимо). Пути /api,/ws вынесены в отдельный ingress вне helm — релиз игры их больше не сносит.

Найдено при приёмке живьём:

  • SEC-16 — безвозвратная утечка ренты PDA-аккаунта ставки: 1 559 040 lamports с кошелька игрока за каждую ставку, аккаунт не закрывается никогда. Подробности — docs/SECURITY.md. Вердикт: на тестах терпимо (маскируется краном), перед mainnet — блокер.
  • Расчёт (выплаты) — корректен 3/3: выигрыш 1.3×, проигрыш 0, ничья — возврат залога.
  • Кран зачисляет на цепь с лагом 8–13 с (шлюз рапортует мгновенно) — отсюда ложный симптом «SOL не обновился».

Деплой-памятка фронта игры (fun-game-front2) — прочитать перед каждым релизом:

helm repo update binom
helm upgrade fun-game-front2 binom/fun-game-front2 -n game \
  --reuse-values --set image.tag=<N> --wait --timeout 4m    # <N> = тег релиза, НЕ опираться на чарт

⚠️ Без явного --set image.tag образ НЕ обновится: в helm/values.yaml захардкожен image.tag, он перебивает Chart.AppVersion → спека не меняется, новый под не создаётся, на домене остаётся старый билд при «Upgrade complete» в helm. Полный разбор — docs/DECISIONS.md (ADR-010). Проверять по кубу/домену: kubectl get pod -n game -l app.kubernetes.io/name=fun-game-front2 \ -o custom-columns='NAME:.metadata.name,IMAGE:.spec.containers[0].image,CREATED:.metadata.creationTimestamp'.

Задачи фронтендеру (отдать во фронт-репо fun-game-front2):

  • Убрать image.tag из helm/values.yaml и опираться только на Chart.AppVersion (в CI он уже подставляется из тега релиза) — тогда helm upgrade перестанет требовать --set.
  • Хэш содержимого в имени ассетов (WebGL.<hash>.data/.wasm/.framework.js) на шаге сборки: сейчас nginx отдаёт ассеты как immutable, max-age=604800 при НЕизменяемых именах, поэтому у уже игравших игроков до 7 суток держится старый билд в кэше браузера.
  • Привести API игры к контракту TS-шлюза (docs/API шлюза / test-front/API.md): тело ставки {address, side, amountUnits}; поля minAmount/maxAmount — строкой; GET /api/bet/{id} у шлюза отсутствует (см. ADR-009).

Фича на будущее — «ждать, если цена не изменилась» (НЕ ДОДЕЛАНА):

  • TODO [pump.meta] Если по сделанной ставке цена на момент экспирации не изменилась (exit == entry, ничья), нужно подождать ещё 15 секунд и попробовать взять цену заново — чтобы ставка не закрывалась возвратом залога на «плоской» цене, а получала шанс на реальный исход. Суть: ставка не меняется → ждём (не финализируем сразу возвратом).
  • TODO Вынести окно ожидания в конфиг: сейчас PRICE_WAIT_TIMEOUT_MS (default 15000) отвечает лишь за ожидание первой цены от NATS (relayer/src/priceSource.ts:9) и к ничьей отношения не имеет — нужен отдельный параметр (напр. TIE_WAIT_TIMEOUT_MS) и логика отложенного ретрая в settle.ts (priceAtOrBefore(expireTime*1000) — сейчас берётся фиксированный момент, ретрай каждые 2 с вечно получит ту же пару, см. SEC-9).
  • TODO Решить, что делать по истечении окна ожидания: возврат залога (текущее поведение SEC-9) или иной исход.

Фаза 7 — Цена из NATS и деплой в куб (2026-09-13)

  • DONE ADR-007: цена SOL из NATS (market.price.solusdt), заглушка удалена
  • DONE natsPrice.ts — подписка NATS, последняя цена, waitForPrice, ×1e8 строковым парсером
  • DONE gateway.ts — WS /ws, GET /price, нормализация /api/... → /...
  • DONE test-front — блок «Цена SOL (WebSocket)»: цена, статус, автопереподключение
  • DONE Dockerfile + helm для шлюза и фронта; деплой в k3s ns game
  • DONE Роут testfront.binom.pw переведён с 76.199 на куб 76.120 (Traefik VDS)
  • DONE Релизы: smart-updown-token 7, test-front 2
  • DONE Документация: docs/PRICE-NATS.md, docs/diagrams/price-flow.puml, ADR-007
  • TODO Пилот: смена entry_price на «реальный источник как оракул» до боя (Pyth/Switchboard)
  • TODO Деградация при отсутствии цены: отдельное решение (бэкфилл/остановка приёма ставок)

Ссылки (дополнено 2026-09-13)

  • Цена: docs/PRICE-NATS.md, диаграмма docs/diagrams/price-flow.puml
  • Безопасность: docs/SECURITY.md — чек-лист SEC-1…SEC-15 (аудит живого стенда 2026-09-13)

⭐ 2026-09-15 (2) — статика игры ушла из куба на nginx (LXC 152), версии каталогами

Где мы: корень smart-updown.binom.pw раздаёт nginx на Proxmox (LXC 152 game-web, 192.168.76.152) со версионированием по каталогам; /api и /ws по-прежнему идут в куб на TS-шлюз отдельным router'ом внешнего Traefik. Решение и разбор — ADR-011 в docs/DECISIONS.md.

Как выпустить новую сборку игры (памятка — вместо helm upgrade фронта):

# 1. CI репозитория fun-game-front2 собирает Unity WebGL и пушит образ
#    images.binom.pw/fun/game-front2:<N>  (тег = номер релиза, как раньше)
# 2. раскладываем на nginx и включаем как активную:
ssh root@192.168.76.66 "pct exec 152 -- /opt/game/tools/publish-release.sh <N> --activate"
# 3. проверяем ДОМЕН (не helm):
curl -sI https://smart-updown.binom.pw/ | grep -iE 'server|last-modified'   # nginx/1.24.0 (Ubuntu)
curl -s -o /dev/null -w '%{http_code}\n' https://smart-updown.binom.pw/<N>/Build/WebGL.framework.js

Без --activate сборка просто ложится в /opt/game/releases/<N>/ и доступна по /<N>/ — корень не меняется. Откат версии = publish-release.sh <старый N> --activate (каталоги не удаляются).

Инструменты: tools/proxmox-nginx/ — unpack-release.sh (распаковать dir-дамп образа), publish-release.sh (тянуть образ + разложить + переключить), game.nginx.conf, smart-updown.traefik.yaml (боевой конфиг внешнего Traefik). Скопированы в LXC 152 в /opt/game/tools/ и /etc/nginx/sites-available/game.

Проверено живьём: / → 200 nginx/1.24.0 (Ubuntu); /27/,/28/ → 200, ассеты версий различаются; /api/state → 200 JSON; /api/ws → цена идёт; сквозной autoclose-check.cjs — 9/9.

Задачи на будущее по этой схеме:

  • TODO [pump.meta] Ссылки на версии — только со слэшем (/28/): голый /28 даёт 301. Если понадобится бесслэшевый вид — добавить в nginx return 301 /$build/.
  • TODO [pump.meta] Чистка старых сборок на 152: каталоги releases/<N> не удаляются сами (~68 МБ на сборку). Политика ретеншена (сколько версий держим) — за владельцем.
  • TODO [pump.meta] Автоматизировать шаг публикации в CI fun-game-front2: после push образа дёргать publish-release.sh на 152 (сейчас — ручной вызов одной командой).
  • TODO [pump.meta] Уточнить, нужен ли под fun-game-front2 в кубе вообще (домен он больше не обслуживает) — либо оставить как «предыдущая схема на откат», либо выключить.

⭐ 2026-09-15 (3) — выпущена сборка 29 + закрыта кэш-мина (редирект корня)

Сборка 29 опубликована и активна. Релиз Gitea 29 (id 1046) на коммите 25f4637 («fix(webgl): не ронять плеер на старте из-за дыма падения и PC URP») → CI run #29 success (~8 мин) → образ images.binom.pw/fun/game-front2:29 → publish-release.sh 29 --activate.

Поймана и исправлена реальная мина (ADR-011-доп). Корень раздавал файлы напрямую, и ассеты Unity (которые index.html зовёт ОТНОСИТЕЛЬНО — buildUrl = "Build") уходили по URL без номера сборки /Build/WebGL.wasm под immutable 7д. Игрок неделю получал бы старый билд. Фикс: корень теперь 302 → /29/; ассеты идут под номером версии; immutable сужен до ~^/[0-9]+/….

Проверено живьём: / → 302 → /29/; браузер приземляется на /29/ и тянет /29/Build/* — все 200, ошибок в консоли нет; /27/,/28/ живы; /api/* и /solana-rpc/ → 200; autoclose-check 9/9; testfront цел.

Памятка на будущее — выпуск сборки целиком:

# 1. релиз в Gitea на нужном коммите (CI срабатывает ТОЛЬКО на release, не на push):
curl -s -u subochev:PASS -X POST "https://git.binom.pw/api/v1/repos/subochev/fun-game-front2/releases" \
  -H 'Content-Type: application/json' \
  -d '{"tag_name":"30","name":"30","target_commitish":"<sha>","draft":false}'
# 2. дождаться прогона (обычно ~8 мин):
#    tools/wait-ci-run.sh  (в фоне, печатает итог)
# 3. разложить на nginx и сделать активной:
ssh root@192.168.76.66 "pct exec 152 -- /opt/game/tools/publish-release.sh 30 --activate"
# 4. проверить ДОМЕН:
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' https://smart-updown.binom.pw/   # 302 -> /30/

TODO:

  • [pump.meta] Автоматизировать шаг 3 (сейчас ручная команда после CI).
  • [pump.meta] Ретеншен старых сборок на 152 (сейчас 27, 28, 29 = ~200 МБ; политика за владельцем).

⭐ 2026-09-15 (4) — именованная сборка sol из zip + два дефекта в ней

Сделано: фронтендер прислал build_sol.zip (вне релизного цикла, без номера). Выложил под именем sol → https://smart-updown.binom.pw/sol/ (см. ADR-012, tools/proxmox-nginx/publish-zip.sh). Активная сборка не тронута — корень по-прежнему /29/. Адрес: /sol/.

Правки в nginx (иначе не работало):

  • локейшн сборок расширен с чисел до имён → обязательно вернуть ^~ служебным путям (/api, /ws, /solana-rpc/), иначе расширенный regex перехватывает /api/... и ломает бэк;
  • immutable — только для номерных каталогов; именованные кэшировать нельзя (могут перезалить);
  • в маску добавлен unityweb (сборки фронтендера на .unityweb-ассетах).

⚠️ Два дефекта в присланной сборке (отдать фронтендеру):

  • [pump.front] Двойной префикс API: игра зовёт /api/api/state → 404. SmartUpdownConfig.ApiBase() для WebGL = Origin()+"/api", а BackendClient.HttpGetAsync("/api/state") добавляет /api/... ещё раз. Лечится в одном месте: либо ApiBase() без /api, либо пути без /api.
  • [pump.front] ReferenceError: getProvider is not defined — падение при подключении Phantom. В framework.js есть вызовы getProvider()/errMsg()/loadWeb3(), но нет определений: в Assets/Plugins/WebGL/SolanaBridge.jslib они объявлены внутри IIFE, а через mergeInto экспортированы только SolanaBridge*-методы. Не регресс от nginx — воспроизводится и на сборке 29.

Важное подтверждение: билд sol в браузере дошёл до игрового цикла (Phase -> Idle → Phase -> Approach) — раньше в headless ни одна сборка так далеко не шла. Статика с nginx отдаётся корректно.

2026-09-15 (5) — исследование: редакторная сборка vs наша Docker/CI (ничего не меняли)

Вердикт: Docker не виноват. Разница — наш BuildScript.cs насильно ставит WebGLCompressionFormat.Disabled; фронт собирает с Brotli + Decompression Fallback (отсюда .unityweb). Было в браузер 26 МБ (52 МБ wasm + gzip на лету), стало бы 16.4 МБ. Код в обеих сборках эквивалентен (маркеры в wasm совпадают), версия Unity одинаковая (6000.3.16f1). Наш конвейер (GameCI-образ + -batchmode -executeMethod) — стандартная практика, не костыль. См. ADR-013.

⚠️ Важная поправка: оба дефекта (/api/api/state → 404 и getProvider is not defined) есть и в НАШЕЙ сборке 29 — это исходники, а не «его сборка». Задача переехала в кодовую правку.

  • решить: переводить ли сборку на Brotli + Decompression Fallback (или gzip_static) — ждём владельца
  • [pump.front] убрать двойной /api в BackendClient/SmartUpdownConfig
  • [pump.front] getProvider: экспортировать хелперы jslib наружу (или инлайнить)
  • [pump.ops] gzip off для .unityweb в nginx LXC 152 (сейчас жмёт уже сжатое)