From 0293882d9afd8200f904015243c8b39fe27a0def Mon Sep 17 00:00:00 2001 From: Mykhailo Kremniov Date: Wed, 13 Aug 2025 22:01:13 +0300 Subject: [PATCH 1/2] Prettify wallet-cli help; require mnemonic during wallet recovery --- Cargo.lock | 7 + Cargo.toml | 2 +- node-gui/backend/src/backend_impl.rs | 2 +- node-gui/backend/src/lib.rs | 19 +- node-gui/backend/src/messages.rs | 4 +- node-gui/src/main_window/mod.rs | 4 +- node-gui/src/widgets/create_hw_wallet.rs | 3 +- wallet/types/src/lib.rs | 17 + wallet/wallet-cli-commands/Cargo.toml | 4 + .../src/command_handler/mod.rs | 6 +- wallet/wallet-cli-commands/src/errors.rs | 2 +- wallet/wallet-cli-commands/src/lib.rs | 407 ++++++++++++++---- wallet/wallet-cli-lib/Cargo.toml | 1 + wallet/wallet-cli-lib/src/errors.rs | 2 +- .../src/repl/interactive/mod.rs | 1 - wallet/wallet-rpc-client/Cargo.toml | 3 +- .../src/handles_client/mod.rs | 2 +- wallet/wallet-rpc-daemon/docs/RPC.md | 211 +++++---- wallet/wallet-rpc-lib/Cargo.toml | 1 + wallet/wallet-rpc-lib/src/rpc/interface.rs | 235 ++++++---- wallet/wallet-rpc-lib/src/rpc/server_impl.rs | 4 +- wallet/wallet-rpc-lib/src/rpc/types.rs | 27 +- 22 files changed, 687 insertions(+), 277 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71cd32b2a8..6cc65bc33d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9170,12 +9170,15 @@ dependencies = [ "consensus", "crossterm", "crypto", + "derive_more", "directories", "dyn-clone", "futures", "hex", "humantime", "itertools 0.14.0", + "jsonrpsee", + "lazy_static", "logging", "mempool", "node-comm", @@ -9185,6 +9188,7 @@ dependencies = [ "prettytable-rs", "randomness", "reedline", + "regex", "rpc", "rstest", "serde", @@ -9219,6 +9223,7 @@ dependencies = [ "consensus", "crossterm", "crypto", + "derive_more", "directories", "futures", "hex", @@ -9304,6 +9309,7 @@ dependencies = [ "chainstate-storage", "common", "crypto", + "derive_more", "hex", "logging", "node-comm", @@ -9351,6 +9357,7 @@ dependencies = [ "common", "consensus", "crypto", + "derive_more", "enum-iterator", "futures", "hex", diff --git a/Cargo.toml b/Cargo.toml index 799a44231a..e183e754ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,7 +126,7 @@ utxo = { path = "utxo" } [workspace.package] edition = "2021" -rust-version = "1.85" +rust-version = "1.88" version = "1.0.2" license = "MIT" diff --git a/node-gui/backend/src/backend_impl.rs b/node-gui/backend/src/backend_impl.rs index d5f3dc7373..5dfd5fe367 100644 --- a/node-gui/backend/src/backend_impl.rs +++ b/node-gui/backend/src/backend_impl.rs @@ -45,6 +45,7 @@ use wallet_rpc_client::handles_client::WalletRpcHandlesClient; use wallet_rpc_lib::{types::HardwareWalletType, EventStream, WalletRpc, WalletService}; use wallet_types::{ scan_blockchain::ScanBlockchain, wallet_type::WalletType, with_locked::WithLocked, + ImportOrCreate, }; use super::{ @@ -60,7 +61,6 @@ use super::{ p2p_event_handler::P2pEventHandler, parse_address, parse_coin_amount, wallet_events::GuiWalletEvents, - ImportOrCreate, }; const TRANSACTION_LIST_PAGE_COUNT: usize = 10; diff --git a/node-gui/backend/src/lib.rs b/node-gui/backend/src/lib.rs index 52a67d24f1..199a6b2478 100644 --- a/node-gui/backend/src/lib.rs +++ b/node-gui/backend/src/lib.rs @@ -16,14 +16,12 @@ pub mod error; pub mod messages; +mod account_id; mod backend_impl; mod chainstate_event_handler; mod p2p_event_handler; mod wallet_events; -mod account_id; -use wallet_types::scan_blockchain::ScanBlockchain; - use std::fmt::Debug; use std::sync::Arc; @@ -54,21 +52,6 @@ pub enum InitNetwork { Regtest, } -#[derive(Debug, Clone, Copy)] -pub enum ImportOrCreate { - Import, - Create, -} - -impl ImportOrCreate { - pub fn should_scan_blockchain(&self) -> ScanBlockchain { - match self { - Self::Create => ScanBlockchain::SkipScanning, - Self::Import => ScanBlockchain::ScanNoWait, - } - } -} - #[derive(Debug, Clone, Copy)] pub enum WalletMode { Cold, diff --git a/node-gui/backend/src/messages.rs b/node-gui/backend/src/messages.rs index 911939220b..b706460c03 100644 --- a/node-gui/backend/src/messages.rs +++ b/node-gui/backend/src/messages.rs @@ -35,9 +35,9 @@ use wallet::account::transaction_list::TransactionList; use wallet_cli_commands::ConsoleCommand; use wallet_controller::types::{Balances, WalletExtraInfo, WalletTypeArgs}; use wallet_rpc_lib::types::PoolInfo; -use wallet_types::wallet_type::WalletType; +use wallet_types::{wallet_type::WalletType, ImportOrCreate}; -use super::{AccountId, BackendError, ImportOrCreate}; +use super::{AccountId, BackendError}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct WalletId(u64); diff --git a/node-gui/src/main_window/mod.rs b/node-gui/src/main_window/mod.rs index acce53b5dd..6fccae16f5 100644 --- a/node-gui/src/main_window/mod.rs +++ b/node-gui/src/main_window/mod.rs @@ -30,13 +30,13 @@ use node_gui_backend::{ BackendEvent, BackendRequest, EncryptionAction, SignedTransactionWrapper, TransactionInfo, WalletId, WalletInfo, }, - BackendSender, ImportOrCreate, InitializedNode, + BackendSender, InitializedNode, }; use p2p::{net::types::services::Services, types::peer_id::PeerId, P2pEvent}; use rfd::AsyncFileDialog; use wallet_cli_commands::ConsoleCommand; use wallet_controller::types::WalletTypeArgs; -use wallet_types::{seed_phrase::StoreSeedPhrase, wallet_type::WalletType}; +use wallet_types::{seed_phrase::StoreSeedPhrase, wallet_type::WalletType, ImportOrCreate}; #[cfg(feature = "trezor")] use crate::widgets::create_hw_wallet::hw_wallet_create_dialog; diff --git a/node-gui/src/widgets/create_hw_wallet.rs b/node-gui/src/widgets/create_hw_wallet.rs index 8884ff217b..4ac17a1ad5 100644 --- a/node-gui/src/widgets/create_hw_wallet.rs +++ b/node-gui/src/widgets/create_hw_wallet.rs @@ -22,7 +22,8 @@ use iced::{ Element, Length, Theme, }; use iced_aw::Card; -use node_gui_backend::ImportOrCreate; + +use wallet_types::ImportOrCreate; pub struct CreateHwWalletDialog { on_import: Box Message>, diff --git a/wallet/types/src/lib.rs b/wallet/types/src/lib.rs index 7ab6dc057b..0741c7bbe3 100644 --- a/wallet/types/src/lib.rs +++ b/wallet/types/src/lib.rs @@ -44,6 +44,8 @@ use common::{ primitives::Amount, }; +use crate::scan_blockchain::ScanBlockchain; + #[derive(Debug, Clone, PartialEq, Eq)] pub struct SignedTxWithFees { pub tx: SignedTransaction, @@ -55,3 +57,18 @@ impl SignedTxWithFees { self.tx.transaction() } } + +#[derive(Debug, Clone, Copy)] +pub enum ImportOrCreate { + Import, + Create, +} + +impl ImportOrCreate { + pub fn should_scan_blockchain(&self) -> ScanBlockchain { + match self { + Self::Create => ScanBlockchain::SkipScanning, + Self::Import => ScanBlockchain::ScanNoWait, + } + } +} diff --git a/wallet/wallet-cli-commands/Cargo.toml b/wallet/wallet-cli-commands/Cargo.toml index 93361f58c7..55134d5354 100644 --- a/wallet/wallet-cli-commands/Cargo.toml +++ b/wallet/wallet-cli-commands/Cargo.toml @@ -31,11 +31,14 @@ wallet-rpc-client = { path = "../wallet-rpc-client" } clap = { workspace = true, features = ["derive"] } async-trait.workspace = true crossterm.workspace = true +derive_more.workspace = true directories.workspace = true dyn-clone.workspace = true humantime.workspace = true hex.workspace = true itertools.workspace = true +lazy_static.workspace = true +regex.workspace = true reedline = { workspace = true, features = ["external_printer"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true @@ -57,6 +60,7 @@ subsystem = { path = "../../subsystem" } test-utils = { path = "../../test-utils" } wallet-test-node = { path = "../wallet-test-node" } +jsonrpsee.workspace = true rstest.workspace = true [features] diff --git a/wallet/wallet-cli-commands/src/command_handler/mod.rs b/wallet/wallet-cli-commands/src/command_handler/mod.rs index 3a9b758cbd..d7b333cca0 100644 --- a/wallet/wallet-cli-commands/src/command_handler/mod.rs +++ b/wallet/wallet-cli-commands/src/command_handler/mod.rs @@ -846,9 +846,9 @@ where } } - WalletCommand::GetBlock { hash } => { - let hash = self.wallet().await?.node_block(hash).await?; - match hash { + WalletCommand::GetBlock { id } => { + let block = self.wallet().await?.node_block(id).await?; + match block { Some(block) => Ok(ConsoleCommand::Print(block)), None => Ok(ConsoleCommand::Print("Not found".to_owned())), } diff --git a/wallet/wallet-cli-commands/src/errors.rs b/wallet/wallet-cli-commands/src/errors.rs index f325b4ac30..faf486fa90 100644 --- a/wallet/wallet-cli-commands/src/errors.rs +++ b/wallet/wallet-cli-commands/src/errors.rs @@ -20,7 +20,7 @@ use wallet_controller::types::GenericCurrencyTransferToTxOutputConversionError; use wallet_rpc_client::{handles_client::WalletRpcHandlesClientError, rpc_client::WalletRpcError}; use wallet_rpc_lib::RpcError; -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, derive_more::Debug)] pub enum WalletCliCommandError { #[error("Invalid quoting")] InvalidQuoting, diff --git a/wallet/wallet-cli-commands/src/lib.rs b/wallet/wallet-cli-commands/src/lib.rs index 2b1eeb5ce6..78d5726c29 100644 --- a/wallet/wallet-cli-commands/src/lib.rs +++ b/wallet/wallet-cli-commands/src/lib.rs @@ -21,6 +21,7 @@ pub use command_handler::CommandHandler; use dyn_clone::DynClone; pub use errors::WalletCliCommandError; use helper_types::YesNo; +use regex::Regex; use rpc::description::{Described, Module}; use wallet_controller::types::WalletTypeArgs; use wallet_rpc_lib::{ @@ -28,7 +29,7 @@ use wallet_rpc_lib::{ ColdWalletRpcDescription, WalletRpcDescription, }; -use std::{fmt::Debug, num::NonZeroUsize, path::PathBuf, time::Duration}; +use std::{collections::BTreeMap, fmt::Debug, num::NonZeroUsize, path::PathBuf, time::Duration}; use clap::{Command, FromArgMatches, Parser, Subcommand}; @@ -54,24 +55,30 @@ pub enum CreateWalletSubCommand { /// File path of the wallet file wallet_path: PathBuf, - /// If 'store-seed-phrase', the seed-phrase will be stored in the wallet file. - /// If 'do-not-store-seed-phrase', the seed-phrase will only be printed on the screen. + /// Specifies whether the seed-phrase should be stored in the wallet file or + /// only printed on the screen. + /// /// Not storing the seed-phrase can be seen as a security measure /// to ensure sufficient secrecy in case that seed-phrase is reused /// elsewhere if this wallet is compromised. whether_to_store_seed_phrase: CliStoreSeedPhrase, - /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). If not specified, a new mnemonic phrase is generated and printed. + /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). + /// + /// If not specified, a new mnemonic phrase will be generated and printed. mnemonic: Option, /// Passphrase along the mnemonic #[arg(long = "passphrase")] passphrase: Option, }, - /// (Beta) Create a wallet using a connected Trezor hardware wallet. Only the public keys will be kept in - /// the software wallet. Cannot specify a mnemonic or passphrase here, - /// the former must have been entered on the hardware during the device setup - /// and the latter will have to be entered every time the device is connected to the host machine. + /// (Beta) Create a wallet using a connected Trezor hardware wallet. + /// + /// Only the public keys will be kept in the wallet file. + /// + /// Cannot specify a mnemonic or passphrase here, the mnemonic must have been entered on + /// the device during its initial setup and the passphrase will have to be entered every + /// time the device is connected to the host machine. #[command()] Trezor { /// File path of the wallet file @@ -79,7 +86,8 @@ pub enum CreateWalletSubCommand { /// Optionally specify the ID for the Trezor device to connect to in case there /// are multiple Trezor devices connected at the same time. - /// If not specified and there are multiple devices connected a choice will be presented + /// + /// If not specified and if there are multiple devices connected, a choice will be presented. #[arg(long)] device_id: Option, }, @@ -115,30 +123,34 @@ impl CreateWalletSubCommand { #[derive(Debug, Subcommand, Clone)] pub enum RecoverWalletSubCommand { - /// Recover a software. + /// Recover a software wallet #[command()] Software { /// File path of the wallet file wallet_path: PathBuf, - /// If 'store-seed-phrase', the seed-phrase will be stored in the wallet file. - /// If 'do-not-store-seed-phrase', the seed-phrase will only be printed on the screen. + /// Specifies whether the seed-phrase should be stored in the wallet file or + /// only printed on the screen. + /// /// Not storing the seed-phrase can be seen as a security measure /// to ensure sufficient secrecy in case that seed-phrase is reused /// elsewhere if this wallet is compromised. whether_to_store_seed_phrase: CliStoreSeedPhrase, - /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). If not specified, a new mnemonic phrase is generated and printed. - mnemonic: Option, + /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). + mnemonic: String, /// Passphrase along the mnemonic #[arg(long = "passphrase")] passphrase: Option, }, - /// (Beta) Recover a wallet using a connected Trezor hardware wallet. Only the public keys will be kept in - /// the software wallet. Cannot specify a mnemonic or passphrase here, - /// the former must have been entered on the hardware during the device setup - /// and the latter will have to be entered every time the device is connected to the host machine. + /// (Beta) Recover a wallet using a connected Trezor hardware wallet. + /// + /// Only the public keys will be kept in the wallet file. + /// + /// Cannot specify a mnemonic or passphrase here, the mnemonic must have been entered on + /// the device during its initial setup and the passphrase will have to be entered every + /// time the device is connected to the host machine. #[command()] Trezor { /// File path of the wallet file @@ -146,7 +158,8 @@ pub enum RecoverWalletSubCommand { /// Optionally specify the ID for the Trezor device to connect to in case there /// are multiple Trezor devices connected at the same time. - /// If not specified and there are multiple devices connected a choice will be presented + /// + /// If not specified and if there are multiple devices connected, a choice will be presented. #[arg(long)] device_id: Option, }, @@ -165,7 +178,7 @@ impl RecoverWalletSubCommand { ( wallet_path, WalletTypeArgs::Software { - mnemonic, + mnemonic: Some(mnemonic), passphrase, store_seed_phrase, }, @@ -203,7 +216,8 @@ pub enum OpenWalletSubCommand { /// Optionally specify the ID for the Trezor device to connect to in case there /// are multiple Trezor devices connected at the same time. - /// If not specified and there are multiple devices connected a choice will be presented. + /// + /// If not specified and if there are multiple devices connected, a choice will be presented. #[arg(long)] device_id: Option, }, @@ -276,28 +290,28 @@ pub enum ColdWalletCommand { /// The new lookahead size lookahead_size: u32, - /// Forces the reduction of lookahead size even below the known last used address - /// the new wallet can lose track of known addresses and balance + /// Forces the reduction of lookahead size even below the last used address; + /// this may cause the wallet to lose track of used addresses and its actual balance. i_know_what_i_am_doing: Option, }, /// Creates a QR code of the provided address #[clap(name = "address-qrcode")] AddressQRCode { - /// A Destination address address: String, }, #[clap(name = "address-new")] NewAddress, - /// Reveal the public key behind this address in hex encoding + /// Reveal the public key behind the specified "public key hash" address as a hex encoded string. #[clap(name = "address-reveal-public-key-as-hex")] RevealPublicKeyHex { public_key_hash: String, }, - /// Reveal the public key behind this address in address encoding. + /// Reveal the public key behind the specified "public key hash" address in address encoding. + /// /// Note that this isn't a normal address to be used in transactions. /// It's preferred to take the address from address-show command #[clap(name = "address-reveal-public-key-as-address")] @@ -421,13 +435,13 @@ pub enum WalletCommand { #[clap(name = "account-utxos")] ListUtxo { - /// The type of utxo to be listed. Default is "all". + /// The type of utxos to be listed. #[arg(value_enum, default_value_t = CliUtxoTypes::All)] utxo_type: CliUtxoTypes, - /// Whether to include locked outputs. Default is "unlocked" + /// Whether to include locked outputs. #[arg(value_enum, default_value_t = CliWithLocked::Unlocked)] with_locked: CliWithLocked, - /// The state of the utxos; e.g., confirmed, unconfirmed, etc. + /// The state of the utxos. #[arg(default_values_t = vec![CliUtxoState::Confirmed])] utxo_states: Vec, }, @@ -499,13 +513,13 @@ pub enum WalletCommand { #[clap(name = "standalone-multisig-utxos")] ListMultisigUtxo { - /// The type of utxo to be listed. Default is "all". + /// The type of utxos to be listed. #[arg(value_enum, default_value_t = CliUtxoTypes::All)] utxo_type: CliUtxoTypes, - /// Whether to include locked outputs. Default is "unlocked" + /// Whether to include locked outputs. #[arg(value_enum, default_value_t = CliWithLocked::Unlocked)] with_locked: CliWithLocked, - /// The state of the utxos; e.g., confirmed, unconfirmed, etc. + /// The state of the utxos. #[arg(default_values_t = vec![CliUtxoState::Confirmed])] utxo_states: Vec, }, @@ -528,6 +542,7 @@ pub enum WalletCommand { icon_uri: Option, /// URI of the media media_uri: Option, + /// URI of the additional metadata additional_metadata_uri: Option, }, @@ -623,7 +638,7 @@ pub enum WalletCommand { /// the original multisig address. /// /// The utxos to pay fees from will be selected automatically; these will be normal, single-sig utxos. - /// The optional `fee_change_address` specifies the destination for the change for the fee payment; + /// The optional "fee change address" specifies the destination for the change for the fee payment; /// If it's unset, the destination will be taken from one of existing single-sig utxos. #[clap(name = "token-make-tx-to-send-from-multisig-address")] #[clap(hide = true)] @@ -646,25 +661,25 @@ pub enum WalletCommand { address: String, /// The amount to be sent, in decimal format amount: DecimalAmount, - /// You can choose what utxos to spend (space separated as additional arguments). A utxo can be from a transaction output or a block reward output: + /// You can choose what utxos to spend (space separated as additional arguments). + /// A utxo can be from a transaction output or a block reward output: /// e.g tx(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,1) or /// block(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,2) #[arg(default_values_t = Vec::::new())] utxos: Vec, }, - #[clap(name = "address-sweep-spendable")] - /// Sweep all spendable coins or tokens from an address or addresses specified in `addresses` - /// or all addresses from this account if `--all` is specified, to the given destination address. - /// Either 1 or more addresses need to be specified in `addresses` without `--all` being set, or - /// `addresses` needs to be empty and `--all` being set. + /// Sweep all spendable coins or tokens from the specified (or all) addresses to the given destination address. /// /// Spendable coins are any coins that are not locked, and tokens that are not frozen or locked. - /// The wallet will automatically calculate the required fees + /// The wallet will automatically calculate the required fees. + #[clap(name = "address-sweep-spendable")] SweepFromAddress { /// The receiving address of the coins or tokens destination_address: String, - /// The addresses to be swept + /// The addresses to be swept. Mutually exclusive with --all. + /// + /// If --all is not specified, this has to contain at least one address. #[arg(required_unless_present("all"))] addresses: Vec, /// Sweep all addresses @@ -690,7 +705,7 @@ pub enum WalletCommand { /// e.g tx(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,1) or /// block(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,2) utxo: String, - /// Optional change address, if not specified it returns the change to the same address from the input + /// Optional change address; if not specified, it returns the change to the same address from the input. #[arg(long = "change")] change_address: Option, }, @@ -720,9 +735,9 @@ pub enum WalletCommand { #[clap(name = "delegation-stake")] DelegateStaking { - /// The amount to be delegated for staking + /// The amount to be delegated for staking. amount: DecimalAmount, - /// The delegation id that was created. Every pool you want to delegate to must have a delegation id. + /// The delegation id. delegation_id: String, }, @@ -790,7 +805,7 @@ pub enum WalletCommand { /// (run it in the wallet that owns the key). staker_address: Option, - /// This specifies the VRF key that will be used to produce POS hashes during staking. + /// This specifies the VRF key that will be used to produce PoS hashes during staking. /// /// The key must be owned by the wallet that will do the actual staking. Leave it empty if the current /// wallet will be the staking wallet. @@ -914,8 +929,8 @@ pub enum WalletCommand { #[clap(name = "node-get-block")] GetBlock { - /// Block hash - hash: String, + /// Block id + id: String, }, #[clap(name = "node-generate-block")] @@ -929,29 +944,40 @@ pub enum WalletCommand { /// For each block height in the specified range, find timestamps where staking is/was possible /// for the given pool. - /// - /// `min_height` must not be zero; `max_height` must not exceed the best block height plus one. - /// - /// If `check_all_timestamps_between_blocks` is "no", `seconds_to_check_for_height + 1` is the number - /// of seconds that will be checked at each height in the range. - /// If `check_all_timestamps_between_blocks` is "yes", `seconds_to_check_for_height` only applies to the - /// last height in the range; for all other heights the maximum timestamp is the timestamp - /// of the next block. #[clap(name = "node-find-timestamps-for-staking")] #[clap(hide = true)] FindTimestampsForStaking { + /// The pool in question pool_id: String, + /// The minimum block height to consider; must not be zero min_height: BlockHeight, + /// The maximum block height to consider; must not exceed the best block height plus one max_height: BlockHeight, + /// If "check_all_timestamps_between_blocks" is "no", this value plus one is the number + /// of seconds that will be checked at each height in the range. + /// + /// If "check_all_timestamps_between_blocks" is "yes", this value only applies to the + /// last height in the range; for all other heights the maximum timestamp is the timestamp + /// of the next block. seconds_to_check_for_height: u64, + /// This determines how "seconds_to_check_for_height" will be interpreted check_all_timestamps_between_blocks: YesNo, }, + /// Return mainchain block ids with heights in the given range using the given step. + /// + /// The purpose of this is to populate CHECKPOINTS_DATA arrays located in + /// `common/src/chain/config/checkpoints_data/` in `mainnet.rs` and `testnet.rs`. #[clap(name = "node-get-block-ids-as-checkpoints")] #[clap(hide = true)] GetBlockIdsAsCheckpoints { + /// The starting height; normally, this will be the last height mentioned in + /// the corresponding CHECKPOINTS_DATA array. start_height: BlockHeight, + /// The end (exclusive) height; normally, this will be some large number, which + /// is definitely bigger than the current best block height, e.g. a million. end_height: BlockHeight, + /// The step; in our current CHECKPOINTS_DATA arrays we use 500 as the step. step: NonZeroUsize, }, @@ -960,12 +986,19 @@ pub enum WalletCommand { /// The transaction outputs, in the format `transfer(address,amount)` /// e.g. transfer(tmt1q8lhgxhycm8e6yk9zpnetdwtn03h73z70c3ha4l7,0.9) outputs: Vec, - /// You can choose what utxos to spend (space separated as additional arguments). A utxo can be from a transaction output or a block reward output: + /// You can choose what utxos to spend (space separated as additional arguments). + /// + /// A utxo can be from a transaction output or a block reward output: /// e.g tx(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,1) or /// block(000000000000000000059fa50103b9683e51e5aba83b8a34c9b98ce67d66136c,2) #[arg(long = "utxos", default_values_t = Vec::::new())] utxos: Vec, + /// This specifies that instead of a (hex encoded) PartiallySignedTransaction + /// the result should be a (hex encoded) "simple" transaction. + /// + /// Note that both variants are accepted by account-sign-raw-transaction, + /// so the presence of this option doesn't matter much. #[arg(long = "only-transaction", default_value_t = false)] only_transaction: bool, }, @@ -983,7 +1016,7 @@ pub enum WalletCommand { ListMainchainTransactions { /// Address to filter by address: Option, - /// limit the number of printed transactions, default is 100 + /// Limit the number of printed transactions. #[arg(long = "limit", default_value_t = 100)] limit: usize, }, @@ -1150,9 +1183,10 @@ const MAIN_HELP_TEMPLATE: &str = "\ {all-args} "; -// Strip out name/version +// Strip out name/version. +// Note: here we expect "about" to have a trailing EOL, see the comments inside get_repl_command. const COMMAND_HELP_TEMPLATE: &str = "\ - {about-with-newline}\n\ + {about}\n\ {usage-heading}\n {usage}\n\ \n\ {all-args}{after-help}\ @@ -1183,30 +1217,113 @@ pub fn get_repl_command(cold_wallet: bool, mutable_wallet: bool) -> Command { repl_command }; - // Customize the help template for all commands to make it more REPL friendly + lazy_static::lazy_static! { + static ref CUSTOM_RPC_TO_CLI_NAME_MAPPINGS: BTreeMap<&'static str, &'static str> = + BTreeMap::from([("account_extended_public_key", "account-extended-public-key-as-hex")]); + } + + let method_desc_from_rpc = COLD_WALLET_DESC + .methods + .iter() + .chain(WALLET_DESC.methods) + .map(|method| { + let expected_cmd_name = + if let Some(cmd_name) = CUSTOM_RPC_TO_CLI_NAME_MAPPINGS.get(&method.name) { + cmd_name.to_string() + } else { + method.name.replace("_", "-") + }; + (expected_cmd_name, method) + }) + .collect::>(); + + // Postprocess the commands: + // 1) Customize the help template to make it more REPL friendly. + // 2) Set display_order of all commands to the same value, so that they get sorted + // in the alphabetical order (except "help", which will be added automatically by clap + // later, so it will always be at the end). + // 3) If the command's "about" is empty, re-use the description of the corresponding RPC + // method. for subcommand in repl_command.get_subcommands_mut() { - let mut new_subcommand = subcommand.clone().help_template(COMMAND_HELP_TEMPLATE); + let mut new_subcommand = + subcommand.clone().help_template(COMMAND_HELP_TEMPLATE).display_order(0); + if new_subcommand.get_about().is_none() { - if let Some(desc) = - COLD_WALLET_DESC.methods.iter().chain(WALLET_DESC.methods).find_map(|method| { - method - .name - .split('_') - .zip(subcommand.get_name().split('-')) - .all(|(x, y)| x == y) - .then_some(method.description) - }) - { - new_subcommand = new_subcommand.about(desc); + if let Some(rpc_method) = method_desc_from_rpc.get(subcommand.get_name()) { + let (about, long_about) = clapify_and_split_rpc_description(rpc_method.description); + + new_subcommand = new_subcommand.about(about); + + if let Some(long_about) = long_about { + new_subcommand = new_subcommand.long_about(long_about); + } } } + // Force-append a single EOL to all "about"s, to make the output of the "help" command nicer. + if let Some(about) = new_subcommand.get_about() { + let new_about = format!("{about}\n"); + new_subcommand = new_subcommand.about(new_about); + } + + // For consistency, we also have to append an EOL to long_about. + // (because "about" is printed by "some_command -h" and "long_about" by "some_command --help"). + if let Some(long_about) = new_subcommand.get_long_about() { + let new_long_about = format!("{long_about}\n"); + new_subcommand = new_subcommand.long_about(new_long_about); + } + *subcommand = new_subcommand; } + // This will add the "help" subcommand. + // (note: if this is not called here, the "help" subcommand will still be added, but later, + // during the `try_get_matches_from` call in `parse_input`). + repl_command.build(); + repl_command } +/// Clap-ify the provided RPC method description and split it into the "about" and "long_about" +/// parts. +/// +/// Note: the generated RPC descriptions look different from those generated by clap: +/// 1) The RPC ones always have at least one trailing EOL, the clap ones don't. +/// 2) The RPC ones preserve all EOLs of the original doc string; in the clap ones all EOLs +/// within each paragraph are removed and empty lines between paragraphs are squashed into one. +/// 3) In the clap ones, if there are more than 1 paragraph, the first paragraph goes into "about" +/// and the whole text goes into "long_about". +/// Also, if the "about" part ends in exactly one period, the period is removed. +fn clapify_and_split_rpc_description(descr: &str) -> (String, Option) { + let descr = descr.trim(); + + lazy_static::lazy_static! { + static ref PARA_SEPARATOR_REGEX:Regex = Regex::new(r"(?m)\n{3,}").expect("regex construction must succeed"); + static ref EXTRA_EOL_REGEX:Regex = Regex::new(r"(?m)([^\n])\n([^\n])").expect("regex construction must succeed"); + } + + let descr = PARA_SEPARATOR_REGEX.replace_all(descr, "\n\n"); + let descr = EXTRA_EOL_REGEX.replace_all(&descr, "$1 $2"); + + let (mut about, long_about) = if let Some(first_double_eol_pos) = descr.find("\n\n") { + ( + // We know that the position is at a char boundary. + #[allow(clippy::string_slice)] + descr[..first_double_eol_pos].to_owned(), + Some(descr.into_owned()), + ) + } else { + (descr.into_owned(), None) + }; + + let mut about_chars = about.chars(); + if about_chars.next_back() == Some('.') && about_chars.next_back() != Some('.') { + about.pop(); + } + + (about, long_about) +} + /// Try to parse REPL input string as a [WalletCommands] pub fn parse_input( line: &str, @@ -1226,3 +1343,149 @@ pub fn parse_input( .map_err(WalletCliCommandError::InvalidCommandInput)?; Ok(Some(command)) } + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + #[rstest] + fn ensure_commands_have_description( + #[values(false, true)] cold_wallet: bool, + #[values(false, true)] mutable_wallet: bool, + ) { + let repl_command = get_repl_command(cold_wallet, mutable_wallet); + + let command_names_without_descr = repl_command + .get_subcommands() + .filter(|command| command.get_about().is_none()) + .map(|command| command.get_name().to_owned()) + .collect::>(); + assert_eq!(command_names_without_descr, Vec::::new()); + } + + mod clapify_rpc_descr_test { + use super::*; + + #[test] + fn test() { + let expected_descrs = BTreeMap::from([ + ("func1", ("This is a test description. It has some EOLs. Also, it ends with a period".to_owned(), None)), + ("func2", ("This is a test description. It has some EOLs. Also, it ends with a double period..".to_owned(), None)), + ("func3", ( + "This is a test description. It has some EOLs. Also, it ends with a period".to_owned(), + Some(concat!( + "This is a test description. It has some EOLs. Also, it ends with a period.\n\n", + "And it has an extra paragraph with extra EOL.").to_owned() + )) + ), + ("func4", ( + "This is a test description. It has some EOLs. Also, it ends with a double period..".to_owned(), + Some(concat!("This is a test description. It has some EOLs. Also, it ends with a double period..\n\n", + "And it has an extra paragraph with extra EOL.\n\n", + "And one more paragraph.").to_owned() + )) + ), + ]); + + let command = TestCommand::augment_subcommands(Command::new("test_cmd")); + + let descrs_from_command = command + .get_subcommands() + .map(|sub_cmd| { + ( + sub_cmd.get_name(), + ( + sub_cmd.get_about().unwrap().to_string(), + sub_cmd.get_long_about().map(|s| s.to_string()), + ), + ) + }) + .collect::>(); + assert_eq!(descrs_from_command, expected_descrs); + + let descrs_from_rpc = TestRpcDescription::DESCRIPTION + .methods + .iter() + .map(|method| { + let (about, long_about) = clapify_and_split_rpc_description(method.description); + + (method.name, (about, long_about)) + }) + .collect::>(); + assert_eq!(descrs_from_rpc, expected_descrs); + } + + #[rpc::describe] + #[rpc::rpc(server, client)] + trait TestRpc { + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a period. + #[method(name = "func1")] + fn func1(&self) -> rpc::RpcResult<()>; + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a double period.. + #[method(name = "func2")] + fn func2(&self) -> rpc::RpcResult<()>; + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a period. + /// + /// And it has an extra paragraph + /// with extra EOL. + #[method(name = "func3")] + fn func3(&self) -> rpc::RpcResult<()>; + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a double period.. + /// + /// And it has an extra paragraph + /// with extra EOL. + /// + /// And one more paragraph. + #[method(name = "func4")] + fn func4(&self) -> rpc::RpcResult<()>; + } + + #[derive(Debug, Parser)] + pub enum TestCommand { + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a period. + #[clap(name = "func1")] + Func1, + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a double period.. + #[clap(name = "func2")] + Func2, + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a period. + /// + /// And it has an extra paragraph + /// with extra EOL. + #[clap(name = "func3")] + Func3, + + /// This is a test description. + /// It has some EOLs. + /// Also, it ends with a double period.. + /// + /// And it has an extra paragraph + /// with extra EOL. + /// + /// And one more paragraph. + #[clap(name = "func4")] + Func4, + } + } +} diff --git a/wallet/wallet-cli-lib/Cargo.toml b/wallet/wallet-cli-lib/Cargo.toml index 7a7077407f..0c7836a6a6 100644 --- a/wallet/wallet-cli-lib/Cargo.toml +++ b/wallet/wallet-cli-lib/Cargo.toml @@ -31,6 +31,7 @@ wallet-cli-commands = { path = "../wallet-cli-commands" } clap = { workspace = true, features = ["derive"] } async-trait.workspace = true crossterm.workspace = true +derive_more.workspace = true directories.workspace = true humantime.workspace = true hex.workspace = true diff --git a/wallet/wallet-cli-lib/src/errors.rs b/wallet/wallet-cli-lib/src/errors.rs index 4538799c85..1cb81c5ac7 100644 --- a/wallet/wallet-cli-lib/src/errors.rs +++ b/wallet/wallet-cli-lib/src/errors.rs @@ -20,7 +20,7 @@ use wallet_cli_commands::WalletCliCommandError; use wallet_rpc_client::rpc_client::WalletRpcError; use wallet_rpc_lib::types::NodeInterface; -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, derive_more::Debug)] pub enum WalletCliError { #[error("File {0} I/O error: {1}")] FileError(PathBuf, String), diff --git a/wallet/wallet-cli-lib/src/repl/interactive/mod.rs b/wallet/wallet-cli-lib/src/repl/interactive/mod.rs index c0334c4bf4..681995bbb3 100644 --- a/wallet/wallet-cli-lib/src/repl/interactive/mod.rs +++ b/wallet/wallet-cli-lib/src/repl/interactive/mod.rs @@ -135,7 +135,6 @@ pub fn run( .get_subcommands() .filter(|command| !command.is_hide_set()) .map(|command| command.get_name().to_owned()) - .chain(std::iter::once("help".to_owned())) .collect::>(); let history = if let Some(file_name) = history_file { diff --git a/wallet/wallet-rpc-client/Cargo.toml b/wallet/wallet-rpc-client/Cargo.toml index e0ff6968e5..764ee2f1d2 100644 --- a/wallet/wallet-rpc-client/Cargo.toml +++ b/wallet/wallet-rpc-client/Cargo.toml @@ -25,9 +25,10 @@ wallet-controller = { path = "../wallet-controller" } wallet-rpc-lib = { path = "../wallet-rpc-lib" } wallet-types = { path = "../types" } -hex.workspace = true async-trait.workspace = true base64.workspace = true +derive_more.workspace = true +hex.workspace = true serde_json.workspace = true thiserror.workspace = true tower.workspace = true diff --git a/wallet/wallet-rpc-client/src/handles_client/mod.rs b/wallet/wallet-rpc-client/src/handles_client/mod.rs index 8a47bcdec1..141b202e05 100644 --- a/wallet/wallet-rpc-client/src/handles_client/mod.rs +++ b/wallet/wallet-rpc-client/src/handles_client/mod.rs @@ -66,7 +66,7 @@ pub struct WalletRpcHandlesClient { server_rpc: Option, } -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, derive_more::Debug)] pub enum WalletRpcHandlesClientError { #[error(transparent)] WalletRpcError(#[from] wallet_rpc_lib::RpcError), diff --git a/wallet/wallet-rpc-daemon/docs/RPC.md b/wallet/wallet-rpc-daemon/docs/RPC.md index 652c25258e..c953091c84 100644 --- a/wallet/wallet-rpc-daemon/docs/RPC.md +++ b/wallet/wallet-rpc-daemon/docs/RPC.md @@ -39,6 +39,9 @@ nothing ### Method `wallet_best_block` +Returns information about the current best block + + Parameters: ``` {} @@ -55,6 +58,7 @@ Returns: ### Method `account_create` Creates a new account with an optional name. + Returns an error if the last created account does not have a transaction history. @@ -104,6 +108,7 @@ Returns: ### Method `standalone_address_label_rename` Add, rename or delete a label to an already added standalone address. + Specifying a label will add or replace the existing one, and not specifying a label will remove the existing one. @@ -126,7 +131,7 @@ nothing ### Method `standalone_add_watch_only_address` -Add a new standalone watch only address not derived from the selected account's key chain +Add a new standalone watch-only address not derived from the selected account's key chain Parameters: @@ -150,7 +155,7 @@ nothing ### Method `standalone_add_private_key_from_hex` -Add a new standalone private key not derived from the selected account's key chain to be watched +Add a new standalone private key not derived from the selected account's key chain Parameters: @@ -174,7 +179,8 @@ nothing ### Method `standalone_add_multisig` -Add a new standalone multi signature address +Add a new standalone multi-signature address. + Use the `transaction_compose` command to use the new multisig address as input or output @@ -200,7 +206,7 @@ string ### Method `standalone_multisig_utxos` -Lists all the utxos owned by a multisig watched by this account +Lists all the utxos owned by multisig addresses watched by this account Parameters: @@ -948,8 +954,9 @@ Returns: ### Method `address_send` -Send a given coin amount to a given address. The wallet will automatically calculate the required information -Optionally, one can also mention the utxos to be used. +Send a given coin amount to a given address. The wallet will automatically calculate the required fees. + +Optionally, you can also mention the utxos to be used. Parameters: @@ -1010,7 +1017,7 @@ Either 1 or more addresses need to be specified in `from_addresses` with `all` s `from_addresses` needs to be empty and `all` set to true. Spendable coins are any coins that are not locked, and tokens that are not frozen or locked. -The wallet will automatically calculate the required fees +The wallet will automatically calculate the required fees. Parameters: @@ -1053,7 +1060,7 @@ Returns: ### Method `staking_sweep_delegation` Sweep all the coins from a delegation to a given address. -The wallet will automatically calculate the required fees +The wallet will automatically calculate the required fees. Parameters: @@ -1096,6 +1103,7 @@ Returns: Creates a transaction that spends from a specific address, and returns the change to the same address (unless one is specified), without signature. + This transaction is used for "withdrawing" small amounts from a cold storage without changing the ownership address. Once this is created, it can be signed using account-sign-raw-transaction in the cold wallet @@ -1152,7 +1160,14 @@ Returns: ### Method `transaction_inspect` -Print the summary of the transaction +Print the summary of a transaction. + +Note that currently this will only work for transactions whose inputs have not been spent +yet (i.e. it won't work if the transaction has already been included in a block). +Also, it doesn't support certain input types (such as account-based inputs). + +The main purpose of this command is to be able to inspect the result of transaction-compose +and account-sign-raw-transaction before sending it to the network. Parameters: @@ -1204,24 +1219,24 @@ Returns: ### Method `staking_create_pool` Create a staking pool. The pool will be capable of creating blocks and gaining rewards, -and will be capable of taking delegations from other users and staking. +as well as taking delegations from other users. The decommission key is the key that can decommission the pool. -Cost per block, and margin ratio are parameters that control how delegators receive rewards. +Cost per block and margin ratio are parameters that control how delegators receive rewards. The cost per block is an amount in coins to be subtracted from the total rewards in a block first, and handed to the staking pool. After subtracting the cost per block, a fraction equal to -margin ratio is taken from what is left, and given to the staking pool. Finally, what is left +the margin ratio is taken from what is left, and given to the staking pool. Finally, what is left is distributed among delegators, pro-rata, based on their delegation amounts. -The optional parameters `staker_address` and `vrf_public_key` specify the key that will sign new blocks -and the VRF key that will be used to produce POS hashes during staking. +The optional "staker address" and "vrf public key" specify, respectively, the key that will sign new blocks +and the VRF key that will be used to produce PoS hashes during staking. You only need to specify them if the wallet where the pool is being created differs from the one where the actual staking will be performed. In such a case, make sure that the specified keys are owned by the wallet that will be used to stake. On the other hand, if the current wallet will be used for staking, just leave them empty and the wallet will select appropriate values itself. -Note: staker_address must be a "public key" address and not a "public key hash" one. +Note: the staker address must be a "public key" address and not a "public key hash" one. Parameters: @@ -1319,9 +1334,11 @@ Returns: ### Method `staking_decommission_pool_request` Create a request to decommission a pool. This assumes that the decommission key is owned -by another wallet. The output of this command should be passed to account-sign-raw-transaction +by another wallet. + +The output of this command should be passed to account-sign-raw-transaction in the wallet that owns the decommission key. The result from signing, assuming success, can -then be broadcast to network to commence with decommissioning. +then be broadcast to network to commence the decommissioning. Parameters: @@ -1346,6 +1363,7 @@ hex string ### Method `delegation_create` Create a delegation to a given pool id and the owner address/destination. + The owner of a delegation is the key authorized to withdraw from the delegation. The delegation creation will result in creating a delegation id, where coins sent to that id will be staked by the pool id provided, automatically. The pool, to which the delegation is made, doesn't have the authority to spend the coins. @@ -1595,7 +1613,7 @@ Returns: ### Method `staking_pool_balance` -Print the balance of available staking pools +Obtain the balance of a staking pool Parameters: @@ -1612,7 +1630,7 @@ Returns: ### Method `delegation_list_ids` -List delegation ids controlled by the selected account in this wallet with their balances +List delegation ids controlled by the selected account in this wallet, with their balances Parameters: @@ -1653,7 +1671,7 @@ Returns: ### Method `token_nft_issue_new` -Issue a new non-fungible token (NFT) from scratch +Issue a new non-fungible token (NFT) Parameters: @@ -1719,9 +1737,10 @@ Returns: ### Method `token_issue_new` -Issue a new fungible token from scratch. -Notice that issuing a token fills an issuers supply. To have tokens that are spendable, -the issuer must "mint" tokens to take from the supply +Issue a new fungible token. + +Notice that issuing a token defines the token's total supply. To have tokens that are spendable, +the issuer must "mint" tokens, taking them from the total supply into the circulating supply. Parameters: @@ -1781,7 +1800,7 @@ Returns: ### Method `token_change_authority` -Change the authority of a token; i.e., the cryptographic authority that can do all authority token operations +Change the authority address of a token. Parameters: @@ -1863,7 +1882,7 @@ Returns: ### Method `token_mint` -Given a token that is already issued, mint new tokens and increase the total supply +Given a token that is already issued, mint new tokens and increase the circulating supply Parameters: @@ -1907,8 +1926,10 @@ Returns: ### Method `token_unmint` -Unmint existing tokens and reduce the total supply -Unminting reduces the total supply and puts the unminted tokens back at the issuer's control. +Unmint existing tokens and reduce the circulating supply. + +Unminting reduces the circulating supply and puts the unminted tokens back at the issuer's control. + The wallet must own the tokens that are being unminted. @@ -1994,10 +2015,11 @@ Returns: ### Method `token_freeze` -Freezing the token (by token authority) forbids any operation with all the tokens (except for the optional unfreeze). +Freeze the token, which forbids any operations with it (except for the optional unfreeze). After a token is frozen, no transfers, spends, or any other operation can be done. -This wallet (and selected account) must own the authority keys to be able to freeze. + +This wallet (and selected account) must own the authority key to be able to freeze. Parameters: @@ -2038,10 +2060,11 @@ Returns: ### Method `token_unfreeze` -By unfreezing the token all operations are available for the tokens again. +Unfreeze the token, making all operations available for it again. -Notice that this is only possible if the tokens were made to be unfreezable during freezing. -This wallet (and selected account) must own the authority keys to be able to unfreeze. +Notice that this is only possible if the token was specified to be unfreezable during freezing. + +This wallet (and selected account) must own the authority key to be able to unfreeze. Parameters: @@ -2081,7 +2104,9 @@ Returns: ### Method `token_send` -Send the given token amount to the given address. The wallet will automatically calculate the required information. +Send the given token amount to the given address. + +The wallet will automatically calculate the required fees. Parameters: @@ -2123,10 +2148,11 @@ Returns: } ``` -### Method `token_make_tx_for_sending_with_intent` +### Method `token_make_tx_to_send_with_intent` + +Create a transaction for sending tokens to the given address, together with the so-called "intent". -Create a transaction for sending tokens to the given address, without submitting it. -The wallet will automatically calculate the required information. +The wallet will automatically calculate the required fees. The "intent" is an arbitrary string that will be concatenated with the id of the created transaction and signed by all the keys that were used to sign the transaction itself; this can be used to declare @@ -2217,7 +2243,9 @@ Returns: ### Method `address_deposit_data` -Store data on the blockchain, the data is provided as hex encoded string. +Store data on the blockchain. + +The data is provided as a hex string. Note that there is a high fee for storing data on the blockchain. @@ -2259,7 +2287,8 @@ Returns: ### Method `create_htlc_transaction` Creates a transaction that locks a given number of coins or tokens in a Hashed Timelock Contract. -Created transaction is not broadcasted by this function. + +The created transaction is not broadcast by this function. Parameters: @@ -2322,7 +2351,8 @@ Returns: Create an order for exchanging "given" amount of an arbitrary currency (coins or tokens) for an arbitrary amount of "asked" currency. -Conclude key is the key that can authorize a conclude order command closing the order and withdrawing + +Conclude key is the key that can authorize a conclude order command, closing the order and withdrawing all the remaining funds from it. @@ -2396,9 +2426,11 @@ Returns: ### Method `conclude_order` -Conclude an order, given its id. This assumes that the conclude key is owned -by the selected account in this wallet. -Optionally output address can be provided where remaining funds from the order are transferred. +Conclude an order, given its id. + +This assumes that the conclude key is owned by the selected account in this wallet. + +Optionally, an output address can be provided where remaining funds from the order are transferred. Parameters: @@ -2441,8 +2473,9 @@ Returns: ### Method `fill_order` -Fill order completely or partially given its id and an amount that satisfy what an order can offer. -Optionally output address can be provided where the exchanged funds from the order are transferred. +Fill order completely or partially given its id and an amount in the order's "asked" currency. + +Optionally, an output address can be provided where the exchanged funds from the order are transferred. Parameters: @@ -2529,7 +2562,7 @@ Returns: ### Method `node_version` -Node version +Obtain the node version Parameters: @@ -2544,7 +2577,7 @@ Returns: ### Method `node_shutdown` -Node shutdown +Shutdown the node Parameters: @@ -2589,7 +2622,7 @@ nothing ### Method `node_disconnect_peer` -Disconnected a remote peer in the node +Disconnect a remote peer in the node Parameters: @@ -2604,7 +2637,7 @@ nothing ### Method `node_list_banned_peers` -List banned addresses/peers in the node +List banned peers in the node Parameters: @@ -2646,7 +2679,7 @@ nothing ### Method `node_unban_peer_address` -Unban address in the node +Unban an address in the node Parameters: @@ -2661,7 +2694,7 @@ nothing ### Method `node_list_discouraged_peers` -List discouraged addresses/peers in the node +List discouraged peers in the node Parameters: @@ -2682,7 +2715,7 @@ Returns: ### Method `node_undiscourage_peer_address` -Undiscourage address in the node +Undiscourage an address in the node Parameters: @@ -2697,7 +2730,7 @@ nothing ### Method `node_peer_count` -Get the number of connected peer in the node +Get the number of connected peers in the node Parameters: @@ -2712,7 +2745,7 @@ number ### Method `node_list_connected_peers` -Get connected peers in the node +List connected peers in the node Parameters: @@ -2749,7 +2782,7 @@ Returns: ### Method `node_list_reserved_peers` -Get reserved peers in the node +List reserved peers in the node Parameters: @@ -2779,7 +2812,7 @@ nothing ### Method `node_remove_reserved_peer` -Remove a reserved peer from the node +Remove a reserved peer in the node Parameters: @@ -2809,7 +2842,7 @@ nothing ### Method `node_chainstate_info` -Returns the current node's chainstate (block height information and more) +Returns the current node's chainstate information (block height and more) Parameters: @@ -2830,7 +2863,8 @@ Returns: ### Method `transaction_abandon` -Abandon an unconfirmed transaction in the wallet database, and make the consumed inputs available to be used again +Abandon an unconfirmed transaction in the wallet database, and make the consumed inputs available to be used again. + Note that this doesn't necessarily mean that the network will agree. This assumes the transaction is either still not confirmed in the network or somehow invalid. @@ -2865,7 +2899,7 @@ Returns: ### Method `transaction_list_by_address` -List mainchain transactions with optional address filter +List transactions owned by this account that have already been included in a block, with an optional address filter. Parameters: @@ -2944,9 +2978,11 @@ hex string ### Method `transaction_compose` -Compose a new transaction from the specified outputs and selected utxos -The transaction is returned in a hex encoded form that can be passed to account-sign-raw-transaction -and also prints the fees that will be paid by the transaction +Compose a new transaction from the specified outputs and selected utxos. + +The transaction is returned in a hex encoded form that can be passed to account-sign-raw-transaction. + +The fees that will be paid by the transaction are also returned. Parameters: @@ -2993,7 +3029,7 @@ Returns: ### Method `node_best_block_id` -Returns the current best block hash +Returns the current best block id Parameters: @@ -3023,7 +3059,7 @@ number ### Method `node_block_id` -Get the block ID of the block at a given height +Get the block id of the block at a given height Parameters: @@ -3041,7 +3077,9 @@ EITHER OF ### Method `node_generate_block` Generate a block with the given transactions to the specified -reward destination. If transactions are None, the block will be +reward destination. + +If no transactions are provided, the block will be generated with available transactions in the mempool @@ -3107,7 +3145,7 @@ Returns: ### Method `node_get_block` -Get a block by its hash, represented with hex encoded bytes +Get a block by its id, represented as hex encoded bytes Parameters: @@ -3124,7 +3162,7 @@ EITHER OF ### Method `node_get_block_ids_as_checkpoints` -Returns mainchain block ids with heights in the range start_height..end_height using +Return mainchain block ids with heights in the range start_height..end_height using the given step. @@ -3164,7 +3202,7 @@ nothing ### Method `version` -Print the version of the wallet software and possibly the git commit hash, if found WWW!! +Print the version of the wallet software and possibly the git commit hash, if found Parameters: @@ -3317,7 +3355,7 @@ EITHER OF ### Method `wallet_close` -Close the currently open wallet file +Close the currently opened wallet file Parameters: @@ -3332,7 +3370,7 @@ nothing ### Method `wallet_info` -Check the current wallet's number of accounts and their names +Obtain certain information about the wallet, such as the number of accounts and their names Parameters: @@ -3461,9 +3499,9 @@ EITHER OF Set the lookahead size for key generation. The lookahead size, also known as the gap limit, determines the number of addresses -to generate and monitor on the blockchain for incoming transactions, following the last -known address with a transaction. -Only reduce this value if you are certain there are no incoming transactions on these addresses. +to generate and monitor on the blockchain, following the last known address used in a transaction. + +Only reduce this value if you are certain there are no incoming transactions using these addresses. Parameters: @@ -3482,6 +3520,7 @@ nothing ### Method `address_show` Show receive-addresses with their usage state. + Note that whether an address is used isn't based on the wallet, but on the blockchain. So if an address is used in a transaction, it will be marked as used only when the transaction is included @@ -3514,7 +3553,7 @@ Returns: ### Method `standalone_address_show` -Show standalone added addresses with their labels. +Show added standalone addresses with their labels. Parameters: @@ -3549,7 +3588,7 @@ Returns: ### Method `standalone_address_details` -Show standalone addresses details. +Show standalone address details. Parameters: @@ -3611,6 +3650,7 @@ Returns: ### Method `address_reveal_public_key` Reveal the public key behind this address in hex encoding and address encoding. + Note that this isn't a normal address to be used in transactions. It's preferred to take the address from address-show command @@ -3634,8 +3674,10 @@ Returns: ### Method `staking_new_vrf_public_key` Issue a new staking VRF (Verifiable Random Function) key for this account. + VRF keys are used as a trustless mechanism to ensure the randomness of the staking process, where no one can control the possible outcomes, to ensure decentralization. + NOTE: Under normal circumstances you don't need to generate VRF keys manually. Creating a new staking pool will do it for you. This is available for specialized use-cases. @@ -3657,6 +3699,7 @@ Returns: ### Method `staking_show_legacy_vrf_key` Shows the legacy VRF key that uses an abandoned derivation mechanism. + This will not be used for new pools and should be avoided @@ -3673,6 +3716,7 @@ Returns: ### Method `staking_show_vrf_public_keys` Show the issued staking VRF (Verifiable Random Function) keys for this account. + These keys are generated when pools are created. VRF keys are used as a trustless mechanism to ensure the randomness of the staking process, where no one can control the possible outcomes, to ensure decentralization. @@ -3695,6 +3739,7 @@ Returns: ### Method `account_extended_public_key` Shows the account's extended public key. + The returned extended public key can be used to derive receiving or change addresses for this account. @@ -3714,10 +3759,12 @@ Returns: ### Method `account_sign_raw_transaction` -Signs the inputs that are not yet signed. -The input is a special format of the transaction serialized to hex. This format is automatically used in this wallet -in functions such as staking-decommission-pool-request. Once all signatures are complete, the result can be broadcast -to the network. +Signs transaction inputs that are not yet signed. + +The input is a hex encoded transaction or PartiallySignedTransaction. This format is +automatically used in this wallet in functions such as staking-decommission-pool-request. + +Once all signatures are complete, the result can be broadcast to the network. Parameters: @@ -3765,7 +3812,7 @@ Returns: ### Method `challenge_sign_plain` -Signs a challenge with a private key corresponding to the provided address destination. +Signs a challenge with a private key corresponding to the provided address. Parameters: @@ -3784,7 +3831,7 @@ hex string ### Method `challenge_sign_hex` -Signs a challenge with a private key corresponding to the provided address destination. +Signs a challenge with a private key corresponding to the provided address. Parameters: @@ -3803,7 +3850,7 @@ hex string ### Method `challenge_verify_plain` -Verifies a signed challenge against an address destination +Verifies a signed challenge against an address. Parameters: @@ -3822,7 +3869,7 @@ nothing ### Method `challenge_verify_hex` -Verifies a signed challenge against an address destination +Verifies a signed challenge against an address. Parameters: diff --git a/wallet/wallet-rpc-lib/Cargo.toml b/wallet/wallet-rpc-lib/Cargo.toml index b1b4f9cc41..5419863cff 100644 --- a/wallet/wallet-rpc-lib/Cargo.toml +++ b/wallet/wallet-rpc-lib/Cargo.toml @@ -29,6 +29,7 @@ p2p-types = { path = "../../p2p/types" } anyhow.workspace = true async-trait.workspace = true clap.workspace = true +derive_more.workspace = true enum-iterator.workspace = true futures.workspace = true hex.workspace = true diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index 6709da136c..ed3c356e25 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -59,6 +59,32 @@ trait WalletEventsRpc { async fn subscribe_wallet_events(&self) -> rpc::subscription::Reply; } +// IMPORTANT: the documentation for the RPC functions below may be re-used as the description for +// the corresponding wallet-cli commands, in the case they don't have a doc comment of their own +// (see the `get_repl_command` function in `wallet-cli-commands`). +// +// So, for any particular RPC function: +// 1) Make sure that either the corresponding wallet-cli command has its own doc comment OR +// that the RPC function's doc comment is generic enough, e.g.: +// a) It doesn't mention the exact parameter names. +// b) It doesn't reference other functions; this is because RPC function names use underscores +// as separators and wallet-cli ones use dashes, so the names are not identical. +// Note however, that we currently do sometimes mention other RPC functions, but using +// "dashed" names, which look fine in the wallet-cli documentation, but ugly-yet-still-understandable +// in the RPC's (which we find acceptable). +// Also, keep an eye on the functions that accept an account number - wallet-cli has the notion +// of a pre-selected "current account", so wallet-cli commands won't have such a parameter. +// Currently, we refer to this parameter as "this account" or "the selected account", which sounds +// somewhat off in the RPC documentation, but is still considered acceptable. +// +// 2) If you go with the re-use, run `wallet-cli` and visually check that the re-used documentation +// makes sense. One possible issue to look for is tautology - the "inherited" description may +// be explaining the parameters (e.g. in some generic way, without mentioning the exact names), +// but the wallet-cli command may have doc strings on its parameters, explaining them again. +// +// 3) In general, the visual quality of the wallet-cli documentation is more important than +// the RPC documentation's, because more people will see the former. + /// RPC methods available in the cold wallet mode. #[rpc::describe] #[rpc::rpc(server, client)] @@ -66,7 +92,7 @@ trait ColdWalletRpc { #[method(name = "shutdown")] async fn shutdown(&self) -> rpc::RpcResult<()>; - /// Print the version of the wallet software and possibly the git commit hash, if found WWW!! + /// Print the version of the wallet software and possibly the git commit hash, if found #[method(name = "version")] async fn version(&self) -> rpc::RpcResult; @@ -102,11 +128,11 @@ trait ColdWalletRpc { hardware_wallet: Option, ) -> rpc::RpcResult; - /// Close the currently open wallet file + /// Close the currently opened wallet file #[method(name = "wallet_close")] async fn close_wallet(&self) -> rpc::RpcResult<()>; - /// Check the current wallet's number of accounts and their names + /// Obtain certain information about the wallet, such as the number of accounts and their names #[method(name = "wallet_info")] async fn wallet_info(&self) -> rpc::RpcResult; @@ -138,9 +164,9 @@ trait ColdWalletRpc { /// Set the lookahead size for key generation. /// /// The lookahead size, also known as the gap limit, determines the number of addresses - /// to generate and monitor on the blockchain for incoming transactions, following the last - /// known address with a transaction. - /// Only reduce this value if you are certain there are no incoming transactions on these addresses. + /// to generate and monitor on the blockchain, following the last known address used in a transaction. + /// + /// Only reduce this value if you are certain there are no incoming transactions using these addresses. #[method(name = "wallet_set_lookahead_size")] async fn set_lookahead_size( &self, @@ -149,6 +175,7 @@ trait ColdWalletRpc { ) -> rpc::RpcResult<()>; /// Show receive-addresses with their usage state. + /// /// Note that whether an address is used isn't based on the wallet, /// but on the blockchain. So if an address is used in a transaction, /// it will be marked as used only when the transaction is included @@ -160,14 +187,14 @@ trait ColdWalletRpc { include_change_addresses: bool, ) -> rpc::RpcResult>; - /// Show standalone added addresses with their labels. + /// Show added standalone addresses with their labels. #[method(name = "standalone_address_show")] async fn get_standalone_addresses( &self, account: AccountArg, ) -> rpc::RpcResult; - /// Show standalone addresses details. + /// Show standalone address details. #[method(name = "standalone_address_details")] async fn get_standalone_address_details( &self, @@ -180,6 +207,7 @@ trait ColdWalletRpc { async fn issue_address(&self, account: AccountArg) -> rpc::RpcResult; /// Reveal the public key behind this address in hex encoding and address encoding. + /// /// Note that this isn't a normal address to be used in transactions. /// It's preferred to take the address from address-show command #[method(name = "address_reveal_public_key")] @@ -190,14 +218,17 @@ trait ColdWalletRpc { ) -> rpc::RpcResult; /// Issue a new staking VRF (Verifiable Random Function) key for this account. + /// /// VRF keys are used as a trustless mechanism to ensure the randomness of the staking process, /// where no one can control the possible outcomes, to ensure decentralization. + /// /// NOTE: Under normal circumstances you don't need to generate VRF keys manually. /// Creating a new staking pool will do it for you. This is available for specialized use-cases. #[method(name = "staking_new_vrf_public_key")] async fn new_vrf_public_key(&self, account: AccountArg) -> rpc::RpcResult; /// Shows the legacy VRF key that uses an abandoned derivation mechanism. + /// /// This will not be used for new pools and should be avoided #[method(name = "staking_show_legacy_vrf_key")] async fn get_legacy_vrf_public_key( @@ -206,6 +237,7 @@ trait ColdWalletRpc { ) -> rpc::RpcResult; /// Show the issued staking VRF (Verifiable Random Function) keys for this account. + /// /// These keys are generated when pools are created. /// VRF keys are used as a trustless mechanism to ensure the randomness of the staking process, /// where no one can control the possible outcomes, to ensure decentralization. @@ -216,6 +248,7 @@ trait ColdWalletRpc { ) -> rpc::RpcResult>; /// Shows the account's extended public key. + /// /// The returned extended public key can be used to derive receiving or change addresses for /// this account. #[method(name = "account_extended_public_key")] @@ -224,11 +257,13 @@ trait ColdWalletRpc { account_arg: AccountArg, ) -> rpc::RpcResult; + /// Signs transaction inputs that are not yet signed. + /// + /// The input is a hex encoded transaction or PartiallySignedTransaction. This format is + /// automatically used in this wallet in functions such as staking-decommission-pool-request. + /// + /// Once all signatures are complete, the result can be broadcast to the network. #[method(name = "account_sign_raw_transaction")] - /// Signs the inputs that are not yet signed. - /// The input is a special format of the transaction serialized to hex. This format is automatically used in this wallet - /// in functions such as staking-decommission-pool-request. Once all signatures are complete, the result can be broadcast - /// to the network. async fn sign_raw_transaction( &self, account: AccountArg, @@ -236,8 +271,8 @@ trait ColdWalletRpc { options: TransactionRequestOptions, ) -> rpc::RpcResult; + /// Signs a challenge with a private key corresponding to the provided address. #[method(name = "challenge_sign_plain")] - /// Signs a challenge with a private key corresponding to the provided address destination. async fn sign_challenge( &self, account: AccountArg, @@ -245,8 +280,8 @@ trait ColdWalletRpc { address: RpcAddress, ) -> rpc::RpcResult; + /// Signs a challenge with a private key corresponding to the provided address. #[method(name = "challenge_sign_hex")] - /// Signs a challenge with a private key corresponding to the provided address destination. async fn sign_challenge_hex( &self, account: AccountArg, @@ -254,8 +289,8 @@ trait ColdWalletRpc { address: RpcAddress, ) -> rpc::RpcResult; + /// Verifies a signed challenge against an address. #[method(name = "challenge_verify_plain")] - /// Verifies a signed challenge against an address destination async fn verify_challenge( &self, message: String, @@ -263,8 +298,8 @@ trait ColdWalletRpc { address: RpcAddress, ) -> rpc::RpcResult<()>; + /// Verifies a signed challenge against an address. #[method(name = "challenge_verify_hex")] - /// Verifies a signed challenge against an address destination async fn verify_challenge_hex( &self, message: RpcHexString, @@ -285,10 +320,12 @@ trait WalletRpc { #[method(name = "wallet_rescan")] async fn rescan(&self) -> rpc::RpcResult<()>; + /// Returns information about the current best block #[method(name = "wallet_best_block")] async fn best_block(&self) -> rpc::RpcResult; /// Creates a new account with an optional name. + /// /// Returns an error if the last created account does not have a transaction history. #[method(name = "account_create")] async fn create_account(&self, name: Option) -> rpc::RpcResult; @@ -303,6 +340,7 @@ trait WalletRpc { ) -> rpc::RpcResult; /// Add, rename or delete a label to an already added standalone address. + /// /// Specifying a label will add or replace the existing one, /// and not specifying a label will remove the existing one. #[method(name = "standalone_address_label_rename")] @@ -313,7 +351,7 @@ trait WalletRpc { label: Option, ) -> rpc::RpcResult<()>; - /// Add a new standalone watch only address not derived from the selected account's key chain + /// Add a new standalone watch-only address not derived from the selected account's key chain #[method(name = "standalone_add_watch_only_address")] async fn add_standalone_address( &self, @@ -323,7 +361,7 @@ trait WalletRpc { no_rescan: Option, ) -> rpc::RpcResult<()>; - /// Add a new standalone private key not derived from the selected account's key chain to be watched + /// Add a new standalone private key not derived from the selected account's key chain #[method(name = "standalone_add_private_key_from_hex")] async fn add_standalone_private_key( &self, @@ -333,7 +371,8 @@ trait WalletRpc { no_rescan: Option, ) -> rpc::RpcResult<()>; - /// Add a new standalone multi signature address + /// Add a new standalone multi-signature address. + /// /// Use the `transaction_compose` command to use the new multisig address as input or output #[method(name = "standalone_add_multisig")] async fn add_standalone_multisig( @@ -345,7 +384,7 @@ trait WalletRpc { no_rescan: Option, ) -> rpc::RpcResult; - /// Lists all the utxos owned by a multisig watched by this account + /// Lists all the utxos owned by multisig addresses watched by this account #[method(name = "standalone_multisig_utxos")] async fn get_multisig_utxos( &self, @@ -377,8 +416,9 @@ trait WalletRpc { options: TxOptionsOverrides, ) -> rpc::RpcResult; - /// Send a given coin amount to a given address. The wallet will automatically calculate the required information - /// Optionally, one can also mention the utxos to be used. + /// Send a given coin amount to a given address. The wallet will automatically calculate the required fees. + /// + /// Optionally, you can also mention the utxos to be used. #[method(name = "address_send")] async fn send_coins( &self, @@ -395,7 +435,7 @@ trait WalletRpc { /// `from_addresses` needs to be empty and `all` set to true. /// /// Spendable coins are any coins that are not locked, and tokens that are not frozen or locked. - /// The wallet will automatically calculate the required fees + /// The wallet will automatically calculate the required fees. #[method(name = "address_sweep_spendable")] async fn sweep_addresses( &self, @@ -407,7 +447,7 @@ trait WalletRpc { ) -> rpc::RpcResult; /// Sweep all the coins from a delegation to a given address. - /// The wallet will automatically calculate the required fees + /// The wallet will automatically calculate the required fees. #[method(name = "staking_sweep_delegation")] async fn sweep_delegation( &self, @@ -419,6 +459,7 @@ trait WalletRpc { /// Creates a transaction that spends from a specific address, /// and returns the change to the same address (unless one is specified), without signature. + /// /// This transaction is used for "withdrawing" small amounts from a cold storage /// without changing the ownership address. Once this is created, /// it can be signed using account-sign-raw-transaction in the cold wallet @@ -436,7 +477,14 @@ trait WalletRpc { options: TransactionRequestOptions, ) -> rpc::RpcResult; - /// Print the summary of the transaction + /// Print the summary of a transaction. + /// + /// Note that currently this will only work for transactions whose inputs have not been spent + /// yet (i.e. it won't work if the transaction has already been included in a block). + /// Also, it doesn't support certain input types (such as account-based inputs). + /// + /// The main purpose of this command is to be able to inspect the result of transaction-compose + /// and account-sign-raw-transaction before sending it to the network. #[method(name = "transaction_inspect")] async fn transaction_inspect( &self, @@ -444,24 +492,24 @@ trait WalletRpc { ) -> rpc::RpcResult; /// Create a staking pool. The pool will be capable of creating blocks and gaining rewards, - /// and will be capable of taking delegations from other users and staking. + /// as well as taking delegations from other users. /// /// The decommission key is the key that can decommission the pool. /// - /// Cost per block, and margin ratio are parameters that control how delegators receive rewards. + /// Cost per block and margin ratio are parameters that control how delegators receive rewards. /// The cost per block is an amount in coins to be subtracted from the total rewards in a block first, /// and handed to the staking pool. After subtracting the cost per block, a fraction equal to - /// margin ratio is taken from what is left, and given to the staking pool. Finally, what is left + /// the margin ratio is taken from what is left, and given to the staking pool. Finally, what is left /// is distributed among delegators, pro-rata, based on their delegation amounts. /// - /// The optional parameters `staker_address` and `vrf_public_key` specify the key that will sign new blocks - /// and the VRF key that will be used to produce POS hashes during staking. + /// The optional "staker address" and "vrf public key" specify, respectively, the key that will sign new blocks + /// and the VRF key that will be used to produce PoS hashes during staking. /// You only need to specify them if the wallet where the pool is being created differs from /// the one where the actual staking will be performed. /// In such a case, make sure that the specified keys are owned by the wallet that will be used to stake. /// On the other hand, if the current wallet will be used for staking, just leave them empty /// and the wallet will select appropriate values itself. - /// Note: staker_address must be a "public key" address and not a "public key hash" one. + /// Note: the staker address must be a "public key" address and not a "public key hash" one. #[method(name = "staking_create_pool")] async fn create_stake_pool( &self, @@ -487,9 +535,11 @@ trait WalletRpc { ) -> rpc::RpcResult; /// Create a request to decommission a pool. This assumes that the decommission key is owned - /// by another wallet. The output of this command should be passed to account-sign-raw-transaction + /// by another wallet. + /// + /// The output of this command should be passed to account-sign-raw-transaction /// in the wallet that owns the decommission key. The result from signing, assuming success, can - /// then be broadcast to network to commence with decommissioning. + /// then be broadcast to network to commence the decommissioning. #[method(name = "staking_decommission_pool_request")] async fn decommission_stake_pool_request( &self, @@ -500,6 +550,7 @@ trait WalletRpc { ) -> rpc::RpcResult>; /// Create a delegation to a given pool id and the owner address/destination. + /// /// The owner of a delegation is the key authorized to withdraw from the delegation. /// The delegation creation will result in creating a delegation id, where coins sent to that id will be staked by the pool id provided, automatically. /// The pool, to which the delegation is made, doesn't have the authority to spend the coins. @@ -557,14 +608,14 @@ trait WalletRpc { account: AccountArg, ) -> rpc::RpcResult>; - /// Print the balance of available staking pools + /// Obtain the balance of a staking pool #[method(name = "staking_pool_balance")] async fn stake_pool_balance( &self, pool_id: RpcAddress, ) -> rpc::RpcResult; - /// List delegation ids controlled by the selected account in this wallet with their balances + /// List delegation ids controlled by the selected account in this wallet, with their balances #[method(name = "delegation_list_ids")] async fn list_delegation_ids(&self, account: AccountArg) -> rpc::RpcResult>; @@ -576,7 +627,7 @@ trait WalletRpc { account: AccountArg, ) -> rpc::RpcResult>; - /// Issue a new non-fungible token (NFT) from scratch + /// Issue a new non-fungible token (NFT) #[method(name = "token_nft_issue_new")] async fn issue_new_nft( &self, @@ -586,9 +637,10 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Issue a new fungible token from scratch. - /// Notice that issuing a token fills an issuers supply. To have tokens that are spendable, - /// the issuer must "mint" tokens to take from the supply + /// Issue a new fungible token. + /// + /// Notice that issuing a token defines the token's total supply. To have tokens that are spendable, + /// the issuer must "mint" tokens, taking them from the total supply into the circulating supply. #[method(name = "token_issue_new")] async fn issue_new_token( &self, @@ -598,7 +650,7 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Change the authority of a token; i.e., the cryptographic authority that can do all authority token operations + /// Change the authority address of a token. #[method(name = "token_change_authority")] async fn change_token_authority( &self, @@ -618,7 +670,7 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Given a token that is already issued, mint new tokens and increase the total supply + /// Given a token that is already issued, mint new tokens and increase the circulating supply #[method(name = "token_mint")] async fn mint_tokens( &self, @@ -629,8 +681,10 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Unmint existing tokens and reduce the total supply - /// Unminting reduces the total supply and puts the unminted tokens back at the issuer's control. + /// Unmint existing tokens and reduce the circulating supply. + /// + /// Unminting reduces the circulating supply and puts the unminted tokens back at the issuer's control. + /// /// The wallet must own the tokens that are being unminted. #[method(name = "token_unmint")] async fn unmint_tokens( @@ -652,10 +706,11 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Freezing the token (by token authority) forbids any operation with all the tokens (except for the optional unfreeze). + /// Freeze the token, which forbids any operations with it (except for the optional unfreeze). /// /// After a token is frozen, no transfers, spends, or any other operation can be done. - /// This wallet (and selected account) must own the authority keys to be able to freeze. + /// + /// This wallet (and selected account) must own the authority key to be able to freeze. #[method(name = "token_freeze")] async fn freeze_token( &self, @@ -665,10 +720,11 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// By unfreezing the token all operations are available for the tokens again. + /// Unfreeze the token, making all operations available for it again. + /// + /// Notice that this is only possible if the token was specified to be unfreezable during freezing. /// - /// Notice that this is only possible if the tokens were made to be unfreezable during freezing. - /// This wallet (and selected account) must own the authority keys to be able to unfreeze. + /// This wallet (and selected account) must own the authority key to be able to unfreeze. #[method(name = "token_unfreeze")] async fn unfreeze_token( &self, @@ -677,7 +733,9 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Send the given token amount to the given address. The wallet will automatically calculate the required information. + /// Send the given token amount to the given address. + /// + /// The wallet will automatically calculate the required fees. #[method(name = "token_send")] async fn send_tokens( &self, @@ -688,8 +746,9 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Create a transaction for sending tokens to the given address, without submitting it. - /// The wallet will automatically calculate the required information. + /// Create a transaction for sending tokens to the given address, together with the so-called "intent". + /// + /// The wallet will automatically calculate the required fees. /// /// The "intent" is an arbitrary string that will be concatenated with the id of the created transaction /// and signed by all the keys that were used to sign the transaction itself; this can be used to declare @@ -698,7 +757,7 @@ trait WalletRpc { /// by the bridge and provide the bridge with the destination address on the foreign chain where you want /// to receive them. In this case you will set "intent" to this foreign destination address; the signed intent /// will then serve as a proof to the bridge that the provided destination address is what it's meant to be. - #[method(name = "token_make_tx_for_sending_with_intent")] + #[method(name = "token_make_tx_to_send_with_intent")] async fn make_tx_for_sending_tokens_with_intent( &self, account: AccountArg, @@ -728,7 +787,9 @@ trait WalletRpc { options: TransactionRequestOptions, ) -> rpc::RpcResult; - /// Store data on the blockchain, the data is provided as hex encoded string. + /// Store data on the blockchain. + /// + /// The data is provided as a hex string. /// Note that there is a high fee for storing data on the blockchain. #[method(name = "address_deposit_data")] async fn deposit_data( @@ -739,7 +800,8 @@ trait WalletRpc { ) -> rpc::RpcResult; /// Creates a transaction that locks a given number of coins or tokens in a Hashed Timelock Contract. - /// Created transaction is not broadcasted by this function. + /// + /// The created transaction is not broadcast by this function. #[method(name = "create_htlc_transaction")] async fn create_htlc_transaction( &self, @@ -752,7 +814,8 @@ trait WalletRpc { /// Create an order for exchanging "given" amount of an arbitrary currency (coins or tokens) for /// an arbitrary amount of "asked" currency. - /// Conclude key is the key that can authorize a conclude order command closing the order and withdrawing + /// + /// Conclude key is the key that can authorize a conclude order command, closing the order and withdrawing /// all the remaining funds from it. #[method(name = "create_order")] async fn create_order( @@ -764,9 +827,11 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Conclude an order, given its id. This assumes that the conclude key is owned - /// by the selected account in this wallet. - /// Optionally output address can be provided where remaining funds from the order are transferred. + /// Conclude an order, given its id. + /// + /// This assumes that the conclude key is owned by the selected account in this wallet. + /// + /// Optionally, an output address can be provided where remaining funds from the order are transferred. #[method(name = "conclude_order")] async fn conclude_order( &self, @@ -776,8 +841,9 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Fill order completely or partially given its id and an amount that satisfy what an order can offer. - /// Optionally output address can be provided where the exchanged funds from the order are transferred. + /// Fill order completely or partially given its id and an amount in the order's "asked" currency. + /// + /// Optionally, an output address can be provided where the exchanged funds from the order are transferred. #[method(name = "fill_order")] async fn fill_order( &self, @@ -798,11 +864,11 @@ trait WalletRpc { options: TransactionOptions, ) -> rpc::RpcResult; - /// Node version + /// Obtain the node version #[method(name = "node_version")] async fn node_version(&self) -> rpc::RpcResult; - /// Node shutdown + /// Shutdown the node #[method(name = "node_shutdown")] async fn node_shutdown(&self) -> rpc::RpcResult<()>; @@ -814,11 +880,11 @@ trait WalletRpc { #[method(name = "node_connect_to_peer")] async fn connect_to_peer(&self, address: String) -> rpc::RpcResult<()>; - /// Disconnected a remote peer in the node + /// Disconnect a remote peer in the node #[method(name = "node_disconnect_peer")] async fn disconnect_peer(&self, peer_id: u64) -> rpc::RpcResult<()>; - /// List banned addresses/peers in the node + /// List banned peers in the node #[method(name = "node_list_banned_peers")] async fn list_banned( &self, @@ -832,29 +898,29 @@ trait WalletRpc { duration: std::time::Duration, ) -> rpc::RpcResult<()>; - /// Unban address in the node + /// Unban an address in the node #[method(name = "node_unban_peer_address")] async fn unban_address(&self, address: BannableAddress) -> rpc::RpcResult<()>; - /// List discouraged addresses/peers in the node + /// List discouraged peers in the node #[method(name = "node_list_discouraged_peers")] async fn list_discouraged( &self, ) -> rpc::RpcResult>; - /// Undiscourage address in the node + /// Undiscourage an address in the node #[method(name = "node_undiscourage_peer_address")] async fn undiscourage_address(&self, address: BannableAddress) -> rpc::RpcResult<()>; - /// Get the number of connected peer in the node + /// Get the number of connected peers in the node #[method(name = "node_peer_count")] async fn peer_count(&self) -> rpc::RpcResult; - /// Get connected peers in the node + /// List connected peers in the node #[method(name = "node_list_connected_peers")] async fn connected_peers(&self) -> rpc::RpcResult>; - /// Get reserved peers in the node + /// List reserved peers in the node #[method(name = "node_list_reserved_peers")] async fn reserved_peers(&self) -> rpc::RpcResult>; @@ -862,7 +928,7 @@ trait WalletRpc { #[method(name = "node_add_reserved_peer")] async fn add_reserved_peer(&self, address: String) -> rpc::RpcResult<()>; - /// Remove a reserved peer from the node + /// Remove a reserved peer in the node #[method(name = "node_remove_reserved_peer")] async fn remove_reserved_peer(&self, address: String) -> rpc::RpcResult<()>; @@ -870,11 +936,12 @@ trait WalletRpc { #[method(name = "node_submit_block")] async fn submit_block(&self, block: HexEncoded) -> rpc::RpcResult<()>; - /// Returns the current node's chainstate (block height information and more) + /// Returns the current node's chainstate information (block height and more) #[method(name = "node_chainstate_info")] async fn chainstate_info(&self) -> rpc::RpcResult; - /// Abandon an unconfirmed transaction in the wallet database, and make the consumed inputs available to be used again + /// Abandon an unconfirmed transaction in the wallet database, and make the consumed inputs available to be used again. + /// /// Note that this doesn't necessarily mean that the network will agree. This assumes the transaction is either still /// not confirmed in the network or somehow invalid. #[method(name = "transaction_abandon")] @@ -891,7 +958,7 @@ trait WalletRpc { account: AccountArg, ) -> rpc::RpcResult>>; - /// List mainchain transactions with optional address filter + /// List transactions owned by this account that have already been included in a block, with an optional address filter. #[method(name = "transaction_list_by_address")] async fn list_transactions_by_address( &self, @@ -924,9 +991,11 @@ trait WalletRpc { transaction_id: Id, ) -> rpc::RpcResult>; - /// Compose a new transaction from the specified outputs and selected utxos - /// The transaction is returned in a hex encoded form that can be passed to account-sign-raw-transaction - /// and also prints the fees that will be paid by the transaction + /// Compose a new transaction from the specified outputs and selected utxos. + /// + /// The transaction is returned in a hex encoded form that can be passed to account-sign-raw-transaction. + /// + /// The fees that will be paid by the transaction are also returned. #[method(name = "transaction_compose")] async fn compose_transaction( &self, @@ -936,7 +1005,7 @@ trait WalletRpc { only_transaction: bool, ) -> rpc::RpcResult; - /// Returns the current best block hash + /// Returns the current best block id #[method(name = "node_best_block_id")] async fn node_best_block_id(&self) -> rpc::RpcResult>; @@ -944,7 +1013,7 @@ trait WalletRpc { #[method(name = "node_best_block_height")] async fn node_best_block_height(&self) -> rpc::RpcResult; - /// Get the block ID of the block at a given height + /// Get the block id of the block at a given height #[method(name = "node_block_id")] async fn node_block_id( &self, @@ -952,7 +1021,9 @@ trait WalletRpc { ) -> rpc::RpcResult>>; /// Generate a block with the given transactions to the specified - /// reward destination. If transactions are None, the block will be + /// reward destination. + /// + /// If no transactions are provided, the block will be /// generated with available transactions in the mempool #[method(name = "node_generate_block")] async fn node_generate_block( @@ -988,11 +1059,11 @@ trait WalletRpc { check_all_timestamps_between_blocks: bool, ) -> rpc::RpcResult>>; - /// Get a block by its hash, represented with hex encoded bytes + /// Get a block by its id, represented as hex encoded bytes #[method(name = "node_get_block")] async fn node_block(&self, block_id: Id) -> rpc::RpcResult>>; - /// Returns mainchain block ids with heights in the range start_height..end_height using + /// Return mainchain block ids with heights in the range start_height..end_height using /// the given step. #[method(name = "node_get_block_ids_as_checkpoints")] async fn node_get_block_ids_as_checkpoints( diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index 23cba42a45..6115720992 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -40,7 +40,7 @@ use wallet_controller::{ }; use wallet_types::{ partially_signed_transaction::PartiallySignedTransaction, scan_blockchain::ScanBlockchain, - signature_status::SignatureStatus, with_locked::WithLocked, + signature_status::SignatureStatus, with_locked::WithLocked, ImportOrCreate, }; use crate::{ @@ -103,6 +103,7 @@ where store_seed_phrase, mnemonic, passphrase, + ImportOrCreate::Create, )?; let options = WalletCreationOptions { @@ -129,6 +130,7 @@ where store_seed_phrase, mnemonic, passphrase, + ImportOrCreate::Import, )?; let options = WalletCreationOptions { diff --git a/wallet/wallet-rpc-lib/src/rpc/types.rs b/wallet/wallet-rpc-lib/src/rpc/types.rs index a4b92ea720..14003c39ac 100644 --- a/wallet/wallet-rpc-lib/src/rpc/types.rs +++ b/wallet/wallet-rpc-lib/src/rpc/types.rs @@ -61,12 +61,12 @@ use wallet_controller::{types::WalletTypeArgs, UtxoState, UtxoType}; pub use wallet_controller::{ControllerConfig, NodeInterface}; use wallet_types::{ partially_signed_transaction::PartiallySignedTransaction, seed_phrase::StoreSeedPhrase, - signature_status::SignatureStatus, KeyPurpose, + signature_status::SignatureStatus, ImportOrCreate, KeyPurpose, }; use crate::service::SubmitError; -#[derive(Debug, thiserror::Error)] +#[derive(derive_more::Debug, thiserror::Error)] pub enum RpcError { #[error("Account index out of supported range")] AcctIndexOutOfRange, @@ -166,6 +166,9 @@ pub enum RpcError { #[error("Either set `all` to sweep all addresses, or provide specific addresses — not both")] InvalidSweepParameters, + + #[error("Wallet recovery requires mnemonic to be specified")] + WalletRecoveryWithoutMnemonic, } impl From> for rpc::Error { @@ -1053,6 +1056,7 @@ impl HardwareWalletType { store_seed_phrase: bool, mnemonic: Option, passphrase: Option, + import_or_create: ImportOrCreate, ) -> Result> { let store_seed_phrase = if store_seed_phrase { StoreSeedPhrase::Store @@ -1061,11 +1065,20 @@ impl HardwareWalletType { }; match hardware_wallet { - None => Ok(WalletTypeArgs::Software { - mnemonic, - passphrase, - store_seed_phrase, - }), + None => { + match import_or_create { + ImportOrCreate::Import => { + ensure!(mnemonic.is_some(), RpcError::WalletRecoveryWithoutMnemonic); + } + ImportOrCreate::Create => {} + }; + + Ok(WalletTypeArgs::Software { + mnemonic, + passphrase, + store_seed_phrase, + }) + } Some(hw_type) => { ensure!( mnemonic.is_none() From 3eef8741e3cef953aa656b11729b3abe2c920e1e Mon Sep 17 00:00:00 2001 From: Mykhailo Kremniov Date: Sun, 17 Aug 2025 16:54:02 +0300 Subject: [PATCH 2/2] Apply review comments --- wallet/wallet-cli-commands/src/lib.rs | 44 +++++++++++++++++----- wallet/wallet-rpc-daemon/docs/RPC.md | 8 ++-- wallet/wallet-rpc-lib/src/rpc/interface.rs | 8 ++-- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/wallet/wallet-cli-commands/src/lib.rs b/wallet/wallet-cli-commands/src/lib.rs index 78d5726c29..d595688890 100644 --- a/wallet/wallet-cli-commands/src/lib.rs +++ b/wallet/wallet-cli-commands/src/lib.rs @@ -55,15 +55,18 @@ pub enum CreateWalletSubCommand { /// File path of the wallet file wallet_path: PathBuf, - /// Specifies whether the seed-phrase should be stored in the wallet file or + /// Specifies whether the seed phrase should be stored in the wallet file or /// only printed on the screen. /// - /// Not storing the seed-phrase can be seen as a security measure - /// to ensure sufficient secrecy in case that seed-phrase is reused + /// Not storing the seed phrase can be seen as a security measure + /// to ensure sufficient secrecy in case that the seed phrase is reused /// elsewhere if this wallet is compromised. + /// + /// Note: if you decide to store the seed phrase, consider encrypting the wallet with + /// the wallet-encrypt-private-keys command, which will also encrypt the seed phrase. whether_to_store_seed_phrase: CliStoreSeedPhrase, - /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). + /// Mnemonic (seed) phrase (12, 15, or 24 words as a single quoted argument). /// /// If not specified, a new mnemonic phrase will be generated and printed. mnemonic: Option, @@ -129,15 +132,18 @@ pub enum RecoverWalletSubCommand { /// File path of the wallet file wallet_path: PathBuf, - /// Specifies whether the seed-phrase should be stored in the wallet file or + /// Specifies whether the seed phrase should be stored in the wallet file or /// only printed on the screen. /// - /// Not storing the seed-phrase can be seen as a security measure - /// to ensure sufficient secrecy in case that seed-phrase is reused + /// Not storing the seed phrase can be seen as a security measure + /// to ensure sufficient secrecy in case that the seed phrase is reused /// elsewhere if this wallet is compromised. + /// + /// Note: if you decide to store the seed phrase, consider encrypting the wallet with + /// the wallet-encrypt-private-keys command, which will also encrypt the seed phrase. whether_to_store_seed_phrase: CliStoreSeedPhrase, - /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). + /// Mnemonic (seed) phrase (12, 15, or 24 words as a single quoted argument). mnemonic: String, /// Passphrase along the mnemonic @@ -226,18 +232,32 @@ pub enum OpenWalletSubCommand { #[derive(Debug, Parser)] #[clap(rename_all = "kebab-case")] pub enum WalletManagementCommand { + /// Create a new wallet. This will create a new file without scanning the blockchain. + /// + /// Use this command if the seed phrase is brand new and has no associated transactions. + /// + /// If, on the other hand, the seed phrase has been used in the past and may have + /// associated transactions, use wallet-recover instead. #[clap(name = "wallet-create")] CreateWallet { #[command(subcommand)] wallet: CreateWalletSubCommand, }, + /// Recover a wallet. This will create a new wallet file and scan the blockchain for associated + /// transactions. + /// + /// Use this command if the seed phrase has been used in the past. + /// + /// If, on the other hand, the seed phrase is brand new, consider using wallet-create, + /// which will save you some time (as scanning the entire blockchain is a lengthy process). #[clap(name = "wallet-recover")] RecoverWallet { #[command(subcommand)] wallet: RecoverWalletSubCommand, }, + /// Open an exiting wallet file. #[clap(name = "wallet-open")] OpenWallet { #[command(subcommand)] @@ -997,8 +1017,12 @@ pub enum WalletCommand { /// This specifies that instead of a (hex encoded) PartiallySignedTransaction /// the result should be a (hex encoded) "simple" transaction. /// - /// Note that both variants are accepted by account-sign-raw-transaction, - /// so the presence of this option doesn't matter much. + /// Producing a "simple" transaction will result in a shorter hex string, but you won't + /// be able to use it with account-sign-raw-transaction in the cold wallet mode, which + /// relies on some additional information contained inside PartiallySignedTransaction. + /// + /// In general, there is no reason in specifying this option unless you care about the size + /// of the resulting hex string. #[arg(long = "only-transaction", default_value_t = false)] only_transaction: bool, }, diff --git a/wallet/wallet-rpc-daemon/docs/RPC.md b/wallet/wallet-rpc-daemon/docs/RPC.md index c953091c84..9dcb80a1d0 100644 --- a/wallet/wallet-rpc-daemon/docs/RPC.md +++ b/wallet/wallet-rpc-daemon/docs/RPC.md @@ -3217,7 +3217,7 @@ string ### Method `wallet_create` -Create a new wallet, this will skip scanning the blockchain +Create a new wallet. This will create a new file without scanning the blockchain. Parameters: @@ -3265,7 +3265,9 @@ Returns: ### Method `wallet_recover` -Recover new wallet, this will rescan the blockchain upon creation +Recover a wallet. This will create a new wallet file and scan the blockchain for associated transactions. + +Note: mnemonic must be specified when recovering a software wallet. Parameters: @@ -3313,7 +3315,7 @@ Returns: ### Method `wallet_open` -Open an exiting wallet by specifying the file location of the wallet file +Open an exiting wallet file. Parameters: diff --git a/wallet/wallet-rpc-lib/src/rpc/interface.rs b/wallet/wallet-rpc-lib/src/rpc/interface.rs index ed3c356e25..57425082ba 100644 --- a/wallet/wallet-rpc-lib/src/rpc/interface.rs +++ b/wallet/wallet-rpc-lib/src/rpc/interface.rs @@ -96,7 +96,7 @@ trait ColdWalletRpc { #[method(name = "version")] async fn version(&self) -> rpc::RpcResult; - /// Create a new wallet, this will skip scanning the blockchain + /// Create a new wallet. This will create a new file without scanning the blockchain. #[method(name = "wallet_create")] async fn create_wallet( &self, @@ -107,7 +107,9 @@ trait ColdWalletRpc { hardware_wallet: Option, ) -> rpc::RpcResult; - /// Recover new wallet, this will rescan the blockchain upon creation + /// Recover a wallet. This will create a new wallet file and scan the blockchain for associated transactions. + /// + /// Note: mnemonic must be specified when recovering a software wallet. #[method(name = "wallet_recover")] async fn recover_wallet( &self, @@ -118,7 +120,7 @@ trait ColdWalletRpc { hardware_wallet: Option, ) -> rpc::RpcResult; - /// Open an exiting wallet by specifying the file location of the wallet file + /// Open an exiting wallet file. #[method(name = "wallet_open")] async fn open_wallet( &self,