npubcash.rs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. //! Example: Requesting and processing a single NpubCash payment
  2. //!
  3. //! This example demonstrates:
  4. //! 1. Setting up a wallet with NpubCash integration
  5. //! 2. Requesting an invoice for a fixed amount via LNURL-pay
  6. //! 3. Waiting for the payment to be processed and ecash to be minted
  7. //!
  8. //! Environment variables:
  9. //! - NOSTR_NSEC: Your Nostr private key (generates new if not provided)
  10. //!
  11. //! Uses constants:
  12. //! - NPUBCASH_URL: https://npubx.cash
  13. //! - MINT_URL: https://fake.thesimplekid.dev (fake mint that auto-pays)
  14. //!
  15. //! This example uses the NpubCash Proof Stream which continuously polls and mints.
  16. use std::sync::Arc;
  17. use std::time::Duration;
  18. use cdk::amount::SplitTarget;
  19. use cdk::nuts::nut00::ProofsMethods;
  20. use cdk::nuts::CurrencyUnit;
  21. use cdk::wallet::Wallet;
  22. use cdk::StreamExt;
  23. use cdk_sqlite::wallet::memory;
  24. use nostr_sdk::{Keys, ToBech32};
  25. const NPUBCASH_URL: &str = "https://npubx.cash";
  26. const MINT_URL: &str = "https://fake.thesimplekid.dev";
  27. const PAYMENT_AMOUNT_MSATS: u64 = 100000; // 100 sats
  28. #[tokio::main]
  29. async fn main() -> Result<(), Box<dyn std::error::Error>> {
  30. tracing_subscriber::fmt()
  31. .with_max_level(tracing::Level::INFO)
  32. .init();
  33. println!("=== NpubCash Example ===\n");
  34. // Setup Nostr keys
  35. let keys = if let Ok(nsec) = std::env::var("NOSTR_NSEC") {
  36. println!("Using provided Nostr keys");
  37. Keys::parse(&nsec)?
  38. } else {
  39. println!("Generating new Nostr keys");
  40. let new_keys = Keys::generate();
  41. println!("Public key (npub): {}", new_keys.public_key().to_bech32()?);
  42. println!(
  43. "Private key (save this!): {}\n",
  44. new_keys.secret_key().to_bech32()?
  45. );
  46. new_keys
  47. };
  48. // Setup wallet
  49. let mint_url = MINT_URL.to_string();
  50. println!("Mint URL: {}", mint_url);
  51. let localstore = memory::empty().await?;
  52. let seed = keys.secret_key().to_secret_bytes();
  53. let mut full_seed = [0u8; 64];
  54. full_seed[..32].copy_from_slice(&seed);
  55. let wallet = Arc::new(Wallet::new(
  56. &mint_url,
  57. CurrencyUnit::Sat,
  58. Arc::new(localstore),
  59. full_seed,
  60. None,
  61. )?);
  62. // Enable NpubCash integration
  63. let npubcash_url = NPUBCASH_URL.to_string();
  64. println!("NpubCash URL: {}", npubcash_url);
  65. wallet.enable_npubcash(npubcash_url.clone()).await?;
  66. println!("✓ NpubCash integration enabled\n");
  67. // Display the npub.cash address
  68. let display_url = npubcash_url
  69. .trim_start_matches("https://")
  70. .trim_start_matches("http://");
  71. println!("Your npub.cash address:");
  72. println!(" {}@{}\n", keys.public_key().to_bech32()?, display_url);
  73. println!("Requesting invoice for 100 sats from the fake mint...");
  74. request_invoice(&keys.public_key().to_bech32()?, PAYMENT_AMOUNT_MSATS).await?;
  75. println!("Invoice requested - the fake mint should auto-pay shortly.\n");
  76. // Check if auto-minting is enabled
  77. // Note: With the new stream API, auto-mint is always enabled as it's the primary purpose of the stream.
  78. println!("Auto-mint is ENABLED - paid quotes will be automatically minted\n");
  79. println!("Waiting for the invoice to be paid and processed...\n");
  80. // Subscribe to quote updates and wait for the single payment
  81. let mut stream =
  82. wallet.npubcash_proof_stream(SplitTarget::default(), None, Duration::from_secs(5));
  83. if let Some(result) = stream.next().await {
  84. match result {
  85. Ok((quote, proofs)) => {
  86. let amount_str = quote
  87. .amount
  88. .map_or("unknown".to_string(), |a| a.to_string());
  89. println!("Received payment for quote {}", quote.id);
  90. println!(" ├─ Amount: {} {}", amount_str, quote.unit);
  91. match proofs.total_amount() {
  92. Ok(amount) => {
  93. println!(" └─ Successfully minted {} sats!", amount);
  94. if let Ok(balance) = wallet.total_balance().await {
  95. println!(" New wallet balance: {} sats", balance);
  96. }
  97. }
  98. Err(e) => println!(" └─ Failed to calculate amount: {}", e),
  99. }
  100. println!();
  101. }
  102. Err(e) => {
  103. println!("Error processing payment: {}", e);
  104. }
  105. }
  106. } else {
  107. println!("No payment received within the timeout period.");
  108. }
  109. // Show final wallet balance
  110. let balance = wallet.total_balance().await?;
  111. println!("Final wallet balance: {} sats\n", balance);
  112. Ok(())
  113. }
  114. /// Request an invoice via LNURL-pay
  115. async fn request_invoice(npub: &str, amount_msats: u64) -> Result<(), Box<dyn std::error::Error>> {
  116. let http_client = reqwest::Client::new();
  117. let lnurlp_url = format!("{}/.well-known/lnurlp/{}", NPUBCASH_URL, npub);
  118. let lnurlp_response: serde_json::Value =
  119. http_client.get(&lnurlp_url).send().await?.json().await?;
  120. let callback = lnurlp_response["callback"]
  121. .as_str()
  122. .ok_or("No callback URL")?;
  123. let invoice_url = format!("{}?amount={}", callback, amount_msats);
  124. let invoice_response: serde_json::Value =
  125. http_client.get(&invoice_url).send().await?.json().await?;
  126. let pr = invoice_response["pr"]
  127. .as_str()
  128. .ok_or("No payment request")?;
  129. println!(" Invoice: {}...", &pr[..50.min(pr.len())]);
  130. Ok(())
  131. }