Skip to content

Commit

Permalink
Merge pull request #1202 from MutinyWallet/zeus-swap-fixes
Browse files Browse the repository at this point in the history
Fixes for olympus swaps
  • Loading branch information
TonyGiorgio authored Jun 6, 2024
2 parents 064f407 + 2af7ee1 commit e2a469b
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 143 deletions.
197 changes: 68 additions & 129 deletions mutiny-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1669,12 +1669,16 @@ impl<S: MutinyStorage> MutinyWallet<S> {
})
}

pub async fn sweep_federation_balance(
pub async fn sweep_federation_balance_to_invoice(
&self,
amount: Option<u64>,
from_federation_id: Option<FederationId>,
to_federation_id: Option<FederationId>,
bolt_11: Bolt11Invoice,
) -> Result<FedimintSweepResult, MutinyError> {
// invoice must have an amount
if bolt_11.amount_milli_satoshis().is_none() {
return Err(MutinyError::BadAmountError);
}

let federation_ids = self.list_federation_ids().await?;
if federation_ids.is_empty() {
return Err(MutinyError::NotFound);
Expand All @@ -1685,42 +1689,8 @@ impl<S: MutinyStorage> MutinyWallet<S> {
.get(&from_federation_id)
.ok_or(MutinyError::NotFound)?;

// decide to sweep to secondary federation or lightning node
let to_federation_client = match to_federation_id {
Some(f) => Some(federation_lock.get(&f).ok_or(MutinyError::NotFound)?),
None => None,
};

let labels = vec![SWAP_LABEL.to_string()];

// if the user provided amount, this is easy
if let Some(amt) = amount {
let (inv, fee) = match to_federation_client {
Some(f) => {
// swap from one federation to another
let inv = f.get_invoice(amt, labels.clone()).await?;
(inv, 0)
}
None => {
// use the lightning node if no to federation selected
self.node_manager
.create_invoice(amt, labels.clone())
.await?
}
};

let bolt_11 = inv.bolt11.expect("create inv had one job");
self.storage
.set_invoice_labels(bolt_11.clone(), labels.clone())?;
let pay_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?;
let total_fees_paid = pay_res.fees_paid.unwrap_or(0) + fee;

return Ok(FedimintSweepResult {
amount: amt,
fees: Some(total_fees_paid),
});
}

// If no amount, figure out the amount to send over
let current_balance = from_fedimint_client.get_balance().await?;
log_debug!(
Expand All @@ -1729,88 +1699,46 @@ impl<S: MutinyStorage> MutinyWallet<S> {
current_balance
);

let fees = from_fedimint_client.gateway_fee().await?;
// FIXME: this is still producing off by one. check round down
let amt = max_spendable_amount(current_balance, &fees)
.map_or(Err(MutinyError::InsufficientBalance), Ok)?;
log_debug!(self.logger, "max spendable: {}", amt);

// try to get an invoice for this exact amount
let (inv, fee) = match to_federation_client {
Some(f) => {
// swap from one federation to another
let inv = f.get_invoice(amt, labels.clone()).await?;
(inv, 0)
}
None => {
// use the lightning node if no to federation selected
self.node_manager
.create_invoice(amt, labels.clone())
.await?
}
};

// check if we can afford that invoice
let inv_amt = inv.amount_sats.ok_or(MutinyError::BadAmountError)?;
let first_invoice_amount = if inv_amt > amt {
log_debug!(self.logger, "adjusting amount to swap to: {}", amt);
inv_amt - (inv_amt - amt)
} else {
inv_amt
};

// if invoice amount changed, create a new invoice
let (inv_to_pay, fee) = if first_invoice_amount != inv_amt {
match to_federation_client {
Some(f) => {
// swap from one federation to another
let inv = f.get_invoice(amt, labels.clone()).await?;
(inv, 0)
}
None => {
// use the lightning node if no to federation selected
self.node_manager
.create_invoice(amt, labels.clone())
.await?
}
}
} else {
(inv.clone(), fee)
};

log_debug!(self.logger, "attempting payment from fedimint client");
let bolt_11 = inv_to_pay.bolt11.expect("create inv had one job");
self.storage
.set_invoice_labels(bolt_11.clone(), labels.clone())?;
let first_invoice_res = from_fedimint_client.pay_invoice(bolt_11, labels).await?;
let pay_result = from_fedimint_client
.pay_invoice(bolt_11.clone(), labels)
.await?;

let remaining_balance = from_fedimint_client.get_balance().await?;
if remaining_balance > 0 {
// there was a remainder when there shouldn't have been
// for now just log this, it is probably just a millisat/1 sat difference
log_warn!(
self.logger,
"remaining fedimint balance: {}",
remaining_balance
"remaining fedimint balance: {remaining_balance}"
);
}

let total_fees = first_invoice_res.fees_paid.unwrap_or(0) + fee;
let outgoing_fee = pay_result.fees_paid.unwrap_or(0);
let incoming_fee = self
.get_invoice(&bolt_11)
.await
.ok()
.and_then(|i| i.fees_paid)
.unwrap_or(0);

let total_fees = outgoing_fee + incoming_fee;
Ok(FedimintSweepResult {
amount: current_balance - total_fees,
amount: bolt_11.amount_milli_satoshis().unwrap_or_default() / 1_000,
fees: Some(total_fees),
})
}

/// Estimate the fee before trying to sweep from federation
pub async fn estimate_sweep_federation_fee(
pub async fn create_sweep_federation_invoice(
&self,
amount: Option<u64>,
from_federation_id: Option<FederationId>,
to_federation_id: Option<FederationId>,
) -> Result<Option<u64>, MutinyError> {
) -> Result<MutinyInvoice, MutinyError> {
if let Some(0) = amount {
return Ok(None);
return Err(MutinyError::BadAmountError);
}

let federation_ids = self.list_federation_ids().await?;
Expand All @@ -1823,47 +1751,58 @@ impl<S: MutinyStorage> MutinyWallet<S> {
let fedimint_client = federation_lock
.get(&from_federation_id)
.ok_or(MutinyError::NotFound)?;
let to_federation_client = match to_federation_id {
Some(f) => Some(federation_lock.get(&f).ok_or(MutinyError::NotFound)?),
None => None,
};
let fees = fedimint_client.gateway_fee().await?;

let (lsp_fee, federation_fee) = {
if let Some(amt) = amount {
// if the user provided amount, this is easy
let incoming_fee = if to_federation_id.is_some() {
0
} else {
self.node_manager.get_lsp_fee(amt).await?
};

let outgoing_fee =
(calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64;

(incoming_fee, outgoing_fee)
if let Some(amt) = amount {
// if the user provided amount, this is easy
let (mut invoice, incoming_fee) = if let Some(fed_client) = to_federation_client {
let invoice = fed_client
.get_invoice(amt, vec![SWAP_LABEL.to_string()])
.await?;
(invoice, 0)
} else {
// If no amount, figure out the amount to send over
let current_balance = fedimint_client.get_balance().await?;
log_debug!(
self.logger,
"current fedimint client balance: {}",
current_balance
);
self.node_manager
.create_invoice(amt, vec![SWAP_LABEL.to_string()])
.await?
};

let amt = max_spendable_amount(current_balance, &fees)
.map_or(Err(MutinyError::InsufficientBalance), Ok)?;
log_debug!(self.logger, "max spendable: {}", amt);
let outgoing_fee =
(calc_routing_fee_msat(amt as f64 * 1_000.0, &fees) / 1_000.0).floor() as u64;

let incoming_fee = if to_federation_id.is_some() {
0
} else {
self.node_manager.get_lsp_fee(amt).await?
};
invoice.fees_paid = Some(incoming_fee + outgoing_fee);
Ok(invoice)
} else {
// If no amount, figure out the amount to send over
let current_balance = fedimint_client.get_balance().await?;
log_debug!(
self.logger,
"current fedimint client balance: {current_balance}"
);

let outgoing_fee = current_balance - amt;
let amt = max_spendable_amount(current_balance, &fees)
.ok_or(MutinyError::InsufficientBalance)?;
log_debug!(self.logger, "max spendable: {amt}");

(incoming_fee, outgoing_fee)
}
};
let (mut invoice, incoming_fee) = if let Some(fed_client) = to_federation_client {
let invoice = fed_client
.get_invoice(amt, vec![SWAP_LABEL.to_string()])
.await?;
(invoice, 0)
} else {
self.node_manager
.create_invoice(amt, vec![SWAP_LABEL.to_string()])
.await?
};

Ok(Some(lsp_fee + federation_fee))
let outgoing_fee = current_balance - amt;

invoice.fees_paid = Some(incoming_fee + outgoing_fee);
Ok(invoice)
}
}

pub async fn send_to_address(
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 @@ -1048,11 +1048,10 @@ impl MutinyWallet {
}

/// Sweep the federation balance into a lightning channel
pub async fn sweep_federation_balance(
pub async fn sweep_federation_balance_to_invoice(
&self,
amount: Option<u64>,
from_federation_id: Option<String>,
to_federation_id: Option<String>,
bolt_11: String,
) -> Result<FedimintSweepResult, MutinyJsError> {
let from_federation_id = match from_federation_id {
Some(f) => {
Expand All @@ -1061,27 +1060,22 @@ impl MutinyWallet {
None => None,
};

let to_federation_id = match to_federation_id {
Some(f) => {
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
}
None => None,
};
let bolt_11 = Bolt11Invoice::from_str(&bolt_11)?;

Ok(self
.inner
.sweep_federation_balance(amount, from_federation_id, to_federation_id)
.sweep_federation_balance_to_invoice(from_federation_id, bolt_11)
.await?
.into())
}

/// Estimate the fee before trying to sweep from federation
pub async fn estimate_sweep_federation_fee(
pub async fn create_sweep_federation_invoice(
&self,
amount: Option<u64>,
from_federation_id: Option<String>,
to_federation_id: Option<String>,
) -> Result<Option<u64>, MutinyJsError> {
) -> Result<MutinyInvoice, MutinyJsError> {
let from_federation_id = match from_federation_id {
Some(f) => {
Some(FederationId::from_str(&f).map_err(|_| MutinyJsError::InvalidArgumentsError)?)
Expand All @@ -1098,8 +1092,9 @@ impl MutinyWallet {

Ok(self
.inner
.estimate_sweep_federation_fee(amount, from_federation_id, to_federation_id)
.await?)
.create_sweep_federation_invoice(amount, from_federation_id, to_federation_id)
.await?
.into())
}

/// Closes a channel with the given outpoint.
Expand Down

0 comments on commit e2a469b

Please sign in to comment.