24 KiB
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через NATSmarket.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, nsgame, ингресс; релиз 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(default15000) отвечает лишь за ожидание первой цены от 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-token7,test-front2 - 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. Если понадобится бесслэшевый вид — добавить в nginxreturn 301 /$build/. - TODO
[pump.meta]Чистка старых сборок на 152: каталогиreleases/<N>не удаляются сами (~68 МБ на сборку). Политика ретеншена (сколько версий держим) — за владельцем. - TODO
[pump.meta]Автоматизировать шаг публикации в CIfun-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 (сейчас жмёт уже сжатое)