Переписать shine_payments и обновить тестовый UI с известным багом state
This commit is contained in:
parent
c5ec32f87a
commit
89d06d317b
@ -4,10 +4,15 @@
|
|||||||
|
|
||||||
## Базовый сервер
|
## Базовый сервер
|
||||||
|
|
||||||
- SSH: `player@45.136.124.227`
|
- SSH: `player@shineup.me`
|
||||||
- Домен: `shineup.me`
|
- Домен: `shineup.me`
|
||||||
- Базовый путь: `/home/player`
|
- Базовый путь: `/home/player`
|
||||||
|
|
||||||
|
Для всех рабочих инструкций и скриптов использовать доменное имя `shineup.me`, а не фиксированный IP:
|
||||||
|
|
||||||
|
- актуальный IP должен браться через DNS-резолв на момент подключения;
|
||||||
|
- ручное дублирование IP в документации и deploy-скриптах не поддерживать.
|
||||||
|
|
||||||
## Локальные команды
|
## Локальные команды
|
||||||
|
|
||||||
- Деплой сервера: `./gradlew deployServer`
|
- Деплой сервера: `./gradlew deployServer`
|
||||||
@ -26,6 +31,20 @@
|
|||||||
- `EXPECTED_CADDY_UI_ROOT=/нужный/путь ./gradlew deployUI`
|
- `EXPECTED_CADDY_UI_ROOT=/нужный/путь ./gradlew deployUI`
|
||||||
- `EXPECTED_CADDY_SITE=example.com ./gradlew deployUI`
|
- `EXPECTED_CADDY_SITE=example.com ./gradlew deployUI`
|
||||||
|
|
||||||
|
## Временные тестовые сайты Solana tickets
|
||||||
|
|
||||||
|
- Для HTML UI программы `shine_payments` используется отдельный временный тестовый сайт.
|
||||||
|
- Основной каталог публикации:
|
||||||
|
- `/home/player/sites/test-solana-tickets.shineup.me`
|
||||||
|
- Рабочие домены:
|
||||||
|
- `https://test-solana-tickets.shineup.me`
|
||||||
|
- `https://test-solana-tickets.shiningpeople.ru`
|
||||||
|
- Назначение:
|
||||||
|
- ручная проверка сценариев покупки билетов;
|
||||||
|
- проверка DAO-инструментов и лимитов менеджеров;
|
||||||
|
- проверка ручного добавления билетов и `step_payout`.
|
||||||
|
- Эти сайты не считать основным UI SHiNE; это отдельная тестовая публикация под Solana-часть.
|
||||||
|
|
||||||
### Важно для локального UI (history-router / Ctrl+F5)
|
### Важно для локального UI (history-router / Ctrl+F5)
|
||||||
|
|
||||||
- Локальный UI **обязательно** поднимать только через `./gradlew startLocal`.
|
- Локальный UI **обязательно** поднимать только через `./gradlew startLocal`.
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
# Сервер `45.136.124.227` (`shineup.me`) — основной
|
|
||||||
|
|
||||||
- Пользователь: `player`
|
|
||||||
- Базовый путь: `/home/player`
|
|
||||||
- Каталог SHiNE: `/home/player/SHiNE`
|
|
||||||
- UI публикация: `/home/player/SHiNE/shine-ui`
|
|
||||||
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
|
||||||
- Данные: `/home/player/SHiNE/shine-server/data/`
|
|
||||||
- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
|
|
||||||
|
|
||||||
## Сервисы
|
|
||||||
|
|
||||||
- `shine-server.service` (systemd)
|
|
||||||
- `caddy.service` (systemd)
|
|
||||||
|
|
||||||
## Caddy
|
|
||||||
|
|
||||||
- Активный конфиг (через systemd `ExecStart`): `/home/player/SHiNE/caddy/Caddyfile`
|
|
||||||
- Для UI:
|
|
||||||
- `root * /home/player/SHiNE/shine-ui`
|
|
||||||
- `try_files {path} /index.html` (SPA fallback)
|
|
||||||
- no-cache заголовки
|
|
||||||
- `reverse_proxy /ws* -> 127.0.0.1:7070`
|
|
||||||
@ -18,7 +18,7 @@
|
|||||||
## Статус
|
## Статус
|
||||||
|
|
||||||
- Резервный сервер для SHiNE.
|
- Резервный сервер для SHiNE.
|
||||||
- Основной прод-сервер: `45.136.124.227` (`shineup.me`).
|
- Основной прод-сервер: `shineup.me` (подключение через `player@shineup.me`, IP определяется через DNS).
|
||||||
|
|
||||||
## Caddy
|
## Caddy
|
||||||
|
|
||||||
|
|||||||
35
Dev_Docs/deploy/servers/shineup.me_main.md
Normal file
35
Dev_Docs/deploy/servers/shineup.me_main.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Сервер `shineup.me` — основной
|
||||||
|
|
||||||
|
- SSH: `player@shineup.me`
|
||||||
|
- Определение IP: через DNS-резолв домена `shineup.me` на момент подключения
|
||||||
|
- Пользователь: `player`
|
||||||
|
- Базовый путь: `/home/player`
|
||||||
|
- Каталог SHiNE: `/home/player/SHiNE`
|
||||||
|
- UI публикация: `/home/player/SHiNE/shine-ui`
|
||||||
|
- Сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
||||||
|
- Данные: `/home/player/SHiNE/shine-server/data/`
|
||||||
|
- Логи сервера: `/home/player/SHiNE/shine-server/logs/app.log`
|
||||||
|
|
||||||
|
## Сервисы
|
||||||
|
|
||||||
|
- `shine-server.service` (systemd)
|
||||||
|
- `caddy.service` (systemd)
|
||||||
|
|
||||||
|
## Caddy
|
||||||
|
|
||||||
|
- Активный конфиг (через systemd `ExecStart`): `/home/player/SHiNE/caddy/Caddyfile`
|
||||||
|
- Для UI:
|
||||||
|
- `root * /home/player/SHiNE/shine-ui`
|
||||||
|
- `try_files {path} /index.html` (SPA fallback)
|
||||||
|
- no-cache заголовки
|
||||||
|
- `reverse_proxy /ws* -> 127.0.0.1:7070`
|
||||||
|
|
||||||
|
## Дополнительно
|
||||||
|
|
||||||
|
- Для отдельной админки `shine_payments` используется каталог:
|
||||||
|
- `/home/player/sites/test-solana-tickets.shineup.me`
|
||||||
|
- Эта публикация используется как временный тестовый сайт для сценариев покупки билетов и выплат `shine_payments`.
|
||||||
|
- Домены этой публикации:
|
||||||
|
- `https://test-solana-tickets.shineup.me`
|
||||||
|
- `https://test-solana-tickets.shiningpeople.ru`
|
||||||
|
- Для всех deploy-скриптов и инструкций использовать именно `player@shineup.me`, без жёсткой фиксации IP.
|
||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.130
|
client.version=1.2.131
|
||||||
server.version=1.2.122
|
server.version=1.2.123
|
||||||
|
|||||||
@ -62,6 +62,11 @@ Push выполнять через `http.extraHeader` (Authorization) без в
|
|||||||
|
|
||||||
- комментарии в `build.gradle` (в корне `shine/`).
|
- комментарии в `build.gradle` (в корне `shine/`).
|
||||||
|
|
||||||
|
Назначение этого UI:
|
||||||
|
|
||||||
|
- это временные тестовые сайты для `shine_payments`;
|
||||||
|
- использовать их для ручной проверки сценариев покупки билетов, менеджерских лимитов и пошаговых выплат.
|
||||||
|
|
||||||
## Известное предупреждение сборки
|
## Известное предупреждение сборки
|
||||||
|
|
||||||
При `cargo build` / `anchor build` для Solana-программ может регулярно появляться предупреждение вида:
|
При `cargo build` / `anchor build` для Solana-программ может регулярно появляться предупреждение вида:
|
||||||
@ -76,6 +81,12 @@ Push выполнять через `http.extraHeader` (Authorization) без в
|
|||||||
|
|
||||||
то это предупреждение считать допустимым и не блокирующим само изменение.
|
то это предупреждение считать допустимым и не блокирующим само изменение.
|
||||||
|
|
||||||
|
Дополнительное правило для рабочих отчётов:
|
||||||
|
|
||||||
|
1. предупреждение именно про `driftsort_main` / `overwrites values in the frame` считать уже зафиксированным;
|
||||||
|
2. при обычных успешных сборках и проверках не выносить его отдельно пользователю как новую проблему;
|
||||||
|
3. упоминать его только если пользователь сам спрашивает про него отдельно или если есть признаки, что изменился характер предупреждения.
|
||||||
|
|
||||||
## Rule: Dictionary Growth Reporting
|
## Rule: Dictionary Growth Reporting
|
||||||
|
|
||||||
Если пользователь просит увеличить количество слов в словарях `shine_login_guard`:
|
Если пользователь просит увеличить количество слов в словарях `shine_login_guard`:
|
||||||
|
|||||||
5771
shine-solana/shine/Cargo.lock
generated
5771
shine-solana/shine/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,12 +9,18 @@
|
|||||||
*
|
*
|
||||||
* По каким URL должен работать UI:
|
* По каким URL должен работать UI:
|
||||||
* https://test-solana-tickets.shineup.me
|
* https://test-solana-tickets.shineup.me
|
||||||
* https://sol.shiningpeople.ru
|
* https://test-solana-tickets.shiningpeople.ru
|
||||||
|
*
|
||||||
|
* Это временные тестовые сайты для сценариев `shine_payments`:
|
||||||
|
* покупка билетов, выдача лимитов менеджеру, ручное добавление билетов и пошаговые выплаты.
|
||||||
|
*
|
||||||
|
* Для SSH/deploy использовать доменное имя `shineup.me`, а не фиксированный IP:
|
||||||
|
* целевой адрес должен разрешаться через DNS на момент деплоя.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
tasks.register("deployUi", Exec) {
|
tasks.register("deployUi", Exec) {
|
||||||
group = "deploy"
|
group = "deploy"
|
||||||
description = "Деплой HTML UI Shine Payments на 45.136.124.227 в /home/player/sites/test-solana-tickets.shineup.me (URL: test-solana-tickets.shineup.me, sol.shiningpeople.ru)"
|
description = "Деплой HTML UI Shine Payments на player@shineup.me в /home/player/sites/test-solana-tickets.shineup.me (URL: test-solana-tickets.shineup.me, test-solana-tickets.shiningpeople.ru)"
|
||||||
|
|
||||||
// Источник локальных UI-страниц:
|
// Источник локальных UI-страниц:
|
||||||
// shine/programs/shine_payments/web/
|
// shine/programs/shine_payments/web/
|
||||||
@ -22,7 +28,7 @@ tasks.register("deployUi", Exec) {
|
|||||||
|
|
||||||
// Целевая директория на сервере:
|
// Целевая директория на сервере:
|
||||||
// /home/player/sites/test-solana-tickets.shineup.me
|
// /home/player/sites/test-solana-tickets.shineup.me
|
||||||
def remoteTarget = "player@45.136.124.227:/home/player/sites/test-solana-tickets.shineup.me/"
|
def remoteTarget = "player@shineup.me:/home/player/sites/test-solana-tickets.shineup.me/"
|
||||||
|
|
||||||
commandLine "rsync", "-av", "--delete", localUiDir, remoteTarget
|
commandLine "rsync", "-av", "--delete", localUiDir, remoteTarget
|
||||||
}
|
}
|
||||||
@ -31,13 +37,13 @@ tasks.register("checkUiRemote", Exec) {
|
|||||||
group = "deploy"
|
group = "deploy"
|
||||||
description = "Проверка на сервере: Caddy-конфиг и наличие новых Program ID в UI"
|
description = "Проверка на сервере: Caddy-конфиг и наличие новых Program ID в UI"
|
||||||
|
|
||||||
commandLine "ssh", "-o", "StrictHostKeyChecking=no", "player@45.136.124.227",
|
commandLine "ssh", "-o", "StrictHostKeyChecking=no", "player@shineup.me",
|
||||||
"set -e; " +
|
"set -e; " +
|
||||||
"echo 'Caddy file:'; " +
|
"echo 'Caddy file:'; " +
|
||||||
"ls -la /home/player/SHiNE/caddy/Caddyfile; " +
|
"ls -la /home/player/SHiNE/caddy/Caddyfile; " +
|
||||||
"echo; " +
|
"echo; " +
|
||||||
"echo 'Домены в Caddy:'; " +
|
"echo 'Домены в Caddy:'; " +
|
||||||
"grep -n 'test-solana-tickets.shineup.me\\|sol.shiningpeople.ru' /home/player/SHiNE/caddy/Caddyfile; " +
|
"grep -n 'test-solana-tickets.shineup.me\\|test-solana-tickets.shiningpeople.ru' /home/player/SHiNE/caddy/Caddyfile; " +
|
||||||
"echo; " +
|
"echo; " +
|
||||||
"echo 'Program ID в загруженных html:'; " +
|
"echo 'Program ID в загруженных html:'; " +
|
||||||
"grep -R -n 'm48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR' /home/player/sites/test-solana-tickets.shineup.me/*.html"
|
"grep -R -n 'm48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR' /home/player/sites/test-solana-tickets.shineup.me/*.html"
|
||||||
|
|||||||
@ -31,9 +31,17 @@
|
|||||||
|
|
||||||
Источник выплат: `inflow_vault` (`ConfigState.inflow_vault`).
|
Источник выплат: `inflow_vault` (`ConfigState.inflow_vault`).
|
||||||
|
|
||||||
|
Порядок очередей:
|
||||||
|
1. сначала `Q1`;
|
||||||
|
2. потом `Q2`;
|
||||||
|
3. потом `Q3`.
|
||||||
|
|
||||||
При шаге выплаты:
|
При шаге выплаты:
|
||||||
1. Из `inflow_vault` переводится `ticket` получателю тикета.
|
1. Из `inflow_vault` переводится `ticket` получателю тикета.
|
||||||
2. Из `inflow_vault` переводится DAO-часть в `dao_wallet`.
|
2. Из `inflow_vault` переводится DAO-часть в `dao_wallet`.
|
||||||
|
- для `Q1` это `1x payout_usd`;
|
||||||
|
- для `Q2` это `2x payout_usd`;
|
||||||
|
- для `Q3` это `3x payout_usd`;
|
||||||
3. Из `inflow_vault` переводится `call_reward_lamports` вызывающему шаг.
|
3. Из `inflow_vault` переводится `call_reward_lamports` вызывающему шаг.
|
||||||
|
|
||||||
Если очереди пусты:
|
Если очереди пусты:
|
||||||
|
|||||||
@ -2,11 +2,17 @@
|
|||||||
|
|
||||||
Документ описывает текущее целевое поведение программы `shine_payments`.
|
Документ описывает текущее целевое поведение программы `shine_payments`.
|
||||||
|
|
||||||
|
Текущая целевая реализация программы:
|
||||||
|
|
||||||
|
- без Anchor;
|
||||||
|
- без использования вспомогательной зависимости из `programs/common`;
|
||||||
|
- на чистом `solana_program` с ручным разбором инструкций, PDA и состояний.
|
||||||
|
|
||||||
Назначение программы:
|
Назначение программы:
|
||||||
|
|
||||||
- хранить общие настройки экономической модели платежей и выплат;
|
- хранить общие настройки экономической модели платежей и выплат;
|
||||||
- принимать покупку тикетов очереди выплат;
|
- принимать покупку тикетов очереди выплат;
|
||||||
- хранить очереди и отдельные ticket PDA;
|
- хранить три очереди и отдельные ticket PDA;
|
||||||
- выдавать лимиты менеджерам на ручное добавление тикетов;
|
- выдавать лимиты менеджерам на ручное добавление тикетов;
|
||||||
- выполнять пошаговые выплаты из inflow-вольта.
|
- выполнять пошаговые выплаты из inflow-вольта.
|
||||||
|
|
||||||
@ -44,6 +50,7 @@
|
|||||||
|
|
||||||
- queue 1 seed prefix: `shine_payments_q1_ticket`
|
- queue 1 seed prefix: `shine_payments_q1_ticket`
|
||||||
- queue 2 seed prefix: `shine_payments_q2_ticket`
|
- queue 2 seed prefix: `shine_payments_q2_ticket`
|
||||||
|
- queue 3 seed prefix: `shine_payments_q3_ticket`
|
||||||
- второй seed: `ticket_index` в little-endian `u64`
|
- второй seed: `ticket_index` в little-endian `u64`
|
||||||
|
|
||||||
### 3.3. Manager allowance PDA
|
### 3.3. Manager allowance PDA
|
||||||
@ -88,6 +95,10 @@
|
|||||||
- `q2_tickets_paid`
|
- `q2_tickets_paid`
|
||||||
- `q2_sum_total_usd_cents`
|
- `q2_sum_total_usd_cents`
|
||||||
- `q2_sum_paid_usd_cents`
|
- `q2_sum_paid_usd_cents`
|
||||||
|
- `q3_tickets_total`
|
||||||
|
- `q3_tickets_paid`
|
||||||
|
- `q3_sum_total_usd_cents`
|
||||||
|
- `q3_sum_paid_usd_cents`
|
||||||
|
|
||||||
### 4.4. `TicketState`
|
### 4.4. `TicketState`
|
||||||
|
|
||||||
@ -109,6 +120,7 @@
|
|||||||
- `manager_wallet: Pubkey`
|
- `manager_wallet: Pubkey`
|
||||||
- `q1_available_usd_cents: u64`
|
- `q1_available_usd_cents: u64`
|
||||||
- `q2_available_usd_cents: u64`
|
- `q2_available_usd_cents: u64`
|
||||||
|
- `q3_available_usd_cents: u64`
|
||||||
|
|
||||||
### 4.6. `VaultState`
|
### 4.6. `VaultState`
|
||||||
|
|
||||||
@ -184,7 +196,7 @@
|
|||||||
### Поведение
|
### Поведение
|
||||||
|
|
||||||
- создаёт `manager_allowance_pda`, если её ещё нет;
|
- создаёт `manager_allowance_pda`, если её ещё нет;
|
||||||
- увеличивает доступные лимиты Q1/Q2 для указанного менеджера;
|
- увеличивает доступные лимиты Q1/Q2/Q3 для указанного менеджера;
|
||||||
- не добавляет тикеты сама.
|
- не добавляет тикеты сама.
|
||||||
|
|
||||||
## 9. Инструкции покупки тикета
|
## 9. Инструкции покупки тикета
|
||||||
@ -226,7 +238,7 @@
|
|||||||
|
|
||||||
### Назначение
|
### Назначение
|
||||||
|
|
||||||
Дать менеджеру возможность вручную создать ticket в `Q1` или `Q2`.
|
Дать менеджеру возможность вручную создать ticket в `Q1`, `Q2` или `Q3`.
|
||||||
|
|
||||||
### Авторизация
|
### Авторизация
|
||||||
|
|
||||||
@ -234,7 +246,7 @@ Signer должен совпадать с `manager_wallet` в `manager_allowance
|
|||||||
|
|
||||||
### Проверки
|
### Проверки
|
||||||
|
|
||||||
- `queue_id` только `1` или `2`;
|
- `queue_id` только `1`, `2` или `3`;
|
||||||
- `payout_usd_cents > 0`;
|
- `payout_usd_cents > 0`;
|
||||||
- доступный allowance по очереди не меньше суммы тикета;
|
- доступный allowance по очереди не меньше суммы тикета;
|
||||||
- ticket PDA ещё не существует.
|
- ticket PDA ещё не существует.
|
||||||
@ -254,7 +266,8 @@ Signer должен совпадать с `manager_wallet` в `manager_allowance
|
|||||||
### Выбор очереди
|
### Выбор очереди
|
||||||
|
|
||||||
- если в `Q1` есть pending ticket, сначала обслуживается `Q1`;
|
- если в `Q1` есть pending ticket, сначала обслуживается `Q1`;
|
||||||
- если `Q1` пустая, обслуживается `Q2`.
|
- если `Q1` пустая, обслуживается `Q2`;
|
||||||
|
- если `Q1` и `Q2` пустые, обслуживается `Q3`.
|
||||||
|
|
||||||
### Что считается pending
|
### Что считается pending
|
||||||
|
|
||||||
@ -291,11 +304,13 @@ next_index = tickets_paid + 1
|
|||||||
|
|
||||||
- для `Q1` = `1`
|
- для `Q1` = `1`
|
||||||
- для `Q2` = `2`
|
- для `Q2` = `2`
|
||||||
|
- для `Q3` = `3`
|
||||||
|
|
||||||
То есть DAO получает:
|
То есть DAO получает:
|
||||||
|
|
||||||
- `1x payout_usd` для Q1;
|
- `1x payout_usd` для Q1;
|
||||||
- `2x payout_usd` для Q2.
|
- `2x payout_usd` для Q2;
|
||||||
|
- `3x payout_usd` для Q3.
|
||||||
|
|
||||||
### Источник денег
|
### Источник денег
|
||||||
|
|
||||||
@ -303,7 +318,7 @@ next_index = tickets_paid + 1
|
|||||||
|
|
||||||
### Если pending нет
|
### Если pending нет
|
||||||
|
|
||||||
Если обе очереди пусты:
|
Если все три очереди пусты:
|
||||||
|
|
||||||
- весь доступный остаток inflow vault переводится в DAO wallet.
|
- весь доступный остаток inflow vault переводится в DAO wallet.
|
||||||
|
|
||||||
@ -324,7 +339,8 @@ next_index = tickets_paid + 1
|
|||||||
Логика:
|
Логика:
|
||||||
|
|
||||||
- если в `Q1` есть pending — следующий ticket определяется в `Q1`;
|
- если в `Q1` есть pending — следующий ticket определяется в `Q1`;
|
||||||
- иначе берётся следующий ticket в `Q2`;
|
- иначе если в `Q2` есть pending — следующий ticket берётся в `Q2`;
|
||||||
|
- иначе следующий ticket берётся в `Q3`;
|
||||||
- если текущий ticket и есть этот ближайший ticket, смена recipient запрещена.
|
- если текущий ticket и есть этот ближайший ticket, смена recipient запрещена.
|
||||||
|
|
||||||
## 13. Pyth oracle и конвертация
|
## 13. Pyth oracle и конвертация
|
||||||
@ -369,8 +385,8 @@ next_index = tickets_paid + 1
|
|||||||
|
|
||||||
- те же PDA seed-правила;
|
- те же PDA seed-правила;
|
||||||
- те же состояния и поля;
|
- те же состояния и поля;
|
||||||
- ту же модель очередей Q1/Q2;
|
- ту же модель очередей Q1/Q2/Q3;
|
||||||
- ту же приоритетность `Q1` над `Q2` в `step_payout`;
|
- ту же приоритетность `Q1 -> Q2 -> Q3` в `step_payout`;
|
||||||
- ту же логику allowance менеджера;
|
- ту же логику allowance менеджера;
|
||||||
- те же oracle-ограничения и округления;
|
- те же oracle-ограничения и округления;
|
||||||
- ту же текущую модель денежных потоков, если она не меняется отдельным решением.
|
- ту же текущую модель денежных потоков, если она не меняется отдельным решением.
|
||||||
|
|||||||
@ -12,17 +12,9 @@ doctest = false
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anchor-lang = "0.31.1"
|
solana-program = "2.1.21"
|
||||||
common = { path = "../common" }
|
|
||||||
pyth-solana-receiver-sdk = { path = "../../.vendor/pyth-crosschain/target_chains/solana/pyth_solana_receiver_sdk" }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
no-entrypoint = []
|
|
||||||
no-idl = []
|
|
||||||
no-log-ix-name = []
|
|
||||||
anchor-debug = []
|
|
||||||
custom-heap = []
|
custom-heap = []
|
||||||
custom-panic = []
|
custom-panic = []
|
||||||
cpi = []
|
|
||||||
idl-build = ["anchor-lang/idl-build"]
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,3 @@
|
|||||||
use common::deploy_config;
|
|
||||||
|
|
||||||
/// `CONFIG_SEED` — seed PDA основного конфига `shine_payments`.
|
/// `CONFIG_SEED` — seed PDA основного конфига `shine_payments`.
|
||||||
pub const CONFIG_SEED: &[u8] = b"shine_payments_config";
|
pub const CONFIG_SEED: &[u8] = b"shine_payments_config";
|
||||||
/// `COEF_LIMIT_SEED` — seed PDA коэффициента, лимита и награды шага выплат.
|
/// `COEF_LIMIT_SEED` — seed PDA коэффициента, лимита и награды шага выплат.
|
||||||
@ -12,6 +10,8 @@ pub const INFLOW_VAULT_SEED: &[u8] = b"shine_payments_inflow_vault";
|
|||||||
pub const Q1_TICKET_SEED: &[u8] = b"shine_payments_q1_ticket";
|
pub const Q1_TICKET_SEED: &[u8] = b"shine_payments_q1_ticket";
|
||||||
/// `Q2_TICKET_SEED` — seed PDA тикетов очереди 2.
|
/// `Q2_TICKET_SEED` — seed PDA тикетов очереди 2.
|
||||||
pub const Q2_TICKET_SEED: &[u8] = b"shine_payments_q2_ticket";
|
pub const Q2_TICKET_SEED: &[u8] = b"shine_payments_q2_ticket";
|
||||||
|
/// `Q3_TICKET_SEED` — seed PDA тикетов очереди 3.
|
||||||
|
pub const Q3_TICKET_SEED: &[u8] = b"shine_payments_q3_ticket";
|
||||||
/// `MANAGER_ALLOWANCE_SEED` — seed PDA лимитов менеджера.
|
/// `MANAGER_ALLOWANCE_SEED` — seed PDA лимитов менеджера.
|
||||||
pub const MANAGER_ALLOWANCE_SEED: &[u8] = b"shine_p_manager_allow";
|
pub const MANAGER_ALLOWANCE_SEED: &[u8] = b"shine_p_manager_allow";
|
||||||
|
|
||||||
@ -45,10 +45,11 @@ pub const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
|
|||||||
|
|
||||||
/// `ORACLE_MAX_AGE_SECS` — максимальный возраст oracle-цены (в секундах), допустимый для расчетов.
|
/// `ORACLE_MAX_AGE_SECS` — максимальный возраст oracle-цены (в секундах), допустимый для расчетов.
|
||||||
pub const ORACLE_MAX_AGE_SECS: u64 = 120;
|
pub const ORACLE_MAX_AGE_SECS: u64 = 120;
|
||||||
/// `PYTH_SOL_USD_FEED_ID` — feed id Pyth для пары SOL/USD (берется из общего deploy-конфига).
|
/// `PYTH_SOL_USD_FEED_ID` — feed id Pyth для пары SOL/USD.
|
||||||
pub const PYTH_SOL_USD_FEED_ID: &str = deploy_config::PYTH_SOL_USD_FEED_ID;
|
pub const PYTH_SOL_USD_FEED_ID: &str =
|
||||||
/// `PYTH_SOL_USD_ACCOUNT` — адрес аккаунта Pyth price update для SOL/USD (берется из общего deploy-конфига).
|
"0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d";
|
||||||
pub const PYTH_SOL_USD_ACCOUNT: &str = deploy_config::PYTH_SOL_USD_ACCOUNT;
|
/// `PYTH_SOL_USD_ACCOUNT` — адрес аккаунта Pyth price update для SOL/USD.
|
||||||
|
pub const PYTH_SOL_USD_ACCOUNT: &str = "7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE";
|
||||||
|
|
||||||
/// `DAO_WALLET` — адрес кошелька DAO-казны для `shine_payments` (берется из общего deploy-конфига).
|
/// `DAO_WALLET` — адрес кошелька DAO-казны для `shine_payments`.
|
||||||
pub const DAO_WALLET: &str = deploy_config::DAO_TREASURY_WALLET;
|
pub const DAO_WALLET: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P";
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
<button id="updateCoefBtn">Обновить</button>
|
<button id="updateCoefBtn">Обновить</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="formula">Лимит покупки Q1 = max(limit_usd_cents - q1_sum_total_usd_cents, 0)</div>
|
<div class="formula">Лимит покупки Q1 = max(limit_usd_cents - q1_sum_total_usd_cents, 0)</div>
|
||||||
<div class="formula">Шаг выплаты Q1 = ticket + dao(1x) + reward; Q2 = ticket + dao(2x) + reward</div>
|
<div class="formula">Шаг выплаты Q1 = ticket + dao(1x) + reward; Q2 = ticket + dao(2x) + reward; Q3 = ticket + dao(3x) + reward</div>
|
||||||
<div id="updateResult" class="muted"></div>
|
<div id="updateResult" class="muted"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -100,6 +100,12 @@
|
|||||||
<div class="row"><button id="loadQ2Btn">Показать очередь 2</button></div>
|
<div class="row"><button id="loadQ2Btn">Показать очередь 2</button></div>
|
||||||
<div id="queue2Table" class="muted"></div>
|
<div id="queue2Table" class="muted"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel">
|
||||||
|
<h3>Очередь 3 (все билеты)</h3>
|
||||||
|
<div class="row"><button id="loadQ3Btn">Показать очередь 3</button></div>
|
||||||
|
<div id="queue3Table" class="muted"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||||
@ -109,13 +115,16 @@
|
|||||||
const RPC_URL = "https://api.devnet.solana.com";
|
const RPC_URL = "https://api.devnet.solana.com";
|
||||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||||
const SEEDS = {
|
const SEEDS = {
|
||||||
config: "shine_payments_v3_config",
|
config: "shine_payments_config",
|
||||||
coef: "shine_payments_v3_coef_limit",
|
coef: "shine_payments_coef_limit",
|
||||||
queues: "shine_payments_v3_queues",
|
queues: "shine_payments_queues",
|
||||||
inflow: "shine_payments_v3_inflow_vault",
|
inflow: "shine_payments_inflow_vault",
|
||||||
ticketQ1: "shine_payments_v3_q1_ticket",
|
ticketQ1: "shine_payments_q1_ticket",
|
||||||
ticketQ2: "shine_payments_v3_q2_ticket",
|
ticketQ2: "shine_payments_q2_ticket",
|
||||||
|
ticketQ3: "shine_payments_q3_ticket",
|
||||||
};
|
};
|
||||||
|
const IX = { init: 1, updateCoefLimit: 2 };
|
||||||
|
const USERS_IX = { initUsersEconomyConfig: 1, updateUsersEconomyConfig: 2 };
|
||||||
const USERS_SEEDS = {
|
const USERS_SEEDS = {
|
||||||
economyConfig: "shine_users_v1_economy_config",
|
economyConfig: "shine_users_v1_economy_config",
|
||||||
};
|
};
|
||||||
@ -162,10 +171,8 @@
|
|||||||
if (!Number.isFinite(v) || v <= 0) throw new Error("Некорректная сумма USD");
|
if (!Number.isFinite(v) || v <= 0) throw new Error("Некорректная сумма USD");
|
||||||
return BigInt(Math.round(v * 100));
|
return BigInt(Math.round(v * 100));
|
||||||
}
|
}
|
||||||
async function ixDiscriminator(name) {
|
function ixData(tag, ...parts) {
|
||||||
const msg = utf8("global:" + name);
|
return concat(new Uint8Array([tag]), ...parts);
|
||||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
|
||||||
return new Uint8Array(hash).slice(0, 8);
|
|
||||||
}
|
}
|
||||||
function isUnauthorizedDao(msg) {
|
function isUnauthorizedDao(msg) {
|
||||||
const s = String(msg || "").toLowerCase();
|
const s = String(msg || "").toLowerCase();
|
||||||
@ -202,7 +209,11 @@
|
|||||||
const q2Paid = readU64(data, o); o += 8;
|
const q2Paid = readU64(data, o); o += 8;
|
||||||
const q2SumTotal = readU64(data, o); o += 8;
|
const q2SumTotal = readU64(data, o); o += 8;
|
||||||
const q2SumPaid = readU64(data, o); o += 8;
|
const q2SumPaid = readU64(data, o); o += 8;
|
||||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid };
|
const q3Total = readU64(data, o); o += 8;
|
||||||
|
const q3Paid = readU64(data, o); o += 8;
|
||||||
|
const q3SumTotal = readU64(data, o); o += 8;
|
||||||
|
const q3SumPaid = readU64(data, o); o += 8;
|
||||||
|
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid, q3Total, q3Paid, q3SumTotal, q3SumPaid };
|
||||||
}
|
}
|
||||||
function parseTicket(data) {
|
function parseTicket(data) {
|
||||||
let o = 0;
|
let o = 0;
|
||||||
@ -263,7 +274,7 @@
|
|||||||
return { usersEconomyConfigPda };
|
return { usersEconomyConfigPda };
|
||||||
}
|
}
|
||||||
function ticketPda(queueId, index) {
|
function ticketPda(queueId, index) {
|
||||||
const seed = queueId === 1 ? SEEDS.ticketQ1 : SEEDS.ticketQ2;
|
const seed = queueId === 1 ? SEEDS.ticketQ1 : (queueId === 2 ? SEEDS.ticketQ2 : SEEDS.ticketQ3);
|
||||||
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
||||||
return pda;
|
return pda;
|
||||||
}
|
}
|
||||||
@ -320,6 +331,7 @@
|
|||||||
<div>Доступно в inflow (сверх ренты): <b>${lamportsToSolStr(core.inflowAvailable)} SOL</b></div>
|
<div>Доступно в inflow (сверх ренты): <b>${lamportsToSolStr(core.inflowAvailable)} SOL</b></div>
|
||||||
<div>Q1: total=${core.queues.q1Total}, paid=${core.queues.q1Paid}, sum_total=${centsToUsdStr(core.queues.q1SumTotal)} USD, sum_paid=${centsToUsdStr(core.queues.q1SumPaid)} USD</div>
|
<div>Q1: total=${core.queues.q1Total}, paid=${core.queues.q1Paid}, sum_total=${centsToUsdStr(core.queues.q1SumTotal)} USD, sum_paid=${centsToUsdStr(core.queues.q1SumPaid)} USD</div>
|
||||||
<div>Q2: total=${core.queues.q2Total}, paid=${core.queues.q2Paid}, sum_total=${centsToUsdStr(core.queues.q2SumTotal)} USD, sum_paid=${centsToUsdStr(core.queues.q2SumPaid)} USD</div>
|
<div>Q2: total=${core.queues.q2Total}, paid=${core.queues.q2Paid}, sum_total=${centsToUsdStr(core.queues.q2SumTotal)} USD, sum_paid=${centsToUsdStr(core.queues.q2SumPaid)} USD</div>
|
||||||
|
<div>Q3: total=${core.queues.q3Total}, paid=${core.queues.q3Paid}, sum_total=${centsToUsdStr(core.queues.q3SumTotal)} USD, sum_paid=${centsToUsdStr(core.queues.q3SumPaid)} USD</div>
|
||||||
`;
|
`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
el.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
el.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
||||||
@ -362,7 +374,6 @@
|
|||||||
else if (!provider.isConnected) await provider.connect();
|
else if (!provider.isConnected) await provider.connect();
|
||||||
|
|
||||||
const pdas = derivePdas();
|
const pdas = derivePdas();
|
||||||
const disc = await ixDiscriminator("init");
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
||||||
@ -371,7 +382,7 @@
|
|||||||
{ pubkey: pdas.inflowPda, isSigner: false, isWritable: true },
|
{ pubkey: pdas.inflowPda, isSigner: false, isWritable: true },
|
||||||
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
|
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
|
||||||
];
|
];
|
||||||
const ix = new solanaWeb3.TransactionInstruction({ programId: PROGRAM_ID, keys, data: disc });
|
const ix = new solanaWeb3.TransactionInstruction({ programId: PROGRAM_ID, keys, data: ixData(IX.init) });
|
||||||
const sig = await sendInstruction(ix);
|
const sig = await sendInstruction(ix);
|
||||||
out.innerHTML = `<span class="ok">Init выполнен. Tx: <code>${sig}</code></span>`;
|
out.innerHTML = `<span class="ok">Init выполнен. Tx: <code>${sig}</code></span>`;
|
||||||
await refreshAll();
|
await refreshAll();
|
||||||
@ -397,8 +408,7 @@
|
|||||||
const rewardLamports = solToLamports(document.getElementById("rewardInput").value.trim());
|
const rewardLamports = solToLamports(document.getElementById("rewardInput").value.trim());
|
||||||
if (rewardLamports > MAX_REWARD_LAMPORTS) throw new Error("Награда не должна быть больше 0.01 SOL");
|
if (rewardLamports > MAX_REWARD_LAMPORTS) throw new Error("Награда не должна быть больше 0.01 SOL");
|
||||||
|
|
||||||
const disc = await ixDiscriminator("update_coef_limit");
|
const data = ixData(IX.updateCoefLimit, u64ToBytes(coefPpm), u64ToBytes(limitUsdCents), u64ToBytes(rewardLamports));
|
||||||
const data = concat(disc, u64ToBytes(coefPpm), u64ToBytes(limitUsdCents), u64ToBytes(rewardLamports));
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: core.pdas.configPda, isSigner: false, isWritable: true },
|
{ pubkey: core.pdas.configPda, isSigner: false, isWritable: true },
|
||||||
@ -427,13 +437,12 @@
|
|||||||
if (!walletPubkey) await connectWallet();
|
if (!walletPubkey) await connectWallet();
|
||||||
else if (!provider.isConnected) await provider.connect();
|
else if (!provider.isConnected) await provider.connect();
|
||||||
const usersPdas = deriveUsersPdas();
|
const usersPdas = deriveUsersPdas();
|
||||||
const disc = await ixDiscriminator("init_users_economy_config");
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: usersPdas.usersEconomyConfigPda, isSigner: false, isWritable: true },
|
{ pubkey: usersPdas.usersEconomyConfigPda, isSigner: false, isWritable: true },
|
||||||
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
|
{ pubkey: solanaWeb3.SystemProgram.programId, isSigner: false, isWritable: false },
|
||||||
];
|
];
|
||||||
const ix = new solanaWeb3.TransactionInstruction({ programId: USERS_PROGRAM_ID, keys, data: disc });
|
const ix = new solanaWeb3.TransactionInstruction({ programId: USERS_PROGRAM_ID, keys, data: ixData(USERS_IX.initUsersEconomyConfig) });
|
||||||
const sig = await sendInstruction(ix);
|
const sig = await sendInstruction(ix);
|
||||||
out.innerHTML = `<span class="ok">Users Economy init выполнен. Tx: <code>${sig}</code></span>`;
|
out.innerHTML = `<span class="ok">Users Economy init выполнен. Tx: <code>${sig}</code></span>`;
|
||||||
await refreshUsersEconomy();
|
await refreshUsersEconomy();
|
||||||
@ -455,9 +464,8 @@
|
|||||||
const startBonusLimit = BigInt(document.getElementById("usersBonusInput").value.trim());
|
const startBonusLimit = BigInt(document.getElementById("usersBonusInput").value.trim());
|
||||||
if (startBonusLimit < 0n) throw new Error("Стартовый бонус не может быть отрицательным");
|
if (startBonusLimit < 0n) throw new Error("Стартовый бонус не может быть отрицательным");
|
||||||
|
|
||||||
const disc = await ixDiscriminator("update_users_economy_config");
|
const data = ixData(
|
||||||
const data = concat(
|
USERS_IX.updateUsersEconomyConfig,
|
||||||
disc,
|
|
||||||
u64ToBytes(registrationFeeLamports),
|
u64ToBytes(registrationFeeLamports),
|
||||||
u64ToBytes(lamportsPerLimitStep),
|
u64ToBytes(lamportsPerLimitStep),
|
||||||
u64ToBytes(startBonusLimit)
|
u64ToBytes(startBonusLimit)
|
||||||
@ -483,17 +491,17 @@
|
|||||||
|
|
||||||
function currentDebtBeforeTicket(ticket, queues) {
|
function currentDebtBeforeTicket(ticket, queues) {
|
||||||
if (ticket.isPaid) return 0n;
|
if (ticket.isPaid) return 0n;
|
||||||
const paidSum = ticket.queueId === 1 ? queues.q1SumPaid : queues.q2SumPaid;
|
const paidSum = ticket.queueId === 1 ? queues.q1SumPaid : (ticket.queueId === 2 ? queues.q2SumPaid : queues.q3SumPaid);
|
||||||
return ticket.debtBefore > paidSum ? (ticket.debtBefore - paidSum) : 0n;
|
return ticket.debtBefore > paidSum ? (ticket.debtBefore - paidSum) : 0n;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function showQueue(queueId) {
|
async function showQueue(queueId) {
|
||||||
const out = document.getElementById(queueId === 1 ? "queue1Table" : "queue2Table");
|
const out = document.getElementById(queueId === 1 ? "queue1Table" : (queueId === 2 ? "queue2Table" : "queue3Table"));
|
||||||
out.textContent = "Загрузка...";
|
out.textContent = "Загрузка...";
|
||||||
try {
|
try {
|
||||||
const core = await loadCore();
|
const core = await loadCore();
|
||||||
if (core.notInited) throw new Error("Сначала выполните init");
|
if (core.notInited) throw new Error("Сначала выполните init");
|
||||||
const total = queueId === 1 ? core.queues.q1Total : core.queues.q2Total;
|
const total = queueId === 1 ? core.queues.q1Total : (queueId === 2 ? core.queues.q2Total : core.queues.q3Total);
|
||||||
if (total === 0n) {
|
if (total === 0n) {
|
||||||
out.innerHTML = `<span class="muted">Очередь ${queueId} пока пустая.</span>`;
|
out.innerHTML = `<span class="muted">Очередь ${queueId} пока пустая.</span>`;
|
||||||
return;
|
return;
|
||||||
@ -555,6 +563,7 @@
|
|||||||
document.getElementById("usersUpdateBtn").addEventListener("click", updateUsersEconomy);
|
document.getElementById("usersUpdateBtn").addEventListener("click", updateUsersEconomy);
|
||||||
document.getElementById("loadQ1Btn").addEventListener("click", () => showQueue(1));
|
document.getElementById("loadQ1Btn").addEventListener("click", () => showQueue(1));
|
||||||
document.getElementById("loadQ2Btn").addEventListener("click", () => showQueue(2));
|
document.getElementById("loadQ2Btn").addEventListener("click", () => showQueue(2));
|
||||||
|
document.getElementById("loadQ3Btn").addEventListener("click", () => showQueue(3));
|
||||||
refreshAll();
|
refreshAll();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -85,11 +85,12 @@
|
|||||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||||
|
|
||||||
const SEEDS = {
|
const SEEDS = {
|
||||||
config: "shine_payments_v3_config",
|
config: "shine_payments_config",
|
||||||
coef: "shine_payments_v3_coef_limit",
|
coef: "shine_payments_coef_limit",
|
||||||
queues: "shine_payments_v3_queues",
|
queues: "shine_payments_queues",
|
||||||
ticketQ1: "shine_payments_v3_q1_ticket",
|
ticketQ1: "shine_payments_q1_ticket",
|
||||||
};
|
};
|
||||||
|
const IX = { buyTicketUsd: 5, buyTicketSol: 6 };
|
||||||
|
|
||||||
const COEF_SCALE = 1_000_000n;
|
const COEF_SCALE = 1_000_000n;
|
||||||
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
||||||
@ -177,10 +178,8 @@
|
|||||||
return (cents * (10_000n - bp)) / 10_000n;
|
return (cents * (10_000n - bp)) / 10_000n;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ixDiscriminator(name) {
|
function ixData(tag, ...parts) {
|
||||||
const msg = utf8("global:" + name);
|
return concat(new Uint8Array([tag]), ...parts);
|
||||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
|
||||||
return new Uint8Array(hash).slice(0, 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseConfig(data) {
|
function parseConfig(data) {
|
||||||
@ -209,7 +208,11 @@
|
|||||||
const q2Paid = readU64(data, o); o += 8;
|
const q2Paid = readU64(data, o); o += 8;
|
||||||
const q2SumTotal = readU64(data, o); o += 8;
|
const q2SumTotal = readU64(data, o); o += 8;
|
||||||
const q2SumPaid = readU64(data, o); o += 8;
|
const q2SumPaid = readU64(data, o); o += 8;
|
||||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid };
|
const q3Total = readU64(data, o); o += 8;
|
||||||
|
const q3Paid = readU64(data, o); o += 8;
|
||||||
|
const q3SumTotal = readU64(data, o); o += 8;
|
||||||
|
const q3SumPaid = readU64(data, o); o += 8;
|
||||||
|
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid, q3Total, q3Paid, q3SumTotal, q3SumPaid };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProvider() {
|
function getProvider() {
|
||||||
@ -354,8 +357,7 @@
|
|||||||
|
|
||||||
const nextIndex = queues.q1Total + 1n;
|
const nextIndex = queues.q1Total + 1n;
|
||||||
const [ticketPda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(SEEDS.ticketQ1), u64ToBytes(nextIndex)], PROGRAM_ID);
|
const [ticketPda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(SEEDS.ticketQ1), u64ToBytes(nextIndex)], PROGRAM_ID);
|
||||||
const disc = await ixDiscriminator("buy_ticket_usd");
|
const data = ixData(IX.buyTicketUsd, u64ToBytes(usdCents), u64ToBytes(maxPayLamports), recipient.toBytes());
|
||||||
const data = concat(disc, u64ToBytes(usdCents), u64ToBytes(maxPayLamports), recipient.toBytes());
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
||||||
@ -394,8 +396,7 @@
|
|||||||
|
|
||||||
const nextIndex = queues.q1Total + 1n;
|
const nextIndex = queues.q1Total + 1n;
|
||||||
const [ticketPda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(SEEDS.ticketQ1), u64ToBytes(nextIndex)], PROGRAM_ID);
|
const [ticketPda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(SEEDS.ticketQ1), u64ToBytes(nextIndex)], PROGRAM_ID);
|
||||||
const disc = await ixDiscriminator("buy_ticket_sol");
|
const data = ixData(IX.buyTicketSol, u64ToBytes(lamports), u64ToBytes(minUsdCents), recipient.toBytes());
|
||||||
const data = concat(disc, u64ToBytes(lamports), u64ToBytes(minUsdCents), recipient.toBytes());
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
{ pubkey: pdas.configPda, isSigner: false, isWritable: true },
|
||||||
|
|||||||
@ -65,6 +65,7 @@
|
|||||||
<label>Кошелек менеджера: <input id="managerWallet" placeholder="Base58" /></label>
|
<label>Кошелек менеджера: <input id="managerWallet" placeholder="Base58" /></label>
|
||||||
<label>Добавить лимит Q1 (USD): <input id="addQ1" value="100" /></label>
|
<label>Добавить лимит Q1 (USD): <input id="addQ1" value="100" /></label>
|
||||||
<label>Добавить лимит Q2 (USD): <input id="addQ2" value="50" /></label>
|
<label>Добавить лимит Q2 (USD): <input id="addQ2" value="50" /></label>
|
||||||
|
<label>Добавить лимит Q3 (USD): <input id="addQ3" value="25" /></label>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<button id="grantBtn">Выдать лимиты</button>
|
<button id="grantBtn">Выдать лимиты</button>
|
||||||
@ -87,9 +88,10 @@
|
|||||||
const RPC_URL = "https://api.devnet.solana.com";
|
const RPC_URL = "https://api.devnet.solana.com";
|
||||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||||
const SEEDS = {
|
const SEEDS = {
|
||||||
config: "shine_payments_v3_config",
|
config: "shine_payments_config",
|
||||||
managerAllowance: "shine_p_v3_manager_allow",
|
managerAllowance: "shine_p_manager_allow",
|
||||||
};
|
};
|
||||||
|
const IX = { grantManagerLimits: 3 };
|
||||||
let walletPubkey = null;
|
let walletPubkey = null;
|
||||||
let configCache = null;
|
let configCache = null;
|
||||||
document.getElementById("programId").textContent = PROGRAM_ID.toBase58();
|
document.getElementById("programId").textContent = PROGRAM_ID.toBase58();
|
||||||
@ -124,10 +126,8 @@
|
|||||||
if (!Number.isFinite(v) || v < 0) throw new Error("Некорректная сумма USD");
|
if (!Number.isFinite(v) || v < 0) throw new Error("Некорректная сумма USD");
|
||||||
return BigInt(Math.round(v * 100));
|
return BigInt(Math.round(v * 100));
|
||||||
}
|
}
|
||||||
async function ixDiscriminator(name) {
|
function ixData(tag, ...parts) {
|
||||||
const msg = utf8("global:" + name);
|
return concat(new Uint8Array([tag]), ...parts);
|
||||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
|
||||||
return new Uint8Array(hash).slice(0, 8);
|
|
||||||
}
|
}
|
||||||
function isUnauthorizedDao(msg) {
|
function isUnauthorizedDao(msg) {
|
||||||
const s = String(msg || "").toLowerCase();
|
const s = String(msg || "").toLowerCase();
|
||||||
@ -147,7 +147,8 @@
|
|||||||
const manager = new solanaWeb3.PublicKey(data.slice(o, o + 32)); o += 32;
|
const manager = new solanaWeb3.PublicKey(data.slice(o, o + 32)); o += 32;
|
||||||
const q1 = readU64(data, o); o += 8;
|
const q1 = readU64(data, o); o += 8;
|
||||||
const q2 = readU64(data, o); o += 8;
|
const q2 = readU64(data, o); o += 8;
|
||||||
return { version, manager, q1, q2 };
|
const q3 = readU64(data, o); o += 8;
|
||||||
|
return { version, manager, q1, q2, q3 };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProvider() {
|
function getProvider() {
|
||||||
@ -216,11 +217,11 @@
|
|||||||
const manager = new solanaWeb3.PublicKey(document.getElementById("managerWallet").value.trim());
|
const manager = new solanaWeb3.PublicKey(document.getElementById("managerWallet").value.trim());
|
||||||
const addQ1 = usdToCents(document.getElementById("addQ1").value.trim());
|
const addQ1 = usdToCents(document.getElementById("addQ1").value.trim());
|
||||||
const addQ2 = usdToCents(document.getElementById("addQ2").value.trim());
|
const addQ2 = usdToCents(document.getElementById("addQ2").value.trim());
|
||||||
if (addQ1 === 0n && addQ2 === 0n) throw new Error("Нужно указать сумму хотя бы для одной очереди.");
|
const addQ3 = usdToCents(document.getElementById("addQ3").value.trim());
|
||||||
|
if (addQ1 === 0n && addQ2 === 0n && addQ3 === 0n) throw new Error("Нужно указать сумму хотя бы для одной очереди.");
|
||||||
|
|
||||||
const allowancePda = deriveManagerAllowancePda(manager);
|
const allowancePda = deriveManagerAllowancePda(manager);
|
||||||
const disc = await ixDiscriminator("grant_manager_limits");
|
const data = ixData(IX.grantManagerLimits, manager.toBytes(), u64ToBytes(addQ1), u64ToBytes(addQ2), u64ToBytes(addQ3));
|
||||||
const data = concat(disc, manager.toBytes(), u64ToBytes(addQ1), u64ToBytes(addQ2));
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: configPda, isSigner: false, isWritable: true },
|
{ pubkey: configPda, isSigner: false, isWritable: true },
|
||||||
@ -258,6 +259,7 @@
|
|||||||
<div>PDA: <code>${allowancePda.toBase58()}</code></div>
|
<div>PDA: <code>${allowancePda.toBase58()}</code></div>
|
||||||
<div>Доступно Q1: <b>${centsToUsdStr(st.q1)} USD</b></div>
|
<div>Доступно Q1: <b>${centsToUsdStr(st.q1)} USD</b></div>
|
||||||
<div>Доступно Q2: <b>${centsToUsdStr(st.q2)} USD</b></div>
|
<div>Доступно Q2: <b>${centsToUsdStr(st.q2)} USD</b></div>
|
||||||
|
<div>Доступно Q3: <b>${centsToUsdStr(st.q3)} USD</b></div>
|
||||||
`;
|
`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
out.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
out.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
||||||
|
|||||||
@ -62,6 +62,7 @@
|
|||||||
<select id="queueId">
|
<select id="queueId">
|
||||||
<option value="1">Очередь 1</option>
|
<option value="1">Очередь 1</option>
|
||||||
<option value="2">Очередь 2</option>
|
<option value="2">Очередь 2</option>
|
||||||
|
<option value="3">Очередь 3</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>Получатель: <input id="recipient" placeholder="Base58 адрес" /></label>
|
<label>Получатель: <input id="recipient" placeholder="Base58 адрес" /></label>
|
||||||
@ -80,11 +81,13 @@
|
|||||||
const RPC_URL = "https://api.devnet.solana.com";
|
const RPC_URL = "https://api.devnet.solana.com";
|
||||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||||
const SEEDS = {
|
const SEEDS = {
|
||||||
managerAllowance: "shine_p_v3_manager_allow",
|
managerAllowance: "shine_p_manager_allow",
|
||||||
queues: "shine_payments_v3_queues",
|
queues: "shine_payments_queues",
|
||||||
ticketQ1: "shine_payments_v3_q1_ticket",
|
ticketQ1: "shine_payments_q1_ticket",
|
||||||
ticketQ2: "shine_payments_v3_q2_ticket",
|
ticketQ2: "shine_payments_q2_ticket",
|
||||||
|
ticketQ3: "shine_payments_q3_ticket",
|
||||||
};
|
};
|
||||||
|
const IX = { managerAddTicket: 7 };
|
||||||
let walletPubkey = null;
|
let walletPubkey = null;
|
||||||
let queuesCache = null;
|
let queuesCache = null;
|
||||||
document.getElementById("programId").textContent = PROGRAM_ID.toBase58();
|
document.getElementById("programId").textContent = PROGRAM_ID.toBase58();
|
||||||
@ -119,10 +122,8 @@
|
|||||||
if (!Number.isFinite(v) || v <= 0) throw new Error("Некорректная сумма USD");
|
if (!Number.isFinite(v) || v <= 0) throw new Error("Некорректная сумма USD");
|
||||||
return BigInt(Math.round(v * 100));
|
return BigInt(Math.round(v * 100));
|
||||||
}
|
}
|
||||||
async function ixDiscriminator(name) {
|
function ixData(tag, ...parts) {
|
||||||
const msg = utf8("global:" + name);
|
return concat(new Uint8Array([tag]), ...parts);
|
||||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
|
||||||
return new Uint8Array(hash).slice(0, 8);
|
|
||||||
}
|
}
|
||||||
function isManagerErrors(msg) {
|
function isManagerErrors(msg) {
|
||||||
const s = String(msg || "").toLowerCase();
|
const s = String(msg || "").toLowerCase();
|
||||||
@ -135,7 +136,8 @@
|
|||||||
const manager = new solanaWeb3.PublicKey(data.slice(o, o + 32)); o += 32;
|
const manager = new solanaWeb3.PublicKey(data.slice(o, o + 32)); o += 32;
|
||||||
const q1 = readU64(data, o); o += 8;
|
const q1 = readU64(data, o); o += 8;
|
||||||
const q2 = readU64(data, o); o += 8;
|
const q2 = readU64(data, o); o += 8;
|
||||||
return { version, manager, q1, q2 };
|
const q3 = readU64(data, o); o += 8;
|
||||||
|
return { version, manager, q1, q2, q3 };
|
||||||
}
|
}
|
||||||
function parseQueues(data) {
|
function parseQueues(data) {
|
||||||
let o = 0;
|
let o = 0;
|
||||||
@ -148,7 +150,11 @@
|
|||||||
const q2Paid = readU64(data, o); o += 8;
|
const q2Paid = readU64(data, o); o += 8;
|
||||||
const q2SumTotal = readU64(data, o); o += 8;
|
const q2SumTotal = readU64(data, o); o += 8;
|
||||||
const q2SumPaid = readU64(data, o); o += 8;
|
const q2SumPaid = readU64(data, o); o += 8;
|
||||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid };
|
const q3Total = readU64(data, o); o += 8;
|
||||||
|
const q3Paid = readU64(data, o); o += 8;
|
||||||
|
const q3SumTotal = readU64(data, o); o += 8;
|
||||||
|
const q3SumPaid = readU64(data, o); o += 8;
|
||||||
|
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid, q3Total, q3Paid, q3SumTotal, q3SumPaid };
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProvider() {
|
function getProvider() {
|
||||||
@ -187,7 +193,7 @@
|
|||||||
return pda;
|
return pda;
|
||||||
}
|
}
|
||||||
function deriveTicketPda(queueId, index) {
|
function deriveTicketPda(queueId, index) {
|
||||||
const seed = queueId === 1 ? SEEDS.ticketQ1 : SEEDS.ticketQ2;
|
const seed = queueId === 1 ? SEEDS.ticketQ1 : (queueId === 2 ? SEEDS.ticketQ2 : SEEDS.ticketQ3);
|
||||||
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
||||||
return pda;
|
return pda;
|
||||||
}
|
}
|
||||||
@ -223,6 +229,7 @@
|
|||||||
<div>PDA лимитов: <code>${core.allowancePda.toBase58()}</code></div>
|
<div>PDA лимитов: <code>${core.allowancePda.toBase58()}</code></div>
|
||||||
<div>Доступно Q1: <b>${centsToUsdStr(core.allowance.q1)} USD</b></div>
|
<div>Доступно Q1: <b>${centsToUsdStr(core.allowance.q1)} USD</b></div>
|
||||||
<div>Доступно Q2: <b>${centsToUsdStr(core.allowance.q2)} USD</b></div>
|
<div>Доступно Q2: <b>${centsToUsdStr(core.allowance.q2)} USD</b></div>
|
||||||
|
<div>Доступно Q3: <b>${centsToUsdStr(core.allowance.q3)} USD</b></div>
|
||||||
`;
|
`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
el.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
el.innerHTML = `<span class="err">${String(e.message || e)}</span>`;
|
||||||
@ -241,15 +248,16 @@
|
|||||||
if (!core.allowance) throw new Error("Для этого кошелька лимиты менеджера не выданы.");
|
if (!core.allowance) throw new Error("Для этого кошелька лимиты менеджера не выданы.");
|
||||||
|
|
||||||
const queueId = Number(document.getElementById("queueId").value);
|
const queueId = Number(document.getElementById("queueId").value);
|
||||||
if (queueId !== 1 && queueId !== 2) throw new Error("Очередь должна быть 1 или 2");
|
if (![1, 2, 3].includes(queueId)) throw new Error("Очередь должна быть 1, 2 или 3");
|
||||||
const recipient = new solanaWeb3.PublicKey(document.getElementById("recipient").value.trim());
|
const recipient = new solanaWeb3.PublicKey(document.getElementById("recipient").value.trim());
|
||||||
const payout = usdToCents(document.getElementById("payoutUsd").value.trim());
|
const payout = usdToCents(document.getElementById("payoutUsd").value.trim());
|
||||||
|
|
||||||
const nextIndex = queueId === 1 ? (core.queues.q1Total + 1n) : (core.queues.q2Total + 1n);
|
const nextIndex = queueId === 1
|
||||||
|
? (core.queues.q1Total + 1n)
|
||||||
|
: (queueId === 2 ? (core.queues.q2Total + 1n) : (core.queues.q3Total + 1n));
|
||||||
const ticketPda = deriveTicketPda(queueId, nextIndex);
|
const ticketPda = deriveTicketPda(queueId, nextIndex);
|
||||||
|
|
||||||
const disc = await ixDiscriminator("manager_add_ticket");
|
const data = ixData(IX.managerAddTicket, new Uint8Array([queueId]), recipient.toBytes(), u64ToBytes(payout));
|
||||||
const data = concat(disc, new Uint8Array([queueId]), recipient.toBytes(), u64ToBytes(payout));
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: core.allowancePda, isSigner: false, isWritable: true },
|
{ pubkey: core.allowancePda, isSigner: false, isWritable: true },
|
||||||
|
|||||||
@ -83,12 +83,14 @@
|
|||||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||||
|
|
||||||
const SEEDS = {
|
const SEEDS = {
|
||||||
config: "shine_payments_v3_config",
|
config: "shine_payments_config",
|
||||||
coef: "shine_payments_v3_coef_limit",
|
coef: "shine_payments_coef_limit",
|
||||||
queues: "shine_payments_v3_queues",
|
queues: "shine_payments_queues",
|
||||||
ticketQ1: "shine_payments_v3_q1_ticket",
|
ticketQ1: "shine_payments_q1_ticket",
|
||||||
ticketQ2: "shine_payments_v3_q2_ticket",
|
ticketQ2: "shine_payments_q2_ticket",
|
||||||
|
ticketQ3: "shine_payments_q3_ticket",
|
||||||
};
|
};
|
||||||
|
const IX = { changeTicketRecipient: 9, stepPayout: 8 };
|
||||||
|
|
||||||
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
const LAMPORTS_PER_SOL = 1_000_000_000n;
|
||||||
let walletPubkey = null;
|
let walletPubkey = null;
|
||||||
@ -133,10 +135,8 @@
|
|||||||
function centsToUsdStr(c) {
|
function centsToUsdStr(c) {
|
||||||
return trimZeros((Number(c) / 100).toFixed(2));
|
return trimZeros((Number(c) / 100).toFixed(2));
|
||||||
}
|
}
|
||||||
async function ixDiscriminator(name) {
|
function ixData(tag, ...parts) {
|
||||||
const msg = utf8("global:" + name);
|
return concat(new Uint8Array([tag]), ...parts);
|
||||||
const hash = await crypto.subtle.digest("SHA-256", msg);
|
|
||||||
return new Uint8Array(hash).slice(0, 8);
|
|
||||||
}
|
}
|
||||||
function isNotEnoughForStep(msg) {
|
function isNotEnoughForStep(msg) {
|
||||||
const s = String(msg || "").toLowerCase();
|
const s = String(msg || "").toLowerCase();
|
||||||
@ -187,7 +187,11 @@
|
|||||||
const q2Paid = readU64(data, o); o += 8;
|
const q2Paid = readU64(data, o); o += 8;
|
||||||
const q2SumTotal = readU64(data, o); o += 8;
|
const q2SumTotal = readU64(data, o); o += 8;
|
||||||
const q2SumPaid = readU64(data, o); o += 8;
|
const q2SumPaid = readU64(data, o); o += 8;
|
||||||
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid };
|
const q3Total = readU64(data, o); o += 8;
|
||||||
|
const q3Paid = readU64(data, o); o += 8;
|
||||||
|
const q3SumTotal = readU64(data, o); o += 8;
|
||||||
|
const q3SumPaid = readU64(data, o); o += 8;
|
||||||
|
return { version, q1Total, q1Paid, q1SumTotal, q1SumPaid, q2Total, q2Paid, q2SumTotal, q2SumPaid, q3Total, q3Paid, q3SumTotal, q3SumPaid };
|
||||||
}
|
}
|
||||||
function parseTicket(data) {
|
function parseTicket(data) {
|
||||||
let o = 0;
|
let o = 0;
|
||||||
@ -232,7 +236,7 @@
|
|||||||
return { configPda, coefPda, queuesPda };
|
return { configPda, coefPda, queuesPda };
|
||||||
}
|
}
|
||||||
function deriveTicketPda(queueId, index) {
|
function deriveTicketPda(queueId, index) {
|
||||||
const seed = queueId === 1 ? SEEDS.ticketQ1 : SEEDS.ticketQ2;
|
const seed = queueId === 1 ? SEEDS.ticketQ1 : (queueId === 2 ? SEEDS.ticketQ2 : SEEDS.ticketQ3);
|
||||||
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
const [pda] = solanaWeb3.PublicKey.findProgramAddressSync([utf8(seed), u64ToBytes(index)], PROGRAM_ID);
|
||||||
return pda;
|
return pda;
|
||||||
}
|
}
|
||||||
@ -262,14 +266,16 @@
|
|||||||
function nextStepQueue(queues) {
|
function nextStepQueue(queues) {
|
||||||
const q1Pending = queues.q1Total - queues.q1Paid;
|
const q1Pending = queues.q1Total - queues.q1Paid;
|
||||||
const q2Pending = queues.q2Total - queues.q2Paid;
|
const q2Pending = queues.q2Total - queues.q2Paid;
|
||||||
|
const q3Pending = queues.q3Total - queues.q3Paid;
|
||||||
if (q1Pending > 0n) return 1;
|
if (q1Pending > 0n) return 1;
|
||||||
if (q2Pending > 0n) return 2;
|
if (q2Pending > 0n) return 2;
|
||||||
|
if (q3Pending > 0n) return 3;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
function nextPayoutTicket(queues) {
|
function nextPayoutTicket(queues) {
|
||||||
const queue = nextStepQueue(queues);
|
const queue = nextStepQueue(queues);
|
||||||
if (queue === 0) return null;
|
if (queue === 0) return null;
|
||||||
const index = queue === 1 ? (queues.q1Paid + 1n) : (queues.q2Paid + 1n);
|
const index = queue === 1 ? (queues.q1Paid + 1n) : (queue === 2 ? (queues.q2Paid + 1n) : (queues.q3Paid + 1n));
|
||||||
return { queue, index };
|
return { queue, index };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,13 +290,13 @@
|
|||||||
<div>Курс SOL/USD (Pyth): <b>${trimZeros((Number(core.pyth.num) / Number(core.pyth.den) / 100).toFixed(6))}</b>, возраст <b>${pythAge} сек</b></div>
|
<div>Курс SOL/USD (Pyth): <b>${trimZeros((Number(core.pyth.num) / Number(core.pyth.den) / 100).toFixed(6))}</b>, возраст <b>${pythAge} сек</b></div>
|
||||||
<div>DAO: <code>${core.config.dao.toBase58()}</code></div>
|
<div>DAO: <code>${core.config.dao.toBase58()}</code></div>
|
||||||
<div class="muted">Сейчас это тестовый DAO-кошелек. В production здесь будет адрес реального DAO.</div>
|
<div class="muted">Сейчас это тестовый DAO-кошелек. В production здесь будет адрес реального DAO.</div>
|
||||||
<div>Обе очереди пусты/полностью выплачены.</div>
|
<div>Все очереди пусты/полностью выплачены.</div>
|
||||||
<div>На inflow vault доступно (сверх ренты): <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
<div>На inflow vault доступно (сверх ренты): <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
||||||
<div class="warn">При шаге эта сумма уйдет в DAO, награда не начисляется.</div>
|
<div class="warn">При шаге эта сумма уйдет в DAO, награда не начисляется.</div>
|
||||||
`;
|
`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : core.queues.q2Paid + 1n;
|
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : (queue === 2 ? core.queues.q2Paid + 1n : core.queues.q3Paid + 1n);
|
||||||
const nextPda = deriveTicketPda(queue, nextIndex);
|
const nextPda = deriveTicketPda(queue, nextIndex);
|
||||||
const nextAi = await connection.getAccountInfo(nextPda, "confirmed");
|
const nextAi = await connection.getAccountInfo(nextPda, "confirmed");
|
||||||
if (!nextAi) {
|
if (!nextAi) {
|
||||||
@ -299,7 +305,7 @@
|
|||||||
}
|
}
|
||||||
const next = parseTicket(nextAi.data);
|
const next = parseTicket(nextAi.data);
|
||||||
const ticketLamports = usdCentsToLamportsCeil(next.payoutUsdCents, core.pyth);
|
const ticketLamports = usdCentsToLamportsCeil(next.payoutUsdCents, core.pyth);
|
||||||
const daoUsd = queue === 1 ? next.payoutUsdCents : (next.payoutUsdCents * 2n);
|
const daoUsd = next.payoutUsdCents * BigInt(queue);
|
||||||
const daoLamports = usdCentsToLamportsCeil(daoUsd, core.pyth);
|
const daoLamports = usdCentsToLamportsCeil(daoUsd, core.pyth);
|
||||||
const need = ticketLamports + daoLamports + core.coef.reward;
|
const need = ticketLamports + daoLamports + core.coef.reward;
|
||||||
const missing = core.available >= need ? 0n : (need - core.available);
|
const missing = core.available >= need ? 0n : (need - core.available);
|
||||||
@ -313,7 +319,7 @@
|
|||||||
<div>DAO на этом шаге: <b>${centsToUsdStr(daoUsd)} USD</b> (~${lamportsToSolStr(daoLamports)} SOL)</div>
|
<div>DAO на этом шаге: <b>${centsToUsdStr(daoUsd)} USD</b> (~${lamportsToSolStr(daoLamports)} SOL)</div>
|
||||||
<div>Награда за шаг: <b>${lamportsToSolStr(core.coef.reward)} SOL</b></div>
|
<div>Награда за шаг: <b>${lamportsToSolStr(core.coef.reward)} SOL</b></div>
|
||||||
<div>Нужно для шага: <b>${lamportsToSolStr(need)} SOL</b></div>
|
<div>Нужно для шага: <b>${lamportsToSolStr(need)} SOL</b></div>
|
||||||
<div>Формула: <b>${queue === 1 ? "ticket + dao(1x) + reward" : "ticket + dao(2x) + reward"}</b></div>
|
<div>Формула: <b>ticket + dao(${queue}x) + reward</b></div>
|
||||||
<div>Доступно в inflow vault: <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
<div>Доступно в inflow vault: <b>${lamportsToSolStr(core.available)} SOL</b></div>
|
||||||
<div>${missing === 0n
|
<div>${missing === 0n
|
||||||
? '<span class="ok">Хватает для шага выплаты.</span>'
|
? '<span class="ok">Хватает для шага выплаты.</span>'
|
||||||
@ -374,8 +380,7 @@
|
|||||||
const newRecipient = new solanaWeb3.PublicKey(newRecipientRaw);
|
const newRecipient = new solanaWeb3.PublicKey(newRecipientRaw);
|
||||||
|
|
||||||
const core = cachedCore || await loadCoreState();
|
const core = cachedCore || await loadCoreState();
|
||||||
const disc = await ixDiscriminator("change_ticket_recipient");
|
const data = ixData(IX.changeTicketRecipient, newRecipient.toBytes());
|
||||||
const data = concat(disc, newRecipient.toBytes());
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: core.pdas.queuesPda, isSigner: false, isWritable: true },
|
{ pubkey: core.pdas.queuesPda, isSigner: false, isWritable: true },
|
||||||
@ -404,7 +409,7 @@
|
|||||||
|
|
||||||
if (idxRaw) {
|
if (idxRaw) {
|
||||||
const idx = BigInt(idxRaw);
|
const idx = BigInt(idxRaw);
|
||||||
for (const queue of [1, 2]) {
|
for (const queue of [1, 2, 3]) {
|
||||||
const pda = deriveTicketPda(queue, idx);
|
const pda = deriveTicketPda(queue, idx);
|
||||||
const ai = await connection.getAccountInfo(pda, "confirmed");
|
const ai = await connection.getAccountInfo(pda, "confirmed");
|
||||||
if (!ai) continue;
|
if (!ai) continue;
|
||||||
@ -413,8 +418,8 @@
|
|||||||
if (results.length === 0) throw new Error(`Тикет #${idx.toString()} не найден ни в одной очереди`);
|
if (results.length === 0) throw new Error(`Тикет #${idx.toString()} не найден ни в одной очереди`);
|
||||||
} else if (walletRaw) {
|
} else if (walletRaw) {
|
||||||
const recipient = new solanaWeb3.PublicKey(walletRaw);
|
const recipient = new solanaWeb3.PublicKey(walletRaw);
|
||||||
for (const queue of [1, 2]) {
|
for (const queue of [1, 2, 3]) {
|
||||||
const total = queue === 1 ? core.queues.q1Total : core.queues.q2Total;
|
const total = queue === 1 ? core.queues.q1Total : (queue === 2 ? core.queues.q2Total : core.queues.q3Total);
|
||||||
for (let i = 1n; i <= total; i++) {
|
for (let i = 1n; i <= total; i++) {
|
||||||
const pda = deriveTicketPda(queue, i);
|
const pda = deriveTicketPda(queue, i);
|
||||||
const ai = await connection.getAccountInfo(pda, "confirmed");
|
const ai = await connection.getAccountInfo(pda, "confirmed");
|
||||||
@ -450,15 +455,14 @@
|
|||||||
nextTicketPda = deriveTicketPda(1, core.queues.q1Paid + 1n);
|
nextTicketPda = deriveTicketPda(1, core.queues.q1Paid + 1n);
|
||||||
recipient = walletPubkey;
|
recipient = walletPubkey;
|
||||||
} else {
|
} else {
|
||||||
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : core.queues.q2Paid + 1n;
|
const nextIndex = queue === 1 ? core.queues.q1Paid + 1n : (queue === 2 ? core.queues.q2Paid + 1n : core.queues.q3Paid + 1n);
|
||||||
nextTicketPda = deriveTicketPda(queue, nextIndex);
|
nextTicketPda = deriveTicketPda(queue, nextIndex);
|
||||||
const ai = await connection.getAccountInfo(nextTicketPda, "confirmed");
|
const ai = await connection.getAccountInfo(nextTicketPda, "confirmed");
|
||||||
if (!ai) throw new Error(`Следующий тикет #${nextIndex.toString()} для очереди ${queue} не найден`);
|
if (!ai) throw new Error(`Следующий тикет #${nextIndex.toString()} для очереди ${queue} не найден`);
|
||||||
recipient = parseTicket(ai.data).recipient;
|
recipient = parseTicket(ai.data).recipient;
|
||||||
}
|
}
|
||||||
|
|
||||||
const disc = await ixDiscriminator("step_payout");
|
const data = ixData(IX.stepPayout);
|
||||||
const data = concat(disc);
|
|
||||||
const keys = [
|
const keys = [
|
||||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||||
{ pubkey: core.pdas.configPda, isSigner: false, isWritable: true },
|
{ pubkey: core.pdas.configPda, isSigner: false, isWritable: true },
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user