diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino index 68057b4..d2c70a1 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #define XPOWERS_CHIP_AXP2101 #include "XPowersLib.h" @@ -80,6 +82,7 @@ static const char *kUsersEconomyConfigSeed = "shine_users_economy_config"; static const char *kPaymentsInflowSeed = "shine_payments_inflow_vault"; static const char *kLastBlockPrefix = "SHiNE_LAST_BLOCK"; static const char *kProgramDerivedAddressMarker = "ProgramDerivedAddress"; +static const char *kDefaultShineServerLogin = "shineupme"; static const uint8_t kBlockTypeRecoveryKey = 0; static const uint8_t kBlockTypeRootKey = 1; static const uint8_t kBlockTypeClientKey = 2; @@ -322,8 +325,10 @@ static int gScanResultCount = 0; static WifiViewMode gWifiViewMode = WIFI_VIEW_OVERVIEW; static String gSolanaRpcUrl = "https://api.devnet.solana.com"; -static String gShineServerUrl = "https://shineup.me"; -static String gServerStatusMessage = "Edit RPC or shine host"; +static String gShineServerLogin = kDefaultShineServerLogin; +static String gShineServerUrl; +static String gResolvedShineServerLogin; +static String gServerStatusMessage = "Edit RPC or server login"; static String gLoginValue; static String gHomeserverValue = "homeserver1"; static bool gSecretConfigured = false; @@ -527,6 +532,11 @@ static void pushU64LE(std::vector &out, uint64_t value); static void pushStrU8(std::vector &out, const String &value); static void pushFixed(std::vector &out, const uint8_t *data, size_t len); static String bytesToBase58(const uint8_t *data, size_t len); +static bool getSystemEpochMs(uint64_t &epochMsOut); +static bool ensureNtpTimeSynced(String &errorOut); +static bool resolveShineServerUrlFromLogin(const String &serverLogin, String &serverUrlOut, String &errorOut); +static String currentShineServerLoginSource(); +static bool ensureCurrentShineServerUrl(String &errorOut); static String buildBaseRpcRequest(const char *method, const String ¶msJson); static bool rpcCallSolana(const char *method, const String ¶msJson, String &payloadOut); static bool rpcResponseHasError(const String &payload); @@ -550,7 +560,7 @@ static std::vector buildLastBlockStateBytes(const String &login, static std::vector buildUnsignedCreateRecord( const String &login, const String &blockchainName, - const String &serverAddress, + const std::vector &accessServers, const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], @@ -561,7 +571,7 @@ static std::vector buildUnsignedCreateRecord( static std::vector buildCreateInstructionData( const String &login, const String &blockchainName, - const String &serverAddress, + const std::vector &accessServers, const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], @@ -890,6 +900,19 @@ static String normalizeLoginValue(const String &value) { return out; } +static bool isValidShineServerLoginValue(const String &value) { + if (value.isEmpty() || value.length() > 20) { + return false; + } + for (size_t i = 0; i < value.length(); ++i) { + char ch = value.charAt(i); + if (!(isAlphaNumeric((unsigned char)ch) || ch == '_')) { + return false; + } + } + return true; +} + static String abbreviateValue(const String &value, size_t head = 8, size_t tail = 6) { if (value.length() <= head + tail + 3) { return value; @@ -1622,6 +1645,16 @@ static String balanceHomeLine() { return gBalanceStatusMessage; } +static String shineServerDisplayLabel() { + if (!gShineServerUrl.isEmpty()) { + return gShineServerUrl; + } + if (!currentShineServerLoginSource().isEmpty()) { + return currentShineServerLoginSource(); + } + return "not set"; +} + static String wifiHomeRichLine() { String ssid = gWifiSavedSsid.isEmpty() ? String("not configured") : gWifiSavedSsid; if (WiFi.status() == WL_CONNECTED) { @@ -1631,7 +1664,7 @@ static String wifiHomeRichLine() { } static String shineHomeRichLine() { - String serverLabel = gShineServerUrl.isEmpty() ? String("not set") : gShineServerUrl; + String serverLabel = shineServerDisplayLabel(); if (gShineStatusLine.endsWith(" connected")) { return String("SHiNE: ") + serverLabel + " #38B26D connected#"; } @@ -1711,6 +1744,96 @@ static uint64_t shineNowMs() { return value > 0 ? (uint64_t)value : (uint64_t)millis(); } +static bool getSystemEpochMs(uint64_t &epochMsOut) { + struct timeval tv {}; + if (gettimeofday(&tv, nullptr) != 0) { + return false; + } + if (tv.tv_sec < 1700000000) { + return false; + } + epochMsOut = (uint64_t)tv.tv_sec * 1000ULL + (uint64_t)(tv.tv_usec / 1000ULL); + return true; +} + +static bool ensureNtpTimeSynced(String &errorOut) { + errorOut = ""; + uint64_t epochMs = 0; + if (getSystemEpochMs(epochMs)) { + return true; + } + configTime(0, 0, "pool.ntp.org", "time.cloudflare.com", "time.google.com"); + for (int i = 0; i < 30; ++i) { + delay(500); + if (getSystemEpochMs(epochMs)) { + return true; + } + } + errorOut = "NTP time sync failed"; + return false; +} + +static bool resolveShineServerUrlFromLogin(const String &serverLogin, String &serverUrlOut, String &errorOut) { + errorOut = ""; + serverUrlOut = ""; + String cleanLogin = normalizeLoginValue(serverLogin); + if (cleanLogin.isEmpty()) { + errorOut = "Shine server login is not set"; + return false; + } + + ShinePdaUserState serverState; + if (!readShineUserPda(cleanLogin, serverState, errorOut)) { + if (errorOut.isEmpty()) { + errorOut = "Failed to read Shine server PDA"; + } + return false; + } + if (!serverState.found) { + errorOut = "Shine server PDA not found"; + return false; + } + if (!serverState.isServer) { + errorOut = "Shine server PDA is not a server"; + return false; + } + if (serverState.serverAddress.isEmpty()) { + errorOut = "Shine server address is empty"; + return false; + } + + serverUrlOut = serverState.serverAddress; + return true; +} + +static String currentShineServerLoginSource() { + if (gCachedAccountPdaValid && gCachedAccountPdaLogin == normalizeLoginValue(gLoginValue) && !gCachedAccountPdaState.accessServers.empty()) { + String fromPda = normalizeLoginValue(gCachedAccountPdaState.accessServers.front()); + if (!fromPda.isEmpty()) { + return fromPda; + } + } + return normalizeLoginValue(gShineServerLogin); +} + +static bool ensureCurrentShineServerUrl(String &errorOut) { + errorOut = ""; + String login = currentShineServerLoginSource(); + if (login.isEmpty()) { + errorOut = "Shine server login is not set"; + return false; + } + if (gShineServerUrl.isEmpty() || gResolvedShineServerLogin != login) { + String resolvedUrl; + if (!resolveShineServerUrlFromLogin(login, resolvedUrl, errorOut)) { + return false; + } + gShineServerUrl = resolvedUrl; + gResolvedShineServerLogin = login; + } + return true; +} + static void shortVecEncode(size_t value, std::vector &out) { do { uint8_t byte = value & 0x7F; @@ -1948,7 +2071,7 @@ static std::vector buildUpdateInstructionData(const ShinePdaUserState & static std::vector buildUnsignedCreateRecord( const String &login, const String &blockchainName, - const String &serverAddress, + const std::vector &accessServers, const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], @@ -1968,7 +2091,7 @@ static std::vector buildUnsignedCreateRecord( pushU32LE(out, 0); out.insert(out.end(), 32, 0); pushStrU8(out, login); - out.push_back(8); + out.push_back(7); out.push_back(kBlockTypeRecoveryKey); out.push_back(0); @@ -1995,17 +2118,12 @@ static std::vector buildUnsignedCreateRecord( pushFixed(out, lastBlockSignature, 64); out.push_back(0); - out.push_back(kBlockTypeServerProfile); - out.push_back(0); - out.push_back(1); - out.push_back(0); - out.push_back(0); - pushStrU8(out, serverAddress); - out.push_back(0); - out.push_back(kBlockTypeAccessServers); out.push_back(0); - out.push_back(0); + out.push_back((uint8_t)accessServers.size()); + for (const auto &value : accessServers) { + pushStrU8(out, value); + } out.push_back(kBlockTypeSessions); out.push_back(0); @@ -2025,7 +2143,7 @@ static std::vector buildUnsignedCreateRecord( static std::vector buildCreateInstructionData( const String &login, const String &blockchainName, - const String &serverAddress, + const std::vector &accessServers, const uint8_t recoveryPub[32], const uint8_t rootPub[32], const uint8_t clientPub[32], @@ -2049,12 +2167,11 @@ static std::vector buildCreateInstructionData( out.insert(out.end(), 32, 0); pushFixed(out, lastBlockSignature, 64); out.push_back(0); - out.push_back(1); - out.push_back(0); - out.push_back(0); - pushStrU8(out, serverAddress); - out.push_back(0); out.push_back(0); + out.push_back((uint8_t)accessServers.size()); + for (const auto &value : accessServers) { + pushStrU8(out, value); + } out.push_back(1); out.push_back(0); out.push_back(0); @@ -2683,11 +2800,15 @@ static bool registerHomeserverOnSolana(String &messageOut) { }; String cleanLogin = normalizeLoginValue(gLoginValue); + String accessServerLogin = normalizeLoginValue(gShineServerLogin); + if (!isValidShineServerLoginValue(accessServerLogin)) { + accessServerLogin = kDefaultShineServerLogin; + } diagDetails += String("trigger=") + gRegisterTriggerSource + "\n"; diagDetails += String("test_uptime_ms=") + String(millis()) + "\n"; diagDetails += String("login=") + cleanLogin + "\n"; diagDetails += String("rpc=") + gSolanaRpcUrl + "\n"; - diagDetails += String("shine_server=") + gShineServerUrl + "\n"; + diagDetails += String("shine_server_login=") + accessServerLogin + "\n"; diagDetails += String("homeserver=") + gHomeserverValue + "\n"; if (cleanLogin.isEmpty()) { @@ -2715,7 +2836,7 @@ static bool registerHomeserverOnSolana(String &messageOut) { gAccountPdaStatusMessage = "User is already registered"; gShowRegisterAccountButton = false; gAccountStatusMessage = "User is already registered"; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered"; + gShineStatusLine = String("SHiNE: ") + (!gShineServerUrl.isEmpty() ? gShineServerUrl : accessServerLogin) + " registered"; refreshAccountPdaStatus(); diagDetails += String("user_pda=") + existingPda + "\n"; saveRegisterDiag("ok", "User is already registered", diagDetails); @@ -2812,10 +2933,18 @@ static bool registerHomeserverOnSolana(String &messageOut) { diagDetails += String("last_block_hash=") + bytesToHexString(lastBlockHash, 32) + "\n"; diagDetails += String("last_block_signature_b64=") + bytesToBase64String(lastBlockSignature, 64) + "\n"; - uint64_t createdAtMs = shineNowMs(); + if (!ensureNtpTimeSynced(messageOut)) { + diagDetails += String("ntp_error=") + messageOut + "\n"; + return failWithDiag(messageOut); + } + uint64_t createdAtMs = 0; + if (!getSystemEpochMs(createdAtMs)) { + return failWithDiag("NTP time is not ready"); + } diagDetails += String("created_at_ms=") + String((unsigned long long)createdAtMs) + "\n"; + std::vector accessServers = {accessServerLogin}; std::vector unsignedRecord = buildUnsignedCreateRecord( - cleanLogin, blockchainName, gShineServerUrl, + cleanLogin, blockchainName, accessServers, recoveryPub, rootPub, clientPub, blockchainPub, lastBlockSignature, startBonusLimit, createdAtMs); @@ -2830,7 +2959,7 @@ static bool registerHomeserverOnSolana(String &messageOut) { diagDetails += String("root_signature_b64=") + bytesToBase64String(rootSignature, 64) + "\n"; std::vector createData = buildCreateInstructionData( - cleanLogin, blockchainName, gShineServerUrl, + cleanLogin, blockchainName, accessServers, recoveryPub, rootPub, clientPub, blockchainPub, lastBlockSignature, rootSignature, createdAtMs); @@ -2901,7 +3030,7 @@ static bool registerHomeserverOnSolana(String &messageOut) { gAccountPdaStatus = ACCOUNT_PDA_OK; gAccountPdaStatusMessage = "User registered"; gShowRegisterAccountButton = false; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " registered"; + gShineStatusLine = String("SHiNE: ") + (!gShineServerUrl.isEmpty() ? gShineServerUrl : accessServerLogin) + " registered"; saveAccountPrefs(); refreshAccountPdaStatus(); messageOut = "Solana registration confirmed"; @@ -3604,7 +3733,7 @@ static void refreshAccountPdaStatus() { if (gLoginValue.isEmpty() || !gSecretConfigured) { gAccountPdaStatus = ACCOUNT_PDA_UNKNOWN; gAccountPdaStatusMessage = "account not configured"; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured"; + gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured"; clearShineSessionState(false); return; } @@ -3614,7 +3743,7 @@ static void refreshAccountPdaStatus() { if (!readShineUserPda(gLoginValue, pdaState, error)) { gAccountPdaStatus = ACCOUNT_PDA_MISMATCH; gAccountPdaStatusMessage = error.isEmpty() ? "solana check failed" : error; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " unavailable"; + gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " unavailable"; clearShineSessionState(false); if (error == "Solana RPC unavailable") { gAccountCheckPending = true; @@ -3626,7 +3755,7 @@ static void refreshAccountPdaStatus() { gAccountPdaStatus = ACCOUNT_PDA_NOT_FOUND; gAccountPdaStatusMessage = "user not found"; gShowRegisterAccountButton = true; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured"; + gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured"; clearShineSessionState(false); return; } @@ -3641,7 +3770,7 @@ static void refreshAccountPdaStatus() { || !base58ToFixed32(gBlockchainPubB58, blockchainPub)) { gAccountPdaStatus = ACCOUNT_PDA_MISMATCH; gAccountPdaStatusMessage = "local keys invalid"; - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured"; + gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured"; clearShineSessionState(false); return; } @@ -3694,13 +3823,13 @@ static void refreshAccountPdaStatus() { gHomeserverPdaActionReason = mismatch; gHomeserverPdaCanFix = true; } - gShineStatusLine = String("SHiNE: ") + (gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl) + " account not configured"; + gShineStatusLine = String("SHiNE: ") + shineServerDisplayLabel() + " account not configured"; clearShineSessionState(false); return; } gCachedAccountPdaState = pdaState; - gCachedAccountPdaLogin = gLoginValue; + gCachedAccountPdaLogin = normalizeLoginValue(gLoginValue); gCachedAccountPdaValid = true; gAccountPdaStatus = ACCOUNT_PDA_OK; gAccountPdaStatusMessage = "ok"; @@ -4338,8 +4467,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) { diagDetails += String("uptime_ms=") + String(millis()) + "\n"; diagDetails += String("login=") + gLoginValue + "\n"; diagDetails += String("homeserver=") + gHomeserverValue + "\n"; - diagDetails += String("server_url=") + gShineServerUrl + "\n"; - diagDetails += String("ws_url=") + shineWsUrl() + "\n"; + diagDetails += String("server_login=") + currentShineServerLoginSource() + "\n"; diagDetails += String("pda_status=") + gAccountPdaStatusMessage + "\n"; if (WiFi.status() != WL_CONNECTED) { diagDetails += "wifi=disconnected\n"; @@ -4352,6 +4480,12 @@ static bool ensureShineSessionAuthenticated(String &errorOut) { if (gAccountPdaStatus != ACCOUNT_PDA_OK) { return failWithDiag("account not ready"); } + if (!ensureCurrentShineServerUrl(errorOut)) { + diagDetails += String("server_resolve_error=") + errorOut + "\n"; + return failWithDiag(errorOut); + } + diagDetails += String("server_url=") + gShineServerUrl + "\n"; + diagDetails += String("ws_url=") + shineWsUrl() + "\n"; String wsUrl = shineWsUrl(); if (wsUrl.isEmpty()) { @@ -4513,7 +4647,7 @@ static bool ensureShineSessionAuthenticated(String &errorOut) { } static void manageShineConnection() { - String serverLabel = gShineServerUrl.isEmpty() ? "not set" : gShineServerUrl; + String serverLabel = shineServerDisplayLabel(); if (gLoginValue.isEmpty() || !gSecretConfigured || gHomeserverValue.isEmpty()) { gShineStatusLine = String("SHiNE: ") + serverLabel + " account not configured"; clearShineSessionState(false); @@ -4543,6 +4677,13 @@ static void manageShineConnection() { } gLastShineAttemptMs = now; String error; + if (!ensureCurrentShineServerUrl(error)) { + gShineStatusLine = String("SHiNE: ") + serverLabel + " unavailable"; + clearShineSessionState(false); + gShineReconnectDelayMs = min(gShineReconnectDelayMs + SHINE_RECONNECT_MIN_MS, (unsigned long)SHINE_RECONNECT_MAX_MS); + return; + } + serverLabel = shineServerDisplayLabel(); if (ensureShineSessionAuthenticated(error)) { gShineStatusLine = String("SHiNE: ") + serverLabel + " connected"; gLastShinePingMs = now; @@ -4626,7 +4767,18 @@ static void loadPrefs() { upsertKnownWifi(gWifiSavedSsid, gWifiSavedPassword); } gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com"); - gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me"); + String storedShineServerLogin = normalizeLoginValue(gPrefs.getString("shine_server_login", "")); + if (!isValidShineServerLoginValue(storedShineServerLogin)) { + String legacyShineServer = normalizeLoginValue(gPrefs.getString("shine_server", "")); + if (isValidShineServerLoginValue(legacyShineServer)) { + storedShineServerLogin = legacyShineServer; + } else { + storedShineServerLogin = kDefaultShineServerLogin; + } + } + gShineServerLogin = storedShineServerLogin; + gShineServerUrl = ""; + gResolvedShineServerLogin = ""; gLoginValue = gPrefs.getString("login", ""); gHomeserverValue = gPrefs.getString("homeserver", "homeserver1"); String walletTypeStored = gPrefs.getString("wallet_type", "client.key"); @@ -4695,7 +4847,8 @@ static void saveWifiPrefs() { static void saveServerPrefs() { gPrefs.putString("solana_rpc", gSolanaRpcUrl); - gPrefs.putString("shine_server", gShineServerUrl); + gPrefs.putString("shine_server_login", gShineServerLogin); + gPrefs.remove("shine_server"); } static void saveAccountPrefs() { @@ -5217,13 +5370,18 @@ static void applyEditorValue() { } if (gEditContext == EDIT_CONTEXT_SHINE_SERVER) { - gShineServerUrl = value; + gShineServerLogin = normalizeLoginValue(value); + if (!isValidShineServerLoginValue(gShineServerLogin)) { + gShineServerLogin = kDefaultShineServerLogin; + } + gShineServerUrl = ""; + gResolvedShineServerLogin = ""; saveServerPrefs(); - gServerStatusMessage = "Shine server saved"; + gServerStatusMessage = "SHiNE server login saved"; clearShineSessionState(false); gShineReconnectDelayMs = SHINE_RECONNECT_MIN_MS; gLastShineAttemptMs = 0; - gShineStatusLine = "SHiNE: reconnect pending"; + gShineStatusLine = String("SHiNE: ") + gShineServerLogin + " reconnect pending"; showScreen(SCREEN_SERVER); return; } @@ -5577,9 +5735,9 @@ static void actionButtonCb(lv_event_t *event) { case ACTION_SERVER_EDIT_SHINE: openEditor(EDIT_CONTEXT_SHINE_SERVER, SCREEN_SERVER, - "EDIT SHINE HOST", + "EDIT SHINE SERVER LOGIN", "", - gShineServerUrl, + gShineServerLogin, false); break; case ACTION_ACCOUNT_EDIT_LOGIN: @@ -6324,8 +6482,12 @@ static void drawServerScreen() { showMessageAt(gServerStatusMessage, 56); showMessageAt(String("Solana: ") + gSolanaRpcUrl, 96); makeButton("SOLANA RPC", 22, 146, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SOLANA, &lv_font_montserrat_24); - showMessageAt(String("Shine: ") + gShineServerUrl, 248); - makeButton("SHINE SERVER", 22, 298, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SHINE, &lv_font_montserrat_24); + showMessageAt(String("SHiNE: ") + shineServerDisplayLabel(), 248); + if (gUserPdaAddress.isEmpty()) { + makeButton("SHiNE SERVER LOGIN", 22, 298, 436, 84, 0x355C7D, ACTION_SERVER_EDIT_SHINE, &lv_font_montserrat_22); + } else { + makeBody("SHiNE server login is read from PDA.", 312, 360); + } makeBody("Swipe right to return to Settings.", 396, 420); makeVersionTag(); } diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md index 24ebcc3..a523ea6 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md @@ -150,12 +150,12 @@ - статусное сообщение; - текущий `Solana RPC` адрес; - кнопку `SOLANA RPC`; -- текущий `Shine server` адрес; -- кнопку `SHINE SERVER`. +- текущий `SHiNE server login` или уже резолвленный адрес; +- кнопку `SHiNE SERVER LOGIN`, если обычный `user PDA` ещё не зарегистрирован. Значения по умолчанию: - Solana RPC: `https://api.devnet.solana.com` -- Shine server: `https://shineup.me` +- SHiNE server login: `shineupme` Нажатие на любую из двух кнопок открывает `TEXT_EDIT_SCREEN`. @@ -229,7 +229,7 @@ Используется для: - пароля Wi-Fi; - Solana RPC; -- Shine server. +- SHiNE server login. Показывает: - заголовок; @@ -291,7 +291,7 @@ Используется `Preferences` (NVS памяти ESP32): - `solana_rpc` -- `shine_server` +- `shine_server_login` ## Хранение аккаунта diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md index 72d67bb..dc55165 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_spec.md @@ -65,12 +65,13 @@ - `user pda address`; - `registration signature`; - `balance`; -- `server api url`; -- `server rpc url`; -- `server ws url`; +- `server login` для первичной привязки; +- `resolved server api url` / `rpc url` / `ws url` после чтения PDA сервера; - флаги: `wifiReady`, `serversReady`, `secretReady`, `registered`, `online`. +Для первой регистрации обычного `user PDA` устройство берёт `createdAtMs` / `updatedAtMs` из NTP прямо перед отправкой транзакции в Solana. Дальше в `user PDA` сохраняется `accessServers`, где по умолчанию лежит `shineupme`. + ## Правило серверной сессии SHiNE При подключении к серверу `SHiNE` устройство должно авторизовываться как homeserver-сеанс: @@ -86,7 +87,7 @@ Кнопка регистрации доступна только если одновременно выполнены условия: 1. настроен и подтверждён `Wi-Fi`; -2. заполнены и подтверждены серверные адреса; +2. задан и подтверждён `SHiNE server login`; 3. задан логин; 4. сгенерирован или введён секрет; 5. баланс кошелька не меньше `0.20 SOL`; @@ -628,7 +629,7 @@ 2. открыть `Подключение -> Wi-Fi`; 3. ввести `SSID` и пароль, нажать `Проверить`; 4. открыть `Подключение -> Серверы`; -5. проверить или задать серверные адреса; +5. проверить или задать `SHiNE server login` (по умолчанию `shineupme`); 6. открыть `Аккаунт`; 7. ввести логин; 8. задать имя homeserver; @@ -637,14 +638,15 @@ 11. при необходимости пополнить баланс; 12. вернуться на `HOME`; 13. нажать `REGISTER ACCOUNT`; -14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1` и при необходимости сообщение о неподключённом `Wi-Fi`; +14. на экране проверки ещё раз увидеть `login`, статус свободного `PDA`, баланс, `homeserver1`, серверный login и при необходимости сообщение о неподключённом `Wi-Fi`; 15. нажать `ЗАРЕГИСТРИРОВАТЬ В СИЯНИИ`; 16. после завершения увидеть либо экран успеха с `user_pda` и `tx signature`, либо подробную ошибку; 17. после успешной регистрации увидеть статус `Homeserver активен`. Примечание: -- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`. +- устройство реально отправляет `create_user_pda` в `shine_users`, а после подтверждения сохраняет `PDA` и `tx signature`; +- при первой регистрации для обычного `user PDA` не заполняется `serverAddress`, а `accessServers` получает `shineupme` или другой выбранный `SHiNE server login`. ## Сценарий входящего запроса diff --git a/VERSION.properties b/VERSION.properties index 151884f..8c11778 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.245 -server.version=1.2.230 +client.version=1.2.246 +server.version=1.2.231