diff --git a/AGENTS.md b/AGENTS.md
index a486665..ff7a0be 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -14,7 +14,6 @@
- Веб-панель администратора сервера (управление Solana PDA сервера) находится в `shine-UI/`:
- точка входа `shine-UI/server-ui.html`;
- остальные файлы серверного UI — в `shine-UI/server-ui/`.
-- Старая папка `shine-server-UI-obsolete/` оставлена только как устаревшая справочная копия и не является актуальной точкой входа.
- Локальный Telegram-бот агента-кодера находится в папке `SHiNE-agent-bot-coder/` и не является кодом основного серверного приложения.
- Solana/Anchor-модуль находится в папке `shine-solana/shine/` и ведётся отдельно от основного server/UI деплоя.
diff --git a/CLAUDE.md b/CLAUDE.md
index d3f6a87..0e5b4c5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -7,5 +7,5 @@
## Справка по подпроектам
- При работе внутри `SHiNE-agent-bot-coder/` — читать `SHiNE-agent-bot-coder/AGENTS.md` и `SHiNE-agent-bot-coder/AGENT.md`.
- При работе внутри `shine-solana/shine/` — читать `shine-solana/shine/AGENTS.md`.
-- При работе внутри `shine-UI/server-ui/` — читать `shine-UI/AGENTS.md`, а старую справочную копию при необходимости смотреть в `shine-server-UI-obsolete/AGENTS.md`.
+- При работе внутри `shine-UI/server-ui/` — читать `shine-UI/AGENTS.md`.
- При работе внутри `SHiNE-server/` — читать `SHiNE-server/AGENTS.md`.
diff --git a/Dev_Docs/Pending_Features/2026-06-03_1508_перенос_server_ui_в_shine_ui.md b/Dev_Docs/Pending_Features/2026-06-03_1508_перенос_server_ui_в_shine_ui.md
index 7f73e07..c3dc4bc 100644
--- a/Dev_Docs/Pending_Features/2026-06-03_1508_перенос_server_ui_в_shine_ui.md
+++ b/Dev_Docs/Pending_Features/2026-06-03_1508_перенос_server_ui_в_shine_ui.md
@@ -12,7 +12,7 @@
4. Загрузку существующей серверной PDA на странице обновления.
5. Обновление `server_address` и `sync_servers` только по `root + device` без blockchain-ключа.
6. Корректное чтение нового формата `ServerProfileBlock` через общий PDA-модуль.
- 7. То, что старая папка `shine-server-UI-obsolete/` не используется как актуальная точка входа.
+ 7. То, что актуальной точкой входа остаётся `shine-UI/server-ui.html`.
- ожидаемый результат:
1. Новые страницы открываются без JS-ошибок.
diff --git a/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md b/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md
deleted file mode 100644
index c6ce5f5..0000000
--- a/Dev_Docs/Pending_Features/2026-06-04_1347_fix_native_ed25519_update_server_pda.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# Фикс native Ed25519 для update server PDA
-
-- Краткое описание:
- В `shine_users` восстановлена нативная проверка подписи через встроенные Solana Ed25519-инструкции без прямой Rust-верификации. Для `create_user_pda` и `update_user_pda` зафиксирован порядок инструкций в транзакции: сначала подпись `root_key`, затем подпись `blockchain_public_key`, затем вызов `shine_users`.
-- Что проверять:
- 1. В `shine-UI/server-ui/update-server-pda.html` загрузить существующий server PDA.
- 2. Ввести правильный пароль, сгенерировать ключи и выполнить `Обновить PDA`.
- 3. Убедиться, что транзакция проходит без `memory allocation failed, out of memory`.
- 4. Отдельно проверить создание server PDA из `shine-UI/server-ui/create-server-pda.html`.
- 5. Отдельно проверить обычную пользовательскую регистрацию через клиентский UI.
-- Ожидаемый результат:
- 1. `update server PDA` проходит успешно.
- 2. `create server PDA` проходит успешно.
- 3. Регистрация обычного пользователя через тот же JS-модуль работы с PDA тоже проходит успешно.
- 4. Одинаковый общий JS-модуль используется и клиентским UI, и server UI.
-- Статус: `pending`
diff --git a/VERSION.properties b/VERSION.properties
index 3094aed..8d6ef7f 100644
--- a/VERSION.properties
+++ b/VERSION.properties
@@ -1,2 +1,2 @@
-client.version=1.2.122
-server.version=1.2.114
+client.version=1.2.123
+server.version=1.2.115
diff --git a/shine-server-UI-obsolete/AGENTS.md b/shine-server-UI-obsolete/AGENTS.md
deleted file mode 100644
index 3d3b5c7..0000000
--- a/shine-server-UI-obsolete/AGENTS.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# AGENTS.md — shine-server-UI-obsolete
-
-## Назначение
-
-`shine-server-UI-obsolete/` — устаревшая автономная веб-панель администратора для управления серверным аккаунтом SHiNE
-в Solana (регистрация и обновление `user_pda` с флагом `is_server=true`).
-
-Эта папка оставлена только как справочная копия старой реализации.
-Актуальная точка входа серверного UI теперь находится в:
-
-- `shine-UI/server-ui.html`
-- `shine-UI/server-ui/`
-
-Никакого бэкенда нет.
-
-## Структура файлов
-
-```
-shine-server-UI-obsolete/
- index.html — главная страница с навигацией
- create-server-pda.html — регистрация нового серверного аккаунта
- update-server-pda.html — обновление адреса/sync_servers существующей PDA
- styles.css — тёмная тема
- js/
- server-pda-core.js — вся логика: парсинг PDA, Borsh, криптография, Solana
-```
-
-## Как пользоваться
-
-### Регистрация сервера (`create-server-pda.html`)
-
-Открыть страницу в браузере (требуется HTTPS для WebCrypto — локально либо через сервер).
-
-Ввести:
-- **Логин сервера** — уникальный логин в Solana (только a-z, 0-9, _ ; без точки ; макс. 20 символов).
-- **Адрес сервера** — полный WebSocket/HTTP URL, например `https://shineup.me/ws`.
-- **sync_servers** — логины SHiNE-аккаунтов серверов-партнёров (по одному на строку).
-
-**Способ ввода ключей (переключатель):**
-
-- **«Из пароля»** — ввести пароль. Ключи автоматически выводятся из логина + пароля
- по той же схеме, что SHiNE-клиент (Argon2id + Ed25519). Занимает 2–5 сек.
- На страницах сервера публичные и приватные ключи показываются в base58, приватный ключ
- хранится как 32-байтовый seed в base58.
-- **«JSON ключей»** — вставить keyBundle JSON с тремя парами (rootPair, devicePair, blockchainPair).
-
-На **device-ключе** должно быть достаточно SOL для оплаты транзакции регистрации.
-
-### Обновление настроек сервера (`update-server-pda.html`)
-
-1. Ввести логин и нажать **«Загрузить PDA»** — страница прочитает существующую PDA из Solana и
- покажет текущие данные.
-2. Изменить адрес сервера или список sync_servers.
-3. Выбрать способ ввода ключей:
- - **«Из пароля»** — ввести пароль (логин берётся из поля выше);
- - **«JSON ключей»** — вставить keyBundle (достаточно rootPair + devicePair).
- Blockchain-ключ для обновления не нужен — существующая подпись из PDA переиспользуется.
- При ручном вводе допустим base58 seed; если blockchain seed не указан, обновление
- использует уже сохранённую подпись последнего блока.
-4. Нажать **«Обновить PDA»**.
-
-## Ключевой файл логики
-
-`js/server-pda-core.js` — автономный ES-модуль (без зависимостей на shine-UI).
-
-Экспортирует:
-- `readServerPdaData({ login, solanaEndpoint })` — читает и парсит PDA из Solana;
-- `registerServerOnSolana({ login, keyBundle, serverAddress, syncServers, ... })`;
-- `updateServerOnSolana({ login, keyBundle, serverAddress, syncServers, ... })`;
-- `parsePdaData(rawBytes)` — парсит бинарный формат PDA (matches Rust `deserialize_record_from_pda`).
-
-## Связанные документы
-
-- Формат PDA: `shine-solana/shine/doc/SHiNE-user-format-v.1.0.md`
-- Деплой Solana-программ: `Dev_Docs/Инициализация_Solana_регистрации/README.md`
-- Синхронизация между серверами: `Dev_Docs/Blockchain/sync-between-servers.md`
-- Настройки сервера: `SHiNE-server/AGENTS.md`
-
-## Правила при доработке
-
-- Формат Borsh-аргументов в `server-pda-core.js` должен строго соответствовать
- `UserMutableFields` в `shine-solana/shine/programs/shine_users/src/users.rs`.
-- Бинарный формат PDA в `buildUnsignedRecordBytesServer` должен совпадать с
- `serialize_unsigned_record` в Rust.
-- При любом изменении формата Solana-программы (`users.rs`) — обновлять `server-pda-core.js`
- и документ формата PDA в том же коммите.
-- Язык кода и комментариев: русский.
diff --git a/shine-server-UI-obsolete/create-server-pda.html b/shine-server-UI-obsolete/create-server-pda.html
deleted file mode 100644
index 4a8a16a..0000000
--- a/shine-server-UI-obsolete/create-server-pda.html
+++ /dev/null
@@ -1,468 +0,0 @@
-
-
-
-
-
- Регистрация сервера — SHiNE Server Admin
-
-
-
-
-
-
-
-
Регистрация серверного аккаунта
-
Создаёт user_pda в Solana с флагом is_server=true
-
-
-
Параметры Solana
-
-
Solana Endpoint
-
-
devnet: https://api.devnet.solana.com · mainnet: https://api.mainnet-beta.solana.com
-
-
-
-
-
Данные сервера
-
-
Логин сервера
-
-
Только a-z, 0-9, _ · без точки · макс. 20 символов
-
-
- Адрес сервера (URL)
-
-
-
- Серверы синхронизации (sync_servers)
-
-
-
- Серверы доступа (access_servers, опционально)
-
-
-
-
-
-
Ключи сервера
-
-
-
Пароль
-
-
- Показать
-
-
Нажмите «Сгенерировать» — поля ниже заполнятся из логина + пароля (Argon2id). Или введите ключи вручную.
-
-
-
- Сгенерировать ключи
-
-
-
-
Секрет (master secret, base58)
-
-
-
-
-
Ключевые пары (base58)
-
-
-
Root Key — подпись PDA-записи
-
Публичный
-
Приватный
-
-
-
Blockchain Key — подпись LastBlockState
-
Публичный
-
Приватный
-
-
-
Device Key — оплата транзакции Solana
-
Публичный
-
Приватный
-
-
Положите SOL на этот адрес перед регистрацией:
-
-
Это Solana-адрес device-ключа (public key в base58 = Solana-адрес). С него оплачивается создание PDA.
-
-
-
-
-
- Зарегистрировать сервер
-
-
-
-
-
-
-
diff --git a/shine-server-UI-obsolete/index.html b/shine-server-UI-obsolete/index.html
deleted file mode 100644
index 955b62a..0000000
--- a/shine-server-UI-obsolete/index.html
+++ /dev/null
@@ -1,58 +0,0 @@
-
-
-
-
-
- SHiNE Server Admin
-
-
-
-
-
SHiNE Server Admin
-
Панель управления Solana PDA для серверного аккаунта SHiNE
-
-
-
-
-
Как это работает
-
- Каждый SHiNE-сервер регистрирует свой аккаунт в Solana в виде user_pda
- с флагом is_server=true.
- В PDA хранятся:
- • адрес сервера (например, https://shineup.me/ws);
- • список серверов-партнёров для синхронизации блокчейна и DM;
- • криптографический корневой ключ сервера.
- Клиенты читают PDA прямо из Solana при попытке дозвониться до пользователя или
- установить WebSocket-соединение через сервер.
-
-
-
-
-
Что потребуется
-
- Для создания: полный keyBundle сервера (rootPair + devicePair + blockchainPair),
- логин сервера (без точки, не более 20 символов), URL-адрес сервера, Solana-эндпоинт,
- достаточный баланс SOL на device-ключе для комиссии.
- Для обновления: только rootPair + devicePair (blockchain-ключ не нужен).
-
-
-
-
-
diff --git a/shine-server-UI-obsolete/js/server-pda-core.js b/shine-server-UI-obsolete/js/server-pda-core.js
deleted file mode 100644
index 480b6d6..0000000
--- a/shine-server-UI-obsolete/js/server-pda-core.js
+++ /dev/null
@@ -1,710 +0,0 @@
-// Логика управления серверной PDA в Solana (shine_users)
-// Автономный модуль для панели администратора сервера SHiNE
-
-const SHINE_USERS_PROGRAM_ID = 'FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm';
-const SHINE_PAYMENTS_PROGRAM_ID = 'm48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR';
-const SHINE_LOGIN_GUARD_PROGRAM_ID = '3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo';
-const ED25519_PROGRAM_ID = 'Ed25519SigVerify111111111111111111111111111';
-const SYSVAR_INSTRUCTIONS_ID = 'Sysvar1nstructions1111111111111111111111111';
-
-// Discriminator create_user_pda (sha256("global:create_user_pda")[0..8])
-const CREATE_USER_PDA_DISCRIMINATOR = new Uint8Array([139, 157, 13, 41, 142, 174, 226, 214]);
-
-let _solanaLib = null;
-async function loadSolanaLib() {
- if (!_solanaLib) _solanaLib = await import('https://esm.sh/@solana/web3.js@1.98.4?bundle');
- return _solanaLib;
-}
-
-let _argon2Lib = null;
-async function loadArgon2() {
- if (!_argon2Lib) _argon2Lib = await import('https://esm.sh/@noble/hashes@1.8.0/argon2.js');
- return _argon2Lib;
-}
-
-// -------------------------------------------------------------------
-// Crypto (WebCrypto, Ed25519)
-// -------------------------------------------------------------------
-
-async function sha256Bytes(bytes) {
- const buf = await crypto.subtle.digest('SHA-256', bytes);
- return new Uint8Array(buf);
-}
-
-async function signEd25519(pkcs8B64, messageBytes) {
- const pkcs8 = Uint8Array.from(atob(pkcs8B64), c => c.charCodeAt(0));
- const key = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, false, ['sign']);
- const sig = await crypto.subtle.sign({ name: 'Ed25519' }, key, messageBytes);
- return new Uint8Array(sig);
-}
-
-function base64ToBytes(b64) {
- return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
-}
-
-function extractSeed32FromPkcs8B64(pkcs8B64) {
- // Ed25519 PKCS8 (48 байт): seed расположен начиная с байта 16
- return base64ToBytes(pkcs8B64).slice(16, 48);
-}
-
-async function anchorDiscriminator(name) {
- const hash = await sha256Bytes(new TextEncoder().encode(`global:${name}`));
- return hash.slice(0, 8);
-}
-
-// -------------------------------------------------------------------
-// Borsh-кодирование (Anchor-совместимое)
-// -------------------------------------------------------------------
-
-function pushU32LE(buf, v) {
- const n = v >>> 0;
- buf.push(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, (n >> 24) & 0xFF);
-}
-
-function pushU64LE(buf, bigV) {
- const b = typeof bigV === 'bigint' ? bigV : BigInt(bigV);
- const lo = Number(b & 0xFFFFFFFFn) >>> 0;
- const hi = Number((b >> 32n) & 0xFFFFFFFFn) >>> 0;
- pushU32LE(buf, lo);
- pushU32LE(buf, hi);
-}
-
-class BorshBuf {
- constructor() { this._b = []; }
- u8(v) { this._b.push(v & 0xFF); }
- u32(v) { pushU32LE(this._b, v); }
- u64(v) { pushU64LE(this._b, v); }
- bool(v) { this.u8(v ? 1 : 0); }
- bytes32(b) { for (const x of b) this._b.push(x); }
- vecU8(b) { this.u32(b.length); for (const x of b) this._b.push(x); }
- str(s) {
- const enc = new TextEncoder().encode(s);
- this.u32(enc.length);
- for (const x of enc) this._b.push(x);
- }
- vecStr(arr) {
- this.u32(arr.length);
- for (const s of arr) this.str(s);
- }
- raw(bytes) { for (const x of bytes) this._b.push(x); }
- result() { return new Uint8Array(this._b); }
-}
-
-// -------------------------------------------------------------------
-// Построение бинарного формата PDA (matches Rust serialize_unsigned_record)
-// -------------------------------------------------------------------
-
-function buildLastBlockStateBytes(login, blockchainName, lastBlockNumber, lastBlockHash32, usedBytes) {
- const enc = new TextEncoder();
- const buf = [];
- for (const x of enc.encode('SHiNE_LAST_BLOCK')) buf.push(x);
- const loginB = enc.encode(login);
- buf.push(loginB.length); for (const x of loginB) buf.push(x);
- const bchB = enc.encode(blockchainName);
- buf.push(bchB.length); for (const x of bchB) buf.push(x);
- pushU32LE(buf, lastBlockNumber);
- for (const x of lastBlockHash32) buf.push(x);
- pushU64LE(buf, usedBytes);
- return new Uint8Array(buf);
-}
-
-function buildUnsignedRecordBytesServer({
- login, createdAtMs, updatedAtMs, recordNumber, prevHash32,
- rootKey32, deviceKey32, blockchainKey32, blockchainName,
- paidLimitBytes, usedBytes, lastBlockNumber, lastBlockHash32, lastBlockSig64, arweaveTxId,
- serverAddress, addressFormatType, addressFormatVersion, syncServers, accessServers, trustedCount,
-}) {
- const enc = new TextEncoder();
- const loginB = enc.encode(login);
- const bchB = enc.encode(blockchainName);
- const buf = [];
-
- // Заголовок: MAGIC(5) + FORMAT_MAJOR(1) + FORMAT_MINOR(1) + record_len_placeholder(2)
- buf.push(0x53, 0x48, 0x69, 0x4E, 0x45, 1, 0, 0, 0); // байты 0..8
-
- pushU64LE(buf, createdAtMs);
- pushU64LE(buf, updatedAtMs);
- pushU32LE(buf, recordNumber);
- for (const x of prevHash32) buf.push(x);
-
- buf.push(loginB.length);
- for (const x of loginB) buf.push(x);
-
- buf.push(6); // blocks_count = 6 (сервер)
-
- // RootKeyBlock (type=1, ver=0)
- buf.push(1, 0);
- for (const x of rootKey32) buf.push(x);
-
- // DeviceKeyBlock (type=2, ver=0)
- buf.push(2, 0);
- for (const x of deviceKey32) buf.push(x);
-
- // BlockchainRegistryBlock (type=3, ver=0, count=1, blockchain_type=1)
- buf.push(3, 0, 1, 1);
- buf.push(bchB.length); for (const x of bchB) buf.push(x);
- for (const x of blockchainKey32) buf.push(x);
- pushU64LE(buf, paidLimitBytes);
- pushU64LE(buf, usedBytes);
- pushU32LE(buf, lastBlockNumber);
- for (const x of lastBlockHash32) buf.push(x);
- for (const x of lastBlockSig64) buf.push(x);
- if (arweaveTxId) {
- buf.push(1);
- const aTxB = enc.encode(arweaveTxId);
- buf.push(aTxB.length); for (const x of aTxB) buf.push(x);
- } else {
- buf.push(0);
- }
-
- // ServerProfileBlock (type=30, ver=0)
- buf.push(30, 0);
- buf.push(1); // is_server = 1
- buf.push(addressFormatType & 0xFF);
- buf.push(addressFormatVersion & 0xFF);
- const srvB = enc.encode(serverAddress);
- buf.push(srvB.length); for (const x of srvB) buf.push(x);
- buf.push(syncServers.length);
- for (const srv of syncServers) {
- const sB = enc.encode(srv);
- buf.push(sB.length); for (const x of sB) buf.push(x);
- }
-
- // AccessServersBlock (type=40, ver=0)
- buf.push(40, 0, accessServers.length);
- for (const srv of accessServers) {
- const sB = enc.encode(srv);
- buf.push(sB.length); for (const x of sB) buf.push(x);
- }
-
- // TrustedStateBlock (type=50, ver=0)
- buf.push(50, 0, trustedCount & 0xFF);
-
- // Записываем record_len: (длина буфера + 64 байта подписи)
- const recLen = buf.length + 64;
- buf[7] = recLen & 0xFF;
- buf[8] = (recLen >> 8) & 0xFF;
-
- return new Uint8Array(buf);
-}
-
-// -------------------------------------------------------------------
-// Borsh-сериализация Anchor-инструкций
-// -------------------------------------------------------------------
-
-function serializeCreateServerPdaArgs({
- login, rootKey32, createdAtMs, deviceKey32, blockchainKey32,
- blockchainName, usedBytes, lastBlockNumber, lastBlockHash32,
- lastBlockSig64, arweaveTxId, serverAddress, addressFormatType,
- addressFormatVersion, syncServers, accessServers, trustedCount, rootSig64,
-}) {
- const b = new BorshBuf();
- b.raw(CREATE_USER_PDA_DISCRIMINATOR);
- b.str(login);
- b.bytes32(rootKey32);
- b.u64(createdAtMs);
- b.u64(0n); // additional_limit
- // UserMutableFields:
- b.bytes32(deviceKey32);
- b.bytes32(blockchainKey32);
- b.str(blockchainName);
- b.u64(usedBytes);
- b.u32(lastBlockNumber);
- b.vecU8(lastBlockHash32);
- b.vecU8(lastBlockSig64);
- b.str(arweaveTxId);
- b.bool(true); // is_server
- b.u8(addressFormatType);
- b.u8(addressFormatVersion);
- b.str(serverAddress);
- b.vecStr(syncServers);
- b.vecStr(accessServers);
- b.u8(trustedCount);
- b.vecU8(rootSig64);
- return b.result();
-}
-
-async function serializeUpdateServerPdaArgs({
- login, rootKey32, createdAtMs, updatedAtMs, version, prevHash32,
- deviceKey32, blockchainKey32, blockchainName, usedBytes,
- lastBlockNumber, lastBlockHash32, lastBlockSig64, arweaveTxId,
- serverAddress, addressFormatType, addressFormatVersion,
- syncServers, accessServers, trustedCount, rootSig64,
-}) {
- const discriminator = await anchorDiscriminator('update_user_pda');
- const b = new BorshBuf();
- b.raw(discriminator);
- b.str(login);
- b.bytes32(rootKey32);
- b.u64(createdAtMs);
- b.u64(updatedAtMs);
- b.u32(version);
- b.vecU8(prevHash32);
- b.u64(0n); // additional_limit
- // UserMutableFields:
- b.bytes32(deviceKey32);
- b.bytes32(blockchainKey32);
- b.str(blockchainName);
- b.u64(usedBytes);
- b.u32(lastBlockNumber);
- b.vecU8(lastBlockHash32);
- b.vecU8(lastBlockSig64);
- b.str(arweaveTxId);
- b.bool(true); // is_server
- b.u8(addressFormatType);
- b.u8(addressFormatVersion);
- b.str(serverAddress);
- b.vecStr(syncServers);
- b.vecStr(accessServers);
- b.u8(trustedCount);
- b.vecU8(rootSig64);
- return b.result();
-}
-
-// -------------------------------------------------------------------
-// Построитель Ed25519-инструкции Solana
-// -------------------------------------------------------------------
-
-function buildEd25519IxData(sig64, pubkey32, msgHash32) {
- const sigOff = 16, pkOff = 80, msgOff = 112;
- const data = new Uint8Array(msgOff + 32);
- const v = new DataView(data.buffer);
- data[0] = 1; data[1] = 0;
- v.setUint16(2, sigOff, true); v.setUint16(4, 0xFFFF, true);
- v.setUint16(6, pkOff, true); v.setUint16(8, 0xFFFF, true);
- v.setUint16(10, msgOff, true); v.setUint16(12, 32, true); v.setUint16(14, 0xFFFF, true);
- data.set(sig64, sigOff);
- data.set(pubkey32, pkOff);
- data.set(msgHash32, msgOff);
- return data;
-}
-
-// -------------------------------------------------------------------
-// Парсер бинарных данных PDA (matches Rust deserialize_record_from_pda)
-// -------------------------------------------------------------------
-
-export function parsePdaData(raw) {
- const d = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
- if (d.length < 9) throw new Error('PDA слишком короткая');
- if (String.fromCharCode(d[0], d[1], d[2], d[3], d[4]) !== 'SHiNE') {
- throw new Error('Неверный magic в PDA');
- }
-
- const view = new DataView(d.buffer, d.byteOffset);
- const recordLen = view.getUint16(7, true);
- if (recordLen < 9 + 64 || recordLen > d.length) throw new Error('Неверный record_len');
-
- // Подписанная часть = байты [0 .. recordLen-64)
- const unsignedBytes = d.slice(0, recordLen - 64);
-
- let cur = 9;
- const ru8 = () => d[cur++];
- const ru32 = () => { const v = view.getUint32(cur, true); cur += 4; return v; };
- const ru64 = () => { const v = view.getBigUint64(cur, true); cur += 8; return v; };
- const rBytes = n => { const s = d.slice(cur, cur + n); cur += n; return s; };
- const rStr = () => { const len = ru8(); return new TextDecoder().decode(rBytes(len)); };
-
- const createdAtMs = ru64();
- const updatedAtMs = ru64();
- const recordNumber = ru32();
- const prevRecordHash = rBytes(32);
- const login = rStr();
- const blocksCount = ru8();
-
- let rootKey32 = null, deviceKey32 = null, blockchainData = null;
- let isServer = false, serverData = null;
- let accessServers = [], trustedCount = 0;
-
- for (let i = 0; i < blocksCount; i++) {
- const blockType = ru8();
- ru8(); // block_version
-
- if (blockType === 1) {
- rootKey32 = rBytes(32);
- } else if (blockType === 2) {
- deviceKey32 = rBytes(32);
- } else if (blockType === 3) {
- const count = ru8();
- const blockchainType = ru8();
- const blockchainName = rStr();
- const blockchainPublicKey = rBytes(32);
- const paidLimitBytes = ru64();
- const usedBytes = ru64();
- const lastBlockNumber = ru32();
- const lastBlockHash = rBytes(32);
- const lastBlockSignature = rBytes(64);
- const arweavePresent = ru8();
- const arweaveTxId = arweavePresent === 1 ? rStr() : '';
- blockchainData = {
- blockchainType, blockchainName, blockchainPublicKey,
- paidLimitBytes, usedBytes, lastBlockNumber,
- lastBlockHash, lastBlockSignature, arweaveTxId,
- };
- } else if (blockType === 30) {
- if (ru8() === 1) {
- isServer = true;
- const addressFormatType = ru8();
- const addressFormatVersion = ru8();
- const serverAddress = rStr();
- const syncCount = ru8();
- const syncServers = [];
- for (let j = 0; j < syncCount; j++) syncServers.push(rStr());
- serverData = { addressFormatType, addressFormatVersion, serverAddress, syncServers };
- }
- } else if (blockType === 40) {
- const cnt = ru8();
- for (let j = 0; j < cnt; j++) accessServers.push(rStr());
- } else if (blockType === 50) {
- trustedCount = ru8();
- }
- }
-
- const signature = d.slice(cur, cur + 64);
-
- return {
- recordLen, unsignedBytes,
- createdAtMs, updatedAtMs, recordNumber, prevRecordHash,
- login, rootKey32, deviceKey32, blockchainData,
- isServer, serverData, accessServers, trustedCount, signature,
- };
-}
-
-// -------------------------------------------------------------------
-// Вспомогательная: читает start_bonus_limit из economy config PDA
-// -------------------------------------------------------------------
-
-function readStartBonusLimit(data) {
- // Borsh: version(u8=1) + reg_fee(u64) + lamports_per_step(u64) = 17 байт до start_bonus_limit
- return new DataView(data.buffer, data.byteOffset, data.byteLength).getBigUint64(17, true);
-}
-
-// -------------------------------------------------------------------
-// Читает и парсит существующую PDA с блокчейна
-// -------------------------------------------------------------------
-
-export async function readServerPdaData({ login, solanaEndpoint }) {
- const solana = await loadSolanaLib();
- const connection = new solana.Connection(String(solanaEndpoint), 'confirmed');
- const loginNorm = String(login).trim().toLowerCase();
- const usersProgram = new solana.PublicKey(SHINE_USERS_PROGRAM_ID);
- const enc = new TextEncoder();
- const [userPda] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('login='), enc.encode(loginNorm)],
- usersProgram,
- );
- const ai = await connection.getAccountInfo(userPda, 'confirmed');
- if (!ai) throw new Error(`PDA не найдена для логина «${loginNorm}»`);
- const parsed = parsePdaData(ai.data);
- parsed.pdaAddress = userPda.toBase58();
- return parsed;
-}
-
-// -------------------------------------------------------------------
-// Регистрация нового серверного аккаунта в Solana
-// -------------------------------------------------------------------
-
-export async function registerServerOnSolana({
- login, keyBundle, serverAddress,
- addressFormatType = 1, addressFormatVersion = 0,
- syncServers = [], accessServers = [],
- solanaEndpoint,
-}) {
- const solana = await loadSolanaLib();
- const connection = new solana.Connection(String(solanaEndpoint), 'confirmed');
-
- const usersProgram = new solana.PublicKey(SHINE_USERS_PROGRAM_ID);
- const paymentsProgram = new solana.PublicKey(SHINE_PAYMENTS_PROGRAM_ID);
- const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID);
- const ed25519Program = new solana.PublicKey(ED25519_PROGRAM_ID);
- const sysvarInstructions = new solana.PublicKey(SYSVAR_INSTRUCTIONS_ID);
-
- const enc = new TextEncoder();
- const loginNorm = String(login).trim().toLowerCase();
- const blockchainName = `${loginNorm}-001`;
- const zeroHash32 = new Uint8Array(32);
-
- const [userPda] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('login='), enc.encode(loginNorm)], usersProgram);
- const [economyConfigPda] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('shine_users_economy_config')], usersProgram);
- const [inflowVault] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('shine_payments_inflow_vault')], paymentsProgram);
-
- const rootKey32 = base64ToBytes(keyBundle.rootPair.publicKeyB64);
- const blockchainKey32 = base64ToBytes(keyBundle.blockchainPair.publicKeyB64);
- const deviceKey32 = base64ToBytes(keyBundle.devicePair.publicKeyB64);
- const deviceKeypair = solana.Keypair.fromSeed(
- extractSeed32FromPkcs8B64(keyBundle.devicePair.privatePkcs8B64));
-
- const ecoAccount = await connection.getAccountInfo(economyConfigPda);
- if (!ecoAccount) throw new Error('Economy config не инициализирован');
- const paidLimitBytes = readStartBonusLimit(ecoAccount.data); // additional_limit = 0
- const createdAtMs = BigInt(Date.now());
-
- // Подписываем LastBlockState ключом блокчейна (начальное состояние: всё нули)
- const lbsBytes = buildLastBlockStateBytes(loginNorm, blockchainName, 0, zeroHash32, 0n);
- const lbsHash = await sha256Bytes(lbsBytes);
- const lastBlockSig64 = await signEd25519(keyBundle.blockchainPair.privatePkcs8B64, lbsHash);
-
- // Строим и подписываем беззнаковую запись PDA корневым ключом
- const unsignedRecord = buildUnsignedRecordBytesServer({
- login: loginNorm, createdAtMs, updatedAtMs: createdAtMs,
- recordNumber: 0, prevHash32: zeroHash32,
- rootKey32, deviceKey32, blockchainKey32, blockchainName,
- paidLimitBytes, usedBytes: 0n, lastBlockNumber: 0,
- lastBlockHash32: zeroHash32, lastBlockSig64, arweaveTxId: '',
- serverAddress, addressFormatType, addressFormatVersion,
- syncServers, accessServers, trustedCount: 0,
- });
- const unsignedHash = await sha256Bytes(unsignedRecord);
- const rootSig64 = await signEd25519(keyBundle.rootPair.privatePkcs8B64, unsignedHash);
-
- const ixData = serializeCreateServerPdaArgs({
- login: loginNorm, rootKey32, createdAtMs, deviceKey32, blockchainKey32,
- blockchainName, usedBytes: 0n, lastBlockNumber: 0,
- lastBlockHash32: zeroHash32, lastBlockSig64, arweaveTxId: '',
- serverAddress, addressFormatType, addressFormatVersion,
- syncServers, accessServers, trustedCount: 0, rootSig64,
- });
-
- const tx = new solana.Transaction().add(
- new solana.TransactionInstruction({
- programId: ed25519Program, keys: [],
- data: buildEd25519IxData(rootSig64, rootKey32, unsignedHash),
- }),
- new solana.TransactionInstruction({
- programId: ed25519Program, keys: [],
- data: buildEd25519IxData(lastBlockSig64, blockchainKey32, lbsHash),
- }),
- new solana.TransactionInstruction({
- programId: usersProgram,
- keys: [
- { pubkey: deviceKeypair.publicKey, isSigner: true, isWritable: true },
- { pubkey: userPda, isSigner: false, isWritable: true },
- { pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false },
- { pubkey: inflowVault, isSigner: false, isWritable: true },
- { pubkey: sysvarInstructions, isSigner: false, isWritable: false },
- { pubkey: economyConfigPda, isSigner: false, isWritable: false },
- { pubkey: loginGuardProgram, isSigner: false, isWritable: false },
- ],
- data: ixData,
- }),
- );
-
- const signature = await solana.sendAndConfirmTransaction(
- connection, tx, [deviceKeypair], { commitment: 'confirmed' });
-
- return { signature, pdaAddress: userPda.toBase58(), blockchainName };
-}
-
-// -------------------------------------------------------------------
-// Обновление серверного профиля в существующей PDA
-// Для обновления нужен только root-ключ (подпись записи) + device-ключ (оплата).
-// Blockchain-ключ не нужен — переиспользуем существующую подпись LastBlockState из PDA.
-// -------------------------------------------------------------------
-
-export async function updateServerOnSolana({
- login, keyBundle, serverAddress,
- addressFormatType, addressFormatVersion,
- syncServers,
- solanaEndpoint,
-}) {
- const solana = await loadSolanaLib();
- const connection = new solana.Connection(String(solanaEndpoint), 'confirmed');
-
- const usersProgram = new solana.PublicKey(SHINE_USERS_PROGRAM_ID);
- const paymentsProgram = new solana.PublicKey(SHINE_PAYMENTS_PROGRAM_ID);
- const ed25519Program = new solana.PublicKey(ED25519_PROGRAM_ID);
- const sysvarInstructions = new solana.PublicKey(SYSVAR_INSTRUCTIONS_ID);
-
- const enc = new TextEncoder();
- const loginNorm = String(login).trim().toLowerCase();
-
- const [userPda] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('login='), enc.encode(loginNorm)], usersProgram);
- const [economyConfigPda] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('shine_users_economy_config')], usersProgram);
- const [inflowVault] = solana.PublicKey.findProgramAddressSync(
- [enc.encode('shine_payments_inflow_vault')], paymentsProgram);
-
- // Читаем существующую PDA
- const ai = await connection.getAccountInfo(userPda, 'confirmed');
- if (!ai) throw new Error(`PDA не найдена для логина «${loginNorm}»`);
- const pda = parsePdaData(ai.data);
- if (!pda.isServer) throw new Error('Эта PDA не является серверной (is_server = false)');
-
- const bch = pda.blockchainData;
- const deviceKeypair = solana.Keypair.fromSeed(
- extractSeed32FromPkcs8B64(keyBundle.devicePair.privatePkcs8B64));
-
- // Формат адреса: берём из аргументов или из существующей PDA
- const fmtType = addressFormatType ?? pda.serverData?.addressFormatType ?? 1;
- const fmtVersion = addressFormatVersion ?? pda.serverData?.addressFormatVersion ?? 0;
-
- // prev_hash = sha256(unsigned_bytes предыдущей записи)
- const prevHash32 = await sha256Bytes(pda.unsignedBytes);
- const updatedAtMs = BigInt(Date.now());
- const newVersion = pda.recordNumber + 1;
-
- // Строим новую беззнаковую запись
- const unsignedRecord = buildUnsignedRecordBytesServer({
- login: loginNorm,
- createdAtMs: pda.createdAtMs, updatedAtMs,
- recordNumber: newVersion, prevHash32,
- rootKey32: pda.rootKey32, deviceKey32: pda.deviceKey32,
- blockchainKey32: bch.blockchainPublicKey, blockchainName: bch.blockchainName,
- paidLimitBytes: bch.paidLimitBytes, usedBytes: bch.usedBytes,
- lastBlockNumber: bch.lastBlockNumber,
- lastBlockHash32: bch.lastBlockHash, lastBlockSig64: bch.lastBlockSignature,
- arweaveTxId: bch.arweaveTxId,
- serverAddress, addressFormatType: fmtType, addressFormatVersion: fmtVersion,
- syncServers, accessServers: pda.accessServers, trustedCount: pda.trustedCount,
- });
- const unsignedHash = await sha256Bytes(unsignedRecord);
- const rootSig64 = await signEd25519(keyBundle.rootPair.privatePkcs8B64, unsignedHash);
-
- // Хэш LastBlockState из существующей PDA (те же данные — та же подпись)
- const lbsBytes = buildLastBlockStateBytes(
- loginNorm, bch.blockchainName,
- bch.lastBlockNumber, bch.lastBlockHash, bch.usedBytes);
- const lbsHash = await sha256Bytes(lbsBytes);
-
- const ixData = await serializeUpdateServerPdaArgs({
- login: loginNorm, rootKey32: pda.rootKey32,
- createdAtMs: pda.createdAtMs, updatedAtMs,
- version: newVersion, prevHash32,
- deviceKey32: pda.deviceKey32, blockchainKey32: bch.blockchainPublicKey,
- blockchainName: bch.blockchainName,
- usedBytes: bch.usedBytes, lastBlockNumber: bch.lastBlockNumber,
- lastBlockHash32: bch.lastBlockHash, lastBlockSig64: bch.lastBlockSignature,
- arweaveTxId: bch.arweaveTxId,
- serverAddress, addressFormatType: fmtType, addressFormatVersion: fmtVersion,
- syncServers, accessServers: pda.accessServers, trustedCount: pda.trustedCount,
- rootSig64,
- });
-
- const tx = new solana.Transaction().add(
- // Ed25519: подпись новой записи корневым ключом
- new solana.TransactionInstruction({
- programId: ed25519Program, keys: [],
- data: buildEd25519IxData(rootSig64, pda.rootKey32, unsignedHash),
- }),
- // Ed25519: переиспользуем существующую подпись LastBlockState из PDA
- new solana.TransactionInstruction({
- programId: ed25519Program, keys: [],
- data: buildEd25519IxData(bch.lastBlockSignature, bch.blockchainPublicKey, lbsHash),
- }),
- new solana.TransactionInstruction({
- programId: usersProgram,
- keys: [
- { pubkey: deviceKeypair.publicKey, isSigner: true, isWritable: true },
- { pubkey: userPda, isSigner: false, isWritable: true },
- { pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false },
- { pubkey: inflowVault, isSigner: false, isWritable: true },
- { pubkey: sysvarInstructions, isSigner: false, isWritable: false },
- { pubkey: economyConfigPda, isSigner: false, isWritable: false },
- ],
- data: ixData,
- }),
- );
-
- const signature = await solana.sendAndConfirmTransaction(
- connection, tx, [deviceKeypair], { commitment: 'confirmed' });
-
- return { signature, pdaAddress: userPda.toBase58() };
-}
-
-// -------------------------------------------------------------------
-// Деривация keyBundle из логина + пароля
-// Идентична логике SHiNE-клиента (crypto-utils.js):
-// masterSecret = Argon2id(login+"\n"+password, salt=sha256("shine-auth-v2|login=...|suffix=master.secret"))
-// rootPair = Ed25519(sha256(base64(master) + "|root.key"))
-// blockchainPair = Ed25519(sha256(base64(master) + "|bch.key"))
-// devicePair = Ed25519(sha256(base64(master) + "|dev.key"))
-// -------------------------------------------------------------------
-
-function _b64urlToStd(s) {
- const n = s.replace(/-/g, '+').replace(/_/g, '/');
- return n + '='.repeat((4 - n.length % 4) % 4);
-}
-
-function _ed25519Pkcs8FromSeed(seed32) {
- const prefix = new Uint8Array([
- 0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
- ]);
- const out = new Uint8Array(prefix.length + 32);
- out.set(prefix); out.set(seed32, prefix.length);
- return out;
-}
-
-async function _deriveEd25519PairFromMasterSecret(masterSecret32, suffix) {
- const enc = new TextEncoder();
- const material = `${btoa(String.fromCharCode(...masterSecret32))}|${suffix}`;
- const seed = await sha256Bytes(enc.encode(material));
- const pkcs8 = _ed25519Pkcs8FromSeed(seed);
- const privateKey = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, true, ['sign']);
- const jwk = await crypto.subtle.exportKey('jwk', privateKey);
- if (!jwk.x) throw new Error(`Не удалось получить публичный ключ (suffix=${suffix})`);
- const pubBytes = base64ToBytes(_b64urlToStd(jwk.x));
- return {
- publicKeyB64: btoa(String.fromCharCode(...pubBytes)),
- privatePkcs8B64: btoa(String.fromCharCode(...pkcs8)),
- };
-}
-
-/**
- * Выводит полный keyBundle из логина и пароля.
- * Та же самая логика, что используется в SHiNE-клиенте при регистрации.
- *
- * @param {string} login — логин сервера (нормализуется в нижний регистр)
- * @param {string} password — пароль
- * @param {function} [onProgress] — коллбэк(0..1) прогресса Argon2id
- * @returns {{ rootPair, blockchainPair, devicePair }}
- */
-export async function deriveKeyBundleFromPassword({ login, password, onProgress }) {
- const { argon2idAsync } = await loadArgon2();
- const enc = new TextEncoder();
- const loginNorm = String(login || '').trim().toLowerCase();
- const pwd = String(password ?? '');
-
- // Salt для master secret = sha256("shine-auth-v2|login=...|suffix=master.secret")[0..16]
- const saltSource = `shine-auth-v2|login=${loginNorm}|suffix=master.secret`;
- const saltFull = await sha256Bytes(enc.encode(saltSource));
- const salt = saltFull.slice(0, 16);
-
- const passBytes = enc.encode(`${loginNorm}\n${pwd}`);
- const masterRaw = await argon2idAsync(passBytes, salt, {
- t: 2, m: 65536, p: 1, dkLen: 32,
- onProgress,
- });
- const masterSecret32 = new Uint8Array(masterRaw);
-
- const [rootPair, blockchainPair, devicePair] = await Promise.all([
- _deriveEd25519PairFromMasterSecret(masterSecret32, 'root.key'),
- _deriveEd25519PairFromMasterSecret(masterSecret32, 'bch.key'),
- _deriveEd25519PairFromMasterSecret(masterSecret32, 'dev.key'),
- ]);
-
- const masterSecretB64 = btoa(String.fromCharCode(...masterSecret32));
- return { masterSecretB64, rootPair, blockchainPair, devicePair };
-}
-
-// -------------------------------------------------------------------
-// Кодирование байт в base58 (для отображения Solana-адреса)
-// -------------------------------------------------------------------
-
-export function base58Encode(bytes) {
- const ALPHA = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
- let num = 0n;
- for (const b of bytes) num = (num << 8n) | BigInt(b);
- let result = '';
- while (num > 0n) {
- result = ALPHA[Number(num % 58n)] + result;
- num /= 58n;
- }
- for (const b of bytes) {
- if (b !== 0) break;
- result = '1' + result;
- }
- return result;
-}
diff --git a/shine-server-UI-obsolete/styles.css b/shine-server-UI-obsolete/styles.css
deleted file mode 100644
index 5f87282..0000000
--- a/shine-server-UI-obsolete/styles.css
+++ /dev/null
@@ -1,193 +0,0 @@
-/* SHiNE Server Admin UI — тёмная тема */
-:root {
- --bg: #111;
- --surface: #1a1a1a;
- --border: #2a2a2a;
- --text: #e0e0e0;
- --text-muted: #888;
- --accent: #4a9eff;
- --accent-hover: #6ab4ff;
- --success: #4caf50;
- --error: #f44336;
- --warning: #ff9800;
- --radius: 8px;
-}
-
-* { box-sizing: border-box; margin: 0; padding: 0; }
-
-body {
- background: var(--bg);
- color: var(--text);
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;
- font-size: 14px;
- line-height: 1.5;
- padding: 24px 16px;
-}
-
-.container {
- max-width: 640px;
- margin: 0 auto;
-}
-
-h1 {
- font-size: 20px;
- font-weight: 600;
- color: var(--accent);
- margin-bottom: 4px;
-}
-
-.subtitle {
- color: var(--text-muted);
- margin-bottom: 24px;
- font-size: 13px;
-}
-
-.card {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 20px;
- margin-bottom: 16px;
-}
-
-.card h2 {
- font-size: 15px;
- font-weight: 600;
- margin-bottom: 16px;
- color: var(--text);
-}
-
-.field {
- margin-bottom: 14px;
-}
-
-label {
- display: block;
- font-size: 12px;
- color: var(--text-muted);
- margin-bottom: 6px;
- text-transform: uppercase;
- letter-spacing: 0.04em;
-}
-
-input[type="text"], input[type="password"], textarea {
- width: 100%;
- background: #0d0d0d;
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--text);
- font-family: monospace;
- font-size: 13px;
- padding: 10px 12px;
- outline: none;
- transition: border-color 0.15s;
- resize: vertical;
-}
-
-input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
- border-color: var(--accent);
-}
-
-input[type="text"][readonly] {
- opacity: 0.6;
-}
-
-textarea {
- min-height: 80px;
-}
-
-.hint {
- font-size: 11px;
- color: var(--text-muted);
- margin-top: 4px;
-}
-
-.btn-row {
- display: flex;
- gap: 10px;
- margin-top: 20px;
- flex-wrap: wrap;
-}
-
-button {
- padding: 10px 20px;
- border-radius: var(--radius);
- border: none;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: opacity 0.15s, background 0.15s;
-}
-
-button:disabled {
- opacity: 0.4;
- cursor: not-allowed;
-}
-
-.btn-primary {
- background: var(--accent);
- color: #fff;
-}
-
-.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }
-
-.btn-secondary {
- background: transparent;
- color: var(--text-muted);
- border: 1px solid var(--border);
-}
-
-.btn-secondary:hover:not(:disabled) {
- border-color: var(--accent);
- color: var(--accent);
-}
-
-.status {
- padding: 12px 16px;
- border-radius: var(--radius);
- font-size: 13px;
- margin-top: 16px;
- word-break: break-all;
- display: none;
-}
-
-.status.info { display: block; background: #1a2433; border: 1px solid #2a4a6a; color: #7bb8ff; }
-.status.success { display: block; background: #1a2e1a; border: 1px solid #2a4a2a; color: #7dcc7d; }
-.status.error { display: block; background: #2e1a1a; border: 1px solid #5a2a2a; color: #f08080; }
-
-.pda-info {
- display: none;
- margin-top: 12px;
-}
-
-.pda-row {
- display: flex;
- justify-content: space-between;
- padding: 6px 0;
- border-bottom: 1px solid var(--border);
- font-size: 12px;
-}
-
-.pda-row:last-child { border-bottom: none; }
-
-.pda-key { color: var(--text-muted); min-width: 160px; }
-.pda-value { color: var(--text); font-family: monospace; text-align: right; word-break: break-all; }
-
-.nav-links {
- margin-bottom: 20px;
-}
-
-.nav-links a {
- color: var(--accent);
- text-decoration: none;
- margin-right: 16px;
- font-size: 13px;
-}
-
-.nav-links a:hover { text-decoration: underline; }
-
-.section-divider {
- border: none;
- border-top: 1px solid var(--border);
- margin: 20px 0;
-}
diff --git a/shine-server-UI-obsolete/update-server-pda.html b/shine-server-UI-obsolete/update-server-pda.html
deleted file mode 100644
index 02ed43e..0000000
--- a/shine-server-UI-obsolete/update-server-pda.html
+++ /dev/null
@@ -1,484 +0,0 @@
-
-
-
-
-
- Обновление PDA сервера — SHiNE Server Admin
-
-
-
-
-
-
-
-
Обновление PDA сервера
-
Меняет адрес сервера или список серверов синхронизации
-
-
-
Параметры Solana
-
- Solana Endpoint
-
-
-
-
-
-
Загрузить существующую PDA
-
- Логин сервера
-
-
-
- Загрузить PDA
-
-
-
-
PDA адрес
-
Версия
-
Создан
-
Обновлён
-
Адрес сервера
-
sync_servers
-
Blockchain
-
Paid limit
-
-
-
-
-
-
-
-
-
-
-