Преглед изворни кода

Add wait.rs which is a good API for Bolt11

Cesar Rodas пре 2 месеци
родитељ
комит
373f361b70

+ 5 - 0
crates/cdk-common/src/error.rs

@@ -114,6 +114,11 @@ pub enum Error {
     /// BIP353 address parsing error
     #[error("Failed to parse BIP353 address: {0}")]
     Bip353Parse(String),
+
+    /// Operation timeout
+    #[error("Operation timeout")]
+    Timeout,
+
     /// BIP353 address resolution error
     #[error("Failed to resolve BIP353 address: {0}")]
     Bip353Resolve(String),

+ 3 - 5
crates/cdk/examples/auth_wallet.rs

@@ -1,4 +1,5 @@
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk::error::Error;
 use cdk::nuts::CurrencyUnit;
@@ -7,7 +8,6 @@ use cdk::{Amount, OidcClient};
 use cdk_common::amount::SplitTarget;
 use cdk_common::{MintInfo, ProofsMethods};
 use cdk_sqlite::wallet::memory;
-use futures::StreamExt;
 use rand::Rng;
 use tracing_subscriber::EnvFilter;
 
@@ -60,11 +60,9 @@ async fn main() -> Result<(), Error> {
 
     let quote = wallet.mint_quote(amount, None).await.unwrap();
     let proofs = wallet
-        .proof_stream(quote, SplitTarget::default(), None)
-        .next()
+        .wait_and_mint_quote(quote, SplitTarget::default(), None, Duration::from_secs(10))
         .await
-        .expect("Some payment")
-        .expect("No error");
+        .unwrap();
 
     println!("Received: {}", proofs.total_amount()?);
 

+ 39 - 36
crates/cdk/examples/melt-token.rs

@@ -1,4 +1,5 @@
 use std::sync::Arc;
+use std::time::Duration;
 
 use bitcoin::hashes::{sha256, Hash};
 use bitcoin::hex::prelude::FromHex;
@@ -7,7 +8,7 @@ use cdk::error::Error;
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::{CurrencyUnit, SecretKey};
 use cdk::wallet::Wallet;
-use cdk::{Amount, StreamExt};
+use cdk::Amount;
 use cdk_sqlite::wallet::memory;
 use lightning_invoice::{Currency, InvoiceBuilder, PaymentSecret};
 use rand::Rng;
@@ -29,46 +30,48 @@ async fn main() -> Result<(), Error> {
     let wallet = Wallet::new(mint_url, unit, Arc::new(localstore), seed, None)?;
 
     let quote = wallet.mint_quote(amount, None).await?;
+    let proofs = wallet
+        .wait_and_mint_quote(
+            quote,
+            Default::default(),
+            Default::default(),
+            Duration::from_secs(10),
+        )
+        .await?;
 
-    let mut proof_streams = wallet.proof_stream(quote, Default::default(), Default::default());
-
-    while let Some(proofs) = proof_streams.next().await {
-        let receive_amount = proofs?.total_amount()?;
-        println!("Received {} from mint {}", receive_amount, mint_url);
+    let receive_amount = proofs.total_amount()?;
+    println!("Received {} from mint {}", receive_amount, mint_url);
 
-        // Now melt what we have
-        // We need to prepare a lightning invoice
-        let private_key = SecretKey::from_slice(
-            &<[u8; 32]>::from_hex(
-                "e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734",
-            )
+    // Now melt what we have
+    // We need to prepare a lightning invoice
+    let private_key = SecretKey::from_slice(
+        &<[u8; 32]>::from_hex("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
             .unwrap(),
-        )
-        .unwrap();
-        let random_bytes = rand::rng().random::<[u8; 32]>();
-        let payment_hash = sha256::Hash::from_slice(&random_bytes).unwrap();
-        let payment_secret = PaymentSecret([42u8; 32]);
-        let invoice_to_be_paid = InvoiceBuilder::new(Currency::Bitcoin)
-            .amount_milli_satoshis(5 * 1000)
-            .description("Pay me".into())
-            .payment_hash(payment_hash)
-            .payment_secret(payment_secret)
-            .current_timestamp()
-            .min_final_cltv_expiry_delta(144)
-            .build_signed(|hash| Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))
-            .unwrap()
-            .to_string();
-        println!("Invoice to be paid: {}", invoice_to_be_paid);
+    )
+    .unwrap();
+    let random_bytes = rand::rng().random::<[u8; 32]>();
+    let payment_hash = sha256::Hash::from_slice(&random_bytes).unwrap();
+    let payment_secret = PaymentSecret([42u8; 32]);
+    let invoice_to_be_paid = InvoiceBuilder::new(Currency::Bitcoin)
+        .amount_milli_satoshis(5 * 1000)
+        .description("Pay me".into())
+        .payment_hash(payment_hash)
+        .payment_secret(payment_secret)
+        .current_timestamp()
+        .min_final_cltv_expiry_delta(144)
+        .build_signed(|hash| Secp256k1::new().sign_ecdsa_recoverable(hash, &private_key))
+        .unwrap()
+        .to_string();
+    println!("Invoice to be paid: {}", invoice_to_be_paid);
 
-        let melt_quote = wallet.melt_quote(invoice_to_be_paid, None).await?;
-        println!(
-            "Melt quote: {} {} {:?}",
-            melt_quote.amount, melt_quote.state, melt_quote,
-        );
+    let melt_quote = wallet.melt_quote(invoice_to_be_paid, None).await?;
+    println!(
+        "Melt quote: {} {} {:?}",
+        melt_quote.amount, melt_quote.state, melt_quote,
+    );
 
-        let melted = wallet.melt(&melt_quote.id).await?;
-        println!("Melted: {:?}", melted);
-    }
+    let melted = wallet.melt(&melt_quote.id).await?;
+    println!("Melted: {:?}", melted);
 
     Ok(())
 }

+ 72 - 0
crates/cdk/examples/mint-token-bolt12-with-stream.rs

@@ -0,0 +1,72 @@
+use std::sync::Arc;
+
+use cdk::error::Error;
+use cdk::nuts::nut00::ProofsMethods;
+use cdk::nuts::CurrencyUnit;
+use cdk::wallet::{SendOptions, Wallet};
+use cdk::{Amount, StreamExt};
+use cdk_sqlite::wallet::memory;
+use rand::random;
+use tracing_subscriber::EnvFilter;
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let default_filter = "debug";
+
+    let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn,rustls=warn";
+
+    let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter));
+
+    // Parse input
+    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+
+    // Initialize the memory store for the wallet
+    let localstore = Arc::new(memory::empty().await?);
+
+    // Generate a random seed for the wallet
+    let seed = random::<[u8; 64]>();
+
+    // Define the mint URL and currency unit
+    let mint_url = "https://fake.thesimplekid.dev";
+    let unit = CurrencyUnit::Sat;
+    let amount = Amount::from(10);
+
+    // Create a new wallet
+    let wallet = Wallet::new(mint_url, unit, localstore, seed, None)?;
+
+    let quotes = vec![
+        wallet.mint_bolt12_quote(None, None).await?,
+        wallet.mint_bolt12_quote(None, None).await?,
+        wallet.mint_bolt12_quote(None, None).await?,
+    ];
+
+    let mut stream = wallet.mints_proof_stream(quotes, Default::default(), None);
+
+    let stop = stream.get_cancel_token();
+
+    let mut processed = 0;
+
+    while let Some(proofs) = stream.next().await {
+        let (mint_quote, proofs) = proofs?;
+
+        // Mint the received amount
+        let receive_amount = proofs.total_amount()?;
+        println!("Received {} from mint {}", receive_amount, mint_quote.id);
+
+        // Send a token with the specified amount
+        let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
+        let token = prepared_send.confirm(None).await?;
+        println!("Token:");
+        println!("{}", token);
+
+        processed += 1;
+
+        if processed == 3 {
+            stop.cancel()
+        }
+    }
+
+    println!("Stopped the loop after {} quotes being minted", processed);
+
+    Ok(())
+}

