Skip to content
This repository was archived by the owner on Feb 3, 2025. It is now read-only.

Commit 2717423

Browse files
Send max with fedimint
1 parent 9bb50a5 commit 2717423

File tree

4 files changed

+188
-29
lines changed

4 files changed

+188
-29
lines changed

mutiny-core/src/federation.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use async_trait::async_trait;
2020
use bdk_chain::ConfirmationTime;
2121
use bip39::Mnemonic;
2222
use bitcoin::{
23-
address::NetworkUnchecked,
23+
address::{NetworkChecked, NetworkUnchecked},
2424
bip32::{ChildNumber, DerivationPath, ExtendedPrivKey},
2525
hashes::Hash,
2626
secp256k1::{Secp256k1, SecretKey, ThirtyTwoByteHash},
@@ -665,11 +665,11 @@ impl<S: MutinyStorage> FederationClient<S> {
665665
/// Send on chain transaction
666666
pub(crate) async fn send_onchain(
667667
&self,
668-
send_to: bitcoin::Address<NetworkUnchecked>,
668+
send_to: bitcoin::Address,
669669
amount: u64,
670670
labels: Vec<String>,
671671
) -> Result<Txid, MutinyError> {
672-
let address = bitcoin30_to_bitcoin29_address(send_to.require_network(self.network)?);
672+
let address = bitcoin30_to_bitcoin29_address(send_to);
673673

674674
let btc_amount = fedimint_ln_common::bitcoin::Amount::from_sat(amount);
675675

@@ -742,6 +742,25 @@ impl<S: MutinyStorage> FederationClient<S> {
742742
Err(MutinyError::PaymentTimeout)
743743
}
744744

745+
pub async fn estimate_tx_fee(
746+
&self,
747+
destination_address: bitcoin::Address,
748+
amount: u64,
749+
) -> Result<u64, MutinyError> {
750+
let address = bitcoin30_to_bitcoin29_address(destination_address);
751+
let btc_amount = fedimint_ln_common::bitcoin::Amount::from_sat(amount);
752+
753+
let wallet_module = self
754+
.fedimint_client
755+
.get_first_module::<WalletClientModule>();
756+
757+
let peg_out_fees = wallet_module
758+
.get_withdraw_fees(address.clone(), btc_amount)
759+
.await?;
760+
761+
Ok(peg_out_fees.amount().to_sat())
762+
}
763+
745764
/// Someone received a payment on our behalf, we need to claim it
746765
pub async fn claim_external_receive(
747766
&self,

mutiny-core/src/lib.rs

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ use async_lock::RwLock;
9898
use bdk_chain::ConfirmationTime;
9999
use bip39::Mnemonic;
100100
use bitcoin::{
101-
address::NetworkUnchecked,
101+
address::{NetworkChecked, NetworkUnchecked},
102102
secp256k1::{PublicKey, ThirtyTwoByteHash},
103103
};
104104
use bitcoin::{bip32::ExtendedPrivKey, Transaction};
@@ -1818,7 +1818,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {
18181818

18191819
pub async fn send_to_address(
18201820
&self,
1821-
send_to: Address<NetworkUnchecked>,
1821+
send_to: Address,
18221822
amount: u64,
18231823
labels: Vec<String>,
18241824
fee_rate: Option<f32>,
@@ -1866,6 +1866,150 @@ impl<S: MutinyStorage> MutinyWallet<S> {
18661866
}
18671867
}
18681868

1869+
/// Estimates the onchain fee for a transaction sending to the given address.
1870+
/// The amount is in satoshis and the fee rate is in sat/vbyte.
1871+
pub async fn estimate_tx_fee(
1872+
&self,
1873+
destination_address: Address,
1874+
amount: u64,
1875+
fee_rate: Option<f32>,
1876+
) -> Result<u64, MutinyError> {
1877+
log_warn!(self.logger, "estimate_tx_fee");
1878+
1879+
// Try each federation first
1880+
let federation_ids = self.list_federation_ids().await?;
1881+
let mut last_federation_error = None;
1882+
for federation_id in federation_ids {
1883+
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
1884+
// Check if the federation has enough balance
1885+
let balance = fedimint_client.get_balance().await?;
1886+
if balance >= amount / 1_000 {
1887+
match fedimint_client
1888+
.estimate_tx_fee(destination_address.clone(), amount.clone())
1889+
.await
1890+
{
1891+
Ok(t) => {
1892+
return Ok(t);
1893+
}
1894+
Err(e) => {
1895+
log_warn!(self.logger, "error estimating fedimint fee: {e}");
1896+
last_federation_error = Some(e);
1897+
}
1898+
}
1899+
}
1900+
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
1901+
}
1902+
// If federation client is not found, continue to next federation
1903+
}
1904+
1905+
let b = self.node_manager.get_balance().await?;
1906+
if b.confirmed + b.unconfirmed > 0 {
1907+
let res = self
1908+
.node_manager
1909+
.estimate_tx_fee(destination_address, amount, fee_rate)?;
1910+
1911+
Ok(res)
1912+
} else {
1913+
Err(last_federation_error.unwrap_or(MutinyError::InsufficientBalance))
1914+
}
1915+
}
1916+
1917+
/// Estimates the onchain fee for a transaction sweep our on-chain balance
1918+
/// to the given address. If the fedimint has a balance, sweep that first.
1919+
/// Do not sweep the on chain wallet unless that is empty.
1920+
///
1921+
/// The fee rate is in sat/vbyte.
1922+
pub async fn estimate_sweep_tx_fee(
1923+
&self,
1924+
destination_address: Address,
1925+
fee_rate: Option<f32>,
1926+
) -> Result<u64, MutinyError> {
1927+
// Try each federation first
1928+
let federation_ids = self.list_federation_ids().await?;
1929+
for federation_id in federation_ids {
1930+
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
1931+
// Check if the federation has enough balance
1932+
let balance = fedimint_client.get_balance().await?;
1933+
match fedimint_client
1934+
.estimate_tx_fee(destination_address.clone(), balance.clone())
1935+
.await
1936+
{
1937+
Ok(t) => {
1938+
return Ok(t);
1939+
}
1940+
Err(e) => return Err(e),
1941+
}
1942+
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
1943+
}
1944+
// If federation client is not found, continue to next federation
1945+
}
1946+
1947+
let b = self.node_manager.get_balance().await?;
1948+
if b.confirmed + b.unconfirmed > 0 {
1949+
let res = self
1950+
.node_manager
1951+
.estimate_sweep_tx_fee(destination_address, fee_rate)?;
1952+
1953+
Ok(res)
1954+
} else {
1955+
log_error!(self.logger, "node manager doesn't have a a balance");
1956+
Err(MutinyError::InsufficientBalance)
1957+
}
1958+
}
1959+
1960+
/// Sweeps all the funds from the wallet to the given address.
1961+
/// The fee rate is in sat/vbyte.
1962+
///
1963+
/// If a fee rate is not provided, one will be used from the fee estimator.
1964+
pub async fn sweep_wallet(
1965+
&self,
1966+
send_to: Address,
1967+
labels: Vec<String>,
1968+
fee_rate: Option<f32>,
1969+
) -> Result<Txid, MutinyError> {
1970+
// Try each federation first
1971+
let federation_ids = self.list_federation_ids().await?;
1972+
for federation_id in federation_ids {
1973+
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
1974+
// Check if the federation has enough balance
1975+
let balance = fedimint_client.get_balance().await?;
1976+
match fedimint_client
1977+
.estimate_tx_fee(send_to.clone(), balance.clone())
1978+
.await
1979+
{
1980+
Ok(f) => {
1981+
match fedimint_client
1982+
.send_onchain(send_to.clone(), balance - f, labels)
1983+
.await
1984+
{
1985+
Ok(t) => return Ok(t),
1986+
Err(e) => {
1987+
log_error!(self.logger, "error sending the fedimint balance");
1988+
return Err(e);
1989+
}
1990+
}
1991+
}
1992+
Err(e) => return Err(e),
1993+
}
1994+
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
1995+
}
1996+
// If federation client is not found, continue to next federation
1997+
}
1998+
1999+
let b = self.node_manager.get_balance().await?;
2000+
if b.confirmed + b.unconfirmed > 0 {
2001+
let res = self
2002+
.node_manager
2003+
.sweep_wallet(send_to.clone(), labels, fee_rate)
2004+
.await?;
2005+
2006+
Ok(res)
2007+
} else {
2008+
log_error!(self.logger, "node manager doesn't have a a balance");
2009+
Err(MutinyError::InsufficientBalance)
2010+
}
2011+
}
2012+
18692013
async fn create_address(&self, labels: Vec<String>) -> Result<bitcoin::Address, MutinyError> {
18702014
// Attempt to create federation invoice if available
18712015
let federation_ids = self.list_federation_ids().await?;
@@ -3245,6 +3389,11 @@ impl<S: MutinyStorage> MutinyWallet<S> {
32453389

32463390
Ok(response.price)
32473391
}
3392+
3393+
/// Returns the network of the wallet.
3394+
pub fn get_network(&self) -> Network {
3395+
self.network
3396+
}
32483397
}
32493398

32503399
impl<S: MutinyStorage> InvoiceHandler for MutinyWallet<S> {

mutiny-core/src/nodemanager.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -706,14 +706,12 @@ impl<S: MutinyStorage> NodeManager<S> {
706706
/// If a fee rate is not provided, one will be used from the fee estimator.
707707
pub async fn send_to_address(
708708
&self,
709-
send_to: Address<NetworkUnchecked>,
709+
send_to: Address,
710710
amount: u64,
711711
labels: Vec<String>,
712712
fee_rate: Option<f32>,
713713
) -> Result<Txid, MutinyError> {
714-
let address = send_to.require_network(self.network)?;
715-
716-
self.wallet.send(address, amount, labels, fee_rate).await
714+
self.wallet.send(send_to, amount, labels, fee_rate).await
717715
}
718716

719717
/// Sweeps all the funds from the wallet to the given address.
@@ -722,18 +720,16 @@ impl<S: MutinyStorage> NodeManager<S> {
722720
/// If a fee rate is not provided, one will be used from the fee estimator.
723721
pub async fn sweep_wallet(
724722
&self,
725-
send_to: Address<NetworkUnchecked>,
723+
send_to: Address,
726724
labels: Vec<String>,
727725
fee_rate: Option<f32>,
728726
) -> Result<Txid, MutinyError> {
729-
let address = send_to.require_network(self.network)?;
730-
731-
self.wallet.sweep(address, labels, fee_rate).await
727+
self.wallet.sweep(send_to, labels, fee_rate).await
732728
}
733729

734730
/// Estimates the onchain fee for a transaction sending to the given address.
735731
/// The amount is in satoshis and the fee rate is in sat/vbyte.
736-
pub fn estimate_tx_fee(
732+
pub(crate) fn estimate_tx_fee(
737733
&self,
738734
destination_address: Address,
739735
amount: u64,
@@ -747,7 +743,7 @@ impl<S: MutinyStorage> NodeManager<S> {
747743
/// to the given address.
748744
///
749745
/// The fee rate is in sat/vbyte.
750-
pub fn estimate_sweep_tx_fee(
746+
pub(crate) fn estimate_sweep_tx_fee(
751747
&self,
752748
destination_address: Address,
753749
fee_rate: Option<f32>,

mutiny-wasm/src/lib.rs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ impl MutinyWallet {
476476
/// Returns the network of the wallet.
477477
#[wasm_bindgen]
478478
pub fn get_network(&self) -> String {
479-
self.inner.node_manager.get_network().to_string()
479+
self.inner.get_network().to_string()
480480
}
481481

482482
/// Gets a new bitcoin address from the wallet.
@@ -545,7 +545,8 @@ impl MutinyWallet {
545545
labels: Vec<String>,
546546
fee_rate: Option<f32>,
547547
) -> Result<String, MutinyJsError> {
548-
let send_to = Address::from_str(&destination_address)?;
548+
let send_to =
549+
Address::from_str(&destination_address)?.require_network(self.inner.get_network())?;
549550
Ok(self
550551
.inner
551552
.send_to_address(send_to, amount, labels, fee_rate)
@@ -583,44 +584,38 @@ impl MutinyWallet {
583584
labels: Vec<String>,
584585
fee_rate: Option<f32>,
585586
) -> Result<String, MutinyJsError> {
586-
let send_to = Address::from_str(&destination_address)?;
587+
let send_to =
588+
Address::from_str(&destination_address)?.require_network(self.inner.get_network())?;
587589
Ok(self
588590
.inner
589-
.node_manager
590591
.sweep_wallet(send_to, labels, fee_rate)
591592
.await?
592593
.to_string())
593594
}
594595

595596
/// Estimates the onchain fee for a transaction sending to the given address.
596597
/// The amount is in satoshis and the fee rate is in sat/vbyte.
597-
pub fn estimate_tx_fee(
598+
pub async fn estimate_tx_fee(
598599
&self,
599600
destination_address: String,
600601
amount: u64,
601602
fee_rate: Option<f32>,
602603
) -> Result<u64, MutinyJsError> {
603604
let addr = Address::from_str(&destination_address)?.assume_checked();
604-
Ok(self
605-
.inner
606-
.node_manager
607-
.estimate_tx_fee(addr, amount, fee_rate)?)
605+
Ok(self.inner.estimate_tx_fee(addr, amount, fee_rate).await?)
608606
}
609607

610608
/// Estimates the onchain fee for a transaction sweep our on-chain balance
611609
/// to the given address.
612610
///
613611
/// The fee rate is in sat/vbyte.
614-
pub fn estimate_sweep_tx_fee(
612+
pub async fn estimate_sweep_tx_fee(
615613
&self,
616614
destination_address: String,
617615
fee_rate: Option<f32>,
618616
) -> Result<u64, MutinyJsError> {
619617
let addr = Address::from_str(&destination_address)?.assume_checked();
620-
Ok(self
621-
.inner
622-
.node_manager
623-
.estimate_sweep_tx_fee(addr, fee_rate)?)
618+
Ok(self.inner.estimate_sweep_tx_fee(addr, fee_rate).await?)
624619
}
625620

626621
/// Estimates the onchain fee for a opening a lightning channel.

0 commit comments

Comments
 (0)