From 2daea67a1e14c998a0954ca587c97b5cd18ee3f0 Mon Sep 17 00:00:00 2001 From: DanS Date: Fri, 5 Jun 2026 10:36:00 -0500 Subject: [PATCH] =?UTF-8?q?feat(lite):=20M3=20=E2=80=94=20new-address=20ge?= =?UTF-8?q?neration=20+=20sync-indicator=20confirmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LiteWalletController::newAddress(shielded) runs the backend "new" command ("zs"/"R" -> do_new_address), parses the ["addr"] response, and returns the new address; the next refresh lists it. Fast (local derivation), safe on the UI thread. - fake_lite_backend returns ["zs1fakenew"]/["R1fakenew"] for "new" by args. - testLiteWalletControllerNewAddress covers shielded/transparent + no-wallet error. Also confirmed (no code needed): the sync-progress indicator already works for lite — balance_tab reads state.sync.* which M2b-3 populates. Per-address balances landed in M2. Remaining M3 is pure UI wiring (receive_tab button -> newAddress, loading/empty states), which isn't verifiable without a GUI session. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...allet-implementation-plan-v2-2026-06-04.md | 6 ++++ src/wallet/lite_wallet_controller.cpp | 30 +++++++++++++++++++ src/wallet/lite_wallet_controller.h | 10 +++++++ tests/fake_lite_backend.h | 6 +++- tests/test_phase4.cpp | 25 ++++++++++++++++ 5 files changed, 76 insertions(+), 1 deletion(-) diff --git a/docs/lite-wallet-implementation-plan-v2-2026-06-04.md b/docs/lite-wallet-implementation-plan-v2-2026-06-04.md index 8b231bf..67f355a 100644 --- a/docs/lite-wallet-implementation-plan-v2-2026-06-04.md +++ b/docs/lite-wallet-implementation-plan-v2-2026-06-04.md @@ -120,6 +120,12 @@ Each milestone is independently demoable and gated by a fake-backend test. Order - Sync status/progress indicator; loading/empty states; new shielded/transparent address generation via the gateway (`receive_tab`); capability-gated surfaces verified (`isUiSurfaceAvailable`, `settings_page.cpp:1494` lite/full branch). - **Exit demo / test:** Full read-only experience — balances, address book, history, live sync progress — against fake then real backend. +> **Status (2026-06-05): testable logic done; pure-UI wiring remains (GUI-unverifiable here).** +> - ✅ **Sync progress indicator already works** — `balance_tab.cpp` reads `state.sync.{syncing,verification_progress,headers,isSynced()}`, which M2b-3 now populates from the lite backend. No code change needed. +> - ✅ **Per-address balances** — done in M2 (Receive/Balance show per-address amounts). +> - ✅ **New-address generation** — `LiteWalletController::newAddress(shielded)` runs the backend `new` command (`"zs"`/`"R"` → `do_new_address`), parses the `["addr"]` response, returns the address; the next refresh lists it. `testLiteWalletControllerNewAddress` covers it. +> - ⏳ **Remaining (pure UI, not verifiable without a GUI session):** wire the `receive_tab` "new address" button to `controller.newAddress()` (+ trigger a refresh); loading/empty states while syncing/unopened; final capability-gating pass. These compile-check only here — defer real verification to a `/run` GUI session. + ### M4 — Send / import / export / shield **Goal:** A user can spend and back up. - Wire `send_tab` (`:780-787` `sendTransaction`) to `litelib_execute` (`send`/`z_sendmany`) via the gateway, with fee + confirmation UI, result parsing, and tx-status polling that updates `WalletState`. diff --git a/src/wallet/lite_wallet_controller.cpp b/src/wallet/lite_wallet_controller.cpp index d7ef3d9..71d5e4f 100644 --- a/src/wallet/lite_wallet_controller.cpp +++ b/src/wallet/lite_wallet_controller.cpp @@ -11,6 +11,7 @@ #include #include +#include #include namespace dragonx { @@ -202,6 +203,35 @@ std::optional LiteWalletController::refreshModel() return mapped.model; } +LiteNewAddressResult LiteWalletController::newAddress(bool shielded) +{ + LiteNewAddressResult out; + if (!walletOpen_.load() || !bridge_) { + out.error = "no wallet is open"; + return out; + } + // Backend address-type tokens: "zs" (shielded) / "R" (transparent) (do_new_address). + const auto result = bridge_->execute("new", shielded ? "zs" : "R"); + if (!result.ok) { + out.error = result.error.empty() ? "new address generation failed" : result.error; + return out; + } + // Response is a JSON array with the new address, e.g. ["zs1..."]. + try { + const auto parsed = nlohmann::json::parse(result.value); + if (parsed.is_array() && !parsed.empty() && parsed[0].is_string()) { + out.address = parsed[0].get(); + } else if (parsed.is_string()) { + out.address = parsed.get(); + } + } catch (...) { + // fall through to the error below + } + out.ok = !out.address.empty(); + if (!out.ok) out.error = "could not parse new address response"; + return out; +} + bool LiteWalletController::refreshWalletState(dragonx::WalletState& state) { auto model = refreshModel(); diff --git a/src/wallet/lite_wallet_controller.h b/src/wallet/lite_wallet_controller.h index 4dd911b..3a24d4a 100644 --- a/src/wallet/lite_wallet_controller.h +++ b/src/wallet/lite_wallet_controller.h @@ -53,6 +53,12 @@ struct LiteWalletControllerOptions { bool allowBridgeCalls = true; }; +struct LiteNewAddressResult { + bool ok = false; + std::string address; + std::string error; +}; + class LiteWalletController { public: LiteWalletController(WalletCapabilities capabilities, @@ -92,6 +98,10 @@ public: // op produces a ready wallet; safe to call once. void startSync(); + // Generate a new address (shielded if true, else transparent) via the backend. Fast (local + // key derivation), safe to call on the UI thread; the next refresh lists the new address. + LiteNewAddressResult newAddress(bool shielded); + // Poll sync status + fetch balance/addresses/transactions, and apply the result into the // app's WalletState. Returns true if state was updated. Safe no-op when no wallet is open. // Synchronous (blocks on the backend); used by tests and as the worker's unit of work. diff --git a/tests/fake_lite_backend.h b/tests/fake_lite_backend.h index 7d21648..f54f949 100644 --- a/tests/fake_lite_backend.h +++ b/tests/fake_lite_backend.h @@ -58,8 +58,12 @@ inline char* liteFakeInitFromPhrase(bool, const char*, const char*, return liteFakeDup("{\"result\":\"restored\"}"); } inline char* liteFakeInitExisting(bool, const char*) { return liteFakeDup("{\"result\":\"opened\"}"); } -inline char* liteFakeExecute(const char* command, const char*) +inline char* liteFakeExecute(const char* command, const char* args) { + // new-address generation returns a JSON array with the new address (type from args: zs/R). + if (command && std::strcmp(command, "new") == 0) { + return liteFakeDup(args && std::strcmp(args, "R") == 0 ? "[\"R1fakenew\"]" : "[\"zs1fakenew\"]"); + } // A command named "boom" yields an "Error:"-prefixed response (the bridge's // looksLikeError contract maps that to ok=false). if (command && std::strcmp(command, "boom") == 0) { diff --git a/tests/test_phase4.cpp b/tests/test_phase4.cpp index 6341437..dafe436 100644 --- a/tests/test_phase4.cpp +++ b/tests/test_phase4.cpp @@ -4718,6 +4718,30 @@ void testLitePerAddressBalances() EXPECT_NEAR(state.t_addresses[0].balance, 0.5, 1e-9); // 50000000 } +// M3: new-address generation via the controller (backend "new" zs/R) returns the address. +void testLiteWalletControllerNewAddress() +{ + using namespace dragonx::wallet; + const auto caps = makeWalletCapabilities(WalletBuildKind::Lite, /*embeddedDaemon*/ false, /*liteBackendLinked*/ true); + const auto conn = defaultLiteConnectionSettings(); + + LiteWalletController controller(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + EXPECT_TRUE(controller.createWallet(LiteWalletCreateRequest{}).walletReady); + + const auto z = controller.newAddress(/*shielded*/ true); + EXPECT_TRUE(z.ok); + EXPECT_TRUE(z.address.rfind("zs1", 0) == 0); + + const auto t = controller.newAddress(/*shielded*/ false); + EXPECT_TRUE(t.ok); + EXPECT_TRUE(t.address.rfind("R1", 0) == 0); + + // No wallet open -> error, no address. + LiteWalletController idle(caps, conn, LiteClientBridge::fromApi(dragonx::test::makeFakeLiteApi())); + const auto none = idle.newAddress(true); + EXPECT_FALSE(none.ok); +} + // Gateway hardening: one command's parse failure must not abort the whole refresh — the // other commands still populate the bundle (graceful degradation against real-shape drift). void testLiteWalletGatewayRefreshSkipsFailedCommand() @@ -4841,6 +4865,7 @@ int main() testLiteChainNameMigration(); testLiteRefreshModelAppliesToWalletState(); testLitePerAddressBalances(); + testLiteWalletControllerNewAddress(); testLiteSyncStatusParserRealShapes(); testLiteWalletControllerRefreshPopulatesState(); testLiteWalletGatewayRefreshSkipsFailedCommand();