+ 59 - 0
crates/cdk/examples/mint-token-bolt12.rs

@@ -0,0 +1,59 @@
+use std::sync::Arc;
+use std::time::Duration;
+
+use cdk::error::Error;
+use cdk::nuts::nut00::ProofsMethods;
+use cdk::nuts::CurrencyUnit;
+use cdk::wallet::{SendOptions, Wallet};
+use cdk::Amount;
+use cdk_sqlite::wallet::memory;
+use rand::random;
+use tracing_subscriber::EnvFilter;
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    let default_filter = "debug";
+
+    let sqlx_filter = "sqlx=warn,hyper_util=warn,reqwest=warn,rustls=warn";
+
+    let env_filter = EnvFilter::new(format!("{},{}", default_filter, sqlx_filter));
+
+    // Parse input
+    tracing_subscriber::fmt().with_env_filter(env_filter).init();
+
+    // Initialize the memory store for the wallet
+    let localstore = Arc::new(memory::empty().await?);
+
+    // Generate a random seed for the wallet
+    let seed = random::<[u8; 64]>();
+
+    // Define the mint URL and currency unit
+    let mint_url = "https://fake.thesimplekid.dev";
+    let unit = CurrencyUnit::Sat;
+    let amount = Amount::from(10);
+
+    // Create a new wallet
+    let wallet = Wallet::new(mint_url, unit, localstore, seed, None)?;
+
+    let quote = wallet.mint_bolt12_quote(None, None).await?;
+    let proofs = wallet
+        .wait_and_mint_quote(
+            quote,
+            Default::default(),
+            Default::default(),
+            Duration::from_secs(10),
+        )
+        .await?;
+
+    // Mint the received amount
+    let receive_amount = proofs.total_amount()?;
+    println!("Received {} from mint {}", receive_amount, mint_url);
+
+    // Send a token with the specified amount
+    let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
+    let token = prepared_send.confirm(None).await?;
+    println!("Token:");
+    println!("{}", token);
+
+    Ok(())
+}

