create_and_wait_payment.rs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. //! Create a lightning address and wait for payment
  2. //!
  3. //! This example demonstrates:
  4. //! - Generating Nostr keys for authentication
  5. //! - Displaying the npub.cash URL for the user's public key
  6. //! - Polling for quote updates
  7. //! - Waiting for payment notifications
  8. //!
  9. //! Note: The current npub.cash SDK only supports reading quotes.
  10. //! To create a new quote/invoice, you would need to:
  11. //! 1. Use the npub.cash web interface or
  12. //! 2. Implement the POST /api/v2/wallet/quotes endpoint
  13. //!
  14. //! This example shows how to monitor for new quotes once they exist.
  15. use std::sync::Arc;
  16. use std::time::Duration;
  17. use cdk_npubcash::{JwtAuthProvider, NpubCashClient};
  18. use nostr_sdk::{Keys, ToBech32};
  19. #[tokio::main]
  20. async fn main() -> Result<(), Box<dyn std::error::Error>> {
  21. #[cfg(not(target_arch = "wasm32"))]
  22. if rustls::crypto::CryptoProvider::get_default().is_none() {
  23. let _ = rustls::crypto::ring::default_provider().install_default();
  24. }
  25. tracing_subscriber::fmt()
  26. .with_max_level(tracing::Level::DEBUG)
  27. .init();
  28. tracing::debug!("Starting NpubCash Payment Monitor");
  29. println!("=== NpubCash Payment Monitor ===\n");
  30. let base_url =
  31. std::env::var("NPUBCASH_URL").unwrap_or_else(|_| "https://npubx.cash".to_string());
  32. tracing::debug!("Using base URL: {}", base_url);
  33. let keys = if let Ok(nsec) = std::env::var("NOSTR_NSEC") {
  34. tracing::debug!("Loading Nostr keys from NOSTR_NSEC environment variable");
  35. println!("Using provided Nostr keys from NOSTR_NSEC");
  36. Keys::parse(&nsec)?
  37. } else {
  38. tracing::debug!("No NOSTR_NSEC found, generating new keys");
  39. println!("No NOSTR_NSEC found, generating new keys");
  40. let new_keys = Keys::generate();
  41. println!("\n⚠️ Save this private key (nsec) to reuse the same identity:");
  42. println!(" {}", new_keys.secret_key().to_bech32()?);
  43. println!();
  44. new_keys
  45. };
  46. let npub = keys.public_key().to_bech32()?;
  47. tracing::debug!("Generated npub: {}", npub);
  48. println!("Public key (npub): {npub}");
  49. println!("\nYour npub.cash address:");
  50. println!(" {npub}/@{base_url}");
  51. println!("\nAnyone can send you ecash at this address!");
  52. println!("{}", "=".repeat(60));
  53. tracing::debug!("Creating JWT auth provider");
  54. let auth_provider = Arc::new(JwtAuthProvider::new(base_url.clone(), keys));
  55. tracing::debug!("Initializing NpubCash client");
  56. let client = NpubCashClient::new(base_url, auth_provider);
  57. println!("\n=== Checking for existing quotes ===");
  58. tracing::debug!("Fetching all existing quotes");
  59. match client.get_quotes(None).await {
  60. Ok(quotes) => {
  61. tracing::debug!("Successfully fetched {} quotes", quotes.len());
  62. if quotes.is_empty() {
  63. println!("No quotes found yet.");
  64. println!("\nTo create a new quote:");
  65. println!(" 1. Visit your npub.cash address in a browser");
  66. println!(" 2. Or use the npub.cash web interface");
  67. println!(" 3. Or implement the POST /api/v2/wallet/quotes endpoint");
  68. } else {
  69. println!("Found {} existing quote(s):", quotes.len());
  70. for (i, quote) in quotes.iter().enumerate() {
  71. tracing::debug!(
  72. "Quote {}: ID={}, amount={}, unit={}",
  73. i + 1,
  74. quote.id,
  75. quote.amount,
  76. quote.unit
  77. );
  78. println!("\n{}. Quote ID: {}", i + 1, quote.id);
  79. println!(" Amount: {} {}", quote.amount, quote.unit);
  80. }
  81. }
  82. }
  83. Err(e) => {
  84. tracing::error!("Error fetching quotes: {}", e);
  85. eprintln!("Error fetching quotes: {e}");
  86. }
  87. }
  88. println!("\n{}", "=".repeat(60));
  89. println!("=== Polling for quote updates ===");
  90. println!("Checking for new payments every 5 seconds... Press Ctrl+C to stop.\n");
  91. tracing::debug!("Starting quote polling with 5 second interval");
  92. // Get initial timestamp for polling
  93. let mut last_timestamp = std::time::SystemTime::now()
  94. .duration_since(std::time::UNIX_EPOCH)?
  95. .as_secs();
  96. // Poll for quotes and handle Ctrl+C
  97. tokio::select! {
  98. _ = async {
  99. loop {
  100. tokio::time::sleep(Duration::from_secs(5)).await;
  101. match client.get_quotes(Some(last_timestamp)).await {
  102. Ok(quotes) => {
  103. if !quotes.is_empty() {
  104. tracing::debug!("Found {} new quotes", quotes.len());
  105. // Update timestamp to most recent quote
  106. if let Some(max_ts) = quotes.iter().map(|q| q.created_at).max() {
  107. last_timestamp = max_ts;
  108. }
  109. for quote in quotes {
  110. tracing::info!(
  111. "New quote received: ID={}, amount={}, unit={}",
  112. quote.id,
  113. quote.amount,
  114. quote.unit
  115. );
  116. println!("🔔 New quote received!");
  117. println!(" Quote ID: {}", quote.id);
  118. println!(" Amount: {} {}", quote.amount, quote.unit);
  119. println!(
  120. " Timestamp: {}",
  121. chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC")
  122. );
  123. println!();
  124. }
  125. }
  126. }
  127. Err(e) => {
  128. tracing::debug!("Error polling quotes: {}", e);
  129. }
  130. }
  131. }
  132. } => {}
  133. _ = tokio::signal::ctrl_c() => {
  134. tracing::info!("Received Ctrl+C signal, stopping payment monitor");
  135. }
  136. }
  137. println!("\n✓ Stopped monitoring for payments");
  138. Ok(())
  139. }