#include "wallet/lite_wallet_server_lifecycle_readiness.h" #include #include namespace dragonx::wallet { namespace { std::string trimCopy(const std::string& value) { auto begin = value.begin(); while (begin != value.end() && std::isspace(static_cast(*begin))) ++begin; auto end = value.end(); while (end != begin && std::isspace(static_cast(*(end - 1)))) --end; return std::string(begin, end); } void addIssue(LiteWalletServerLifecycleReadinessResult& result, LiteWalletServerLifecycleReadinessIssue issue, std::string message) { result.issues.push_back(LiteWalletServerLifecycleReadinessIssueInfo{issue, std::move(message)}); } LiteWalletServerLifecycleReadinessResult stoppedResult( LiteWalletServerLifecycleReadinessResult result, LiteWalletServerLifecycleReadinessStatus status, LiteWalletServerLifecycleReadinessIssue issue, std::string message) { result.status = status; addIssue(result, issue, std::move(message)); result.error = result.issues.back().message; result.lifecycleStatus = WalletBackendStatus{WalletBackendState::Unavailable, result.error, {}, {}, 0.0}; return result; } LiteRedactedPrivateData redactedPrivateField(LitePrivateDataKind kind, const std::string& value) { return LiteRedactedPrivateData{kind, !trimCopy(value).empty(), redactLitePrivateDataValue(value)}; } bool privateDataIsRedacted(const std::vector& privateData) { for (const auto& item : privateData) { if (!item.present && item.redactedValue != "") return false; if (item.present && item.redactedValue != "") return false; } return true; } LiteWalletSelectedServerDisplayReport displayReportForSelection( const LiteServerSelectionResult& selection) { LiteWalletSelectedServerDisplayReport report; if (!selection.ok) { report.status = LiteWalletSelectedServerDisplayStatus::Missing; report.message = selection.error.empty() ? "no usable lite server is selected" : selection.error; return report; } report.ok = true; report.status = selection.customServer ? LiteWalletSelectedServerDisplayStatus::CustomServer : LiteWalletSelectedServerDisplayStatus::SelectedServer; report.label = selection.server.label; report.url = selection.server.url; report.serverIndex = selection.serverIndex; report.customServer = selection.customServer; report.message = selection.customServer ? "custom lite server selected for display" : "configured lite server selected for display"; return report; } LiteWalletLifecyclePlan lifecyclePlanForRequest( LiteWalletLifecycleOperation operation, const LiteServerSelectionResult& selection) { LiteWalletLifecyclePlan plan; plan.operation = operation; plan.bridgeExecutionAllowed = false; if (!selection.ok) { plan.error = selection.error.empty() ? "no usable lite server is selected" : selection.error; return plan; } plan.ok = true; plan.server = selection.server; plan.serverIndex = selection.serverIndex; plan.customServer = selection.customServer; return plan; } LiteWalletLifecycleUiRequestPlan requestPlanForInput( const LiteWalletServerLifecycleReadinessInput& input, const LiteServerSelectionResult& selection) { LiteWalletLifecycleUiRequestPlan plan; plan.operation = input.operation; plan.lifecyclePlan = lifecyclePlanForRequest(input.operation, selection); if (!plan.lifecyclePlan.ok) { plan.error = plan.lifecyclePlan.error; return plan; } switch (input.operation) { case LiteWalletLifecycleOperation::CreateNew: plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::Passphrase, input.createRequest.passphrase)); plan.requestSummary = "operation=create;server=;passphrase=" + std::string(input.createRequest.passphrase.empty() ? "" : ""); break; case LiteWalletLifecycleOperation::OpenExisting: if (trimCopy(input.openRequest.walletPath).empty()) { plan.error = "lite wallet open requires a wallet path selected by the UI"; return plan; } plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::WalletPath, input.openRequest.walletPath)); plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::Passphrase, input.openRequest.passphrase)); plan.requestSummary = "operation=open;server=;wallet_path=;passphrase=" + std::string(input.openRequest.passphrase.empty() ? "" : ""); break; case LiteWalletLifecycleOperation::RestoreFromSeed: if (trimCopy(input.restoreRequest.seedPhrase).empty()) { plan.error = "lite wallet restore requires redacted seed material metadata"; return plan; } plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::SeedPhrase, input.restoreRequest.seedPhrase)); plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::WalletPath, input.restoreRequest.walletPath)); plan.privateData.push_back(redactedPrivateField( LitePrivateDataKind::Passphrase, input.restoreRequest.passphrase)); plan.requestSummary = "operation=restore;server=;seed=;wallet_path=" + std::string(input.restoreRequest.walletPath.empty() ? "" : "") + ";passphrase=" + std::string(input.restoreRequest.passphrase.empty() ? "" : ""); break; } plan.privateInputsRedacted = privateDataIsRedacted(plan.privateData); plan.lifecyclePlan.privateData = plan.privateData; plan.ok = plan.privateInputsRedacted; if (!plan.ok) plan.error = "lite wallet lifecycle private inputs are not redacted"; return plan; } LiteWalletServerSelectionPersistencePlan persistencePlanForInput( const LiteWalletServerLifecycleReadinessInput& input, const LiteServerSelectionResult& selection) { LiteWalletServerSelectionPersistencePlan plan; plan.settingsLoaded = input.persistence.settingsLoaded; plan.persistedSelectionIntentAccepted = input.persistence.havePersistedSelectionIntent; plan.wouldPersistSelectedServer = input.persistence.persistSelectedServer; plan.persistenceOwnerAccepted = input.persistence.persistenceOwnerReady; plan.selectionMode = input.settings.selectionMode; if (selection.ok) { plan.selectedServerUrl = selection.server.url; plan.selectedServerIndex = selection.serverIndex; plan.selectedServerCustom = selection.customServer; } plan.settingsWritten = false; plan.ok = true; return plan; } WalletBackendStatus readyLifecycleStatus(const LiteWalletSelectedServerDisplayReport& display) { const std::string serverLabel = display.label.empty() ? display.url : display.label; return WalletBackendStatus{ WalletBackendState::Disconnected, "lite lifecycle UI readiness accepted for " + serverLabel + "; wallet lifecycle execution is still disabled", {}, {}, 0.0 }; } } // namespace const char* liteWalletServerLifecycleReadinessStatusName( LiteWalletServerLifecycleReadinessStatus status) { switch (status) { case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: return "ReadyForFutureLifecycle"; case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild"; case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability"; case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: return "WaitingForServerSelection"; case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: return "WaitingForPersistenceIntent"; case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: return "WaitingForDisplayStatus"; case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: return "WaitingForLifecycleUi"; case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: return "WaitingForPrivateDataRedaction"; case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: return "WaitingForSyncPlannerFeed"; case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: return "RuntimeExecutionDisabled"; } return "Unknown"; } const char* liteWalletServerLifecycleReadinessIssueName( LiteWalletServerLifecycleReadinessIssue issue) { switch (issue) { case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: return "FullNodeBuild"; case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing"; case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: return "PersistedSettingsNotLoaded"; case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: return "PersistedServerSelectionIntentMissing"; case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: return "ServerSelectionMissing"; case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: return "ServerPersistenceOwnerMissing"; case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: return "SelectedServerDisplayMissing"; case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: return "LifecycleUiOwnerMissing"; case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: return "LifecycleOperationUnconfirmed"; case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: return "OpenWalletPathMissing"; case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: return "RestoreSeedMissing"; case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: return "PrivateDataRedactionMissing"; case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: return "SyncPlannerFeedMissing"; case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: return "RealLifecycleExecutionRequested"; } return "Unknown"; } const char* liteWalletSelectedServerDisplayStatusName( LiteWalletSelectedServerDisplayStatus status) { switch (status) { case LiteWalletSelectedServerDisplayStatus::Hidden: return "Hidden"; case LiteWalletSelectedServerDisplayStatus::SelectedServer: return "SelectedServer"; case LiteWalletSelectedServerDisplayStatus::CustomServer: return "CustomServer"; case LiteWalletSelectedServerDisplayStatus::Missing: return "Missing"; } return "Unknown"; } LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness( const LiteWalletServerLifecycleReadinessInput& input, LiteWalletServerLifecycleReadinessOptions options) { LiteWalletServerLifecycleReadinessResult result; result.capabilities = input.capabilities; result.persistencePlan = persistencePlanForInput(input, {}); if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild, LiteWalletServerLifecycleReadinessIssue::FullNodeBuild, "lite server lifecycle readiness requires a lite build"); } result.liteBuildAccepted = true; if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability, LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing, "lite backend capability is required before lifecycle UI readiness can feed sync planners"); } result.backendCapabilityAccepted = true; if (options.requirePersistedSettingsLoaded && !input.persistence.settingsLoaded) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded, "lite server settings must be loaded before lifecycle UI readiness is evaluated"); } if (options.requirePersistedSelectionIntent && !input.persistence.havePersistedSelectionIntent) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing, "lite server selection persistence intent is missing"); } result.selectedServer = selectLiteServer(input.settings); result.persistencePlan = persistencePlanForInput(input, result.selectedServer); if (!result.selectedServer.ok) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection, LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing, result.selectedServer.error.empty() ? "no usable lite server is selected" : result.selectedServer.error); } result.serverSelectionAccepted = true; if (input.persistence.persistSelectedServer && !input.persistence.persistenceOwnerReady) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent, LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing, "lite server selection persistence owner is not ready"); } result.persistenceIntentAccepted = true; result.persistencePlan.ok = true; result.selectedServerDisplay = displayReportForSelection(result.selectedServer); if (options.requireSelectedServerDisplay && !input.ui.selectedServerDisplayReady) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus, LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing, "selected lite server display status is not ready"); } result.selectedServerDisplayAccepted = true; if (options.requireLifecycleUiOwner && !input.ui.lifecycleUiOwnerReady) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing, "lite wallet lifecycle UI owner is not ready"); } result.lifecycleUiOwnerAccepted = true; if (input.ui.realLifecycleExecutionRequested) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled, LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested, "real lite wallet lifecycle execution is disabled in this scaffold"); } if (options.requireOperationConfirmation && !input.ui.operationConfirmed) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed, "lite wallet lifecycle operation requires explicit UI confirmation"); } result.requestPlan = requestPlanForInput(input, result.selectedServer); if (!result.requestPlan.ok) { const auto issue = input.operation == LiteWalletLifecycleOperation::OpenExisting ? LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing : LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing; return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi, issue, result.requestPlan.error.empty() ? "lite wallet lifecycle UI request is incomplete" : result.requestPlan.error); } result.requestAccepted = true; if (options.requirePrivateDataRedaction && !input.ui.privateDataRedactionReady) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction, LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing, "lite wallet lifecycle private-data redaction owner is not ready"); } result.privateDataRedactionAccepted = true; if (options.requireSyncPlannerFeed && !input.ui.syncPlannerFeedReady) { return stoppedResult( std::move(result), LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed, LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing, "lite lifecycle readiness feed for sync planners is not ready"); } result.syncPlannerFeedAccepted = true; result.ok = true; result.status = LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle; result.futureLifecycleCouldBeEnabled = true; result.lifecycleReportCouldFeedSyncPlanners = true; result.lifecycleStatus = readyLifecycleStatus(result.selectedServerDisplay); return result; } } // namespace dragonx::wallet