+ 20 - 22
crates/cdk/examples/mint-token.rs

@@ -1,10 +1,11 @@
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk::error::Error;
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::CurrencyUnit;
 use cdk::wallet::{SendOptions, Wallet};
-use cdk::{Amount, StreamExt};
+use cdk::Amount;
 use cdk_sqlite::wallet::memory;
 use rand::random;
 use tracing_subscriber::EnvFilter;
@@ -35,27 +36,24 @@ async fn main() -> Result<(), Error> {
     let wallet = Wallet::new(mint_url, unit, localstore, seed, None)?;
 
     let quote = wallet.mint_quote(amount, None).await?;
-    let mut proof_streams = wallet.proof_stream(quote, Default::default(), Default::default());
-
-    while let Some(proofs) = proof_streams.next().await {
-        let proofs = match proofs {
-            Ok(proofs) => proofs,
-            Err(err) => {
-                tracing::error!("Proof stream ended with {:?}", err);
-                break;
-            }
-        };
-
-        // Mint the received amount
-        let receive_amount = proofs.total_amount()?;
-        println!("Received {} from mint {}", receive_amount, mint_url);
-
-        // Send a token with the specified amount
-        let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
-        let token = prepared_send.confirm(None).await?;
-        println!("Token:");
-        println!("{}", token);
-    }
+    let proofs = wallet
+        .wait_and_mint_quote(
+            quote,
+            Default::default(),
+            Default::default(),
+            Duration::from_secs(10),
+        )
+        .await?;
+
+    // Mint the received amount
+    let receive_amount = proofs.total_amount()?;
+    println!("Received {} from mint {}", receive_amount, mint_url);
+
+    // Send a token with the specified amount
+    let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
+    let token = prepared_send.confirm(None).await?;
+    println!("Token:");
+    println!("{}", token);
 
     Ok(())
 }

+ 56 - 53
crates/cdk/examples/p2pk.rs

@@ -1,9 +1,10 @@
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk::error::Error;
 use cdk::nuts::{CurrencyUnit, SecretKey, SpendingConditions};
 use cdk::wallet::{ReceiveOptions, SendOptions, Wallet};
-use cdk::{Amount, StreamExt};
+use cdk::Amount;
 use cdk_sqlite::wallet::memory;
 use rand::random;
 use tracing_subscriber::EnvFilter;
