From e7f1550808ca5388acad6dc20e65134161adcdaa Mon Sep 17 00:00:00 2001 From: Lee Clagett Date: Fri, 18 Apr 2025 22:23:42 -0400 Subject: [PATCH] Update lwsf version, and add some crude subaddress checking --- external/lwsf | 2 +- src/events.cpp | 1 + src/events.h | 1 + src/lwcli_config.h | 31 ++++++++++++++++++++--- src/views/history.cpp | 8 +++--- src/views/manager.cpp | 19 +++++++++++--- src/views/settings.cpp | 47 +++++++++++++++++++++++++---------- src/views/wallet.cpp | 56 +++++++++++++++++++++++++++++++----------- 8 files changed, 126 insertions(+), 39 deletions(-) diff --git a/external/lwsf b/external/lwsf index 4e40b43..d7fb796 160000 --- a/external/lwsf +++ b/external/lwsf @@ -1 +1 @@ -Subproject commit 4e40b4316b7d7a185c533c87c447848ac4f9cb88 +Subproject commit d7fb7961f8d3142969db22051189782d66b09f69 diff --git a/src/events.cpp b/src/events.cpp index 9762ba4..fd3fc97 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -33,4 +33,5 @@ namespace lwcli { namespace event /* Keep under 15 characters so that libstdc++ and libc++ can use small string optmization. */ const ftxui::Event lock_wallet = ftxui::Event::Special("lwcli.lockw"); + const ftxui::Event refresh_wallet = ftxui::Event::Special("lwcli.refresh"); }} diff --git a/src/events.h b/src/events.h index 755d86c..f4f4e4f 100644 --- a/src/events.h +++ b/src/events.h @@ -45,5 +45,6 @@ namespace lwcli { namespace event }; extern const ftxui::Event lock_wallet; + extern const ftxui::Event refresh_wallet; }} // lwcli // event diff --git a/src/lwcli_config.h b/src/lwcli_config.h index a3d6096..0b5dde7 100644 --- a/src/lwcli_config.h +++ b/src/lwcli_config.h @@ -37,6 +37,18 @@ namespace lwcli { namespace config { inline Monero::NetworkType network = Monero::MAINNET; + //! Max length for small-string optimization expectations + constexpr const unsigned sso_max = 15; + + //! \return True if `str` passes sso requirements. + constexpr bool verify_sso(const std::string_view str) noexcept + { return str.size() <= sso_max; } + + //! \return True if all `strs` pass sso requirements. + template + constexpr bool verify_sso(const T... strs) noexcept + { return ( ... && verify_sso(strs)); } + constexpr const std::string_view default_language{"English"}; //! Timeout interval for inactivity with open wallet @@ -46,9 +58,20 @@ namespace lwcli { namespace config constexpr const std::string_view default_url{"http://127.0.0.1:8080"}; constexpr const std::chrono::seconds default_refresh_interval{30}; - constexpr const std::string_view proxy{"lwcli.server.proxy"}; - constexpr const std::string_view refresh_interval{"lwcli.server.refresh_interval"}; - constexpr const std::string_view ssl{"lwcli.server.ssl"}; - constexpr const std::string_view url{"lwcli.server.url"}; + constexpr const std::string_view proxy{"lwcli.ser.proxy"}; + constexpr const std::string_view refresh_interval{"lwcli.ser.refr"}; + constexpr const std::string_view ssl{"lwcli.ser.ssl"}; + constexpr const std::string_view url{"lwcli.ser.url"}; + + static_assert(verify_sso(proxy, refresh_interval, ssl, url)); } + + constexpr const std::uint32_t default_major_lookahead = 50; + constexpr const std::uint32_t default_minor_lookahead = 200; + + constexpr const std::string_view major_lookahead{"lwcli.wal.maj_l"}; + constexpr const std::string_view minor_lookahead{"lwcli.wal.min_l"}; + + static_assert(verify_sso(major_lookahead, minor_lookahead)); + }} // lwcli // config diff --git a/src/views/history.cpp b/src/views/history.cpp index 6ff79cb..fb72411 100644 --- a/src/views/history.cpp +++ b/src/views/history.cpp @@ -282,7 +282,7 @@ namespace lwcli { namespace view // perform transalation lookup once base_rows = { - {_("Date"), _("Amount"), _("Payment ID"), _("Desription"), _("Block"), _("Fee"), _("Hash (abbr)")}, + {_("Date"), _("Amount"), _("Payment ID"), _("Desription"), _("Block"), _("Fee"), _("Hash")}, { "", "", "", "", "", "", "" } }; } @@ -397,14 +397,16 @@ namespace lwcli { namespace view if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos) payment_id.clear(); + char const* const extended_payment_id = 16 < payment_id.size() ? + "..." : ""; rows.push_back({ std::string{date}, print_amount(amount, direction), - payment_id.substr(0, 16), + payment_id.substr(0, 16) + extended_payment_id, tx->description(), std::to_string(tx->blockHeight()), print_money(tx->fee()), - tx->hash().substr(0, 16) + tx->hash().substr(0, 16) + "..." }); ++i; diff --git a/src/views/manager.cpp b/src/views/manager.cpp index 6a2c81c..3cc6f87 100644 --- a/src/views/manager.cpp +++ b/src/views/manager.cpp @@ -117,9 +117,10 @@ namespace lwcli { namespace view std::string server; std::string proxy; bool ssl; + bool subaddresses; new_wallet(std::string default_file) - : wallet_base(std::move(default_file)), confirm(), language(config::default_language), server(config::server::default_url), proxy(), ssl(false) + : wallet_base(std::move(default_file)), confirm(), language(config::default_language), server(config::server::default_url), proxy(), ssl(false), subaddresses(true) {} void setup(Monero::Wallet& wal) @@ -128,6 +129,15 @@ namespace lwcli { namespace view wal.setCacheAttribute(std::string{config::server::url}, server); wal.setCacheAttribute(std::string{config::server::proxy}, proxy); wal.setCacheAttribute(std::string{config::server::ssl}, std::to_string(int(ssl))); + + const std::uint32_t major_lookahead = subaddresses ? + config::default_major_lookahead : 0; + const std::uint32_t minor_lookahead = subaddresses ? + config::default_minor_lookahead : 0; + + wal.setCacheAttribute(std::string{config::major_lookahead}, std::to_string(major_lookahead)); + wal.setCacheAttribute(std::string{config::minor_lookahead}, std::to_string(minor_lookahead)); + wal.setSubaddressLookahead(major_lookahead, minor_lookahead); } }; @@ -239,6 +249,7 @@ namespace lwcli { namespace view enclosed->config.password.clear(); enclosed->config.confirm.clear(); enclosed->state->overlay = view::keys(prepped, true /* show warning */); + prepped->startRefresh(); enclosed->state->wal = prepped; } } @@ -260,7 +271,7 @@ namespace lwcli { namespace view {ftxui::text(_("Language: ")), last_input(&enclosed->config.language)}, {ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)}, {ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)}, - {ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)} + {ftxui::text(_("Options: ")), ftxui::Container::Horizontal({ftxui::Checkbox(_("TLS/SSL Cert Check "), &enclosed->config.ssl), ftxui::Checkbox(_("Subaddresses"), &enclosed->config.subaddresses)})} }, create }; @@ -335,7 +346,7 @@ namespace lwcli { namespace view {ftxui::text(_("Height: ")), last_input(&enclosed->height)}, {ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)}, {ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)}, - {ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)} + {ftxui::text(_("Options: ")), ftxui::Container::Horizontal({ftxui::Checkbox(_("TLS/SSL Cert Check "), &enclosed->config.ssl), ftxui::Checkbox(_("Subaddresses"), &enclosed->config.subaddresses)})} }, recover }; @@ -421,7 +432,7 @@ namespace lwcli { namespace view {ftxui::text(_("Language: ")), last_input(&enclosed->config.language)}, {ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)}, {ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)}, - {ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)} + {ftxui::text(_("Options: ")), ftxui::Container::Horizontal({ftxui::Checkbox(_("TLS/SSL Cert Check "), &enclosed->config.ssl), ftxui::Checkbox(_("Subaddresses"), &enclosed->config.subaddresses)})} }, recover }; diff --git a/src/views/settings.cpp b/src/views/settings.cpp index 6e90191..7da8f4c 100644 --- a/src/views/settings.cpp +++ b/src/views/settings.cpp @@ -38,6 +38,7 @@ #include "events.h" #include "lwcli_config.h" #include "translate.h" +#include "util.h" #include "views/history.h" @@ -45,16 +46,6 @@ namespace lwcli { namespace view { namespace { - std::optional from_string(const std::string_view str) noexcept - { - std::uint64_t out = 0; - const auto end = str.data() + str.size(); - auto [ptr, ec] = std::from_chars(str.data(), end, out); - if (bool(ec) || ptr != end) - return std::nullopt; - return out; - } - bool set_proxy(Monero::Wallet& wal, const std::string& proxy) { wal.setProxy(proxy); @@ -88,6 +79,34 @@ namespace lwcli { namespace view return true; } + bool set_major_lookahead(Monero::Wallet& wal, const std::string& major) + { + const auto major_integer = from_string(major); + if (!major_integer) + return false; + + auto minor = from_string(wal.getCacheAttribute(std::string{config::minor_lookahead})); + if (!minor) + minor = config::default_minor_lookahead; + + wal.setSubaddressLookahead(*major_integer, *minor); + return true; + } + + bool set_minor_lookahead(Monero::Wallet& wal, const std::string& minor) + { + const auto minor_integer = from_string(minor); + if (!minor_integer) + return false; + + auto major = from_string(wal.getCacheAttribute(std::string{config::major_lookahead})); + if (!major) + major = config::default_major_lookahead; + + wal.setSubaddressLookahead(*major, *minor_integer); + return true; + } + struct option { using updater = bool(Monero::Wallet&, const std::string&); @@ -96,11 +115,13 @@ namespace lwcli { namespace view updater* const update; }; - const std::array options{{ - {config::server::proxy, _("Proxy"), set_proxy}, + const std::array options{{ + {config::server::url, _("API Server"), set_url}, {config::server::refresh_interval, _("Refresh Interval (seconds)"), set_refresh}, {config::server::ssl, _("TLS/SSL Cert Check"), set_ssl}, - {config::server::url, _("API Server"), set_url} + {config::server::proxy, _("Proxy"), set_proxy}, + {config::major_lookahead, _("Subaddress Major Lookahead"), set_major_lookahead}, + {config::minor_lookahead, _("Subaddress Minor Lookahead"), set_minor_lookahead} }}; ftxui::Component last_input(std::string* str) diff --git a/src/views/wallet.cpp b/src/views/wallet.cpp index efcbbce..a19869a 100644 --- a/src/views/wallet.cpp +++ b/src/views/wallet.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -58,14 +59,14 @@ namespace lwcli { namespace view const std::shared_ptr wal = state->wal; return ftxui::Container::Horizontal({ ftxui::Button("[s]end", [] () {}, ascii()), - ftxui::Button("address [b]ook", [] () {}, ascii()), + ftxui::Button("[b]ook", [] () {}, ascii()), ftxui::Button("[a]ccounts", [] () {}, ascii()), ftxui::Button("[r]efresh", [wal] () { wal->refreshAsync(); }, ascii()), ftxui::Button("s[e]ttings", [state] () { state->overlay = settings(state->wal); }, ascii()) }); } - class wallet_ final : public ftxui::ComponentBase + class wallet_ final : public ftxui::ComponentBase, Monero::WalletListener { wallet_state state_; ftxui::Element title_; @@ -83,6 +84,28 @@ namespace lwcli { namespace view return ui_; } + void moneySpent(const std::string &txId, uint64_t amount) override final + {} + + void moneyReceived(const std::string &txId, uint64_t amount) override final + {} + + void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) override final + {} + + void newBlock(uint64_t height) override final + {} + + void updated() override final + {} + + void refreshed() override final + { + ftxui::ScreenInteractive* const active = ftxui::ScreenInteractive::Active(); + if (active) + active->PostEvent(event::refresh_wallet); + } + public: explicit wallet_(std::shared_ptr&& data) : ftxui::ComponentBase(), @@ -96,11 +119,17 @@ namespace lwcli { namespace view { if (!state_.wal) throw std::runtime_error{"Unexpected nullptr Monero wallet"}; + state_.wal->setListener(this); bar_ = menu_bar(&state_); title_ = ftxui::text(_("lwcli Wallet (Primary ") + state_.wal->mainAddress() + ")"); update_account(); } + virtual ~wallet_() noexcept override final + { + state_.wal->setListener(nullptr); + } + void update_account() { if (active_account_ != selected_account_) @@ -120,7 +149,9 @@ namespace lwcli { namespace view { const bool has_overlay = bool(state_.overlay); - if (state_.overlay) + if (event == event::refresh_wallet) + return true; + else if (state_.overlay) state_.overlay->OnEvent(std::move(event)); else if (event == ftxui::Event::CtrlQ) throw event::close{}; @@ -159,16 +190,13 @@ namespace lwcli { namespace view const bool connected = state_.wal->connected() == Monero::Wallet::ConnectionStatus_Connected; - std::string status; - if (!connected) - { - status = "Disconnected"; - const std::string& error = state_.wal->errorString(); - if (!error.empty()) - status += ": " + error; - } - else - status = "Connected"; + int status = 0; + std::string error; + state_.wal->statusWithErrorString(status, error); + + std::string message = connected ? "Connected" : "Disconnected"; + if (status != Monero::Wallet::Status_Ok) + message.append(": ").append(error); auto screen = ftxui::vbox({ title_, @@ -176,7 +204,7 @@ namespace lwcli { namespace view ftxui::separator(), history_->Render() | ftxui::yflex_shrink, ftxui::filler(), - ftxui::inverted(decorate::banner(ftxui::text(std::move(status)))) + ftxui::inverted(decorate::banner(ftxui::text(std::move(message)))) }); if (state_.overlay)