269 lines
24 KiB
Markdown
269 lines
24 KiB
Markdown
# 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` в кубе вообще (домен он больше
|
||
не обслуживает) — либо оставить как «предыдущая схема на откат», либо выключить.
|
||
|
||
## ⭐ 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` цел.
|
||
|
||
**Памятка на будущее — выпуск сборки целиком:**
|
||
```bash
|
||
# 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 (сейчас жмёт уже сжатое)
|