@@ -34,58 +35,60 @@ async fn main() -> Result<(), Error> {
     let wallet = Wallet::new(mint_url, unit, localstore, seed, None).unwrap();
 
     let quote = wallet.mint_quote(amount, None).await?;
-
-    let mut proof_streams = wallet.proof_stream(quote, Default::default(), Default::default());
-
-    while let Some(proofs) = proof_streams.next().await {
-        let proofs = proofs?;
-
-        // Mint the received amount
-        println!(
-            "Minted nuts: {:?}",
-            proofs.into_iter().map(|p| p.amount).collect::<Vec<_>>()
-        );
-
-        // Generate a secret key for spending conditions
-        let secret = SecretKey::generate();
-
-        // Create spending conditions using the generated public key
-        let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
-
-        // Get the total balance of the wallet
-        let bal = wallet.total_balance().await?;
-        println!("Total balance: {}", bal);
-
-        // Send a token with the specified amount and spending conditions
-        let prepared_send = wallet
-            .prepare_send(
-                10.into(),
-                SendOptions {
-                    conditions: Some(spending_conditions),
-                    include_fee: true,
-                    ..Default::default()
-                },
-            )
-            .await?;
-        println!("Fee: {}", prepared_send.fee());
-        let token = prepared_send.confirm(None).await?;
-
-        println!("Created token locked to pubkey: {}", secret.public_key());
-        println!("{}", token);
-
-        // Receive the token using the secret key
-        let amount = wallet
-            .receive(
-                &token.to_string(),
-                ReceiveOptions {
-                    p2pk_signing_keys: vec![secret],
-                    ..Default::default()
-                },
-            )
-            .await?;
-
-        println!("Redeemed locked token worth: {}", u64::from(amount));
-    }
+    let proofs = wallet
+        .wait_and_mint_quote(
+            quote,
+            Default::default(),
+            Default::default(),
+            Duration::from_secs(10),
+        )
+        .await?;
+
+    // Mint the received amount
+    println!(
+        "Minted nuts: {:?}",
+        proofs.into_iter().map(|p| p.amount).collect::<Vec<_>>()
+    );
+
+    // Generate a secret key for spending conditions
+    let secret = SecretKey::generate();
+
+    // Create spending conditions using the generated public key
+    let spending_conditions = SpendingConditions::new_p2pk(secret.public_key(), None);
+
+    // Get the total balance of the wallet
+    let bal = wallet.total_balance().await?;
+    println!("Total balance: {}", bal);
+
+    // Send a token with the specified amount and spending conditions
+    let prepared_send = wallet
+        .prepare_send(
+            10.into(),
+            SendOptions {
+                conditions: Some(spending_conditions),
+                include_fee: true,
+                ..Default::default()
+            },
+        )
+        .await?;
+    println!("Fee: {}", prepared_send.fee());
+    let token = prepared_send.confirm(None).await?;
+
+    println!("Created token locked to pubkey: {}", secret.public_key());
+    println!("{}", token);
+
+    // Receive the token using the secret key
+    let amount = wallet
+        .receive(
+            &token.to_string(),
+            ReceiveOptions {
+                p2pk_signing_keys: vec![secret],
+                ..Default::default()
+            },
+        )
+        .await?;
+
+    println!("Redeemed locked token worth: {}", u64::from(amount));
 
     Ok(())
 }

+ 13 - 8
crates/cdk/examples/proof-selection.rs

@@ -2,11 +2,12 @@
 
 use std::collections::HashMap;
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::CurrencyUnit;
 use cdk::wallet::Wallet;
-use cdk::{Amount, StreamExt};
+use cdk::Amount;
 use cdk_common::nut02::KeySetInfosMethods;
 use cdk_sqlite::wallet::memory;
 use rand::random;
@@ -31,14 +32,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
         let amount = Amount::from(amount);
 
         let quote = wallet.mint_quote(amount, None).await?;
+        let proofs = wallet
+            .wait_and_mint_quote(
+                quote,
+                Default::default(),
+                Default::default(),
+                Duration::from_secs(10),
+            )
+            .await?;
 
