Files
pump-game-ops/TASKS.md
T
Porfiry f105c1adff ADR-011: статика игры вынесена из куба на nginx (LXC 152 game-web), версии каталогами
- корень smart-updown.binom.pw -> nginx на Proxmox (LXC 152, 192.168.76.152)
- /api,/ws -> куб (router PathPrefix на внешнем Traefik VDS), куб не менялся
- версионирование: /opt/game/releases/<N>/, активная = симлинк /opt/game/current
- tools/proxmox-nginx: unpack-release.sh, publish-release.sh, game.nginx.conf, smart-updown.traefik.yaml, README
- приёмка живьём: корень 200 nginx/1.24.0 (Ubuntu), /27/ /28/ 200, /api/state 200 JSON, /api/ws цена, autoclose-check 9/9
- питфоллы: OCI-manifest в skopeo dir-дампе, слэш в конце версии, /solana-rpc мимо PathPrefix /api
2026-09-15 17:07:30 +03:00

196 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]`
- [x] **DONE** Решить «вверх/вниз» — предмет ставки = цена SOL (ADR-006), валюта = фантики
- [x] **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)
- [x] **DONE** Создана копия контракта `smart-updown-token` (ветка main, чистый) — работаем тут
- [x] **DONE** Перевести `create_bet` с SOL (lamports) на SPL-токен: приём фантиков с ATA игрока на ваулт
- [x] **DONE** Выплата в `close_bet` фантиками из ATA-ваулта на ATA игрока
- [x] **DONE** `state`/`constants`: единицы = token units (decimals=6), + VAULT_SEED
- [x] **DONE** Обновить IDL (idl/ + relayer/idl/) + Anchor-тесты (10/10) — коммит 45d64d4
- [x] **DONE** Localnet стенд поднят заново (ledger пересобран), контракт задеплоен, mint создан, `init` прогнан (2026-09-13)
- [x] **DONE** E2E живьём: ставка → close по реальной цене из NATS (`entryPrice=10195000000` при курсе 101.95)
## Фаза 2 — Релейер и цена (АКТИВНАЯ — следующий шаг)
- [ ] **IN_PROGRESS** SolanaClient/ops: подписывать/отправлять токеновую ставку (ATA + ваулт) — перевести релейер с SOL на токен
- [ ] **TODO** readGlobal: баланс vault_token вместо SOL; ensureVaultFunded — наливать токен в vault
- [x] **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)
- [x] **DONE** Каркас: Phantom, балансы (фантики+SOL), swap пополнение/вывод через `/api/pump/swap`
- [x] **DONE** Ставка вверх/вниз через контракт с поллингом до SETTLED
- [ ] **TODO** Перевести подпись под токеновую версию контракта (после Фазы 1)
- [x] **DONE** Реальный провайдер цены вместо RandomPriceSource (за нами) → `binance-market` через NATS (ADR-007/ADR-008)
- [x] **DONE** Живая цена SOL по WebSocket (`wss://testfront.binom.pw/api/ws`), вывод на странице — релиз 2
- [x] **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`) — прочитать перед каждым релизом:**
```bash
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)
- [x] **DONE** ADR-007: цена SOL из NATS (`market.price.solusdt`), заглушка удалена
- [x] **DONE** `natsPrice.ts` — подписка NATS, последняя цена, `waitForPrice`, ×1e8 строковым парсером
- [x] **DONE** `gateway.ts` — WS `/ws`, `GET /price`, нормализация `/api/...` → `/...`
- [x] **DONE** `test-front` — блок «Цена SOL (WebSocket)»: цена, статус, автопереподключение
- [x] **DONE** Dockerfile + helm для шлюза и фронта; деплой в k3s ns `game`
- [x] **DONE** Роут `testfront.binom.pw` переведён с 76.199 на куб 76.120 (Traefik VDS)
- [x] **DONE** Релизы: `smart-updown-token` 7, `test-front` 2
- [x] **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 фронта):**
```bash
# 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` в кубе вообще (домен он больше
не обслуживает) — либо оставить как «предыдущая схема на откат», либо выключить.