mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-19 13:44:55 +01:00
refactor: update send functionality across wallet components (#925)
* refactor: update send functionality across wallet components --------- Co-authored-by: thesimplekid <tsk@thesimplekid.com>
This commit is contained in:
@@ -92,7 +92,7 @@ pub async fn pay_request(
|
||||
)
|
||||
.await?;
|
||||
|
||||
let token = matching_wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
|
||||
// We need the keysets information to properly convert from token proof to proof
|
||||
let keysets_info = match matching_wallet
|
||||
|
||||
@@ -221,7 +221,7 @@ pub async fn send(
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let token = wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
|
||||
match sub_command_args.v3 {
|
||||
true => {
|
||||
|
||||
@@ -70,11 +70,8 @@ async fn test_swap_to_send() {
|
||||
.expect("Failed to get ys")
|
||||
)
|
||||
);
|
||||
let token = wallet_alice
|
||||
.send(
|
||||
prepared_send,
|
||||
Some(SendMemo::for_token("test_swapt_to_send")),
|
||||
)
|
||||
let token = prepared_send
|
||||
.confirm(Some(SendMemo::for_token("test_swapt_to_send")))
|
||||
.await
|
||||
.expect("Failed to send token");
|
||||
let keysets_info = wallet_alice.get_mint_keysets().await.unwrap();
|
||||
|
||||
@@ -64,7 +64,7 @@ async fn test_swap() {
|
||||
|
||||
assert_eq!(fee, 1.into());
|
||||
|
||||
let send = wallet.send(send, None).await.unwrap();
|
||||
let send = send.confirm(None).await.unwrap();
|
||||
|
||||
let rec_amount = wallet
|
||||
.receive(&send.to_string(), ReceiveOptions::default())
|
||||
|
||||
@@ -101,7 +101,7 @@ async fn main() {
|
||||
|
||||
// Send the token
|
||||
let prepared_send = wallet.prepare_send(Amount::ONE, SendOptions::default()).await.unwrap();
|
||||
let token = wallet.send(prepared_send, None).await.unwrap();
|
||||
let token = prepared_send.confirm(None).await.unwrap();
|
||||
|
||||
println!("{}", token);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ async fn main() -> Result<(), Error> {
|
||||
let prepared_send = wallet
|
||||
.prepare_send(10.into(), SendOptions::default())
|
||||
.await?;
|
||||
let token = wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
|
||||
println!("Created token: {}", token);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ async fn main() -> Result<(), Error> {
|
||||
|
||||
// Send a token with the specified amount
|
||||
let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
|
||||
let token = wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
println!("Token:");
|
||||
println!("{}", token);
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ async fn main() -> Result<(), Error> {
|
||||
)
|
||||
.await?;
|
||||
println!("Fee: {}", prepared_send.fee());
|
||||
let token = wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
|
||||
println!("Created token locked to pubkey: {}", secret.public_key());
|
||||
println!("{}", token);
|
||||
|
||||
@@ -59,7 +59,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
// Send the token
|
||||
let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
|
||||
let token = wallet.send(prepared_send, None).await?;
|
||||
let token = prepared_send.confirm(None).await?;
|
||||
|
||||
println!("{}", token);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use tokio::sync::RwLock;
|
||||
use tracing::instrument;
|
||||
|
||||
use super::receive::ReceiveOptions;
|
||||
use super::send::{PreparedSend, SendMemo, SendOptions};
|
||||
use super::send::{PreparedSend, SendOptions};
|
||||
use super::Error;
|
||||
use crate::amount::SplitTarget;
|
||||
use crate::mint_url::MintUrl;
|
||||
@@ -177,22 +177,6 @@ impl MultiMintWallet {
|
||||
wallet.prepare_send(amount, opts).await
|
||||
}
|
||||
|
||||
/// Create cashu token
|
||||
#[instrument(skip(self))]
|
||||
pub async fn send(
|
||||
&self,
|
||||
wallet_key: &WalletKey,
|
||||
send: PreparedSend,
|
||||
memo: Option<SendMemo>,
|
||||
) -> Result<Token, Error> {
|
||||
let wallets = self.wallets.read().await;
|
||||
let wallet = wallets
|
||||
.get(wallet_key)
|
||||
.ok_or(Error::UnknownWallet(wallet_key.clone()))?;
|
||||
|
||||
wallet.send(send, memo).await
|
||||
}
|
||||
|
||||
/// Mint quote for wallet
|
||||
#[instrument(skip(self))]
|
||||
pub async fn mint_quote(
|
||||
|
||||
@@ -21,7 +21,7 @@ impl Wallet {
|
||||
/// ```no_compile
|
||||
/// let send = wallet.prepare_send(Amount::from(10), SendOptions::default()).await?;
|
||||
/// assert!(send.fee() <= Amount::from(1));
|
||||
/// let token = wallet.send(send, None).await?;
|
||||
/// let token = send.confirm(None).await?;
|
||||
/// ```
|
||||
#[instrument(skip(self), err)]
|
||||
pub async fn prepare_send(
|
||||
@@ -188,6 +188,7 @@ impl Wallet {
|
||||
|
||||
// Return prepared send
|
||||
Ok(PreparedSend {
|
||||
wallet: self.clone(),
|
||||
amount,
|
||||
options: opts,
|
||||
proofs_to_swap,
|
||||
@@ -196,135 +197,11 @@ impl Wallet {
|
||||
send_fee,
|
||||
})
|
||||
}
|
||||
|
||||
/// Finalize A Send Transaction
|
||||
///
|
||||
/// This function finalizes a send transaction by constructing a token the [`PreparedSend`].
|
||||
/// See [`Wallet::prepare_send`] for more information.
|
||||
#[instrument(skip(self), err)]
|
||||
pub async fn send(&self, send: PreparedSend, memo: Option<SendMemo>) -> Result<Token, Error> {
|
||||
tracing::info!("Sending prepared send");
|
||||
let total_send_fee = send.fee();
|
||||
let mut proofs_to_send = send.proofs_to_send;
|
||||
|
||||
// Get active keyset ID
|
||||
let active_keyset_id = self.fetch_active_keyset().await?.id;
|
||||
tracing::debug!("Active keyset ID: {:?}", active_keyset_id);
|
||||
|
||||
// Get keyset fees
|
||||
let keyset_fee_ppk = self.get_keyset_fees_by_id(active_keyset_id).await?;
|
||||
tracing::debug!("Keyset fees: {:?}", keyset_fee_ppk);
|
||||
|
||||
// Calculate total send amount
|
||||
let total_send_amount = send.amount + send.send_fee;
|
||||
tracing::debug!("Total send amount: {}", total_send_amount);
|
||||
|
||||
// Swap proofs if necessary
|
||||
if !send.proofs_to_swap.is_empty() {
|
||||
let swap_amount = total_send_amount - proofs_to_send.total_amount()?;
|
||||
tracing::debug!("Swapping proofs; swap_amount={:?}", swap_amount);
|
||||
if let Some(proofs) = self
|
||||
.swap(
|
||||
Some(swap_amount),
|
||||
SplitTarget::None,
|
||||
send.proofs_to_swap,
|
||||
send.options.conditions.clone(),
|
||||
false, // already included in swap_amount
|
||||
)
|
||||
.await?
|
||||
{
|
||||
proofs_to_send.extend(proofs);
|
||||
}
|
||||
}
|
||||
tracing::debug!(
|
||||
"Proofs to send: {:?}",
|
||||
proofs_to_send.iter().map(|p| p.amount).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Check if sufficient proofs are available
|
||||
if send.amount > proofs_to_send.total_amount()? {
|
||||
return Err(Error::InsufficientFunds);
|
||||
}
|
||||
|
||||
// Check if proofs are reserved or unspent
|
||||
let sendable_proof_ys = self
|
||||
.get_proofs_with(
|
||||
Some(vec![State::Reserved, State::Unspent]),
|
||||
send.options.conditions.clone().map(|c| vec![c]),
|
||||
)
|
||||
.await?
|
||||
.ys()?;
|
||||
if proofs_to_send
|
||||
.ys()?
|
||||
.iter()
|
||||
.any(|y| !sendable_proof_ys.contains(y))
|
||||
{
|
||||
tracing::warn!("Proofs to send are not reserved or unspent");
|
||||
return Err(Error::UnexpectedProofState);
|
||||
}
|
||||
|
||||
// Update proofs state to pending spent
|
||||
tracing::debug!(
|
||||
"Updating proofs state to pending spent: {:?}",
|
||||
proofs_to_send.ys()?
|
||||
);
|
||||
self.localstore
|
||||
.update_proofs_state(proofs_to_send.ys()?, State::PendingSpent)
|
||||
.await?;
|
||||
|
||||
// Include token memo
|
||||
let send_memo = send.options.memo.or(memo);
|
||||
let memo = send_memo.and_then(|m| if m.include_memo { Some(m.memo) } else { None });
|
||||
|
||||
// Add transaction to store
|
||||
self.localstore
|
||||
.add_transaction(Transaction {
|
||||
mint_url: self.mint_url.clone(),
|
||||
direction: TransactionDirection::Outgoing,
|
||||
amount: send.amount,
|
||||
fee: total_send_fee,
|
||||
unit: self.unit.clone(),
|
||||
ys: proofs_to_send.ys()?,
|
||||
timestamp: unix_time(),
|
||||
memo: memo.clone(),
|
||||
metadata: send.options.metadata,
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Create and return token
|
||||
Ok(Token::new(
|
||||
self.mint_url.clone(),
|
||||
proofs_to_send,
|
||||
memo,
|
||||
self.unit.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Cancel prepared send
|
||||
pub async fn cancel_send(&self, send: PreparedSend) -> Result<(), Error> {
|
||||
tracing::info!("Cancelling prepared send");
|
||||
|
||||
// Double-check proofs state
|
||||
let reserved_proofs = self.get_reserved_proofs().await?.ys()?;
|
||||
if !send
|
||||
.proofs()
|
||||
.ys()?
|
||||
.iter()
|
||||
.all(|y| reserved_proofs.contains(y))
|
||||
{
|
||||
return Err(Error::UnexpectedProofState);
|
||||
}
|
||||
|
||||
self.localstore
|
||||
.update_proofs_state(send.proofs().ys()?, State::Unspent)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepared send
|
||||
pub struct PreparedSend {
|
||||
wallet: Wallet,
|
||||
amount: Amount,
|
||||
options: SendOptions,
|
||||
proofs_to_swap: Proofs,
|
||||
@@ -375,6 +252,133 @@ impl PreparedSend {
|
||||
pub fn fee(&self) -> Amount {
|
||||
self.swap_fee + self.send_fee
|
||||
}
|
||||
|
||||
/// Confirm the prepared send and create a token
|
||||
#[instrument(skip(self), err)]
|
||||
pub async fn confirm(self, memo: Option<SendMemo>) -> Result<Token, Error> {
|
||||
tracing::info!("Confirming prepared send");
|
||||
let total_send_fee = self.fee();
|
||||
let mut proofs_to_send = self.proofs_to_send;
|
||||
|
||||
// Get active keyset ID
|
||||
let active_keyset_id = self.wallet.fetch_active_keyset().await?.id;
|
||||
tracing::debug!("Active keyset ID: {:?}", active_keyset_id);
|
||||
|
||||
// Get keyset fees
|
||||
let keyset_fee_ppk = self.wallet.get_keyset_fees_by_id(active_keyset_id).await?;
|
||||
tracing::debug!("Keyset fees: {:?}", keyset_fee_ppk);
|
||||
|
||||
// Calculate total send amount
|
||||
let total_send_amount = self.amount + self.send_fee;
|
||||
tracing::debug!("Total send amount: {}", total_send_amount);
|
||||
|
||||
// Swap proofs if necessary
|
||||
if !self.proofs_to_swap.is_empty() {
|
||||
let swap_amount = total_send_amount - proofs_to_send.total_amount()?;
|
||||
tracing::debug!("Swapping proofs; swap_amount={:?}", swap_amount);
|
||||
if let Some(proofs) = self
|
||||
.wallet
|
||||
.swap(
|
||||
Some(swap_amount),
|
||||
SplitTarget::None,
|
||||
self.proofs_to_swap,
|
||||
self.options.conditions.clone(),
|
||||
false, // already included in swap_amount
|
||||
)
|
||||
.await?
|
||||
{
|
||||
proofs_to_send.extend(proofs);
|
||||
}
|
||||
}
|
||||
tracing::debug!(
|
||||
"Proofs to send: {:?}",
|
||||
proofs_to_send.iter().map(|p| p.amount).collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
// Check if sufficient proofs are available
|
||||
if self.amount > proofs_to_send.total_amount()? {
|
||||
return Err(Error::InsufficientFunds);
|
||||
}
|
||||
|
||||
// Check if proofs are reserved or unspent
|
||||
let sendable_proof_ys = self
|
||||
.wallet
|
||||
.get_proofs_with(
|
||||
Some(vec![State::Reserved, State::Unspent]),
|
||||
self.options.conditions.clone().map(|c| vec![c]),
|
||||
)
|
||||
.await?
|
||||
.ys()?;
|
||||
if proofs_to_send
|
||||
.ys()?
|
||||
.iter()
|
||||
.any(|y| !sendable_proof_ys.contains(y))
|
||||
{
|
||||
tracing::warn!("Proofs to send are not reserved or unspent");
|
||||
return Err(Error::UnexpectedProofState);
|
||||
}
|
||||
|
||||
// Update proofs state to pending spent
|
||||
tracing::debug!(
|
||||
"Updating proofs state to pending spent: {:?}",
|
||||
proofs_to_send.ys()?
|
||||
);
|
||||
self.wallet
|
||||
.localstore
|
||||
.update_proofs_state(proofs_to_send.ys()?, State::PendingSpent)
|
||||
.await?;
|
||||
|
||||
// Include token memo
|
||||
let send_memo = self.options.memo.or(memo);
|
||||
let memo = send_memo.and_then(|m| if m.include_memo { Some(m.memo) } else { None });
|
||||
|
||||
// Add transaction to store
|
||||
self.wallet
|
||||
.localstore
|
||||
.add_transaction(Transaction {
|
||||
mint_url: self.wallet.mint_url.clone(),
|
||||
direction: TransactionDirection::Outgoing,
|
||||
amount: self.amount,
|
||||
fee: total_send_fee,
|
||||
unit: self.wallet.unit.clone(),
|
||||
ys: proofs_to_send.ys()?,
|
||||
timestamp: unix_time(),
|
||||
memo: memo.clone(),
|
||||
metadata: self.options.metadata,
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Create and return token
|
||||
Ok(Token::new(
|
||||
self.wallet.mint_url.clone(),
|
||||
proofs_to_send,
|
||||
memo,
|
||||
self.wallet.unit.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Cancel the prepared send
|
||||
pub async fn cancel(self) -> Result<(), Error> {
|
||||
tracing::info!("Cancelling prepared send");
|
||||
|
||||
// Double-check proofs state
|
||||
let reserved_proofs = self.wallet.get_reserved_proofs().await?.ys()?;
|
||||
if !self
|
||||
.proofs()
|
||||
.ys()?
|
||||
.iter()
|
||||
.all(|y| reserved_proofs.contains(y))
|
||||
{
|
||||
return Err(Error::UnexpectedProofState);
|
||||
}
|
||||
|
||||
self.wallet
|
||||
.localstore
|
||||
.update_proofs_state(self.proofs().ys()?, State::Unspent)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for PreparedSend {
|
||||
|
||||
Reference in New Issue
Block a user