f105c1adff
- корень 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
196 lines
17 KiB
Markdown
196 lines
17 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` в кубе вообще (домен он больше
|
||
не обслуживает) — либо оставить как «предыдущая схема на откат», либо выключить.
|