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

Commit

Permalink
Send max with fedimint
Browse files Browse the repository at this point in the history
  • Loading branch information
AnthonyRonning committed May 6, 2024
1 parent 9bb50a5 commit 2717423
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 29 deletions.
25 changes: 22 additions & 3 deletions mutiny-core/src/federation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use async_trait::async_trait;
use bdk_chain::ConfirmationTime;
use bip39::Mnemonic;
use bitcoin::{
address::NetworkUnchecked,
address::{NetworkChecked, NetworkUnchecked},
bip32::{ChildNumber, DerivationPath, ExtendedPrivKey},
hashes::Hash,
secp256k1::{Secp256k1, SecretKey, ThirtyTwoByteHash},
Expand Down Expand Up @@ -665,11 +665,11 @@ impl<S: MutinyStorage> FederationClient<S> {
/// Send on chain transaction
pub(crate) async fn send_onchain(
&self,
send_to: bitcoin::Address<NetworkUnchecked>,
send_to: bitcoin::Address,
amount: u64,
labels: Vec<String>,
) -> Result<Txid, MutinyError> {
let address = bitcoin30_to_bitcoin29_address(send_to.require_network(self.network)?);
let address = bitcoin30_to_bitcoin29_address(send_to);

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

Expand Down Expand Up @@ -742,6 +742,25 @@ impl<S: MutinyStorage> FederationClient<S> {
Err(MutinyError::PaymentTimeout)
}

pub async fn estimate_tx_fee(
&self,
destination_address: bitcoin::Address,
amount: u64,
) -> Result<u64, MutinyError> {
let address = bitcoin30_to_bitcoin29_address(destination_address);
let btc_amount = fedimint_ln_common::bitcoin::Amount::from_sat(amount);

let wallet_module = self
.fedimint_client
.get_first_module::<WalletClientModule>();

let peg_out_fees = wallet_module
.get_withdraw_fees(address.clone(), btc_amount)
.await?;

Ok(peg_out_fees.amount().to_sat())
}

/// Someone received a payment on our behalf, we need to claim it
pub async fn claim_external_receive(
&self,
Expand Down
153 changes: 151 additions & 2 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ use async_lock::RwLock;
use bdk_chain::ConfirmationTime;
use bip39::Mnemonic;
use bitcoin::{
address::NetworkUnchecked,
address::{NetworkChecked, NetworkUnchecked},
secp256k1::{PublicKey, ThirtyTwoByteHash},
};
use bitcoin::{bip32::ExtendedPrivKey, Transaction};
Expand Down Expand Up @@ -1818,7 +1818,7 @@ impl<S: MutinyStorage> MutinyWallet<S> {

pub async fn send_to_address(
&self,
send_to: Address<NetworkUnchecked>,
send_to: Address,
amount: u64,
labels: Vec<String>,
fee_rate: Option<f32>,
Expand Down Expand Up @@ -1866,6 +1866,150 @@ impl<S: MutinyStorage> MutinyWallet<S> {
}
}

/// Estimates the onchain fee for a transaction sending to the given address.
/// The amount is in satoshis and the fee rate is in sat/vbyte.
pub async fn estimate_tx_fee(
&self,
destination_address: Address,
amount: u64,
fee_rate: Option<f32>,
) -> Result<u64, MutinyError> {
log_warn!(self.logger, "estimate_tx_fee");

// Try each federation first
let federation_ids = self.list_federation_ids().await?;
let mut last_federation_error = None;
for federation_id in federation_ids {
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
// Check if the federation has enough balance
let balance = fedimint_client.get_balance().await?;
if balance >= amount / 1_000 {
match fedimint_client
.estimate_tx_fee(destination_address.clone(), amount.clone())
.await
{
Ok(t) => {
return Ok(t);
}
Err(e) => {
log_warn!(self.logger, "error estimating fedimint fee: {e}");
last_federation_error = Some(e);
}
}
}
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
}
// If federation client is not found, continue to next federation
}

let b = self.node_manager.get_balance().await?;
if b.confirmed + b.unconfirmed > 0 {
let res = self
.node_manager
.estimate_tx_fee(destination_address, amount, fee_rate)?;

Ok(res)
} else {
Err(last_federation_error.unwrap_or(MutinyError::InsufficientBalance))
}
}

/// Estimates the onchain fee for a transaction sweep our on-chain balance
/// to the given address. If the fedimint has a balance, sweep that first.
/// Do not sweep the on chain wallet unless that is empty.
///
/// The fee rate is in sat/vbyte.
pub async fn estimate_sweep_tx_fee(
&self,
destination_address: Address,
fee_rate: Option<f32>,
) -> Result<u64, MutinyError> {
// Try each federation first
let federation_ids = self.list_federation_ids().await?;
for federation_id in federation_ids {
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
// Check if the federation has enough balance
let balance = fedimint_client.get_balance().await?;
match fedimint_client
.estimate_tx_fee(destination_address.clone(), balance.clone())
.await
{
Ok(t) => {
return Ok(t);
}
Err(e) => return Err(e),
}
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
}
// If federation client is not found, continue to next federation
}

let b = self.node_manager.get_balance().await?;
if b.confirmed + b.unconfirmed > 0 {
let res = self
.node_manager
.estimate_sweep_tx_fee(destination_address, fee_rate)?;

Ok(res)
} else {
log_error!(self.logger, "node manager doesn't have a a balance");
Err(MutinyError::InsufficientBalance)
}
}

/// Sweeps all the funds from the wallet to the given address.
/// The fee rate is in sat/vbyte.
///
/// If a fee rate is not provided, one will be used from the fee estimator.
pub async fn sweep_wallet(
&self,
send_to: Address,
labels: Vec<String>,
fee_rate: Option<f32>,
) -> Result<Txid, MutinyError> {
// Try each federation first
let federation_ids = self.list_federation_ids().await?;
for federation_id in federation_ids {
if let Some(fedimint_client) = self.federations.read().await.get(&federation_id) {
// Check if the federation has enough balance
let balance = fedimint_client.get_balance().await?;
match fedimint_client
.estimate_tx_fee(send_to.clone(), balance.clone())
.await
{
Ok(f) => {
match fedimint_client
.send_onchain(send_to.clone(), balance - f, labels)
.await
{
Ok(t) => return Ok(t),
Err(e) => {
log_error!(self.logger, "error sending the fedimint balance");
return Err(e);
}
}
}
Err(e) => return Err(e),
}
// If payment fails or invoice amount is None or balance is not sufficient, continue to next federation
}
// If federation client is not found, continue to next federation
}

let b = self.node_manager.get_balance().await?;
if b.confirmed + b.unconfirmed > 0 {
let res = self
.node_manager
.sweep_wallet(send_to.clone(), labels, fee_rate)
.await?;

Ok(res)
} else {
log_error!(self.logger, "node manager doesn't have a a balance");
Err(MutinyError::InsufficientBalance)
}
}

async fn create_address(&self, labels: Vec<String>) -> Result<bitcoin::Address, MutinyError> {
// Attempt to create federation invoice if available
let federation_ids = self.list_federation_ids().await?;
Expand Down Expand Up @@ -3245,6 +3389,11 @@ impl<S: MutinyStorage> MutinyWallet<S> {

Ok(response.price)
}

/// Returns the network of the wallet.
pub fn get_network(&self) -> Network {
self.network
}
}

impl<S: MutinyStorage> InvoiceHandler for MutinyWallet<S> {
Expand Down
16 changes: 6 additions & 10 deletions mutiny-core/src/nodemanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,14 +706,12 @@ impl<S: MutinyStorage> NodeManager<S> {
/// If a fee rate is not provided, one will be used from the fee estimator.
pub async fn send_to_address(
&self,
send_to: Address<NetworkUnchecked>,
send_to: Address,
amount: u64,
labels: Vec<String>,
fee_rate: Option<f32>,
) -> Result<Txid, MutinyError> {
let address = send_to.require_network(self.network)?;

self.wallet.send(address, amount, labels, fee_rate).await
self.wallet.send(send_to, amount, labels, fee_rate).await
}

/// Sweeps all the funds from the wallet to the given address.
Expand All @@ -722,18 +720,16 @@ impl<S: MutinyStorage> NodeManager<S> {
/// If a fee rate is not provided, one will be used from the fee estimator.
pub async fn sweep_wallet(
&self,
send_to: Address<NetworkUnchecked>,
send_to: Address,
labels: Vec<String>,
fee_rate: Option<f32>,
) -> Result<Txid, MutinyError> {
let address = send_to.require_network(self.network)?;

self.wallet.sweep(address, labels, fee_rate).await
self.wallet.sweep(send_to, labels, fee_rate).await
}

/// Estimates the onchain fee for a transaction sending to the given address.
/// The amount is in satoshis and the fee rate is in sat/vbyte.
pub fn estimate_tx_fee(
pub(crate) fn estimate_tx_fee(
&self,
destination_address: Address,
amount: u64,
Expand All @@ -747,7 +743,7 @@ impl<S: MutinyStorage> NodeManager<S> {
/// to the given address.
///
/// The fee rate is in sat/vbyte.
pub fn estimate_sweep_tx_fee(
pub(crate) fn estimate_sweep_tx_fee(
&self,
destination_address: Address,
fee_rate: Option<f32>,
Expand Down
23 changes: 9 additions & 14 deletions mutiny-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ impl MutinyWallet {
/// Returns the network of the wallet.
#[wasm_bindgen]
pub fn get_network(&self) -> String {
self.inner.node_manager.get_network().to_string()
self.inner.get_network().to_string()
}

/// Gets a new bitcoin address from the wallet.
Expand Down Expand Up @@ -545,7 +545,8 @@ impl MutinyWallet {
labels: Vec<String>,
fee_rate: Option<f32>,
) -> Result<String, MutinyJsError> {
let send_to = Address::from_str(&destination_address)?;
let send_to =
Address::from_str(&destination_address)?.require_network(self.inner.get_network())?;
Ok(self
.inner
.send_to_address(send_to, amount, labels, fee_rate)
Expand Down Expand Up @@ -583,44 +584,38 @@ impl MutinyWallet {
labels: Vec<String>,
fee_rate: Option<f32>,
) -> Result<String, MutinyJsError> {
let send_to = Address::from_str(&destination_address)?;
let send_to =
Address::from_str(&destination_address)?.require_network(self.inner.get_network())?;
Ok(self
.inner
.node_manager
.sweep_wallet(send_to, labels, fee_rate)
.await?
.to_string())
}

/// Estimates the onchain fee for a transaction sending to the given address.
/// The amount is in satoshis and the fee rate is in sat/vbyte.
pub fn estimate_tx_fee(
pub async fn estimate_tx_fee(
&self,
destination_address: String,
amount: u64,
fee_rate: Option<f32>,
) -> Result<u64, MutinyJsError> {
let addr = Address::from_str(&destination_address)?.assume_checked();
Ok(self
.inner
.node_manager
.estimate_tx_fee(addr, amount, fee_rate)?)
Ok(self.inner.estimate_tx_fee(addr, amount, fee_rate).await?)
}

/// Estimates the onchain fee for a transaction sweep our on-chain balance
/// to the given address.
///
/// The fee rate is in sat/vbyte.
pub fn estimate_sweep_tx_fee(
pub async fn estimate_sweep_tx_fee(
&self,
destination_address: String,
fee_rate: Option<f32>,
) -> Result<u64, MutinyJsError> {
let addr = Address::from_str(&destination_address)?.assume_checked();
Ok(self
.inner
.node_manager
.estimate_sweep_tx_fee(addr, fee_rate)?)
Ok(self.inner.estimate_sweep_tx_fee(addr, fee_rate).await?)
}

/// Estimates the onchain fee for a opening a lightning channel.
Expand Down

0 comments on commit 2717423

Please sign in to comment.