diff --git a/integrationtests/tests/tests_lnd.rs b/integrationtests/tests/tests_lnd.rs index 52467879..a292b95a 100644 --- a/integrationtests/tests/tests_lnd.rs +++ b/integrationtests/tests/tests_lnd.rs @@ -155,7 +155,9 @@ async fn test_bolt11_mint() -> anyhow::Result<()> { assert!(keysets.is_ok()); // create wallet - let localstore = SqliteLocalStore::with_in_memory().await?; + let tmp_dir = tempfile::tempdir()?; + let wallet_file = tmp_dir.path().join("wallet.db"); + let localstore = SqliteLocalStore::with_path(wallet_file.to_str().unwrap().to_owned()).await?; let wallet = WalletBuilder::default() .with_client(client) .with_localstore(localstore) @@ -195,5 +197,83 @@ async fn test_bolt11_mint() -> anyhow::Result<()> { let balance = wallet.get_balance().await?; assert_eq!(5_000, balance); + // send tokens + let exported_tokens = wallet.send_tokens(100).await?; + assert_eq!(100, exported_tokens.total_amount()); + let balance = wallet.get_balance().await?; + assert_eq!(4_900, balance); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 1)] +async fn test_bolt11_send() -> anyhow::Result<()> { + // create postgres container that will be destroyed after the test is done + let docker = clients::Cli::default(); + let node = Postgres::default().with_host_auth(); + let img = RunnableImage::from(node).with_tag("16.2-alpine"); + let node = docker.run(img); + let host_port = node.get_host_port_ipv4(5432); + + fund_mint_lnd(2_000_000).await?; + open_channel_with_wallet(500_000).await?; + + // start mint server + tokio::spawn(async move { + let lnd_settings = LndLightningSettings::new( + lnd_client::LND_MINT_ADDRESS.parse().expect("invalid url"), + "../data/lnd-mint/tls.cert".into(), + "../data/lnd-mint/data/chain/bitcoin/regtest/admin.macaroon".into(), + ); + + start_mint(host_port, LightningType::Lnd(lnd_settings.clone()), None) + .await + .expect("Could not start mint server"); + }); + + // Wait for the server to start + tokio::time::sleep(Duration::from_millis(800)).await; + + let client = CrossPlatformHttpClient::new(); + let mint_url = Url::parse("http://127.0.0.1:8686")?; + let keys = client.get_keys(&mint_url).await; + assert!(keys.is_ok()); + + let keysets = client.get_keysets(&mint_url).await; + assert!(keysets.is_ok()); + + // create wallet + let tmp_dir = tempfile::tempdir()?; + let wallet_file = tmp_dir.path().join("wallet.db"); + let localstore = SqliteLocalStore::with_path(wallet_file.to_str().unwrap().to_owned()).await?; + let wallet = WalletBuilder::default() + .with_client(client) + .with_localstore(localstore) + .with_mint_url(mint_url) + .build() + .await?; + + // get initial balance + let balance = wallet.get_balance().await?; + assert_eq!(0, balance, "Initial balance should be 0"); + + // mint some tokens + let mint_amount = 2_000; + let mint_quote = wallet.create_quote_bolt11(mint_amount).await?; + let hash = mint_quote.clone().quote; + + sleep_until(Instant::now() + Duration::from_millis(1_000)).await; + let mint_result = wallet + .mint_tokens(&PaymentMethod::Bolt11, mint_amount.into(), hash.clone()) + .await?; + assert_eq!(2_000, mint_result.total_amount()); + + let balance = wallet.get_balance().await?; + assert_eq!(2_000, balance); + + // send tokens + let exported_tokens = wallet.send_tokens(100).await?; + assert_eq!(100, exported_tokens.total_amount()); + let balance = wallet.get_balance().await?; + assert_eq!(1_900, balance); Ok(()) } diff --git a/moksha-wallet/src/localstore/sqlite.rs b/moksha-wallet/src/localstore/sqlite.rs index d3edf2c2..5bfaae37 100644 --- a/moksha-wallet/src/localstore/sqlite.rs +++ b/moksha-wallet/src/localstore/sqlite.rs @@ -34,13 +34,21 @@ impl LocalStore for SqliteLocalStore { .proofs() .iter() .map(|p| p.secret.to_owned()) - .collect::>() - .join(", "); + .collect::>(); + + let placeholders: Vec = (1..=proof_secrets.len()) + .map(|i| format!("?{}", i)) + .collect(); + let sql = format!( + "DELETE FROM proofs WHERE secret IN ({})", + placeholders.join(",") + ); + let mut query = sqlx::query(&sql); + for secret in &proof_secrets { + query = query.bind(secret); + } + query.execute(&mut **tx).await?; - sqlx::query("DELETE FROM proofs WHERE secret in (?);") - .bind(proof_secrets) - .execute(&mut **tx) - .await?; Ok(()) } @@ -99,7 +107,7 @@ impl LocalStore for SqliteLocalStore { ) -> Result<(), MokshaWalletError> { sqlx::query( r#"INSERT INTO keysets (keyset_id, mint_url, currency_unit, last_index, public_keys, active) VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT(keyset_id, mint_url) DO UPDATE SET currency_unit = $3, last_index = $4, public_keys = $5, active = $6; + ON CONFLICT(keyset_id, mint_url) DO UPDATE SET currency_unit = $3, public_keys = $5, active = $6; "#) .bind(keyset.keyset_id.to_owned()) .bind(keyset.mint_url.as_str()) @@ -262,4 +270,37 @@ mod tests { tx.commit().await?; Ok(()) } + + #[tokio::test] + async fn test_delete_multiple_proofs() -> anyhow::Result<()> { + let localstore = SqliteLocalStore::with_in_memory().await?; + let mut tx = localstore.begin_tx().await?; + + let tokens: TokenV3 = read_fixture("token_60.cashu")? + .trim() + .to_string() + .try_into()?; + localstore.add_proofs(&mut tx, &tokens.proofs()).await?; + + let loaded_tokens = localstore.get_proofs(&mut tx).await?; + + assert_eq!(tokens.proofs(), loaded_tokens); + + let proofs = tokens + .tokens + .first() + .expect("Tokens is empty") + .proofs + .proofs(); + let proof_4 = proofs.first().expect("Proof is empty").to_owned(); + let proof_8 = proofs.get(1).expect("Proof is empty").to_owned(); + + let delete_proofs = vec![proof_4, proof_8].into(); + localstore.delete_proofs(&mut tx, &delete_proofs).await?; + + let result_tokens = localstore.get_proofs(&mut tx).await?; + assert_eq!(48, result_tokens.total_amount()); + tx.commit().await?; + Ok(()) + } }