-        let mut proof_streams = wallet.proof_stream(quote, Default::default(), Default::default());
-
-        while let Some(proofs) = proof_streams.next().await {
-            // Mint the received amount
-            let receive_amount = proofs?.total_amount()?;
-            println!("Minted {}", receive_amount);
-        }
+        // Mint the received amount
+        let receive_amount = proofs.total_amount()?;
+        println!("Minted {}", receive_amount);
     }
 
     // Get unspent proofs

+ 20 - 15
crates/cdk/examples/wallet.rs

@@ -1,9 +1,10 @@
 use std::sync::Arc;
+use std::time::Duration;
 
 use cdk::nuts::nut00::ProofsMethods;
 use cdk::nuts::CurrencyUnit;
 use cdk::wallet::{SendOptions, Wallet};
-use cdk::{Amount, StreamExt};
+use cdk::Amount;
 use cdk_sqlite::wallet::memory;
 use rand::random;
 
@@ -24,20 +25,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
     let wallet = Wallet::new(mint_url, unit, localstore, seed, None)?;
 
     let quote = wallet.mint_quote(amount, None).await?;
-
-    let mut proof_streams = wallet.proof_stream(quote, Default::default(), Default::default());
-
-    while let Some(proofs) = proof_streams.next().await {
-        // Mint the received amount
-        let receive_amount = proofs?.total_amount()?;
-        println!("Minted {}", receive_amount);
-
-        // Send the token
-        let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
-        let token = prepared_send.confirm(None).await?;
-
-        println!("{}", token);
-    }
+    let proofs = wallet
+        .wait_and_mint_quote(
+            quote,
+            Default::default(),
+            Default::default(),
+            Duration::from_secs(10),
+        )
+        .await?;
+
+    // Mint the received amount
+    let receive_amount = proofs.total_amount()?;
+    println!("Minted {}", receive_amount);
+
+    // Send the token
+    let prepared_send = wallet.prepare_send(amount, SendOptions::default()).await?;
+    let token = prepared_send.confirm(None).await?;
+
+    println!("{}", token);
 
     Ok(())
 }

+ 1 - 0
crates/cdk/src/wallet/streams/mod.rs

@@ -12,6 +12,7 @@ use super::{Wallet, WalletSubscription};
 
 pub mod payment;
 pub mod proof;
+mod wait;
 
 /// Shared type
 #[cfg(not(target_arch = "wasm32"))]

+ 50 - 0
crates/cdk/src/wallet/streams/wait.rs

@@ -0,0 +1,50 @@
+use cdk_common::amount::SplitTarget;
+use cdk_common::wallet::MintQuote;
+use cdk_common::{Amount, Error, Proofs, SpendingConditions};
+use futures::future::BoxFuture;
+use futures::StreamExt;
+use tokio::time::{timeout, Duration};
+
+use super::Wallet;
+
+impl Wallet {
+    #[inline(always)]
+    /// Mints a mint quote once it is paid
+    pub async fn wait_and_mint_quote(
+        &self,
+        quote: MintQuote,
+        amount_split_target: SplitTarget,
+        spending_conditions: Option<SpendingConditions>,
+        timeout_duration: Duration,
+    ) -> Result<Proofs, Error> {
+        let mut stream = self.proof_stream(quote, amount_split_target, spending_conditions);
+
+        timeout(timeout_duration, async move {
+            stream.next().await.ok_or(Error::Internal)?
+        })
+        .await
+        .map_err(|_| Error::Timeout)?
+    }
+
+    /// Returns a BoxFuture that will wait for payment on the given event with a timeout check
+    #[allow(private_bounds)]
+    pub fn wait_for_payment(
+        &self,
+        event: &MintQuote,
+        timeout_duration: Duration,
+    ) -> BoxFuture<'_, Result<Option<Amount>, Error>> {
+        let mut stream = self.payment_stream(event);
+
+        Box::pin(async move {
+            timeout(timeout_duration, async {
+                stream
+                    .next()
+                    .await
+                    .ok_or(Error::Internal)?
+                    .map(|(_quote, amount)| amount)
+            })
+            .await
+            .map_err(|_| Error::Timeout)?
+        })
+    }
+}