SHA256
Подготовка перед деплоем в mainnet
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
# DEBUG: тестирование сетевого соединения между двумя клиентами
|
||||
|
||||
Документ описывает временный debug-контур для проверки WebRTC соединения между двумя активными WS-сессиями.
|
||||
|
||||
## 1) Подготовка
|
||||
|
||||
|
||||
0. Убедись, что в `application.properties` включен параметр:
|
||||
`debug.tempApi.enabled=true`
|
||||
1. Создай файл `.debug-token` в корне проекта на основе `debug-token.example`.
|
||||
2. В `.debug-token` должна быть одна строка: секретный токен.
|
||||
3. Перезапусти сервер.
|
||||
|
||||
## 2) API debug
|
||||
|
||||
Базовый заголовок для всех запросов:
|
||||
|
||||
```bash
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>"
|
||||
```
|
||||
|
||||
### 2.1 Получить список живых клиентов
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
http://localhost:7070/debug/ws/clients | jq
|
||||
```
|
||||
|
||||
Ответ содержит `sessionId`, `login`, `ip`, `userAgent`, и клиентскую информацию.
|
||||
|
||||
### 2.2 Запустить debug-соединение между двумя сессиями
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"initiatorSessionId": "SESSION_ID_A",
|
||||
"responderSessionId": "SESSION_ID_B",
|
||||
"clearDebugLog": false
|
||||
}' \
|
||||
http://localhost:7070/debug/ws/connect | jq
|
||||
```
|
||||
|
||||
В ответе придёт `runId`. Его используй для фильтра логов.
|
||||
|
||||
### 2.3 Читать последние N debug-логов
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||
"http://localhost:7070/debug/ws/logs?limit=200&runId=<RUN_ID>" | jq
|
||||
```
|
||||
|
||||
## 3) Операционный сценарий “Codex + пользователь”
|
||||
|
||||
1. Codex поднимает сервер и сообщает пользователю ссылку на UI.
|
||||
2. Codex пишет пользователю: **«Запусти двух клиентов и скажи “продолжай”»**.
|
||||
3. Пользователь запускает два клиента (лучше под разными логинами).
|
||||
4. Пользователь пишет: **«продолжай»**.
|
||||
5. Codex:
|
||||
- вызывает `/debug/ws/clients`,
|
||||
- выбирает 2 сессии,
|
||||
- вызывает `/debug/ws/connect`,
|
||||
- получает `runId`,
|
||||
- читает `/debug/ws/logs?runId=...` и сообщает прогресс.
|
||||
6. Если соединение не удалось:
|
||||
- Codex сообщает ошибки по логам,
|
||||
- при необходимости просит перезапустить 2 клиента,
|
||||
- повторяет запуск debug-run.
|
||||
|
||||
## 4) Какие сообщения считать успехом
|
||||
|
||||
- `peer_connection_connected`
|
||||
- `debug_connection_success`
|
||||
- `signal_sent_200/210/220` без ошибок
|
||||
|
||||
## 5) Что говорить пользователю в ходе прогона (через «колонку»/чат)
|
||||
|
||||
Рекомендуемые фразы:
|
||||
|
||||
- «Сервер запущен. Запусти двух клиентов и напиши “продолжай”.»
|
||||
- «Вижу 2 активные сессии, запускаю тест соединения.»
|
||||
- «Тест запущен, runId=... Сейчас проверяю логи.»
|
||||
- «Соединение установлено / не установлено. Ниже причины и следующий шаг.»
|
||||
|
||||
## 6) Ограничения
|
||||
|
||||
- Механизм временный, не для production-эксплуатации.
|
||||
- Доступ к debug API имеет любой, кто знает токен.
|
||||
- Рекомендуется тестить между разными логинами.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Синхронизация mainnet-адресов Solana по UI/серверу/ESP32
|
||||
|
||||
- статус: `pending`
|
||||
|
||||
## Кратко
|
||||
|
||||
Во все основные клиентские и серверные точки проекта протянуты актуальные mainnet-адреса программ:
|
||||
|
||||
- `shine_login_guard`: `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
|
||||
- `shine_users`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
|
||||
Также по умолчанию переключены основные RPC defaults на `https://api.mainnet-beta.solana.com` там, где это нужно для рабочего mainnet-сценария.
|
||||
|
||||
## Что проверять
|
||||
|
||||
1. Основной UI:
|
||||
- отображение и использование новых program id;
|
||||
- регистрация/чтение Solana PDA в mainnet;
|
||||
- экран кошелька и покупка лимита.
|
||||
|
||||
2. Server UI:
|
||||
- создание server PDA в mainnet;
|
||||
- чтение PDA;
|
||||
- обновление PDA;
|
||||
- корректная блокировка devnet-автопополнения при mainnet endpoint.
|
||||
|
||||
3. Browser plugin wallet:
|
||||
- резолв `user_pda` и `server PDA` через mainnet RPC.
|
||||
|
||||
4. Web UI `shine_payments`:
|
||||
- чтение очередей;
|
||||
- покупка билета;
|
||||
- admin/manager/dao tools открываются с новыми program id и mainnet RPC.
|
||||
|
||||
5. ESP32 homeserver UI:
|
||||
- отображаются новые program id;
|
||||
- дефолтный Solana RPC теперь mainnet.
|
||||
|
||||
## Ожидаемый результат
|
||||
|
||||
- Во всех перечисленных поверхностях используются новые mainnet program id.
|
||||
- Запросы по умолчанию идут в mainnet, кроме явно devnet-утилит.
|
||||
- Нет обращений к старым devnet program id.
|
||||
|
||||
@@ -46,9 +46,9 @@ DAO в текущем виде не является отдельной Anchor-
|
||||
|
||||
| Программа | Program ID |
|
||||
| --- | --- |
|
||||
| `shine_login_guard` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||
| `shine_users` | `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ` |
|
||||
| `shine_payments` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||
| `shine_login_guard` | `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh` |
|
||||
| `shine_users` | `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6` |
|
||||
| `shine_payments` | `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v` |
|
||||
|
||||
Если эти адреса меняются, нужно синхронно обновить:
|
||||
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
|
||||
| Роль | Адрес |
|
||||
| --- | --- |
|
||||
| `SHINE_LOGIN_GUARD_PROGRAM_ID` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||
| `SHINE_USERS_PROGRAM_ID` | `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ` |
|
||||
| `SHINE_PAYMENTS_PROGRAM_ID` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||
| `DAO_AUTHORITY` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||
| `DAO_TREASURY_WALLET` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||
| `SHINE_LOGIN_GUARD_PROGRAM_ID` | `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh` |
|
||||
| `SHINE_USERS_PROGRAM_ID` | `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6` |
|
||||
| `SHINE_PAYMENTS_PROGRAM_ID` | `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v` |
|
||||
| `DAO_AUTHORITY` | `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq` |
|
||||
| `DAO_TREASURY_WALLET` | `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq` |
|
||||
|
||||
## Постоянные PDA
|
||||
|
||||
|
||||
@@ -1,125 +1,103 @@
|
||||
# Деплой и инициализация Solana-регистрации (две обязательные программы)
|
||||
# Деплой и инициализация Solana-регистрации
|
||||
|
||||
## Коротко
|
||||
|
||||
Для рабочей регистрации пользователя нужны **обе** программы:
|
||||
Для рабочей регистрации пользователя нужны три программы:
|
||||
|
||||
1. `shine_users` — хранение и обновление `user_pda`, economy-конфиг, логика регистрации.
|
||||
2. `shine_login_guard` — проверка/классификация логина (CPI из `shine_users`).
|
||||
2. `shine_login_guard` — проверка/классификация логина через CPI из `shine_users`.
|
||||
3. `shine_payments` — приём платежей за регистрацию и докупку лимита через `inflow_vault PDA`.
|
||||
|
||||
Если задеплоена только одна из них — регистрация неработоспособна.
|
||||
Если `shine_users` или `shine_login_guard` не совпадают по адресам с UI/сервером, регистрация неработоспособна.
|
||||
|
||||
## Актуальные адреса (devnet)
|
||||
## Актуальные адреса (mainnet)
|
||||
|
||||
- `shine_users` (регистрация пользователей):
|
||||
`3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ`
|
||||
- `shine_users`:
|
||||
`SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
- `shine_login_guard`:
|
||||
`3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`
|
||||
`SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
|
||||
- `shine_payments`:
|
||||
`c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||
`SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
|
||||
## Подтверждение деплоя
|
||||
Временный on-chain admin для `shine_users` и `shine_payments` до перевода управления на DAO:
|
||||
|
||||
- Сеть: `https://api.devnet.solana.com`
|
||||
- `shine_users`:
|
||||
- `Program ID`: `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ`
|
||||
- TX deploy: `5VzfpSirFCRqPUZfvAt3eADY9KnowW79PKZ1pCQAa2DJGiztj4dUYYXrSQNmWEhPVu6mPSDfcuHzFyEVmoKLa9DM`
|
||||
- `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq`
|
||||
|
||||
## Подтверждение текущего mainnet deploy
|
||||
|
||||
- Сеть: `https://api.mainnet-beta.solana.com`
|
||||
- `shine_login_guard`:
|
||||
- `Program ID`: `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`
|
||||
- TX deploy: `5iptngPYrLLjPE3Xby24zyNW3edVUnBNLBx785vjojMoq5JNLFNQvLNAm3jNYHbpf2B36qtbpTNzcvUNyRDqm1Mf`
|
||||
- `Program ID`: `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
|
||||
- TX deploy: `5gCxrw5gj7ig5C3HFf4AUrHq2pMW1LxgfLUj4jtmQvTqXnR7xp1iySf7m63S3MaimpeUyYhQR1zpPFJyPi6NTAv7`
|
||||
- `shine_users`:
|
||||
- `Program ID`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
- TX deploy: `5Cwp53EegPv6aV69Ztxj7nXomHt9H9ZaXSgWvDfLPeR5EAaU38ADsQjn9h8xShU34AgttYGwWaVWiicF2vLwJXU9`
|
||||
- `shine_payments`:
|
||||
- `Program ID`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
- TX deploy: `2vzsM85bTwSxdYvzWKZMpAg7Gqxs8R7CC5T5LTm7cRFizhr11QZvgDKqP77b8n2mcmA6DF4RqwrUrPzQ8bsQhvCn`
|
||||
|
||||
## Порядок деплоя (devnet)
|
||||
## Как адреса связаны между собой
|
||||
|
||||
1. Убедиться, что CLI смотрит в devnet и у кошелька есть SOL.
|
||||
2. Собрать и задеплоить `shine_login_guard`.
|
||||
3. Собрать и задеплоить `shine_users`.
|
||||
4. Проверить, что адреса совпадают между:
|
||||
- `Anchor.toml`
|
||||
- `declare_id!` в `programs/*/src/lib.rs`
|
||||
- UI/серверными константами.
|
||||
5. Выполнить `init_users_economy_config` (один раз на программу `shine_users`).
|
||||
|
||||
Пример команд:
|
||||
|
||||
```bash
|
||||
cd shine-solana/shine
|
||||
solana config get
|
||||
solana balance
|
||||
|
||||
anchor build -p shine_login_guard
|
||||
anchor deploy -p shine_login_guard
|
||||
|
||||
anchor build -p shine_users
|
||||
anchor deploy -p shine_users
|
||||
```
|
||||
- `shine_users` внутри кода вызывает `shine_login_guard`.
|
||||
- `shine_users` внутри кода знает `shine_payments` и переводит оплату в `inflow_vault PDA` этой программы.
|
||||
- `shine_users` и `shine_payments` пока управляются временным адресом `aiShm...`.
|
||||
- Позже эти hardcoded authority-адреса планируется заменить на адреса DAO через upgrade программ.
|
||||
|
||||
## Куда вписаны адреса в проекте
|
||||
|
||||
### UI
|
||||
|
||||
- Общие Solana-константы:
|
||||
- `shine-UI/js/solana-programs.js`
|
||||
- Страница инициализации:
|
||||
- `shine-UI/js/pages/solana-users-init-view.js`
|
||||
- Переход на страницу:
|
||||
- `shine-UI/js/pages/developer-settings-view.js`
|
||||
- `shine-UI/js/solana-programs.js`
|
||||
- `shine-UI/js/pages/wallet-view.js`
|
||||
- `shine-UI/js/services/solana-register-service.js`
|
||||
- `shine-UI/server-ui/*.html`
|
||||
|
||||
### Browser plugin wallet
|
||||
|
||||
- Резолвер PDA и проверка адресов:
|
||||
- `SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js`
|
||||
- Проверка текущего wallet по PDA:
|
||||
- `SHiNE-browser-plugin-wallet/background.js`
|
||||
- Отображение состояния в popup:
|
||||
- `SHiNE-browser-plugin-wallet/popup.js`
|
||||
- `SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js`
|
||||
|
||||
### Сервер
|
||||
|
||||
- Серверные константы Solana:
|
||||
- `shine-server-config/src/main/java/utils/config/SolanaProgramsConfig.java`
|
||||
- `SHiNE-server/shine-server-config/src/main/java/utils/config/SolanaProgramsConfig.java`
|
||||
|
||||
### Solana / Anchor
|
||||
|
||||
- `shine-solana/shine/Anchor.toml`
|
||||
- `shine-solana/shine/programs/shine_users/src/lib.rs` (`declare_id!`)
|
||||
- `shine-solana/shine/programs/shine_login_guard/src/lib.rs` (`declare_id!`)
|
||||
- `shine-solana/shine/programs/shine_payments/src/lib.rs` (`declare_id!`)
|
||||
- `shine-solana/shine/programs/shine_users/src/lib.rs`
|
||||
- `shine-solana/shine/programs/shine_users/src/settings.rs`
|
||||
- `shine-solana/shine/programs/shine_login_guard/src/lib.rs`
|
||||
- `shine-solana/shine/programs/shine_payments/src/lib.rs`
|
||||
- `shine-solana/shine/programs/shine_payments/src/settings.rs`
|
||||
|
||||
### Где ещё нужно синхронизировать адреса после нового deploy
|
||||
### Дополнительные потребители
|
||||
|
||||
- UI-константы и все потребители в `shine-UI/js/*`
|
||||
- browser-plugin-wallet
|
||||
- серверный `SolanaProgramsConfig.java`
|
||||
- Anchor-конфиг и `declare_id!` в Solana-модуле
|
||||
- документы:
|
||||
- `Dev_Docs/Solana/user_pda/README.md`
|
||||
- `shine-solana/shine/doc/programs/shine_users.md`
|
||||
- `shine-solana/shine/doc/devnet_keys_and_deploy.md`
|
||||
- `shine-solana/shine/programs/shine_payments/web/*.html`
|
||||
- `ESP32/.../shine_homeserver_main.ino`
|
||||
- `ESP32/.../shine_homeserver_ui.ino`
|
||||
|
||||
## Как запустить инициализацию economy PDA
|
||||
|
||||
1. Открыть UI.
|
||||
2. Перейти: `Профиль -> Настройки -> Настройки разработчика -> Solana: init регистрации`.
|
||||
3. Подключить кошелёк (Phantom, devnet).
|
||||
3. Подключить mainnet-кошелёк.
|
||||
4. Нажать `Запустить init_users_economy_config`.
|
||||
5. Дождаться статуса `Успешно`.
|
||||
|
||||
Страница сама вычисляет PDA `users_economy_config` по seed:
|
||||
Страница вычисляет PDA `users_economy_config` по seed:
|
||||
|
||||
- seed: `shine_users_economy_config`
|
||||
- program: `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ`
|
||||
- program: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
|
||||
## Кто оплачивает create/update user_pda
|
||||
|
||||
- И обычная регистрация `create_user_pda`, и последующее `update_user_pda` оплачиваются с `clientKey`.
|
||||
- В UI это означает, что Solana fee payer всегда берётся из `device`-ключа пользователя/сервера.
|
||||
- В UI это означает, что Solana fee payer всегда берётся из `device`-ключа пользователя или сервера.
|
||||
- `rootKey` нужен для подписи unsigned PDA-записи, но не оплачивает транзакцию.
|
||||
- Для server UI это особенно важно: перед `create` и `update` нужно пополнять именно Solana-адрес `clientKey`.
|
||||
|
||||
## Важно
|
||||
|
||||
- `init_users_economy_config` выполняется один раз на программу.
|
||||
Если PDA уже создан, повторный вызов вернёт ошибку "already initialized" (это нормальное поведение).
|
||||
- Серверные приватные ключи для Solana не используются как отдельный backend-wallet: в UI/server UI транзакцию оплачивает именно `clientKey`, а содержимое записи подписывает `rootKey`.
|
||||
- `shine_users` внутри `create_user_pda` требует корректный адрес `shine_login_guard` для CPI-классификации логина.
|
||||
Несовпадение адреса приведёт к ошибке регистрации.
|
||||
- `init_users_economy_config` выполняется один раз на программу. Если PDA уже создан, повторный вызов вернёт ошибку `already initialized`.
|
||||
- Серверные приватные ключи для Solana не используются как отдельный backend-wallet: транзакцию оплачивает `clientKey`, а содержимое записи подписывает `rootKey`.
|
||||
- `shine_users` внутри `create_user_pda` требует корректный адрес `shine_login_guard` для CPI-классификации логина.
|
||||
- При новом devnet deploy планируется использовать те же program keypair, чтобы `program id` на devnet совпадали с mainnet.
|
||||
|
||||
+5
-5
@@ -70,10 +70,10 @@ int ge25519_frombytes(ge25519_p3 *h, const unsigned char *s);
|
||||
#define TEXT_EDIT_PANEL_H 330
|
||||
#define TEST_VERSION "SHiNE homeserver (v.0.18)"
|
||||
|
||||
static const char *kShineUsersProgramId = "3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ";
|
||||
static const char *kShineUsersProgramId = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6";
|
||||
static const char *kShineUsersUserPdaSeedPrefix = "user_login=";
|
||||
static const char *kShineLoginGuardProgramId = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
static const char *kShinePaymentsProgramId = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
|
||||
static const char *kShineLoginGuardProgramId = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh";
|
||||
static const char *kShinePaymentsProgramId = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v";
|
||||
static const char *kSystemProgramId = "11111111111111111111111111111111";
|
||||
static const char *kEd25519ProgramId = "Ed25519SigVerify111111111111111111111111111";
|
||||
static const char *kSysvarInstructionsId = "Sysvar1nstructions1111111111111111111111111";
|
||||
@@ -335,7 +335,7 @@ static String gScanResults[MAX_SCAN_RESULTS];
|
||||
static int gScanResultCount = 0;
|
||||
static WifiViewMode gWifiViewMode = WIFI_VIEW_OVERVIEW;
|
||||
|
||||
static String gSolanaRpcUrl = "https://api.devnet.solana.com";
|
||||
static String gSolanaRpcUrl = "https://api.mainnet-beta.solana.com";
|
||||
static String gShineServerLogin = kDefaultShineServerLogin;
|
||||
static String gShineServerUrl;
|
||||
static String gResolvedShineServerLogin;
|
||||
@@ -5271,7 +5271,7 @@ static void loadPrefs() {
|
||||
if (!gWifiSavedSsid.isEmpty() && findKnownWifiIndex(gWifiSavedSsid) < 0) {
|
||||
upsertKnownWifi(gWifiSavedSsid, gWifiSavedPassword);
|
||||
}
|
||||
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
|
||||
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.mainnet-beta.solana.com");
|
||||
String storedShineServerLogin = normalizeLoginValue(gPrefs.getString("shine_server_login", ""));
|
||||
if (!isValidShineServerLoginValue(storedShineServerLogin)) {
|
||||
String legacyShineServer = normalizeLoginValue(gPrefs.getString("shine_server", ""));
|
||||
|
||||
+4
-4
@@ -239,9 +239,9 @@ static DerivedKeyState gDerivedKeys = {};
|
||||
static const char *kSystemProgramId = "11111111111111111111111111111111";
|
||||
static const char *kEd25519ProgramId = "Ed25519SigVerify111111111111111111111111111";
|
||||
static const char *kSysvarInstructionsId = "Sysvar1nstructions1111111111111111111111111";
|
||||
static const char *kShineUsersProgramId = "3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ";
|
||||
static const char *kShineLoginGuardProgramId = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
static const char *kShinePaymentsProgramId = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
|
||||
static const char *kShineUsersProgramId = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6";
|
||||
static const char *kShineLoginGuardProgramId = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh";
|
||||
static const char *kShinePaymentsProgramId = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v";
|
||||
static const char *kUsersSeedPrefix = "user_login=";
|
||||
static const char *kUsersEconomyConfigSeed = "shine_users_economy_config";
|
||||
static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault";
|
||||
@@ -1314,7 +1314,7 @@ static String defaultApiUrl() {
|
||||
}
|
||||
|
||||
static String defaultRpcUrl() {
|
||||
return "https://api.devnet.solana.com";
|
||||
return "https://api.mainnet-beta.solana.com";
|
||||
}
|
||||
|
||||
static String defaultWsUrl() {
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@
|
||||
- кнопку `SHiNE SERVER LOGIN`, если обычный `user PDA` ещё не зарегистрирован.
|
||||
|
||||
Значения по умолчанию:
|
||||
- Solana RPC: `https://api.devnet.solana.com`
|
||||
- Solana RPC: `https://api.mainnet-beta.solana.com`
|
||||
- SHiNE server login: `shineupme`
|
||||
|
||||
Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { base64ToBytes } from './crypto-utils.js';
|
||||
import { PublicKey } from './vendor/solana-publickey-bundle.js';
|
||||
|
||||
const SOLANA_ENDPOINT_DEFAULT = 'https://api.devnet.solana.com';
|
||||
const SHINE_USERS_PROGRAM_ID = '3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ';
|
||||
const SOLANA_ENDPOINT_DEFAULT = 'https://api.mainnet-beta.solana.com';
|
||||
const SHINE_USERS_PROGRAM_ID = 'SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6';
|
||||
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
|
||||
const DEFAULT_SHINE_SERVER_LOGIN = 'shineupme';
|
||||
const DEFAULT_SHINE_SERVER_ADDRESS = 'shineup.me';
|
||||
|
||||
@@ -42,8 +42,8 @@ shine-UI/server-ui.html
|
||||
Для обновления — только root + device (blockchain-ключ не нужен).
|
||||
|
||||
Актуальные адреса программ Solana (devnet):
|
||||
- `shine_users`: `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ`
|
||||
- `shine_payments`: `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||
- `shine_users`: `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
|
||||
Подробнее: `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||
|
||||
|
||||
+7
-7
@@ -1,20 +1,20 @@
|
||||
package utils.config;
|
||||
|
||||
/**
|
||||
* Публичные адреса Solana-программ SHiNE (жёстко зафиксированы для devnet).
|
||||
* Публичные адреса Solana-программ SHiNE (жёстко зафиксированы для mainnet).
|
||||
* Секреты на сервере не хранятся.
|
||||
*/
|
||||
public final class SolanaProgramsConfig {
|
||||
|
||||
private SolanaProgramsConfig() {}
|
||||
|
||||
public static final String SOLANA_CLUSTER = "devnet";
|
||||
public static final String SOLANA_RPC_URL = "https://api.devnet.solana.com";
|
||||
public static final String SOLANA_CLUSTER = "mainnet-beta";
|
||||
public static final String SOLANA_RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
|
||||
// Программа регистрации пользователей (shine_users), задеплоена в devnet.
|
||||
public static final String SHINE_USERS_PROGRAM_ID = "3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ";
|
||||
// Программа регистрации пользователей (shine_users), задеплоена в mainnet.
|
||||
public static final String SHINE_USERS_PROGRAM_ID = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6";
|
||||
|
||||
// Отдельно фиксируем адреса связанной инфраструктуры, чтобы UI/сервер ссылались одинаково.
|
||||
public static final String SHINE_LOGIN_GUARD_PROGRAM_ID = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
public static final String SHINE_PAYMENTS_PROGRAM_ID = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
|
||||
public static final String SHINE_LOGIN_GUARD_PROGRAM_ID = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh";
|
||||
public static final String SHINE_PAYMENTS_PROGRAM_ID = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v";
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
client.version=1.2.293
|
||||
server.version=1.2.273
|
||||
client.version=1.2.294
|
||||
server.version=1.2.274
|
||||
|
||||
@@ -5,8 +5,8 @@ const crypto = require('crypto');
|
||||
const web3 = require('@solana/web3.js');
|
||||
|
||||
const RPC = process.env.SOLANA_RPC || 'https://api.devnet.solana.com';
|
||||
const LOGIN_GUARD_PROGRAM_ID = '3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo';
|
||||
const SIM_PAYER = 'FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P';
|
||||
const LOGIN_GUARD_PROGRAM_ID = 'SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh';
|
||||
const SIM_PAYER = 'SHiJ58FvbJQJnPEUa5mZNVpWd1ym9CkjjPgeHYzUJmh';
|
||||
const login = String(process.argv[2] || '').trim().toLowerCase();
|
||||
|
||||
if (!login) {
|
||||
@@ -84,4 +84,3 @@ main().catch((e) => {
|
||||
console.error(e?.stack || e?.message || String(e));
|
||||
process.exit(10);
|
||||
});
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ function sessionArgsOrThrow() {
|
||||
return { login, storagePwd };
|
||||
}
|
||||
|
||||
const SHINE_PAYMENTS_PROGRAM_ID = 'c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW';
|
||||
const SHINE_PAYMENTS_PROGRAM_ID = 'SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v';
|
||||
const SHINE_PAYMENTS_ORACLE_ACCOUNT = '7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE';
|
||||
const SHINE_PAYMENTS_SEEDS = {
|
||||
config: 'shine_payments_config',
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
|
||||
|
||||
const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1;
|
||||
const PRECHECK_SIM_PAYER = 'FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P';
|
||||
const PRECHECK_SIM_PAYER = 'SHiJ58FvbJQJnPEUa5mZNVpWd1ym9CkjjPgeHYzUJmh';
|
||||
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
|
||||
|
||||
let solanaLibPromise = null;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const SOLANA_CLUSTER = 'devnet';
|
||||
export const SOLANA_ENDPOINT_DEFAULT = 'https://api.devnet.solana.com';
|
||||
export const SOLANA_CLUSTER = 'mainnet-beta';
|
||||
export const SOLANA_ENDPOINT_DEFAULT = 'https://api.mainnet-beta.solana.com';
|
||||
|
||||
// Единый файл актуальных Solana Program ID для UI-части SHiNE.
|
||||
// При смене адресов обновлять этот файл и все его потребители внутри shine-UI.
|
||||
export const SHINE_USERS_PROGRAM_ID = '3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ';
|
||||
export const SHINE_USERS_PROGRAM_ID = 'SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6';
|
||||
export const SHINE_USERS_ECONOMY_CONFIG_SEED = 'shine_users_economy_config';
|
||||
export const SHINE_LOGIN_GUARD_PROGRAM_ID = '3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo';
|
||||
export const SHINE_PAYMENTS_PROGRAM_ID = 'c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW';
|
||||
export const SHINE_LOGIN_GUARD_PROGRAM_ID = 'SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh';
|
||||
export const SHINE_PAYMENTS_PROGRAM_ID = 'SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v';
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.devnet.solana.com" />
|
||||
<div class="hint">devnet: https://api.devnet.solana.com · mainnet: https://api.mainnet-beta.solana.com</div>
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
<div class="hint">mainnet: https://api.mainnet-beta.solana.com · devnet: https://api.devnet.solana.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
<div class="sol-ttl">Положите SOL на этот адрес перед регистрацией:</div>
|
||||
<div class="sol-adr" id="solAdr"></div>
|
||||
<div class="sol-ht">Это Solana-адрес device-ключа (public key в base58 = Solana-адрес). С него оплачиваются и создание, и обновление PDA.</div>
|
||||
<button class="btn-secondary sol-topup-btn" id="btnTopupDevnet" type="button">Открыть пополнение DEVNET</button>
|
||||
<button class="btn-secondary sol-topup-btn" id="btnTopupDevnet" type="button">Открыть тестовое пополнение DEVNET</button>
|
||||
<button class="btn-secondary sol-balance-btn" id="btnRefreshBalance" type="button">Показать / обновить баланс device</button>
|
||||
<div class="sol-balance" id="deviceBalance">Баланс device ещё не запрашивался.</div>
|
||||
</div>
|
||||
|
||||
@@ -40,6 +40,9 @@ $('serverAddress').placeholder = defaultServerAddress;
|
||||
|
||||
$('btnTopupDevnet').addEventListener('click', () => {
|
||||
try {
|
||||
if (!String($('endpoint').value || '').includes('devnet')) {
|
||||
throw new Error('Автопополнение доступно только для devnet. Для mainnet пополните device-кошелёк вручную.');
|
||||
}
|
||||
openDevnetTopup($('devPub').value);
|
||||
} catch (error) {
|
||||
setStatus($('status'), error?.message || String(error), 'error');
|
||||
|
||||
@@ -104,6 +104,9 @@ resetExpectedKeysUi();
|
||||
|
||||
$('btnTopupDevnet').addEventListener('click', () => {
|
||||
try {
|
||||
if (!String($('endpoint').value || '').includes('devnet')) {
|
||||
throw new Error('Автопополнение доступно только для devnet. Для mainnet пополните device-кошелёк вручную.');
|
||||
}
|
||||
openDevnetTopup($('devPub').value);
|
||||
} catch (error) {
|
||||
setStatus($('status'), error?.message || String(error), 'error');
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.devnet.solana.com" />
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.devnet.solana.com" />
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<div class="sol-ttl">Положите SOL на этот адрес перед обновлением:</div>
|
||||
<div class="sol-adr" id="solAdr"></div>
|
||||
<div class="sol-ht">Это Solana-адрес (base58) device-ключа. С него оплачивается транзакция.</div>
|
||||
<button class="btn-secondary sol-topup-btn" id="btnTopupDevnet" type="button">Открыть пополнение DEVNET</button>
|
||||
<button class="btn-secondary sol-topup-btn" id="btnTopupDevnet" type="button">Открыть тестовое пополнение DEVNET</button>
|
||||
<button class="btn-secondary sol-balance-btn" id="btnRefreshBalance" type="button">Показать / обновить баланс device</button>
|
||||
<div class="sol-balance" id="deviceBalance">Баланс device ещё не запрашивался.</div>
|
||||
</div>
|
||||
|
||||
@@ -6,21 +6,26 @@ resolution = true
|
||||
skip-lint = false
|
||||
|
||||
[programs.devnet]
|
||||
shine_payments = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW"
|
||||
shine_users = "3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ"
|
||||
shine_login_guard = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo"
|
||||
shine_payments = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v"
|
||||
shine_users = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6"
|
||||
shine_login_guard = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh"
|
||||
|
||||
[programs.localnet]
|
||||
shine_payments = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW"
|
||||
shine_users = "3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ"
|
||||
shine_login_guard = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo"
|
||||
shine_payments = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v"
|
||||
shine_users = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6"
|
||||
shine_login_guard = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh"
|
||||
|
||||
[programs.mainnet]
|
||||
shine_login_guard = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh"
|
||||
shine_users = "SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6"
|
||||
shine_payments = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v"
|
||||
|
||||
[registry]
|
||||
url = "https://api.apr.dev"
|
||||
|
||||
[provider]
|
||||
cluster = "devnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
cluster = "mainnet"
|
||||
wallet = "/home/ai/work/SHiNE/keys_for_create_DAO/dao-issuer.json"
|
||||
|
||||
[workspace]
|
||||
members = ["programs/shine_users", "programs/shine_payments", "programs/shine_login_guard"]
|
||||
|
||||
Generated
+86
-1
@@ -14,6 +14,15 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-attribute-access-control"
|
||||
version = "0.31.1"
|
||||
@@ -140,6 +149,7 @@ dependencies = [
|
||||
"anchor-derive-accounts",
|
||||
"anchor-derive-serde",
|
||||
"anchor-derive-space",
|
||||
"anchor-lang-idl",
|
||||
"base64 0.21.7",
|
||||
"bincode",
|
||||
"borsh 0.10.4",
|
||||
@@ -157,6 +167,7 @@ dependencies = [
|
||||
"anchor-lang-idl-spec",
|
||||
"anyhow",
|
||||
"heck",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
@@ -180,6 +191,7 @@ checksum = "5dc7a6d90cc643df0ed2744862cdf180587d1e5d28936538c18fc8908489ed67"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
"cargo_toml",
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -398,6 +410,16 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cargo_toml"
|
||||
version = "0.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a98356df42a2eb1bd8f1793ae4ee4de48e384dd974ce5eac8eee802edb7492be"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"toml 0.8.22",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.23"
|
||||
@@ -900,7 +922,7 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1044,6 +1066,35 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
@@ -1118,6 +1169,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.9"
|
||||
@@ -1156,6 +1216,7 @@ dependencies = [
|
||||
name = "shine_login_guard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"solana-program",
|
||||
]
|
||||
|
||||
@@ -2082,11 +2143,26 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
@@ -2095,10 +2171,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
@@ -46,5 +46,5 @@ tasks.register("checkUiRemote", Exec) {
|
||||
"grep -n 'test-solana-tickets.shineup.me\\|test-solana-tickets.shiningpeople.ru' /home/player/SHiNE/caddy/Caddyfile; " +
|
||||
"echo; " +
|
||||
"echo 'Program ID в загруженных html:'; " +
|
||||
"grep -R -n 'c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW' /home/player/sites/test-solana-tickets.shineup.me/*.html"
|
||||
"grep -R -n 'SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v' /home/player/sites/test-solana-tickets.shineup.me/*.html"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# create-SHiNE-DAO
|
||||
|
||||
Чистый актуальный комплект для создания SHiNE DAO с governance NFT и для работы с NFT, которые:
|
||||
Чистый актуальный комплект для создания SHiNE DAO с переносимым governance mint (`Liquid`) и для работы с NFT, которые:
|
||||
|
||||
1. можно выпускать через голосование DAO;
|
||||
2. можно сжигать через голосование DAO;
|
||||
@@ -15,16 +15,18 @@
|
||||
|
||||
## Что внутри
|
||||
|
||||
- `dao.config.env.example` - шаблон конфига для создания самого DAO.
|
||||
- `dao.config.env.example` - шаблон конфига для создания mint и DAO в три шага.
|
||||
- `scripts/01_create_governance_mint.js` - создание governance mint и metadata без депозита в Realm.
|
||||
- `scripts/02_create_dao_with_governance_nfts.js` - создание `Realm / TokenOwnerRecord / Governance / Treasury` без deposit и без передачи authority.
|
||||
- `scripts/03_transfer_dao_authorities.js` - передача `mint authority`, `freeze authority`, `realm authority` и `metadata update authority` на DAO.
|
||||
- `nft-flow.config.env.example` - шаблон конфига для выпуска и burn NFT через уже созданное DAO.
|
||||
- `scripts/01_check_dao_config.sh` - предстаровая проверка конфига DAO.
|
||||
- `scripts/02_create_dao_with_governance_nfts.js` - создание DAO с governance NFT.
|
||||
- `scripts/03_prepare_second_voter.js` - подготовка второго голосующего.
|
||||
- `scripts/04_create_empty_nft_template_with_metadata.js` - создание NFT mint-шаблона с metadata и картинкой.
|
||||
- `scripts/05_propose_vote_mint_nft.js` - proposal/sign/vote на mint NFT.
|
||||
- `scripts/06_execute_mint_nft.js` - execute mint proposal.
|
||||
- `scripts/07_propose_vote_burn_nft.js` - proposal/sign/vote на burn NFT.
|
||||
- `scripts/08_execute_burn_nft.js` - execute burn proposal.
|
||||
- `scripts/01_check_dao_config.sh` - проверка конфига перед созданием DAO из существующего mint.
|
||||
- `scripts/04_prepare_second_voter.js` - подготовка второго голосующего.
|
||||
- `scripts/05_create_empty_nft_template_with_metadata.js` - создание NFT mint-шаблона с metadata и картинкой.
|
||||
- `scripts/06_propose_vote_mint_nft.js` - proposal/sign/vote на mint NFT.
|
||||
- `scripts/07_execute_mint_nft.js` - execute mint proposal.
|
||||
- `scripts/08_propose_vote_burn_nft.js` - proposal/sign/vote на burn NFT.
|
||||
- `scripts/09_execute_burn_nft.js` - execute burn proposal.
|
||||
- `docs/FUTURE_FAST_BURN_EXECUTOR.md` - описание будущей программы для быстрого burn одного конкретного `mint`.
|
||||
|
||||
## Подтвержденная база, на которой собран комплект
|
||||
@@ -74,9 +76,9 @@ npm install
|
||||
- `solana-keygen`
|
||||
- `node`
|
||||
|
||||
## Быстрый сценарий: создать DAO и запустить NFT-flow
|
||||
## Быстрый сценарий: создать mint, раздать токены, создать DAO и затем передать все права на DAO
|
||||
|
||||
### 1. Создать конфиг DAO
|
||||
### 1. Создать конфиг
|
||||
|
||||
```bash
|
||||
cp create-SHiNE-DAO/dao.config.env.example create-SHiNE-DAO/dao.config.env
|
||||
@@ -86,29 +88,116 @@ cp create-SHiNE-DAO/dao.config.env.example create-SHiNE-DAO/dao.config.env
|
||||
|
||||
- имя DAO;
|
||||
- путь к keypair создателя;
|
||||
- имя и символ governance NFT;
|
||||
- `DAO_GOV_NFT_SUPPLY` (обычно `10`);
|
||||
- имя и символ governance mint;
|
||||
- при необходимости `DAO_GOV_MINT_KEYPAIR` для красивого или фиксированного mint address;
|
||||
- `DAO_GOV_TOKEN_METADATA_URI` и `DAO_GOV_TOKEN_IMAGE_URL`.
|
||||
|
||||
### 2. Проверить конфиг DAO
|
||||
Обычно используются два keypair-файла:
|
||||
|
||||
- `DAO_ISSUER_KEYPAIR` - временный кошелёк создателя/оператора;
|
||||
- `DAO_GOV_MINT_KEYPAIR` - keypair самого mint, если нужен красивый адрес governance mint.
|
||||
|
||||
Сгенерировать их можно так:
|
||||
|
||||
```bash
|
||||
solana-keygen new --outfile ./dao-issuer.json
|
||||
solana-keygen new --outfile ./dao-gov-mint.json
|
||||
```
|
||||
|
||||
Публичные ключи посмотреть так:
|
||||
|
||||
```bash
|
||||
solana-keygen pubkey ./dao-issuer.json
|
||||
solana-keygen pubkey ./dao-gov-mint.json
|
||||
```
|
||||
|
||||
Практическое правило:
|
||||
|
||||
- первый keypair обычно временный, его красивый адрес не важен;
|
||||
- красивым обычно имеет смысл делать именно `DAO_GOV_MINT_KEYPAIR`, потому что это постоянный публичный адрес governance mint.
|
||||
|
||||
### 2. Создать governance mint
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/01_create_governance_mint.js ./create-SHiNE-DAO/dao.config.env
|
||||
```
|
||||
|
||||
Этот шаг:
|
||||
|
||||
1. создаёт mint;
|
||||
2. прикрепляет metadata на Arweave;
|
||||
3. оставляет у кошелька создателя:
|
||||
- `mint authority`
|
||||
- `freeze authority`
|
||||
- `metadata update authority`
|
||||
|
||||
Актуальные Arweave-ссылки для governance mint сейчас такие:
|
||||
|
||||
- image: `https://arweave.net/rPGo-uYIFoLxMBVTGyPr5K6fuF0y9xL-8TlAaZyBJgY`
|
||||
- metadata: `https://arweave.net/PdupvtRhj_DTBjTLcjD7Z5GSiEcFUf01GHaYMDs6gGw`
|
||||
|
||||
### 3. Раздать токены вручную
|
||||
|
||||
```bash
|
||||
spl-token mint <GOVERNANCE_MINT> 1 <WALLET_1>
|
||||
spl-token mint <GOVERNANCE_MINT> 1 <WALLET_2>
|
||||
```
|
||||
|
||||
После этого можно руками наминтить управляющие токены на нужные кошельки.
|
||||
На этом этапе токен ещё обычный и переносимый.
|
||||
|
||||
### 4. Проверить конфиг DAO
|
||||
|
||||
```bash
|
||||
./create-SHiNE-DAO/scripts/01_check_dao_config.sh ./create-SHiNE-DAO/dao.config.env
|
||||
```
|
||||
|
||||
### 3. Создать DAO
|
||||
Перед проверкой в `dao.config.env` нужно указать:
|
||||
|
||||
- `DAO_GOV_TOKEN_MINT` - уже созданный mint;
|
||||
- `DAO_REALM_NAME` - новое имя DAO;
|
||||
- `DAO_ISSUER_KEYPAIR` - кошелёк создателя;
|
||||
- `DAO_VOTING_TIME_SEC` - по умолчанию `86400` секунд (1 день);
|
||||
- `DAO_APPROVAL_THRESHOLD_PERCENT`.
|
||||
|
||||
### 5. Создать DAO
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/02_create_dao_with_governance_nfts.js ./create-SHiNE-DAO/dao.config.env
|
||||
```
|
||||
|
||||
Этот шаг:
|
||||
|
||||
- создаёт `Realm`;
|
||||
- создаёт `TokenOwnerRecord` для кошелька создателя без deposit;
|
||||
- создаёт `Governance` и `Native Treasury`;
|
||||
- задаёт community token type = `Liquid`;
|
||||
- не делает deposit токенов;
|
||||
- не передаёт authority.
|
||||
|
||||
После этого из отчета взять:
|
||||
|
||||
- `realm`
|
||||
- `governance`
|
||||
- `communityMint`
|
||||
- `tokenOwnerRecord`
|
||||
|
||||
### 4. Создать конфиг NFT-flow
|
||||
### 6. Передать все authority на DAO
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/03_transfer_dao_authorities.js ./create-SHiNE-DAO/dao.config.env
|
||||
```
|
||||
|
||||
Этот шаг передаёт на DAO:
|
||||
|
||||
- `mint authority`
|
||||
- `freeze authority`
|
||||
- `realm authority`
|
||||
- `metadata update authority`
|
||||
|
||||
После этого у создателя не должно остаться административных прав на governance mint и Realm.
|
||||
|
||||
### 7. Создать конфиг NFT-flow
|
||||
|
||||
```bash
|
||||
cp create-SHiNE-DAO/nft-flow.config.env.example create-SHiNE-DAO/nft-flow.config.env
|
||||
@@ -123,33 +212,33 @@ cp create-SHiNE-DAO/nft-flow.config.env.example create-SHiNE-DAO/nft-flow.config
|
||||
- `VOTER2_KEYPAIR`
|
||||
- metadata URI и картинку для целевого NFT.
|
||||
|
||||
### 5. Подготовить второго голосующего
|
||||
### 8. Подготовить второго голосующего
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/03_prepare_second_voter.js ./create-SHiNE-DAO/nft-flow.config.env
|
||||
node create-SHiNE-DAO/scripts/04_prepare_second_voter.js ./create-SHiNE-DAO/nft-flow.config.env
|
||||
```
|
||||
|
||||
### 6. Создать NFT mint-шаблон с metadata
|
||||
### 9. Создать NFT mint-шаблон с metadata
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/04_create_empty_nft_template_with_metadata.js ./create-SHiNE-DAO/nft-flow.config.env
|
||||
node create-SHiNE-DAO/scripts/05_create_empty_nft_template_with_metadata.js ./create-SHiNE-DAO/nft-flow.config.env
|
||||
```
|
||||
|
||||
Скрипт вернет `mint` будущего NFT.
|
||||
|
||||
### 7. Поднять proposal на mint NFT
|
||||
### 10. Поднять proposal на mint NFT
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/05_propose_vote_mint_nft.js \
|
||||
node create-SHiNE-DAO/scripts/06_propose_vote_mint_nft.js \
|
||||
./create-SHiNE-DAO/nft-flow.config.env \
|
||||
<TARGET_WALLET> \
|
||||
<NFT_MINT>
|
||||
```
|
||||
|
||||
### 8. Выполнить mint proposal
|
||||
### 11. Выполнить mint proposal
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/06_execute_mint_nft.js \
|
||||
node create-SHiNE-DAO/scripts/07_execute_mint_nft.js \
|
||||
./create-SHiNE-DAO/nft-flow.config.env \
|
||||
<PROPOSAL> \
|
||||
<PROPOSAL_TX> \
|
||||
@@ -157,19 +246,19 @@ node create-SHiNE-DAO/scripts/06_execute_mint_nft.js \
|
||||
<NFT_MINT>
|
||||
```
|
||||
|
||||
### 9. Поднять proposal на burn NFT
|
||||
### 12. Поднять proposal на burn NFT
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/07_propose_vote_burn_nft.js \
|
||||
node create-SHiNE-DAO/scripts/08_propose_vote_burn_nft.js \
|
||||
./create-SHiNE-DAO/nft-flow.config.env \
|
||||
<CURRENT_OWNER_WALLET> \
|
||||
<NFT_MINT>
|
||||
```
|
||||
|
||||
### 10. Выполнить burn proposal
|
||||
### 13. Выполнить burn proposal
|
||||
|
||||
```bash
|
||||
node create-SHiNE-DAO/scripts/08_execute_burn_nft.js \
|
||||
node create-SHiNE-DAO/scripts/09_execute_burn_nft.js \
|
||||
./create-SHiNE-DAO/nft-flow.config.env \
|
||||
<PROPOSAL> \
|
||||
<PROPOSAL_TX> \
|
||||
@@ -190,12 +279,18 @@ node create-SHiNE-DAO/scripts/08_execute_burn_nft.js \
|
||||
|
||||
## Что важно про стартовые governance NFT
|
||||
|
||||
На старте governance NFT mint'ятся создателю DAO, а затем депонируются в Realm.
|
||||
В новом трёхшаговом flow governance mint создается отдельно, токены можно распределить вручную до создания DAO, а затем отдельным шагом DAO получает права на:
|
||||
|
||||
Практически это означает:
|
||||
- `mint authority`
|
||||
- `freeze authority`
|
||||
- `realm authority`
|
||||
- `metadata update authority`
|
||||
|
||||
- первоначальный voting power принадлежит создателю DAO;
|
||||
- затем governance NFT можно дополнительно распределять между другими участниками.
|
||||
Это сделано специально, чтобы:
|
||||
|
||||
1. сначала спокойно распределить токены;
|
||||
2. затем создать Realm без блокировки стартовых токенов;
|
||||
3. и только потом окончательно передать контроль DAO.
|
||||
|
||||
## Future
|
||||
|
||||
|
||||
@@ -4,28 +4,33 @@
|
||||
DAO_CLUSTER="devnet"
|
||||
|
||||
# Для каждого нового запуска должен быть новый realm name.
|
||||
DAO_REALM_NAME="SHiNE NFT DAO 2026-06-30"
|
||||
DAO_REALM_NAME="SHiNE DAO2"
|
||||
|
||||
# Governance NFT коллекция DAO
|
||||
DAO_GOV_NFT_NAME="SHiNE Governance NFT"
|
||||
DAO_GOV_NFT_SYMBOL="SHINE-GOV"
|
||||
DAO_GOV_TOKEN_DESCRIPTION="Governance NFT for SHiNE DAO"
|
||||
DAO_GOV_TOKEN_DESCRIPTION="Токен для голосования в SHiNE DAO. Voting token for SHiNE DAO."
|
||||
|
||||
# Metadata JSON governance mint
|
||||
DAO_GOV_TOKEN_METADATA_URI="https://arweave.net/CHANGE_ME_METADATA_JSON"
|
||||
DAO_GOV_TOKEN_IMAGE_URL="https://arweave.net/CHANGE_ME_IMAGE"
|
||||
# Шаг 1: создание mint
|
||||
# Первый keypair временный: он нужен только как оператор для старта.
|
||||
# Красивым обычно имеет смысл делать именно mint keypair.
|
||||
DAO_GOV_MINT_KEYPAIR="/home/ai/work/SHiNE/keys_for_create_DAO/dao-gov-mint.json"
|
||||
|
||||
# Стартовое число governance NFT
|
||||
DAO_GOV_NFT_SUPPLY="10"
|
||||
# Metadata для governance mint
|
||||
DAO_GOV_TOKEN_METADATA_URI="https://arweave.net/PdupvtRhj_DTBjTLcjD7Z5GSiEcFUf01GHaYMDs6gGw"
|
||||
DAO_GOV_TOKEN_IMAGE_URL="https://arweave.net/rPGo-uYIFoLxMBVTGyPr5K6fuF0y9xL-8TlAaZyBJgY"
|
||||
|
||||
# Минимум для тестов Realms - 3600
|
||||
DAO_VOTING_TIME_SEC="3600"
|
||||
# Шаг 2: создание DAO из уже существующего mint
|
||||
DAO_GOV_TOKEN_MINT=""
|
||||
|
||||
# Дефолт для новых DAO - 86400 секунд (1 день)
|
||||
DAO_VOTING_TIME_SEC="86400"
|
||||
|
||||
# 51..100
|
||||
DAO_APPROVAL_THRESHOLD_PERCENT="51"
|
||||
|
||||
# Создатель DAO
|
||||
DAO_ISSUER_KEYPAIR="$HOME/.config/solana/phantomWallet.json"
|
||||
# Создатель DAO / временный оператор до передачи authority на DAO
|
||||
DAO_ISSUER_KEYPAIR="/home/ai/work/SHiNE/keys_for_create_DAO/dao-issuer.json"
|
||||
|
||||
# Governance program (Realms)
|
||||
SPL_GOVERNANCE_PROGRAM_ID="GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw"
|
||||
|
||||
@@ -23,7 +23,7 @@ require_cmd solana
|
||||
require_cmd solana-keygen
|
||||
require_cmd node
|
||||
|
||||
if [[ -z "${DAO_REALM_NAME:-}" || -z "${DAO_CLUSTER:-}" || -z "${DAO_ISSUER_KEYPAIR:-}" || -z "${SPL_GOVERNANCE_PROGRAM_ID:-}" ]]; then
|
||||
if [[ -z "${DAO_REALM_NAME:-}" || -z "${DAO_CLUSTER:-}" || -z "${DAO_ISSUER_KEYPAIR:-}" || -z "${SPL_GOVERNANCE_PROGRAM_ID:-}" || -z "${DAO_GOV_TOKEN_MINT:-}" ]]; then
|
||||
echo "Ошибка: обязательные поля конфига пустые"
|
||||
exit 1
|
||||
fi
|
||||
@@ -33,7 +33,7 @@ if [[ ! -f "$DAO_ISSUER_KEYPAIR" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! [[ "${DAO_VOTING_TIME_SEC}" =~ ^[0-9]+$ ]] || ! [[ "${DAO_GOV_NFT_SUPPLY}" =~ ^[0-9]+$ ]] || ! [[ "${DAO_APPROVAL_THRESHOLD_PERCENT}" =~ ^[0-9]+$ ]]; then
|
||||
if ! [[ "${DAO_VOTING_TIME_SEC}" =~ ^[0-9]+$ ]] || ! [[ "${DAO_APPROVAL_THRESHOLD_PERCENT}" =~ ^[0-9]+$ ]]; then
|
||||
echo "Ошибка: числовые параметры заданы некорректно"
|
||||
exit 1
|
||||
fi
|
||||
@@ -45,6 +45,12 @@ fi
|
||||
|
||||
ISSUER_PUBKEY="$(solana-keygen pubkey "$DAO_ISSUER_KEYPAIR")"
|
||||
ISSUER_BALANCE="$(solana balance "$ISSUER_PUBKEY" --url "$DAO_CLUSTER" 2>/dev/null || true)"
|
||||
MINT_PUBKEY="$DAO_GOV_TOKEN_MINT"
|
||||
|
||||
if ! node -e "const { PublicKey } = require('@solana/web3.js'); new PublicKey(process.argv[1])" "$MINT_PUBKEY" >/dev/null 2>&1; then
|
||||
echo "Ошибка: DAO_GOV_TOKEN_MINT не является валидным pubkey"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REALM_PDA="$(node - "$DAO_REALM_NAME" "$SPL_GOVERNANCE_PROGRAM_ID" <<'NODE'
|
||||
const { PublicKey } = require("@solana/web3.js");
|
||||
@@ -63,6 +69,11 @@ if solana account "$REALM_PDA" --url "$DAO_CLUSTER" >/dev/null 2>&1; then
|
||||
REALM_EXISTS="yes"
|
||||
fi
|
||||
|
||||
MINT_EXISTS="no"
|
||||
if solana account "$MINT_PUBKEY" --url "$DAO_CLUSTER" >/dev/null 2>&1; then
|
||||
MINT_EXISTS="yes"
|
||||
fi
|
||||
|
||||
cat <<EOF
|
||||
============================================================
|
||||
ПРОВЕРКА create-SHiNE-DAO
|
||||
@@ -72,11 +83,12 @@ Realm name: $DAO_REALM_NAME
|
||||
Realm PDA: $REALM_PDA
|
||||
Realm уже существует: $REALM_EXISTS
|
||||
Governance program: $SPL_GOVERNANCE_PROGRAM_ID
|
||||
Governance mint: $MINT_PUBKEY
|
||||
Mint уже существует: $MINT_EXISTS
|
||||
Эмиттер (issuer): $ISSUER_PUBKEY
|
||||
Баланс эмиттера: ${ISSUER_BALANCE:-unknown}
|
||||
Governance NFT name: $DAO_GOV_NFT_NAME
|
||||
Governance NFT symbol: $DAO_GOV_NFT_SYMBOL
|
||||
Governance NFT supply: $DAO_GOV_NFT_SUPPLY
|
||||
Voting time (sec): $DAO_VOTING_TIME_SEC
|
||||
Threshold %: $DAO_APPROVAL_THRESHOLD_PERCENT
|
||||
Конфиг: $CONFIG_PATH
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const BN = require("bn.js");
|
||||
const {
|
||||
Connection,
|
||||
Keypair,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_PROGRAM_ID,
|
||||
AuthorityType,
|
||||
getMintLen,
|
||||
createInitializeMintInstruction,
|
||||
createSetAuthorityInstruction,
|
||||
} = require("@solana/spl-token");
|
||||
const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults");
|
||||
const { createSignerFromKeypair, signerIdentity, percentAmount, none, some } = require("@metaplex-foundation/umi");
|
||||
const { fromWeb3JsKeypair, fromWeb3JsPublicKey } = require("@metaplex-foundation/umi-web3js-adapters");
|
||||
const { mplTokenMetadata, createV1, TokenStandard } = require("@metaplex-foundation/mpl-token-metadata");
|
||||
const {
|
||||
PublicKey,
|
||||
assertRequired,
|
||||
clusterUrl,
|
||||
ensureArweaveUri,
|
||||
ensureDir,
|
||||
loadKeypair,
|
||||
nowStamp,
|
||||
parseEnvConfig,
|
||||
resolveConfigPath,
|
||||
} = require("./js_common");
|
||||
|
||||
function lamportsToSol(lamports) {
|
||||
return Number(lamports) / 1_000_000_000;
|
||||
}
|
||||
|
||||
async function attachGovernanceMetadata(cfg, cluster, issuer, mintPubkey, mintKeypair) {
|
||||
ensureArweaveUri("DAO_GOV_TOKEN_METADATA_URI", cfg.DAO_GOV_TOKEN_METADATA_URI);
|
||||
ensureArweaveUri("DAO_GOV_TOKEN_IMAGE_URL", cfg.DAO_GOV_TOKEN_IMAGE_URL);
|
||||
|
||||
const umi = createUmi(clusterUrl(cluster));
|
||||
const issuerSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(issuer));
|
||||
const mintSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(mintKeypair));
|
||||
umi.use(signerIdentity(issuerSigner));
|
||||
umi.use(mplTokenMetadata());
|
||||
|
||||
const builder = createV1(umi, {
|
||||
mint: mintSigner,
|
||||
authority: issuerSigner,
|
||||
payer: issuerSigner,
|
||||
updateAuthority: issuerSigner,
|
||||
name: cfg.DAO_GOV_NFT_NAME,
|
||||
symbol: cfg.DAO_GOV_NFT_SYMBOL,
|
||||
uri: cfg.DAO_GOV_TOKEN_METADATA_URI,
|
||||
sellerFeeBasisPoints: percentAmount(0),
|
||||
tokenStandard: TokenStandard.Fungible,
|
||||
decimals: some(0),
|
||||
creators: none(),
|
||||
collection: none(),
|
||||
uses: none(),
|
||||
collectionDetails: none(),
|
||||
ruleSet: none(),
|
||||
printSupply: none(),
|
||||
primarySaleHappened: false,
|
||||
isMutable: true,
|
||||
isCollection: false,
|
||||
splTokenProgram: fromWeb3JsPublicKey(TOKEN_PROGRAM_ID),
|
||||
});
|
||||
|
||||
const result = await builder.sendAndConfirm(umi);
|
||||
return Buffer.from(result.signature).toString("base64");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2], "dao.config.env");
|
||||
if (!fs.existsSync(configPath)) throw new Error(`Конфиг не найден: ${configPath}`);
|
||||
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
[
|
||||
"DAO_CLUSTER",
|
||||
"DAO_GOV_NFT_NAME",
|
||||
"DAO_GOV_NFT_SYMBOL",
|
||||
"DAO_GOV_TOKEN_DESCRIPTION",
|
||||
"DAO_ISSUER_KEYPAIR",
|
||||
"SPL_GOVERNANCE_PROGRAM_ID",
|
||||
"DAO_GOV_TOKEN_METADATA_URI",
|
||||
"DAO_GOV_TOKEN_IMAGE_URL",
|
||||
].forEach((k) => assertRequired(cfg, k));
|
||||
|
||||
const cluster = cfg.DAO_CLUSTER;
|
||||
const connection = new Connection(clusterUrl(cluster), "confirmed");
|
||||
const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR));
|
||||
const mintKeypair = cfg.DAO_GOV_MINT_KEYPAIR
|
||||
? loadKeypair(path.resolve(cfg.DAO_GOV_MINT_KEYPAIR))
|
||||
: Keypair.generate();
|
||||
const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||
|
||||
const startBalance = await connection.getBalance(issuer.publicKey, "confirmed");
|
||||
console.log("============================================================");
|
||||
console.log("CREATE SHiNE GOVERNANCE MINT");
|
||||
console.log("------------------------------------------------------------");
|
||||
console.log("Сеть: ", cluster);
|
||||
console.log("Mint: ", mintKeypair.publicKey.toBase58());
|
||||
console.log("Issuer: ", issuer.publicKey.toBase58());
|
||||
console.log("Governance program:", governanceProgramId.toBase58());
|
||||
console.log("Баланс до: ", `${lamportsToSol(startBalance)} SOL`);
|
||||
console.log("Name: ", cfg.DAO_GOV_NFT_NAME);
|
||||
console.log("Symbol: ", cfg.DAO_GOV_NFT_SYMBOL);
|
||||
console.log("Metadata URI: ", cfg.DAO_GOV_TOKEN_METADATA_URI);
|
||||
console.log("Image URL: ", cfg.DAO_GOV_TOKEN_IMAGE_URL);
|
||||
console.log("============================================================");
|
||||
|
||||
const mintLen = getMintLen([]);
|
||||
const mintRent = await connection.getMinimumBalanceForRentExemption(mintLen);
|
||||
const txMint = new Transaction().add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: issuer.publicKey,
|
||||
newAccountPubkey: mintKeypair.publicKey,
|
||||
space: mintLen,
|
||||
lamports: mintRent,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
createInitializeMintInstruction(mintKeypair.publicKey, 0, issuer.publicKey, issuer.publicKey, TOKEN_PROGRAM_ID)
|
||||
);
|
||||
const sigMint = await sendAndConfirmTransaction(connection, txMint, [issuer, mintKeypair], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const sigMetadata = await attachGovernanceMetadata(cfg, cluster, issuer, mintKeypair.publicKey, mintKeypair);
|
||||
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
cluster,
|
||||
mint: mintKeypair.publicKey.toBase58(),
|
||||
issuer: issuer.publicKey.toBase58(),
|
||||
mintAuthority: issuer.publicKey.toBase58(),
|
||||
freezeAuthority: issuer.publicKey.toBase58(),
|
||||
metadataUpdateAuthority: issuer.publicKey.toBase58(),
|
||||
governanceProgramId: governanceProgramId.toBase58(),
|
||||
metadataUri: cfg.DAO_GOV_TOKEN_METADATA_URI,
|
||||
imageUrl: cfg.DAO_GOV_TOKEN_IMAGE_URL,
|
||||
txMint: sigMint,
|
||||
txMetadata: sigMetadata,
|
||||
};
|
||||
const runsDir = path.resolve(__dirname, "..", "local", "runs");
|
||||
ensureDir(runsDir);
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_governance_mint.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
console.log("Готово. Governance mint создан.");
|
||||
console.log("mint:", mintKeypair.publicKey.toBase58());
|
||||
console.log("report:", reportPath);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("CreateMint error:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const { Connection, PublicKey, Transaction, sendAndConfirmTransaction } = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_PROGRAM_ID,
|
||||
getAssociatedTokenAddressSync,
|
||||
createAssociatedTokenAccountIdempotentInstruction,
|
||||
createMintToInstruction,
|
||||
getMint,
|
||||
} = require("@solana/spl-token");
|
||||
const { clusterUrl, loadKeypair, parseEnvConfig, resolveConfigPath } = require("./js_common");
|
||||
|
||||
async function main() {
|
||||
const targetWallet = process.argv[3];
|
||||
const amountRaw = process.argv[4] || "1";
|
||||
if (!targetWallet) {
|
||||
throw new Error("Usage: node 01a_mint_governance_token.js <config.env> <target_wallet> [amount]");
|
||||
}
|
||||
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2], "dao.config.env"));
|
||||
if (!cfg.DAO_GOV_TOKEN_MINT) throw new Error("В конфиге пустой DAO_GOV_TOKEN_MINT");
|
||||
if (!cfg.DAO_ISSUER_KEYPAIR) throw new Error("В конфиге пустой DAO_ISSUER_KEYPAIR");
|
||||
|
||||
const conn = new Connection(clusterUrl(cfg.DAO_CLUSTER || "devnet"), "confirmed");
|
||||
const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR));
|
||||
const mint = new PublicKey(cfg.DAO_GOV_TOKEN_MINT);
|
||||
const target = new PublicKey(targetWallet);
|
||||
const amount = BigInt(amountRaw);
|
||||
|
||||
const mintInfo = await getMint(conn, mint, "confirmed", TOKEN_PROGRAM_ID);
|
||||
if (mintInfo.decimals !== 0) {
|
||||
throw new Error(`Ожидался governance mint с decimals=0, а получено ${mintInfo.decimals}`);
|
||||
}
|
||||
|
||||
const ata = getAssociatedTokenAddressSync(mint, target, false, TOKEN_PROGRAM_ID);
|
||||
const tx = new Transaction().add(
|
||||
createAssociatedTokenAccountIdempotentInstruction(
|
||||
issuer.publicKey,
|
||||
ata,
|
||||
target,
|
||||
mint,
|
||||
TOKEN_PROGRAM_ID
|
||||
),
|
||||
createMintToInstruction(
|
||||
mint,
|
||||
ata,
|
||||
issuer.publicKey,
|
||||
amount,
|
||||
[],
|
||||
TOKEN_PROGRAM_ID
|
||||
)
|
||||
);
|
||||
|
||||
const sig = await sendAndConfirmTransaction(conn, tx, [issuer], { commitment: "confirmed" });
|
||||
|
||||
console.log("Mint done.");
|
||||
console.log("target:", target.toBase58());
|
||||
console.log("ata:", ata.toBase58());
|
||||
console.log("amount:", amount.toString());
|
||||
console.log("signature:", sig);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("MintGovernanceToken error:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
+68
-174
@@ -7,20 +7,14 @@ const readline = require("readline");
|
||||
const BN = require("bn.js");
|
||||
const {
|
||||
Connection,
|
||||
Keypair,
|
||||
SystemProgram,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_PROGRAM_ID,
|
||||
AuthorityType,
|
||||
getMintLen,
|
||||
createInitializeMintInstruction,
|
||||
getAssociatedTokenAddressSync,
|
||||
createAssociatedTokenAccountIdempotentInstruction,
|
||||
createMintToInstruction,
|
||||
createSetAuthorityInstruction,
|
||||
getAccount,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
MintMaxVoteWeightSource,
|
||||
@@ -32,17 +26,11 @@ const {
|
||||
GoverningTokenConfigAccountArgs,
|
||||
GoverningTokenType,
|
||||
withCreateRealm,
|
||||
withDepositGoverningTokens,
|
||||
withCreateTokenOwnerRecord,
|
||||
withCreateGovernance,
|
||||
withCreateNativeTreasury,
|
||||
withSetRealmAuthority,
|
||||
SetRealmAuthorityAction,
|
||||
} = require("@solana/spl-governance");
|
||||
const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults");
|
||||
const { createSignerFromKeypair, signerIdentity, percentAmount, none, some } = require("@metaplex-foundation/umi");
|
||||
const { fromWeb3JsKeypair, fromWeb3JsPublicKey } = require("@metaplex-foundation/umi-web3js-adapters");
|
||||
const { mplTokenMetadata, createV1, TokenStandard } = require("@metaplex-foundation/mpl-token-metadata");
|
||||
const { PublicKey, assertRequired, clusterUrl, ensureArweaveUri, ensureDir, loadKeypair, parseEnvConfig, resolveConfigPath, nowStamp } = require("./js_common");
|
||||
const { clusterUrl, ensureDir, loadKeypair, parseEnvConfig, resolveConfigPath, nowStamp } = require("./js_common");
|
||||
|
||||
function lamportsToSol(lamports) {
|
||||
return Number(lamports) / 1_000_000_000;
|
||||
@@ -57,43 +45,6 @@ async function askYes() {
|
||||
return answer.trim() === "YES";
|
||||
}
|
||||
|
||||
async function attachGovernanceMetadata(cfg, cluster, issuer, mintPubkey, mintKeypair) {
|
||||
ensureArweaveUri("DAO_GOV_TOKEN_METADATA_URI", cfg.DAO_GOV_TOKEN_METADATA_URI);
|
||||
ensureArweaveUri("DAO_GOV_TOKEN_IMAGE_URL", cfg.DAO_GOV_TOKEN_IMAGE_URL);
|
||||
|
||||
const umi = createUmi(clusterUrl(cluster));
|
||||
const issuerSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(issuer));
|
||||
const mintSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(mintKeypair));
|
||||
umi.use(signerIdentity(issuerSigner));
|
||||
umi.use(mplTokenMetadata());
|
||||
|
||||
const builder = createV1(umi, {
|
||||
mint: mintSigner,
|
||||
authority: issuerSigner,
|
||||
payer: issuerSigner,
|
||||
updateAuthority: issuerSigner,
|
||||
name: cfg.DAO_GOV_NFT_NAME,
|
||||
symbol: cfg.DAO_GOV_NFT_SYMBOL,
|
||||
uri: cfg.DAO_GOV_TOKEN_METADATA_URI,
|
||||
sellerFeeBasisPoints: percentAmount(0),
|
||||
tokenStandard: TokenStandard.Fungible,
|
||||
decimals: some(0),
|
||||
creators: none(),
|
||||
collection: none(),
|
||||
uses: none(),
|
||||
collectionDetails: none(),
|
||||
ruleSet: none(),
|
||||
printSupply: none(),
|
||||
primarySaleHappened: false,
|
||||
isMutable: true,
|
||||
isCollection: false,
|
||||
splTokenProgram: fromWeb3JsPublicKey(TOKEN_PROGRAM_ID),
|
||||
});
|
||||
|
||||
const result = await builder.sendAndConfirm(umi);
|
||||
return Buffer.from(result.signature).toString("base64");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2], "dao.config.env");
|
||||
if (!fs.existsSync(configPath)) throw new Error(`Конфиг не найден: ${configPath}`);
|
||||
@@ -104,26 +55,31 @@ async function main() {
|
||||
"DAO_REALM_NAME",
|
||||
"DAO_GOV_NFT_NAME",
|
||||
"DAO_GOV_NFT_SYMBOL",
|
||||
"DAO_GOV_NFT_SUPPLY",
|
||||
"DAO_VOTING_TIME_SEC",
|
||||
"DAO_APPROVAL_THRESHOLD_PERCENT",
|
||||
"DAO_ISSUER_KEYPAIR",
|
||||
"SPL_GOVERNANCE_PROGRAM_ID",
|
||||
"DAO_GOV_TOKEN_METADATA_URI",
|
||||
"DAO_GOV_TOKEN_IMAGE_URL",
|
||||
].forEach((k) => assertRequired(cfg, k));
|
||||
"DAO_GOV_TOKEN_MINT",
|
||||
].forEach((k) => {
|
||||
if (!cfg[k]) throw new Error(`В конфиге отсутствует обязательный параметр: ${k}`);
|
||||
});
|
||||
|
||||
const cluster = cfg.DAO_CLUSTER;
|
||||
const connection = new Connection(clusterUrl(cluster), "confirmed");
|
||||
const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR));
|
||||
const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||
const supply = Number(cfg.DAO_GOV_NFT_SUPPLY);
|
||||
const votingTimeSec = Number(cfg.DAO_VOTING_TIME_SEC);
|
||||
const thresholdPct = Number(cfg.DAO_APPROVAL_THRESHOLD_PERCENT);
|
||||
const mint = new PublicKey(cfg.DAO_GOV_TOKEN_MINT);
|
||||
|
||||
if (!Number.isInteger(supply) || supply <= 0) throw new Error("DAO_GOV_NFT_SUPPLY должен быть > 0");
|
||||
if (!Number.isInteger(votingTimeSec) || votingTimeSec < 3600) throw new Error("DAO_VOTING_TIME_SEC должен быть >= 3600");
|
||||
if (!Number.isInteger(thresholdPct) || thresholdPct < 51 || thresholdPct > 100) throw new Error("DAO_APPROVAL_THRESHOLD_PERCENT должен быть в диапазоне 51..100");
|
||||
const mintInfoBefore = await connection.getParsedAccountInfo(mint, "confirmed");
|
||||
if (!mintInfoBefore.value) throw new Error(`Governing mint не найден: ${mint.toBase58()}`);
|
||||
const mintAccountBefore = await connection.getAccountInfo(mint, "confirmed");
|
||||
if (!mintAccountBefore || !mintAccountBefore.owner.equals(TOKEN_PROGRAM_ID)) {
|
||||
throw new Error(`Mint должен быть классическим SPL Token mint: ${mint.toBase58()}`);
|
||||
}
|
||||
|
||||
const mintParsed = mintInfoBefore.value.data.parsed.info;
|
||||
const mintAuthorityBefore = mintParsed.mintAuthority || null;
|
||||
const freezeAuthorityBefore = mintParsed.freezeAuthority || null;
|
||||
const issuerAta = getAssociatedTokenAddressSync(mint, issuer.publicKey, false, TOKEN_PROGRAM_ID);
|
||||
const issuerAtaInfo = await getAccount(connection, issuerAta, "confirmed").catch(() => null);
|
||||
|
||||
const [realmPda] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("governance"), Buffer.from(cfg.DAO_REALM_NAME, "utf8")],
|
||||
@@ -135,18 +91,19 @@ async function main() {
|
||||
|
||||
const startBalance = await connection.getBalance(issuer.publicKey, "confirmed");
|
||||
console.log("============================================================");
|
||||
console.log("CREATE SHiNE DAO WITH GOVERNANCE NFT");
|
||||
console.log("CREATE SHiNE DAO FROM EXISTING GOVERNANCE MINT");
|
||||
console.log("------------------------------------------------------------");
|
||||
console.log("Сеть: ", cluster);
|
||||
console.log("Realm name: ", cfg.DAO_REALM_NAME);
|
||||
console.log("Realm PDA: ", realmPda.toBase58());
|
||||
console.log("Issuer: ", issuer.publicKey.toBase58());
|
||||
console.log("Supply: ", supply);
|
||||
console.log("Voting time: ", votingTimeSec);
|
||||
console.log("Threshold %: ", thresholdPct);
|
||||
console.log("Governance mint: ", mint.toBase58());
|
||||
console.log("Issuer ATA: ", issuerAta.toBase58());
|
||||
console.log("Issuer ATA есть: ", issuerAtaInfo ? "yes" : "no");
|
||||
console.log("Issuer ATA amount:", issuerAtaInfo ? issuerAtaInfo.amount.toString() : "0");
|
||||
console.log("Баланс до: ", `${lamportsToSol(startBalance)} SOL`);
|
||||
console.log("Metadata URI: ", cfg.DAO_GOV_TOKEN_METADATA_URI);
|
||||
console.log("Image URL: ", cfg.DAO_GOV_TOKEN_IMAGE_URL);
|
||||
console.log("Mint authority: ", mintAuthorityBefore || "null");
|
||||
console.log("Freeze authority:", freezeAuthorityBefore || "null");
|
||||
console.log("============================================================");
|
||||
|
||||
if (!(await askYes())) {
|
||||
@@ -154,40 +111,11 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const mintKeypair = Keypair.generate();
|
||||
const mintLen = getMintLen([]);
|
||||
const mintRent = await connection.getMinimumBalanceForRentExemption(mintLen);
|
||||
const issuerAta = getAssociatedTokenAddressSync(mintKeypair.publicKey, issuer.publicKey, false, TOKEN_PROGRAM_ID);
|
||||
|
||||
const txMint = new Transaction().add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: issuer.publicKey,
|
||||
newAccountPubkey: mintKeypair.publicKey,
|
||||
space: mintLen,
|
||||
lamports: mintRent,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
createInitializeMintInstruction(mintKeypair.publicKey, 0, issuer.publicKey, issuer.publicKey, TOKEN_PROGRAM_ID),
|
||||
createAssociatedTokenAccountIdempotentInstruction(
|
||||
issuer.publicKey,
|
||||
issuerAta,
|
||||
issuer.publicKey,
|
||||
mintKeypair.publicKey,
|
||||
TOKEN_PROGRAM_ID
|
||||
),
|
||||
createMintToInstruction(mintKeypair.publicKey, issuerAta, issuer.publicKey, supply, [], TOKEN_PROGRAM_ID)
|
||||
);
|
||||
const sigMint = await sendAndConfirmTransaction(connection, txMint, [issuer, mintKeypair], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const sigMetadata = await attachGovernanceMetadata(cfg, cluster, issuer, mintKeypair.publicKey, mintKeypair);
|
||||
|
||||
const ixRealm = [];
|
||||
const communityTokenConfig = new GoverningTokenConfigAccountArgs({
|
||||
voterWeightAddin: undefined,
|
||||
maxVoterWeightAddin: undefined,
|
||||
tokenType: GoverningTokenType.Membership,
|
||||
tokenType: GoverningTokenType.Liquid,
|
||||
});
|
||||
const realmPk = await withCreateRealm(
|
||||
ixRealm,
|
||||
@@ -195,7 +123,7 @@ async function main() {
|
||||
PROGRAM_VERSION_V3,
|
||||
cfg.DAO_REALM_NAME,
|
||||
issuer.publicKey,
|
||||
mintKeypair.publicKey,
|
||||
mint,
|
||||
issuer.publicKey,
|
||||
undefined,
|
||||
MintMaxVoteWeightSource.FULL_SUPPLY_FRACTION,
|
||||
@@ -203,29 +131,32 @@ async function main() {
|
||||
communityTokenConfig,
|
||||
undefined
|
||||
);
|
||||
const sigRealm = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealm), [issuer], { commitment: "confirmed" });
|
||||
const sigRealm = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealm), [issuer], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const ixDeposit = [];
|
||||
const tokenOwnerRecordPk = await withDepositGoverningTokens(
|
||||
ixDeposit,
|
||||
const ixTokenOwnerRecord = [];
|
||||
const tokenOwnerRecordPk = await withCreateTokenOwnerRecord(
|
||||
ixTokenOwnerRecord,
|
||||
governanceProgramId,
|
||||
PROGRAM_VERSION_V3,
|
||||
realmPk,
|
||||
issuerAta,
|
||||
mintKeypair.publicKey,
|
||||
issuer.publicKey,
|
||||
issuer.publicKey,
|
||||
issuer.publicKey,
|
||||
new BN(supply),
|
||||
true
|
||||
mint,
|
||||
issuer.publicKey
|
||||
);
|
||||
const sigTokenOwnerRecord = await sendAndConfirmTransaction(
|
||||
connection,
|
||||
new Transaction().add(...ixTokenOwnerRecord),
|
||||
[issuer],
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
const sigDeposit = await sendAndConfirmTransaction(connection, new Transaction().add(...ixDeposit), [issuer], { commitment: "confirmed" });
|
||||
|
||||
const governanceConfig = new GovernanceConfig({
|
||||
communityVoteThreshold: new VoteThreshold({ type: VoteThresholdType.YesVotePercentage, value: thresholdPct }),
|
||||
communityVoteThreshold: new VoteThreshold({ type: VoteThresholdType.YesVotePercentage, value: Number(cfg.DAO_APPROVAL_THRESHOLD_PERCENT || 51) }),
|
||||
minCommunityTokensToCreateProposal: new BN(1),
|
||||
minInstructionHoldUpTime: 0,
|
||||
baseVotingTime: votingTimeSec,
|
||||
baseVotingTime: Number(cfg.DAO_VOTING_TIME_SEC || 86400),
|
||||
communityVoteTipping: VoteTipping.Early,
|
||||
minCouncilTokensToCreateProposal: new BN(0),
|
||||
councilVoteThreshold: new VoteThreshold({ type: VoteThresholdType.Disabled }),
|
||||
@@ -249,82 +180,45 @@ async function main() {
|
||||
issuer.publicKey
|
||||
);
|
||||
const treasuryPk = await withCreateNativeTreasury(ixGov, governanceProgramId, PROGRAM_VERSION_V3, governancePk, issuer.publicKey);
|
||||
const sigGov = await sendAndConfirmTransaction(connection, new Transaction().add(...ixGov), [issuer], { commitment: "confirmed" });
|
||||
const sigGov = await sendAndConfirmTransaction(connection, new Transaction().add(...ixGov), [issuer], {
|
||||
commitment: "confirmed",
|
||||
});
|
||||
|
||||
const ixSetMintAuthority = [
|
||||
createSetAuthorityInstruction(
|
||||
mintKeypair.publicKey,
|
||||
issuer.publicKey,
|
||||
AuthorityType.MintTokens,
|
||||
governancePk,
|
||||
[],
|
||||
TOKEN_PROGRAM_ID
|
||||
),
|
||||
];
|
||||
const sigSetMintAuthority = await sendAndConfirmTransaction(connection, new Transaction().add(...ixSetMintAuthority), [issuer], { commitment: "confirmed" });
|
||||
|
||||
const ixRealmAuthority = [];
|
||||
withSetRealmAuthority(
|
||||
ixRealmAuthority,
|
||||
governanceProgramId,
|
||||
PROGRAM_VERSION_V3,
|
||||
realmPk,
|
||||
issuer.publicKey,
|
||||
governancePk,
|
||||
SetRealmAuthorityAction.SetChecked
|
||||
);
|
||||
const sigSetRealmAuthority = await sendAndConfirmTransaction(connection, new Transaction().add(...ixRealmAuthority), [issuer], { commitment: "confirmed" });
|
||||
|
||||
const endBalance = await connection.getBalance(issuer.publicKey, "confirmed");
|
||||
const spentLamports = startBalance - endBalance;
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
cluster,
|
||||
configPath,
|
||||
realmName: cfg.DAO_REALM_NAME,
|
||||
governanceProgramId: governanceProgramId.toBase58(),
|
||||
governingMint: mint.toBase58(),
|
||||
issuer: issuer.publicKey.toBase58(),
|
||||
communityMint: mintKeypair.publicKey.toBase58(),
|
||||
issuerAta: issuerAta.toBase58(),
|
||||
realm: realmPk.toBase58(),
|
||||
tokenOwnerRecord: tokenOwnerRecordPk.toBase58(),
|
||||
governance: governancePk.toBase58(),
|
||||
nativeTreasury: treasuryPk.toBase58(),
|
||||
metadataUri: cfg.DAO_GOV_TOKEN_METADATA_URI,
|
||||
imageUrl: cfg.DAO_GOV_TOKEN_IMAGE_URL,
|
||||
txMint: sigMint,
|
||||
txMetadata: sigMetadata,
|
||||
tokenOwnerRecord: tokenOwnerRecordPk.toBase58(),
|
||||
mintAuthorityBefore,
|
||||
freezeAuthorityBefore,
|
||||
txRealm: sigRealm,
|
||||
txDeposit: sigDeposit,
|
||||
txTokenOwnerRecord: sigTokenOwnerRecord,
|
||||
txGovernanceTreasury: sigGov,
|
||||
txSetMintAuthorityToGovernance: sigSetMintAuthority,
|
||||
txSetRealmAuthority: sigSetRealmAuthority,
|
||||
tokenSupply: supply,
|
||||
votingTimeSec,
|
||||
thresholdPercent: thresholdPct,
|
||||
startBalanceSol: lamportsToSol(startBalance),
|
||||
endBalanceSol: lamportsToSol(endBalance),
|
||||
spentSol: lamportsToSol(spentLamports),
|
||||
communityTokenType: "Liquid",
|
||||
};
|
||||
const runsDir = path.resolve(__dirname, "..", "local", "runs");
|
||||
ensureDir(runsDir);
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_create_dao_from_existing_mint.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
const reportsDir = path.resolve(__dirname, "..", "local", "runs");
|
||||
ensureDir(reportsDir);
|
||||
const base = `${nowStamp()}_${cfg.DAO_REALM_NAME.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 80)}_dao`;
|
||||
const jsonPath = path.join(reportsDir, `${base}.json`);
|
||||
const txtPath = path.join(reportsDir, `${base}.txt`);
|
||||
fs.writeFileSync(jsonPath, JSON.stringify(report, null, 2));
|
||||
fs.writeFileSync(txtPath, Object.entries(report).map(([k, v]) => `${k}: ${v}`).join("\n") + "\n");
|
||||
|
||||
console.log("DAO создан.");
|
||||
console.log("communityMint:", report.communityMint);
|
||||
console.log("realm:", report.realm);
|
||||
console.log("governance:", report.governance);
|
||||
console.log("nativeTreasury:", report.nativeTreasury);
|
||||
console.log("spentSol:", report.spentSol);
|
||||
console.log("report:", jsonPath);
|
||||
console.log("DAO created.");
|
||||
console.log("communityMint:", mint.toBase58());
|
||||
console.log("realm:", realmPk.toBase58());
|
||||
console.log("governance:", governancePk.toBase58());
|
||||
console.log("nativeTreasury:", treasuryPk.toBase58());
|
||||
console.log("tokenOwnerRecord:", tokenOwnerRecordPk.toBase58());
|
||||
console.log("spentSol:", "see report via tx fees + rent");
|
||||
console.log("report:", reportPath);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("Ошибка создания DAO:", e?.message || e);
|
||||
console.error("CreateDAO error:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env node
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const readline = require("readline");
|
||||
const {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
TOKEN_PROGRAM_ID,
|
||||
AuthorityType,
|
||||
createSetAuthorityInstruction,
|
||||
} = require("@solana/spl-token");
|
||||
const {
|
||||
PROGRAM_VERSION_V3,
|
||||
getRealm,
|
||||
withSetRealmAuthority,
|
||||
SetRealmAuthorityAction,
|
||||
} = require("@solana/spl-governance");
|
||||
const { createUmi } = require("@metaplex-foundation/umi-bundle-defaults");
|
||||
const {
|
||||
createSignerFromKeypair,
|
||||
signerIdentity,
|
||||
none,
|
||||
some,
|
||||
} = require("@metaplex-foundation/umi");
|
||||
const { fromWeb3JsKeypair, fromWeb3JsPublicKey } = require("@metaplex-foundation/umi-web3js-adapters");
|
||||
const {
|
||||
mplTokenMetadata,
|
||||
fetchMetadataFromSeeds,
|
||||
updateV1,
|
||||
} = require("@metaplex-foundation/mpl-token-metadata");
|
||||
const {
|
||||
clusterUrl,
|
||||
ensureDir,
|
||||
loadKeypair,
|
||||
nowStamp,
|
||||
parseEnvConfig,
|
||||
resolveConfigPath,
|
||||
} = require("./js_common");
|
||||
|
||||
function lamportsToSol(lamports) {
|
||||
return Number(lamports) / 1_000_000_000;
|
||||
}
|
||||
|
||||
async function askYes() {
|
||||
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
||||
const answer = await new Promise((resolve) =>
|
||||
rl.question("Введите YES для передачи authority на DAO: ", resolve)
|
||||
);
|
||||
rl.close();
|
||||
return answer.trim() === "YES";
|
||||
}
|
||||
|
||||
async function fetchMetadataState(cluster, issuer, mint) {
|
||||
const umi = createUmi(clusterUrl(cluster));
|
||||
const issuerSigner = createSignerFromKeypair(umi, fromWeb3JsKeypair(issuer));
|
||||
umi.use(signerIdentity(issuerSigner));
|
||||
umi.use(mplTokenMetadata());
|
||||
|
||||
const metadata = await fetchMetadataFromSeeds(umi, { mint: fromWeb3JsPublicKey(mint) });
|
||||
return {
|
||||
umi,
|
||||
issuerSigner,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchMetadataUpdateAuthority(cluster, mint) {
|
||||
const umi = createUmi(clusterUrl(cluster));
|
||||
umi.use(mplTokenMetadata());
|
||||
const metadata = await fetchMetadataFromSeeds(umi, { mint: fromWeb3JsPublicKey(mint) });
|
||||
return metadata.updateAuthority.toString();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const configPath = resolveConfigPath(process.argv[2], "dao.config.env");
|
||||
if (!fs.existsSync(configPath)) throw new Error(`Конфиг не найден: ${configPath}`);
|
||||
|
||||
const cfg = parseEnvConfig(configPath);
|
||||
[
|
||||
"DAO_CLUSTER",
|
||||
"DAO_REALM_NAME",
|
||||
"DAO_ISSUER_KEYPAIR",
|
||||
"SPL_GOVERNANCE_PROGRAM_ID",
|
||||
"DAO_GOV_TOKEN_MINT",
|
||||
].forEach((k) => {
|
||||
if (!cfg[k]) throw new Error(`В конфиге отсутствует обязательный параметр: ${k}`);
|
||||
});
|
||||
|
||||
const cluster = cfg.DAO_CLUSTER;
|
||||
const connection = new Connection(clusterUrl(cluster), "confirmed");
|
||||
const issuer = loadKeypair(path.resolve(cfg.DAO_ISSUER_KEYPAIR));
|
||||
const governanceProgramId = new PublicKey(cfg.SPL_GOVERNANCE_PROGRAM_ID);
|
||||
const mint = new PublicKey(cfg.DAO_GOV_TOKEN_MINT);
|
||||
|
||||
const [realmPk] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("governance"), Buffer.from(cfg.DAO_REALM_NAME, "utf8")],
|
||||
governanceProgramId
|
||||
);
|
||||
const [governancePk] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("account-governance"), realmPk.toBuffer(), realmPk.toBuffer()],
|
||||
governanceProgramId
|
||||
);
|
||||
|
||||
const realmInfo = await connection.getAccountInfo(realmPk, "confirmed");
|
||||
if (!realmInfo) throw new Error(`Realm не найден: ${realmPk.toBase58()}. Сначала выполните шаг 2.`);
|
||||
|
||||
const governanceInfo = await connection.getAccountInfo(governancePk, "confirmed");
|
||||
if (!governanceInfo) {
|
||||
throw new Error(`Governance PDA не найден: ${governancePk.toBase58()}. Сначала выполните шаг 2.`);
|
||||
}
|
||||
const realmBefore = await getRealm(connection, realmPk);
|
||||
const realmAuthorityBefore = realmBefore.account.authority?.toBase58?.() || null;
|
||||
if (realmAuthorityBefore !== issuer.publicKey.toBase58()) {
|
||||
throw new Error(`Realm authority уже не у issuer: ${realmAuthorityBefore}`);
|
||||
}
|
||||
|
||||
const mintInfoBefore = await connection.getParsedAccountInfo(mint, "confirmed");
|
||||
if (!mintInfoBefore.value) throw new Error(`Mint не найден: ${mint.toBase58()}`);
|
||||
const mintParsedBefore = mintInfoBefore.value.data.parsed.info;
|
||||
const mintAuthorityBefore = mintParsedBefore.mintAuthority || null;
|
||||
const freezeAuthorityBefore = mintParsedBefore.freezeAuthority || null;
|
||||
|
||||
if (mintAuthorityBefore && mintAuthorityBefore !== issuer.publicKey.toBase58()) {
|
||||
throw new Error(`Mint authority уже не у issuer: ${mintAuthorityBefore}`);
|
||||
}
|
||||
if (freezeAuthorityBefore && freezeAuthorityBefore !== issuer.publicKey.toBase58()) {
|
||||
throw new Error(`Freeze authority уже не у issuer: ${freezeAuthorityBefore}`);
|
||||
}
|
||||
|
||||
const { umi, metadata, issuerSigner } = await fetchMetadataState(cluster, issuer, mint);
|
||||
const metadataUpdateAuthorityBefore = metadata.updateAuthority.toString();
|
||||
if (metadataUpdateAuthorityBefore !== issuer.publicKey.toBase58()) {
|
||||
throw new Error(`Metadata update authority уже не у issuer: ${metadataUpdateAuthorityBefore}`);
|
||||
}
|
||||
|
||||
const startBalance = await connection.getBalance(issuer.publicKey, "confirmed");
|
||||
console.log("============================================================");
|
||||
console.log("TRANSFER DAO AUTHORITIES");
|
||||
console.log("------------------------------------------------------------");
|
||||
console.log("Сеть: ", cluster);
|
||||
console.log("Realm: ", realmPk.toBase58());
|
||||
console.log("Governance PDA: ", governancePk.toBase58());
|
||||
console.log("Issuer: ", issuer.publicKey.toBase58());
|
||||
console.log("Governance mint: ", mint.toBase58());
|
||||
console.log("Баланс до: ", `${lamportsToSol(startBalance)} SOL`);
|
||||
console.log("Mint authority до: ", mintAuthorityBefore || "null");
|
||||
console.log("Freeze authority до: ", freezeAuthorityBefore || "null");
|
||||
console.log("Realm authority до: ", realmAuthorityBefore || "null");
|
||||
console.log("Metadata authority до: ", metadataUpdateAuthorityBefore);
|
||||
console.log("============================================================");
|
||||
|
||||
if (!(await askYes())) {
|
||||
console.log("Отменено.");
|
||||
return;
|
||||
}
|
||||
|
||||
const ixTransferAuthorities = [
|
||||
createSetAuthorityInstruction(
|
||||
mint,
|
||||
issuer.publicKey,
|
||||
AuthorityType.MintTokens,
|
||||
governancePk,
|
||||
[],
|
||||
TOKEN_PROGRAM_ID
|
||||
),
|
||||
createSetAuthorityInstruction(
|
||||
mint,
|
||||
issuer.publicKey,
|
||||
AuthorityType.FreezeAccount,
|
||||
governancePk,
|
||||
[],
|
||||
TOKEN_PROGRAM_ID
|
||||
),
|
||||
];
|
||||
const sigTransferAuthorities = await sendAndConfirmTransaction(
|
||||
connection,
|
||||
new Transaction().add(...ixTransferAuthorities),
|
||||
[issuer],
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
|
||||
const ixSetRealmAuthority = [];
|
||||
withSetRealmAuthority(
|
||||
ixSetRealmAuthority,
|
||||
governanceProgramId,
|
||||
PROGRAM_VERSION_V3,
|
||||
realmPk,
|
||||
issuer.publicKey,
|
||||
governancePk,
|
||||
SetRealmAuthorityAction.SetChecked
|
||||
);
|
||||
const sigSetRealmAuthority = await sendAndConfirmTransaction(
|
||||
connection,
|
||||
new Transaction().add(...ixSetRealmAuthority),
|
||||
[issuer],
|
||||
{ commitment: "confirmed" }
|
||||
);
|
||||
|
||||
const metadataTx = await updateV1(umi, {
|
||||
authority: issuerSigner,
|
||||
mint: fromWeb3JsPublicKey(mint),
|
||||
newUpdateAuthority: some(fromWeb3JsPublicKey(governancePk)),
|
||||
data: none(),
|
||||
primarySaleHappened: none(),
|
||||
isMutable: none(),
|
||||
collection: { __kind: "None" },
|
||||
collectionDetails: { __kind: "None" },
|
||||
uses: { __kind: "None" },
|
||||
ruleSet: { __kind: "None" },
|
||||
authorizationData: none(),
|
||||
}).sendAndConfirm(umi);
|
||||
const sigMetadataAuthority = Buffer.from(metadataTx.signature).toString("base64");
|
||||
|
||||
const mintInfoAfter = await connection.getParsedAccountInfo(mint, "confirmed");
|
||||
const mintParsedAfter = mintInfoAfter.value.data.parsed.info;
|
||||
const mintAuthorityAfter = mintParsedAfter.mintAuthority || null;
|
||||
const freezeAuthorityAfter = mintParsedAfter.freezeAuthority || null;
|
||||
const realmAfter = await getRealm(connection, realmPk);
|
||||
const realmAuthorityAfter = realmAfter.account.authority?.toBase58?.() || null;
|
||||
|
||||
const metadataUpdateAuthorityAfter = await fetchMetadataUpdateAuthority(cluster, mint);
|
||||
|
||||
const report = {
|
||||
createdAt: new Date().toISOString(),
|
||||
cluster,
|
||||
realmName: cfg.DAO_REALM_NAME,
|
||||
governanceProgramId: governanceProgramId.toBase58(),
|
||||
issuer: issuer.publicKey.toBase58(),
|
||||
governingMint: mint.toBase58(),
|
||||
realm: realmPk.toBase58(),
|
||||
governance: governancePk.toBase58(),
|
||||
mintAuthorityBefore,
|
||||
freezeAuthorityBefore,
|
||||
metadataUpdateAuthorityBefore,
|
||||
realmAuthorityBefore,
|
||||
mintAuthorityAfter,
|
||||
freezeAuthorityAfter,
|
||||
realmAuthorityAfter,
|
||||
metadataUpdateAuthorityAfter,
|
||||
txTransferAuthorities: sigTransferAuthorities,
|
||||
txSetRealmAuthority: sigSetRealmAuthority,
|
||||
txSetMetadataUpdateAuthority: sigMetadataAuthority,
|
||||
};
|
||||
const runsDir = path.resolve(__dirname, "..", "local", "runs");
|
||||
ensureDir(runsDir);
|
||||
const reportPath = path.join(runsDir, `${nowStamp()}_transfer_dao_authorities.json`);
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
|
||||
console.log("Все authority переданы на DAO.");
|
||||
console.log("realm:", realmPk.toBase58());
|
||||
console.log("governance:", governancePk.toBase58());
|
||||
console.log("mintAuthority после:", mintAuthorityAfter || "null");
|
||||
console.log("freezeAuthority после:", freezeAuthorityAfter || "null");
|
||||
console.log("realmAuthority после:", realmAuthorityAfter || "null");
|
||||
console.log("metadataAuthority после:", metadataUpdateAuthorityAfter);
|
||||
console.log("report:", reportPath);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("TransferAuthorities error:", e?.message || e);
|
||||
process.exit(1);
|
||||
});
|
||||
+1
-1
@@ -22,7 +22,7 @@ const { clusterUrl, ensureDir, loadKeypair, nowStamp, parseEnvConfig, resolveCon
|
||||
async function main() {
|
||||
const targetWallet = process.argv[3];
|
||||
const nftMintStr = process.argv[4];
|
||||
if (!targetWallet || !nftMintStr) throw new Error("Usage: node 05_propose_vote_mint_nft.js <config.env> <target_wallet> <nft_mint>");
|
||||
if (!targetWallet || !nftMintStr) throw new Error("Usage: node 06_propose_vote_mint_nft.js <config.env> <target_wallet> <nft_mint>");
|
||||
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2], "nft-flow.config.env"));
|
||||
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||
+1
-1
@@ -13,7 +13,7 @@ async function main() {
|
||||
const proposalTx = new PublicKey(process.argv[4]);
|
||||
const target = new PublicKey(process.argv[5]);
|
||||
const nftMint = new PublicKey(process.argv[6]);
|
||||
if (!process.argv[6]) throw new Error("Usage: node 06_execute_mint_nft.js <config.env> <proposal> <proposalTx> <targetWallet> <nftMint>");
|
||||
if (!process.argv[6]) throw new Error("Usage: node 07_execute_mint_nft.js <config.env> <proposal> <proposalTx> <targetWallet> <nftMint>");
|
||||
|
||||
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||
const main = loadKeypair(path.resolve(__dirname, "..", cfg.MAIN_KEYPAIR));
|
||||
+1
-1
@@ -21,7 +21,7 @@ const { clusterUrl, ensureDir, loadKeypair, nowStamp, parseEnvConfig, resolveCon
|
||||
async function main() {
|
||||
const currentOwnerWallet = process.argv[3];
|
||||
const nftMintStr = process.argv[4];
|
||||
if (!currentOwnerWallet || !nftMintStr) throw new Error("Usage: node 07_propose_vote_burn_nft.js <config.env> <current_owner_wallet> <nft_mint>");
|
||||
if (!currentOwnerWallet || !nftMintStr) throw new Error("Usage: node 08_propose_vote_burn_nft.js <config.env> <current_owner_wallet> <nft_mint>");
|
||||
|
||||
const cfg = parseEnvConfig(resolveConfigPath(process.argv[2], "nft-flow.config.env"));
|
||||
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||
+1
-1
@@ -13,7 +13,7 @@ async function main() {
|
||||
const proposalTx = new PublicKey(process.argv[4]);
|
||||
const currentOwner = new PublicKey(process.argv[5]);
|
||||
const nftMint = new PublicKey(process.argv[6]);
|
||||
if (!process.argv[6]) throw new Error("Usage: node 08_execute_burn_nft.js <config.env> <proposal> <proposalTx> <currentOwnerWallet> <nftMint>");
|
||||
if (!process.argv[6]) throw new Error("Usage: node 09_execute_burn_nft.js <config.env> <proposal> <proposalTx> <currentOwnerWallet> <nftMint>");
|
||||
|
||||
const conn = new Connection(clusterUrl(cfg.CLUSTER), "confirmed");
|
||||
const main = loadKeypair(path.resolve(__dirname, "..", cfg.MAIN_KEYPAIR));
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
Текущий program id:
|
||||
|
||||
- `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`
|
||||
- `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
|
||||
|
||||
## 3. Состояние
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@
|
||||
|
||||
Текущий program id:
|
||||
|
||||
- `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||
- `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
|
||||
Текущий зашитый административный кошелек:
|
||||
|
||||
- `DAO_WALLET`: `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq`
|
||||
|
||||
## 2. Основная модель
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
|
||||
## 2. Program ID и внешние зависимости
|
||||
|
||||
Текущий program id devnet/localnet:
|
||||
Текущий program id:
|
||||
|
||||
- `3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ`
|
||||
- `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6`
|
||||
|
||||
Внешние зависимости по логике:
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
- системная программа Solana;
|
||||
- sysvar `instructions`.
|
||||
|
||||
Текущие зашитые внешние адреса:
|
||||
|
||||
- `DAO_AUTHORITY`: `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq`
|
||||
- `shine_payments`: `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v`
|
||||
- `shine_login_guard`: `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh`
|
||||
|
||||
## 3. PDA и seed-правила
|
||||
|
||||
### 3.1. Пользовательская PDA
|
||||
|
||||
@@ -18,3 +18,8 @@ solana-program = "2.1.21"
|
||||
default = []
|
||||
no-entrypoint = []
|
||||
cpi = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies.anchor-lang]
|
||||
version = "0.31.1"
|
||||
optional = true
|
||||
|
||||
@@ -18,7 +18,7 @@ pub const CLASS_TRADEMARK: u32 = 2;
|
||||
pub const MAX_WORDS_PER_LOGIN: usize = 3;
|
||||
pub const MAX_LOGIN_LEN: usize = 20;
|
||||
|
||||
solana_program::declare_id!("3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo");
|
||||
solana_program::declare_id!("SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh");
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::str::FromStr;
|
||||
|
||||
pub mod settings;
|
||||
|
||||
solana_program::declare_id!("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
solana_program::declare_id!("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
const IX_INIT: u8 = 1;
|
||||
|
||||
@@ -55,4 +55,4 @@ pub const PYTH_SOL_USD_FEED_ID: &str =
|
||||
pub const PYTH_SOL_USD_ACCOUNT: &str = "7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE";
|
||||
|
||||
/// `DAO_WALLET` — адрес кошелька DAO-казны для `shine_payments`.
|
||||
pub const DAO_WALLET: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P";
|
||||
pub const DAO_WALLET: &str = "aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq";
|
||||
|
||||
@@ -110,9 +110,9 @@
|
||||
|
||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||
<script>
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
const USERS_PROGRAM_ID = new solanaWeb3.PublicKey("3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ");
|
||||
const RPC_URL = "https://api.devnet.solana.com";
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
const USERS_PROGRAM_ID = new solanaWeb3.PublicKey("SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6");
|
||||
const RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
const SEEDS = {
|
||||
config: "shine_payments_config",
|
||||
@@ -350,7 +350,7 @@
|
||||
try {
|
||||
const usersPdas = deriveUsersPdas();
|
||||
const ai = await connection.getAccountInfo(usersPdas.usersEconomyConfigPda, "confirmed");
|
||||
document.getElementById("usersDaoAllowed").textContent = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P";
|
||||
document.getElementById("usersDaoAllowed").textContent = "aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq";
|
||||
if (!ai) {
|
||||
out.innerHTML = `<span class="warn">PDA Users Economy еще не инициализирован.</span><div>PDA: <code>${usersPdas.usersEconomyConfigPda.toBase58()}</code></div>`;
|
||||
return;
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
|
||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||
<script>
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
const RPC_URL = "https://api.devnet.solana.com";
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
const RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
const ORACLE_ACCOUNT = new solanaWeb3.PublicKey("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<div class="panel">
|
||||
<div class="warn">
|
||||
Пока реального DAO-голосования нет: роль DAO выполняет тестовый кошелек
|
||||
<code>FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P</code>.<br />
|
||||
<code>aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq</code>.<br />
|
||||
Позже это заменяется на вызов из настоящего DAO-казначейства/голосования.
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,8 +84,8 @@
|
||||
|
||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||
<script>
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
const RPC_URL = "https://api.devnet.solana.com";
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
const RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
const SEEDS = {
|
||||
config: "shine_payments_config",
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
|
||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||
<script>
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
const RPC_URL = "https://api.devnet.solana.com";
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
const RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
const SEEDS = {
|
||||
managerAllowance: "shine_p_manager_allow",
|
||||
|
||||
@@ -77,8 +77,8 @@
|
||||
|
||||
<script src="https://unpkg.com/@solana/web3.js@1.95.3/lib/index.iife.min.js"></script>
|
||||
<script>
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW");
|
||||
const RPC_URL = "https://api.devnet.solana.com";
|
||||
const PROGRAM_ID = new solanaWeb3.PublicKey("SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v");
|
||||
const RPC_URL = "https://api.mainnet-beta.solana.com";
|
||||
const ORACLE_ACCOUNT = new solanaWeb3.PublicKey("7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE");
|
||||
const connection = new solanaWeb3.Connection(RPC_URL, "confirmed");
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{convert::TryFrom, str::FromStr};
|
||||
|
||||
pub mod settings;
|
||||
|
||||
solana_program::declare_id!("3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ");
|
||||
solana_program::declare_id!("SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6");
|
||||
entrypoint!(process_instruction);
|
||||
|
||||
const MAGIC: &[u8; 5] = b"SHiNE";
|
||||
|
||||
@@ -12,14 +12,14 @@ pub const USERS_ECONOMY_CONFIG_SPACE: usize = 32;
|
||||
pub const PROMO_SELLER_PDA_SPACE: usize = 64;
|
||||
|
||||
/// Адрес DAO authority, который имеет право обновлять economy-конфиг.
|
||||
pub const DAO_AUTHORITY: &str = "FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P";
|
||||
pub const DAO_AUTHORITY: &str = "aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq";
|
||||
|
||||
/// Адрес программы `shine_payments`, от которой вычисляется PDA inflow-вольта.
|
||||
pub const SHINE_PAYMENTS_PROGRAM_ID: &str = "c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW";
|
||||
pub const SHINE_PAYMENTS_PROGRAM_ID: &str = "SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v";
|
||||
/// Seed inflow-вольта в программе `shine_payments`.
|
||||
pub const SHINE_PAYMENTS_INFLOW_VAULT_SEED: &[u8] = b"shine_payments_inflow_vault";
|
||||
/// Адрес отдельной программы проверки премиальности логина.
|
||||
pub const SHINE_LOGIN_GUARD_PROGRAM_ID: &str = "3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo";
|
||||
pub const SHINE_LOGIN_GUARD_PROGRAM_ID: &str = "SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh";
|
||||
/// Доменный префикс для подписи promo-кода.
|
||||
pub const PROMO_SIGN_PREFIX: &str = "shine_promo_v1:";
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const LIMIT_STEP = 10_000n;
|
||||
const START_BONUS_LIMIT = 100_000n;
|
||||
const USERS_ECONOMY_CONFIG_SEED = "shine_users_economy_config";
|
||||
const SHINE_PAYMENTS_PROGRAM_ID = new PublicKey(
|
||||
"c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW"
|
||||
"SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v"
|
||||
);
|
||||
const SHINE_PAYMENTS_INFLOW_VAULT_SEED = "shine_payments_inflow_vault";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user