diff --git a/node/src/bin/space-cli.rs b/node/src/bin/space-cli.rs index fef1b85..bd4071f 100644 --- a/node/src/bin/space-cli.rs +++ b/node/src/bin/space-cli.rs @@ -196,6 +196,14 @@ enum Commands { #[arg(long, short)] fee_rate: Option, }, + /// List last transactions + #[command(name = "listtransactions")] + ListTransactions { + #[arg(default_value = "10")] + count: usize, + #[arg(default_value = "0")] + skip: usize, + }, /// List won spaces including ones /// still in auction with a winning bid #[command(name = "listspaces")] @@ -535,6 +543,13 @@ async fn handle_commands( let spaces = cli.client.wallet_list_bidouts(&cli.wallet).await?; println!("{}", serde_json::to_string_pretty(&spaces)?); } + Commands::ListTransactions { count, skip } => { + let txs = cli + .client + .wallet_list_transactions(&cli.wallet, count, skip) + .await?; + println!("{}", serde_json::to_string_pretty(&txs)?); + } Commands::ListSpaces => { let spaces = cli.client.wallet_list_spaces(&cli.wallet).await?; println!("{}", serde_json::to_string_pretty(&spaces)?); diff --git a/node/src/rpc.rs b/node/src/rpc.rs index a45c01d..4873bff 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -44,7 +44,8 @@ use crate::{ source::BitcoinRpc, store::{ChainState, LiveSnapshot, RolloutEntry, Sha256}, wallets::{ - AddressKind, Balance, RpcWallet, TxResponse, WalletCommand, WalletOutput, WalletResponse, + AddressKind, Balance, RpcWallet, TxInfo, TxResponse, WalletCommand, WalletOutput, + WalletResponse, }, }; @@ -168,6 +169,14 @@ pub trait Rpc { fee_rate: FeeRate, ) -> Result, ErrorObjectOwned>; + #[method(name = "walletlisttransactions")] + async fn wallet_list_transactions( + &self, + wallet: &str, + count: usize, + skip: usize, + ) -> Result, ErrorObjectOwned>; + #[method(name = "walletforcespend")] async fn wallet_force_spend( &self, @@ -729,6 +738,19 @@ impl RpcServer for RpcServerImpl { .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::)) } + async fn wallet_list_transactions( + &self, + wallet: &str, + count: usize, + skip: usize, + ) -> Result, ErrorObjectOwned> { + self.wallet(&wallet) + .await? + .send_list_transactions(count, skip) + .await + .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::)) + } + async fn wallet_force_spend( &self, wallet: &str, diff --git a/node/src/wallets.rs b/node/src/wallets.rs index 5ca7049..545175f 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -56,6 +56,15 @@ pub struct TxResponse { pub raw: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TxInfo { + pub txid: Txid, + pub confirmed: bool, + pub sent: Amount, + pub received: Amount, + pub fee: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletResponse { pub result: Vec, @@ -86,6 +95,11 @@ pub enum WalletCommand { fee_rate: FeeRate, resp: crate::rpc::Responder>>, }, + ListTransactions { + count: usize, + skip: usize, + resp: crate::rpc::Responder>>, + }, ListSpaces { resp: crate::rpc::Responder>>, }, @@ -291,6 +305,10 @@ impl RpcWallet { WalletCommand::ListUnspent { resp } => { _ = resp.send(Self::list_unspent(wallet, state)); } + WalletCommand::ListTransactions { count, skip, resp } => { + let transactions = Self::list_transactions(wallet, count, skip); + _ = resp.send(transactions); + } WalletCommand::ListSpaces { resp } => { let result = Self::list_unspent(wallet, state); match result { @@ -448,6 +466,36 @@ impl RpcWallet { Ok(SpacesAwareCoinSelection::new(excluded, confirmed_only)) } + fn list_transactions( + wallet: &mut SpacesWallet, + count: usize, + skip: usize, + ) -> anyhow::Result> { + let mut transactions: Vec<_> = wallet.spaces.transactions().collect(); + transactions.sort(); + + Ok(transactions + .iter() + .rev() + .skip(skip) + .take(count) + .map(|ctx| { + let tx = ctx.tx_node.tx.clone(); + let txid = ctx.tx_node.txid.clone(); + let confirmed = ctx.chain_position.is_confirmed(); + let (sent, received) = wallet.spaces.sent_and_received(&tx); + let fee = wallet.spaces.calculate_fee(&tx).ok(); + TxInfo { + txid, + confirmed, + sent, + received, + fee, + } + }) + .collect()) + } + fn list_unspent( wallet: &mut SpacesWallet, store: &mut LiveSnapshot, @@ -884,6 +932,18 @@ impl RpcWallet { resp_rx.await? } + pub async fn send_list_transactions( + &self, + count: usize, + skip: usize, + ) -> anyhow::Result> { + let (resp, resp_rx) = oneshot::channel(); + self.sender + .send(WalletCommand::ListTransactions { count, skip, resp }) + .await?; + resp_rx.await? + } + pub async fn send_force_spend( &self, outpoint: OutPoint, diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 0d4da37..e1895f6 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -159,7 +159,7 @@ impl SpacesWallet { } pub fn get_info(&self) -> WalletInfo { - let mut descriptors = Vec::with_capacity(4); + let mut descriptors = Vec::with_capacity(2); descriptors.push(DescriptorInfo { descriptor: self