feat(lite): M3 — new-address generation + sync-indicator confirmation

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-06-05 10:36:00 -05:00
parent e6b91ca661
commit 2daea67a1e
5 changed files with 76 additions and 1 deletions

View File

@@ -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) {

View File

@@ -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();