mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-23 23:55:01 +01:00
Test fees (#698)
* feat: Add Docker container setup for Nutshell mint in test-nutshell recipe * test: Add wait mechanism for Nutshell docker container startup * test: Modify Nutshell wallet tests to run sequentially * fix: mintd set input fee pkk * feat: fee tests * fix: melt returning fee in change * fix: fee tests * fix: fee tests
This commit is contained in:
7
.github/workflows/nutshell_itest.yml
vendored
7
.github/workflows/nutshell_itest.yml
vendored
@@ -7,11 +7,6 @@ jobs:
|
||||
name: Nutshell Mint Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Pull and start mint
|
||||
run: |
|
||||
docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:latest poetry run mint
|
||||
- name: Check running containers
|
||||
run: docker ps
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Nix
|
||||
@@ -21,7 +16,7 @@ jobs:
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Test Nutshell
|
||||
run: nix develop -i -L .#stable --command just test-nutshell
|
||||
run: nix develop -i -L .#integration --command just test-nutshell
|
||||
- name: Show logs if tests fail
|
||||
if: failure()
|
||||
run: docker logs nutshell
|
||||
|
||||
@@ -207,7 +207,7 @@ impl MintPayment for FakeWallet {
|
||||
payment_proof: Some("".to_string()),
|
||||
payment_lookup_id: payment_hash,
|
||||
status: payment_status,
|
||||
total_spent: melt_quote.amount,
|
||||
total_spent: melt_quote.amount + 1.into(),
|
||||
unit: melt_quote.unit,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,11 +2,14 @@ use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use cashu::Bolt11Invoice;
|
||||
use cdk::amount::{Amount, SplitTarget};
|
||||
use cdk::nuts::{MintQuoteState, NotificationPayload, State};
|
||||
use cdk::wallet::WalletSubscription;
|
||||
use cdk::Wallet;
|
||||
use init_regtest::get_mint_url;
|
||||
use cdk_fake_wallet::create_fake_invoice;
|
||||
use init_regtest::{get_lnd_dir, get_mint_url, LND_RPC_ADDR};
|
||||
use ln_regtest_rs::ln_client::{LightningClient, LndClient};
|
||||
use tokio::time::{sleep, timeout, Duration};
|
||||
|
||||
pub mod init_auth_mint;
|
||||
@@ -145,3 +148,71 @@ pub fn get_second_mint_url_from_env() -> String {
|
||||
Err(_) => get_mint_url("1"),
|
||||
}
|
||||
}
|
||||
|
||||
// This is the ln wallet we use to send/receive ln payements as the wallet
|
||||
pub async fn init_lnd_client() -> LndClient {
|
||||
let lnd_dir = get_lnd_dir("one");
|
||||
let cert_file = lnd_dir.join("tls.cert");
|
||||
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
||||
LndClient::new(
|
||||
format!("https://{}", LND_RPC_ADDR),
|
||||
cert_file,
|
||||
macaroon_file,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Pays a Bolt11Invoice if it's on the regtest network, otherwise returns Ok
|
||||
///
|
||||
/// This is useful for tests that need to pay invoices in regtest mode but
|
||||
/// should be skipped in other environments.
|
||||
pub async fn pay_if_regtest(invoice: &Bolt11Invoice) -> Result<()> {
|
||||
// Check if the invoice is for the regtest network
|
||||
if invoice.network() == bitcoin::Network::Regtest {
|
||||
println!("Regtest invoice");
|
||||
let lnd_client = init_lnd_client().await;
|
||||
lnd_client.pay_invoice(invoice.to_string()).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
// Not a regtest invoice, just return Ok
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if we're running in regtest mode based on environment variable
|
||||
///
|
||||
/// Checks the CDK_TEST_REGTEST environment variable:
|
||||
/// - If set to "1", "true", or "yes" (case insensitive), returns true
|
||||
/// - Otherwise returns false
|
||||
pub fn is_regtest_env() -> bool {
|
||||
match env::var("CDK_TEST_REGTEST") {
|
||||
Ok(val) => {
|
||||
let val = val.to_lowercase();
|
||||
val == "1" || val == "true" || val == "yes"
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a real invoice if in regtest mode, otherwise returns a fake invoice
|
||||
///
|
||||
/// Uses the is_regtest_env() function to determine whether to
|
||||
/// create a real regtest invoice or a fake one for testing.
|
||||
pub async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
|
||||
if is_regtest_env() {
|
||||
// In regtest mode, create a real invoice
|
||||
let lnd_client = init_lnd_client().await;
|
||||
lnd_client
|
||||
.create_invoice(amount_sat)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to create regtest invoice: {}", e))
|
||||
} else {
|
||||
// Not in regtest mode, create a fake invoice
|
||||
let fake_invoice = create_fake_invoice(
|
||||
amount_sat.expect("Amount must be defined") * 1_000,
|
||||
"".to_string(),
|
||||
);
|
||||
Ok(fake_invoice.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,93 +15,24 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::{char, env};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::{bail, Result};
|
||||
use bip39::Mnemonic;
|
||||
use cashu::{MeltBolt11Request, PreMintSecrets};
|
||||
use cdk::amount::{Amount, SplitTarget};
|
||||
use cdk::nuts::nut00::ProofsMethods;
|
||||
use cdk::nuts::{CurrencyUnit, MeltQuoteState, NotificationPayload, State};
|
||||
use cdk::wallet::{HttpClient, MintConnector, Wallet};
|
||||
use cdk_fake_wallet::create_fake_invoice;
|
||||
use cdk_integration_tests::init_regtest::{get_lnd_dir, LND_RPC_ADDR};
|
||||
use cdk_integration_tests::{get_mint_url_from_env, wait_for_mint_to_be_paid};
|
||||
use cdk_integration_tests::{
|
||||
create_invoice_for_env, get_mint_url_from_env, pay_if_regtest, wait_for_mint_to_be_paid,
|
||||
};
|
||||
use cdk_sqlite::wallet::memory;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use lightning_invoice::Bolt11Invoice;
|
||||
use ln_regtest_rs::ln_client::{LightningClient, LndClient};
|
||||
use serde_json::json;
|
||||
use tokio::time::timeout;
|
||||
use tokio_tungstenite::connect_async;
|
||||
use tokio_tungstenite::tungstenite::protocol::Message;
|
||||
|
||||
// This is the ln wallet we use to send/receive ln payements as the wallet
|
||||
async fn init_lnd_client() -> LndClient {
|
||||
let lnd_dir = get_lnd_dir("one");
|
||||
let cert_file = lnd_dir.join("tls.cert");
|
||||
let macaroon_file = lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon");
|
||||
LndClient::new(
|
||||
format!("https://{}", LND_RPC_ADDR),
|
||||
cert_file,
|
||||
macaroon_file,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Pays a Bolt11Invoice if it's on the regtest network, otherwise returns Ok
|
||||
///
|
||||
/// This is useful for tests that need to pay invoices in regtest mode but
|
||||
/// should be skipped in other environments.
|
||||
async fn pay_if_regtest(invoice: &Bolt11Invoice) -> Result<()> {
|
||||
// Check if the invoice is for the regtest network
|
||||
if invoice.network() == bitcoin::Network::Regtest {
|
||||
println!("Regtest invoice");
|
||||
let lnd_client = init_lnd_client().await;
|
||||
lnd_client.pay_invoice(invoice.to_string()).await?;
|
||||
Ok(())
|
||||
} else {
|
||||
// Not a regtest invoice, just return Ok
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if we're running in regtest mode based on environment variable
|
||||
///
|
||||
/// Checks the CDK_TEST_REGTEST environment variable:
|
||||
/// - If set to "1", "true", or "yes" (case insensitive), returns true
|
||||
/// - Otherwise returns false
|
||||
fn is_regtest_env() -> bool {
|
||||
match env::var("CDK_TEST_REGTEST") {
|
||||
Ok(val) => {
|
||||
let val = val.to_lowercase();
|
||||
val == "1" || val == "true" || val == "yes"
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a real invoice if in regtest mode, otherwise returns a fake invoice
|
||||
///
|
||||
/// Uses the is_regtest_env() function to determine whether to
|
||||
/// create a real regtest invoice or a fake one for testing.
|
||||
async fn create_invoice_for_env(amount_sat: Option<u64>) -> Result<String> {
|
||||
if is_regtest_env() {
|
||||
// In regtest mode, create a real invoice
|
||||
let lnd_client = init_lnd_client().await;
|
||||
lnd_client
|
||||
.create_invoice(amount_sat)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Failed to create regtest invoice: {}", e))
|
||||
} else {
|
||||
// Not in regtest mode, create a fake invoice
|
||||
let fake_invoice = create_fake_invoice(
|
||||
amount_sat.expect("Amount must be defined") * 1_000,
|
||||
"".to_string(),
|
||||
);
|
||||
Ok(fake_invoice.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_notification<T: StreamExt<Item = Result<Message, E>> + Unpin, E: Debug>(
|
||||
reader: &mut T,
|
||||
timeout_to_wait: Duration,
|
||||
@@ -263,7 +194,7 @@ async fn test_happy_mint_melt_round_trip() -> Result<()> {
|
||||
///
|
||||
/// This ensures the basic minting flow works correctly from quote to token issuance.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_happy_mint_melt() -> Result<()> {
|
||||
async fn test_happy_mint() -> Result<()> {
|
||||
let wallet = Wallet::new(
|
||||
&get_mint_url_from_env(),
|
||||
CurrencyUnit::Sat,
|
||||
@@ -330,7 +261,7 @@ async fn test_restore() -> Result<()> {
|
||||
.mint(&mint_quote.id, SplitTarget::default(), None)
|
||||
.await?;
|
||||
|
||||
assert!(wallet.total_balance().await? == 100.into());
|
||||
assert_eq!(wallet.total_balance().await?, 100.into());
|
||||
|
||||
let wallet_2 = Wallet::new(
|
||||
&get_mint_url_from_env(),
|
||||
@@ -340,18 +271,23 @@ async fn test_restore() -> Result<()> {
|
||||
None,
|
||||
)?;
|
||||
|
||||
assert!(wallet_2.total_balance().await? == 0.into());
|
||||
assert_eq!(wallet_2.total_balance().await?, 0.into());
|
||||
|
||||
let restored = wallet_2.restore().await?;
|
||||
let proofs = wallet_2.get_unspent_proofs().await?;
|
||||
|
||||
let expected_fee = wallet.get_proofs_fee(&proofs).await?;
|
||||
wallet_2
|
||||
.swap(None, SplitTarget::default(), proofs, None, false)
|
||||
.await?;
|
||||
|
||||
assert!(restored == 100.into());
|
||||
assert_eq!(restored, 100.into());
|
||||
|
||||
assert_eq!(wallet_2.total_balance().await?, 100.into());
|
||||
// Since we have to do a swap we expect to restore amount - fee
|
||||
assert_eq!(
|
||||
wallet_2.total_balance().await?,
|
||||
Amount::from(100) - expected_fee
|
||||
);
|
||||
|
||||
let proofs = wallet.get_unspent_proofs().await?;
|
||||
|
||||
|
||||
@@ -634,6 +634,50 @@ async fn test_mint_enforce_fee() {
|
||||
let _ = mint_bob.process_swap_request(swap_request).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_mint_change_with_fee_melt() {
|
||||
setup_tracing();
|
||||
let mint_bob = create_and_start_test_mint()
|
||||
.await
|
||||
.expect("Failed to create test mint");
|
||||
|
||||
mint_bob
|
||||
.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, &HashMap::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let wallet_alice = create_test_wallet_for_mint(mint_bob.clone())
|
||||
.await
|
||||
.expect("Failed to create test wallet");
|
||||
|
||||
// Alice gets 100 sats
|
||||
fund_wallet(
|
||||
wallet_alice.clone(),
|
||||
100,
|
||||
Some(SplitTarget::Value(Amount::ONE)),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to fund wallet");
|
||||
|
||||
let proofs = wallet_alice
|
||||
.get_unspent_proofs()
|
||||
.await
|
||||
.expect("Could not get proofs");
|
||||
|
||||
let fake_invoice = create_fake_invoice(1000, "".to_string());
|
||||
|
||||
let melt_quote = wallet_alice
|
||||
.melt_quote(fake_invoice.to_string(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let w = wallet_alice
|
||||
.melt_proofs(&melt_quote.id, proofs)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(w.change.unwrap().total_amount().unwrap(), 97.into());
|
||||
}
|
||||
/// Tests concurrent double-spending attempts by trying to use the same proofs
|
||||
/// in 3 swap transactions simultaneously using tokio tasks
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
|
||||
|
||||
@@ -112,7 +112,7 @@ async fn get_wallet_balance(base_url: &str) -> u64 {
|
||||
}
|
||||
|
||||
/// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test]
|
||||
async fn test_nutshell_wallet_mint() {
|
||||
// Get the wallet URL from environment variable
|
||||
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
|
||||
@@ -137,7 +137,7 @@ async fn test_nutshell_wallet_mint() {
|
||||
}
|
||||
|
||||
/// Test the Nutshell wallet's ability to mint tokens from a Lightning invoice
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test]
|
||||
async fn test_nutshell_wallet_swap() {
|
||||
// Get the wallet URL from environment variable
|
||||
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
|
||||
@@ -194,11 +194,12 @@ async fn test_nutshell_wallet_swap() {
|
||||
|
||||
let token_received = balance - initial_balance;
|
||||
|
||||
assert_eq!(token_received, send_amount);
|
||||
let fee = 1;
|
||||
assert_eq!(token_received, send_amount - fee);
|
||||
}
|
||||
|
||||
/// Test the Nutshell wallet's ability to melt tokens to pay a Lightning invoice
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
#[tokio::test]
|
||||
async fn test_nutshell_wallet_melt() {
|
||||
// Get the wallet URL from environment variable
|
||||
let base_url = std::env::var("WALLET_URL").expect("Wallet url is not set");
|
||||
|
||||
125
crates/cdk-integration-tests/tests/test_fees.rs
Normal file
125
crates/cdk-integration-tests/tests/test_fees.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use bip39::Mnemonic;
|
||||
use cashu::{Bolt11Invoice, ProofsMethods};
|
||||
use cdk::amount::{Amount, SplitTarget};
|
||||
use cdk::nuts::CurrencyUnit;
|
||||
use cdk::wallet::{SendKind, SendOptions, Wallet};
|
||||
use cdk_integration_tests::{
|
||||
create_invoice_for_env, get_mint_url_from_env, pay_if_regtest, wait_for_mint_to_be_paid,
|
||||
};
|
||||
use cdk_sqlite::wallet::memory;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_swap() -> Result<()> {
|
||||
let seed = Mnemonic::generate(12)?.to_seed_normalized("");
|
||||
let wallet = Wallet::new(
|
||||
&get_mint_url_from_env(),
|
||||
CurrencyUnit::Sat,
|
||||
Arc::new(memory::empty().await?),
|
||||
&seed,
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mint_quote = wallet.mint_quote(100.into(), None).await?;
|
||||
|
||||
let invoice = Bolt11Invoice::from_str(&mint_quote.request)?;
|
||||
pay_if_regtest(&invoice).await?;
|
||||
|
||||
let _mint_amount = wallet
|
||||
.mint(&mint_quote.id, SplitTarget::default(), None)
|
||||
.await?;
|
||||
|
||||
let proofs: Vec<Amount> = wallet
|
||||
.get_unspent_proofs()
|
||||
.await?
|
||||
.iter()
|
||||
.map(|p| p.amount)
|
||||
.collect();
|
||||
|
||||
println!("{:?}", proofs);
|
||||
|
||||
let send = wallet
|
||||
.prepare_send(
|
||||
4.into(),
|
||||
SendOptions {
|
||||
send_kind: SendKind::OfflineExact,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let proofs = send.proofs();
|
||||
|
||||
let fee = wallet.get_proofs_fee(&proofs).await?;
|
||||
|
||||
assert_eq!(fee, 1.into());
|
||||
|
||||
let send = wallet.send(send, None).await?;
|
||||
|
||||
let rec_amount = wallet
|
||||
.receive(&send.to_string(), SplitTarget::default(), &[], &[])
|
||||
.await?;
|
||||
|
||||
assert_eq!(rec_amount, 3.into());
|
||||
|
||||
let wallet_balance = wallet.total_balance().await?;
|
||||
|
||||
assert_eq!(wallet_balance, 99.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
|
||||
async fn test_fake_melt_change_in_quote() -> Result<()> {
|
||||
let wallet = Wallet::new(
|
||||
&get_mint_url_from_env(),
|
||||
CurrencyUnit::Sat,
|
||||
Arc::new(memory::empty().await?),
|
||||
&Mnemonic::generate(12)?.to_seed_normalized(""),
|
||||
None,
|
||||
)?;
|
||||
|
||||
let mint_quote = wallet.mint_quote(100.into(), None).await?;
|
||||
|
||||
let bolt11 = Bolt11Invoice::from_str(&mint_quote.request)?;
|
||||
|
||||
pay_if_regtest(&bolt11).await?;
|
||||
|
||||
wait_for_mint_to_be_paid(&wallet, &mint_quote.id, 60).await?;
|
||||
|
||||
let _mint_amount = wallet
|
||||
.mint(&mint_quote.id, SplitTarget::default(), None)
|
||||
.await?;
|
||||
|
||||
let invoice_amount = 9;
|
||||
|
||||
let invoice = create_invoice_for_env(Some(invoice_amount)).await?;
|
||||
|
||||
let melt_quote = wallet.melt_quote(invoice.to_string(), None).await?;
|
||||
|
||||
let proofs = wallet.get_unspent_proofs().await?;
|
||||
|
||||
let proofs_total = proofs.total_amount().unwrap();
|
||||
|
||||
let fee = wallet.get_proofs_fee(&proofs).await?;
|
||||
let melt = wallet.melt_proofs(&melt_quote.id, proofs.clone()).await?;
|
||||
let change = melt.change.unwrap().total_amount().unwrap();
|
||||
let idk = proofs.total_amount()? - Amount::from(invoice_amount) - change;
|
||||
|
||||
println!("{}", idk);
|
||||
println!("{}", fee);
|
||||
println!("{}", proofs_total);
|
||||
println!("{}", change);
|
||||
|
||||
let ln_fee = 1;
|
||||
|
||||
assert_eq!(
|
||||
wallet.total_balance().await?,
|
||||
Amount::from(100 - invoice_amount - u64::from(fee) - ln_fee)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -207,6 +207,10 @@ async fn main() -> anyhow::Result<()> {
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||
mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
||||
}
|
||||
|
||||
let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
|
||||
|
||||
mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
||||
@@ -226,6 +230,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::new(lnbits),
|
||||
)
|
||||
.await?;
|
||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||
mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
||||
}
|
||||
|
||||
let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
|
||||
|
||||
@@ -246,6 +253,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::new(lnd),
|
||||
)
|
||||
.await?;
|
||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||
mint_builder = mint_builder.set_unit_fee(&CurrencyUnit::Sat, input_fee)?;
|
||||
}
|
||||
|
||||
let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, CurrencyUnit::Sat);
|
||||
|
||||
@@ -272,6 +282,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
fake.clone(),
|
||||
)
|
||||
.await?;
|
||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||
mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
|
||||
}
|
||||
|
||||
let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, unit);
|
||||
|
||||
@@ -308,6 +321,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::new(processor),
|
||||
)
|
||||
.await?;
|
||||
if let Some(input_fee) = settings.info.input_fee_ppk {
|
||||
mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
|
||||
}
|
||||
|
||||
let nut17_supported = SupportedMethods::new(PaymentMethod::Bolt11, unit);
|
||||
mint_builder = mint_builder.add_supported_websockets(nut17_supported);
|
||||
|
||||
@@ -292,6 +292,20 @@ impl MintBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the input fee ppk for a given unit
|
||||
///
|
||||
/// The unit **MUST** already have been added with a ln backend
|
||||
pub fn set_unit_fee(mut self, unit: &CurrencyUnit, input_fee_ppk: u64) -> Result<Self, Error> {
|
||||
let (input_fee, _max_order) = self
|
||||
.supported_units
|
||||
.get_mut(unit)
|
||||
.ok_or(Error::UnsupportedUnit)?;
|
||||
|
||||
*input_fee = input_fee_ppk;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Build mint
|
||||
pub async fn build(&self) -> anyhow::Result<Mint> {
|
||||
let localstore = self
|
||||
|
||||
@@ -669,7 +669,10 @@ impl Mint {
|
||||
return Err(Error::BlindedMessageAlreadySigned);
|
||||
}
|
||||
|
||||
let change_target = melt_request.proofs_amount()? - total_spent;
|
||||
let fee = self.get_proofs_fee(melt_request.inputs()).await?;
|
||||
|
||||
let change_target = melt_request.proofs_amount()? - total_spent - fee;
|
||||
|
||||
let mut amounts = change_target.split();
|
||||
let mut change_sigs = Vec::with_capacity(amounts.len());
|
||||
|
||||
|
||||
34
justfile
34
justfile
@@ -66,12 +66,34 @@ test-all db="memory":
|
||||
./misc/fake_itests.sh "{{db}}"
|
||||
|
||||
test-nutshell:
|
||||
#!/usr/bin/env bash
|
||||
export CDK_TEST_MINT_URL=http://127.0.0.1:3338
|
||||
export LN_BACKEND=FAKEWALLET
|
||||
cargo test -p cdk-integration-tests --test happy_path_mint_wallet
|
||||
unset CDK_TEST_MINT_URL
|
||||
unset LN_BACKEND
|
||||
#!/usr/bin/env bash
|
||||
docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY -e MINT_INPUT_FEE_PPK=100 cashubtc/nutshell:latest poetry run mint
|
||||
|
||||
# Wait for the Nutshell service to be ready
|
||||
echo "Waiting for Nutshell to start..."
|
||||
max_attempts=30
|
||||
attempt=0
|
||||
while ! curl -s http://127.0.0.1:3338/v1/info > /dev/null; do
|
||||
attempt=$((attempt+1))
|
||||
if [ $attempt -ge $max_attempts ]; then
|
||||
echo "Nutshell failed to start after $max_attempts attempts"
|
||||
docker stop nutshell
|
||||
docker rm nutshell
|
||||
exit 1
|
||||
fi
|
||||
echo "Waiting for Nutshell to start (attempt $attempt/$max_attempts)..."
|
||||
sleep 1
|
||||
done
|
||||
echo "Nutshell is ready!"
|
||||
|
||||
export CDK_TEST_MINT_URL=http://127.0.0.1:3338
|
||||
export LN_BACKEND=FAKEWALLET
|
||||
cargo test -p cdk-integration-tests --test happy_path_mint_wallet
|
||||
cargo test -p cdk-integration-tests --test test_fees
|
||||
unset CDK_TEST_MINT_URL
|
||||
unset LN_BACKEND
|
||||
docker stop nutshell
|
||||
docker rm nutshell
|
||||
|
||||
|
||||
# run `cargo clippy` on everything
|
||||
|
||||
@@ -33,6 +33,7 @@ cleanup() {
|
||||
unset CDK_MINTD_LN_BACKEND CDK_MINTD_FAKE_WALLET_SUPPORTED_UNITS CDK_MINTD_MNEMONIC
|
||||
unset CDK_MINTD_FAKE_WALLET_FEE_PERCENT CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN CDK_MINTD_DATABASE
|
||||
unset TEST_STATUS
|
||||
unset CDK_MINTD_INPUT_FEE_PPK
|
||||
echo "Cleanup complete."
|
||||
}
|
||||
|
||||
@@ -55,6 +56,7 @@ export CDK_MINTD_MNEMONIC="eye survey guilt napkin crystal cup whisper salt lugg
|
||||
export CDK_MINTD_FAKE_WALLET_FEE_PERCENT="0"
|
||||
export CDK_MINTD_FAKE_WALLET_RESERVE_FEE_MIN="1"
|
||||
export CDK_MINTD_DATABASE="redb"
|
||||
export CDK_MINTD_INPUT_FEE_PPK="100"
|
||||
|
||||
|
||||
echo "Starting fake mintd"
|
||||
@@ -150,10 +152,12 @@ fi
|
||||
# Export URLs as environment variables
|
||||
export MINT_URL=${MINT_URL}
|
||||
export WALLET_URL=${WALLET_URL}
|
||||
export CDK_TEST_MINT_URL=${MINT_URL}
|
||||
|
||||
# Run the integration test
|
||||
echo "Running integration test..."
|
||||
cargo test -p cdk-integration-tests --tests nutshell_wallet
|
||||
cargo test -p cdk-integration-tests --test nutshell_wallet
|
||||
cargo test -p cdk-integration-tests --test test_fees
|
||||
TEST_STATUS=$?
|
||||
|
||||
# Exit with the test status
|
||||
|
||||
Reference in New Issue
Block a user