feat(lite): wire send + new-address GUI to the lite controller (M3/M4)

Route the existing receive/balance/send UI to the lite controller in lite builds,
with no per-tab UI changes — the existing buttons just work:

- App::createNewZAddress / createNewTAddress: lite branch calls
  lite_wallet_->newAddress() (synchronous local key derivation), injects the new
  address into WalletState so the UI selects it next frame, and invokes the
  receive-tab callback. Placed before the full-node !connected guard.
- App::sendTransaction: lite branch builds a LiteSendRequest (DRGX -> zatoshis,
  memo; `from`/`fee` ignored since the backend selects inputs and adds the fee),
  fires the controller's async broadcast, and stashes the send_tab callback.
- App::update: drains takeBroadcastResult() and delivers txid/error to the stored
  callback, so the send_tab's existing "sending.../sent" flow works unchanged.

All branches guard on lite_wallet_ (null in full-node). Verified: lite app +
test suite + full-node variant all build/link clean; hygiene clean.

Backup/import UI (export seed/keys, import) is deferred — it needs new
secret-display UI rather than an existing button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 13:10:56 -05:00
parent 5d317f6be3
commit eb6114ee19
3 changed files with 64 additions and 1 deletions

View File

@@ -407,6 +407,14 @@ void App::update()
if (lite_wallet_->takeRefreshedModel(liteModel)) {
wallet::applyLiteRefreshModelToWalletState(liteModel, state_);
}
// Deliver a completed async send/shield result to the waiting send_tab callback.
wallet::LiteBroadcastResult broadcast;
if (lite_wallet_->takeBroadcastResult(broadcast)) {
if (lite_send_callback_) {
lite_send_callback_(broadcast.ok, broadcast.ok ? broadcast.txid : broadcast.error);
lite_send_callback_ = nullptr;
}
}
}
async_tasks_.reapCompleted();

View File

@@ -419,6 +419,9 @@ private:
std::unique_ptr<config::Settings> settings_;
std::unique_ptr<wallet::LiteWalletController> lite_wallet_; // lite builds w/ linked backend
// Pending send_tab callback for an in-flight lite send (delivered in update() once the
// controller's async broadcast result arrives). Only one lite send runs at a time.
std::function<void(bool, const std::string&)> lite_send_callback_;
std::unique_ptr<daemon::DaemonController> daemon_controller_;
std::unique_ptr<daemon::XmrigManager> xmrig_manager_;
util::AsyncTaskManager async_tasks_;

View File

@@ -34,6 +34,7 @@
#include "rpc/rpc_worker.h"
#include "rpc/connection.h"
#include "config/settings.h"
#include "wallet/lite_wallet_controller.h" // lite send/new-address routing
#include "daemon/daemon_controller.h"
#include "daemon/embedded_daemon.h"
#include "daemon/xmrig_manager.h"
@@ -1609,6 +1610,23 @@ void App::applyDefaultBanlist()
void App::createNewZAddress(std::function<void(const std::string&)> callback)
{
// Lite build: derive locally via the controller (fast, no network). The backend auto-saves
// new addresses; the next lite refresh lists it with a balance.
if (lite_wallet_) {
const auto result = lite_wallet_->newAddress(/*shielded*/ true);
if (result.ok) {
AddressInfo info;
info.address = result.address;
info.type = "shielded";
info.balance = 0.0;
state_.z_addresses.push_back(info);
state_.addresses.push_back(info);
address_list_dirty_ = true;
}
if (callback) callback(result.ok ? result.address : std::string());
return;
}
if (!state_.connected || !rpc_ || !worker_) return;
worker_->post([this, callback]() -> rpc::RPCWorker::MainCb {
@@ -1640,6 +1658,22 @@ void App::createNewZAddress(std::function<void(const std::string&)> callback)
void App::createNewTAddress(std::function<void(const std::string&)> callback)
{
// Lite build: derive locally via the controller (see createNewZAddress).
if (lite_wallet_) {
const auto result = lite_wallet_->newAddress(/*shielded*/ false);
if (result.ok) {
AddressInfo info;
info.address = result.address;
info.type = "transparent";
info.balance = 0.0;
state_.t_addresses.push_back(info);
state_.addresses.push_back(info);
address_list_dirty_ = true;
}
if (callback) callback(result.ok ? result.address : std::string());
return;
}
if (!state_.connected || !rpc_ || !worker_) return;
worker_->post([this, callback]() -> rpc::RPCWorker::MainCb {
@@ -1944,10 +1978,28 @@ void App::backupWallet(const std::string& destination, std::function<void(bool,
// Transaction Operations
// ============================================================================
void App::sendTransaction(const std::string& from, const std::string& to,
void App::sendTransaction(const std::string& from, const std::string& to,
double amount, double fee, const std::string& memo,
std::function<void(bool success, const std::string& result)> callback)
{
// Lite build: route to the controller's async broadcast. `from`/`fee` are ignored — the
// backend selects inputs and adds the network fee itself. The result (txid/error) is
// delivered to `callback` from update() once takeBroadcastResult() yields it.
if (lite_wallet_) {
wallet::LiteSendRequest req;
wallet::LiteSendRecipient recipient;
recipient.address = to;
recipient.amountZatoshis = static_cast<std::uint64_t>(std::llround(amount * 100000000.0));
recipient.memo = memo;
req.recipients.push_back(std::move(recipient));
if (!lite_wallet_->sendTransaction(req)) {
if (callback) callback(false, "A send is already in progress, or no wallet is open");
return;
}
lite_send_callback_ = std::move(callback); // delivered from update()
return;
}
if (!state_.connected || !rpc_) {
if (callback) callback(false, "Not connected");
return;