Refactor MintBuilder (#887)

* Refactor MintBuilder
* Validate azp instead of aud for client id
This commit is contained in:
David Caseria
2025-07-19 12:13:11 -04:00
committed by GitHub
parent 7f0e261a25
commit f018465aa6
20 changed files with 386 additions and 395 deletions

View File

@@ -20,7 +20,7 @@ pub(crate) async fn handle(
let mut subscription = context
.state
.mint
.pubsub_manager
.pubsub_manager()
.try_subscribe(params)
.await
.map_err(|_| WsError::ParseError)?;

View File

@@ -82,7 +82,7 @@ async fn get_device_code_token(mint_info: &MintInfo, client_id: &str) -> (String
.expect("Nut21 defined")
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the OIDC configuration
let oidc_config = oidc_client

View File

@@ -93,7 +93,7 @@ async fn get_access_token(
.expect("Nut21 defined")
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration
let token_url = oidc_client

View File

@@ -164,7 +164,7 @@ async fn refresh_access_token(
.ok_or_else(|| anyhow::anyhow!("OIDC discovery information not available"))?
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration
let token_url = oidc_client.get_oidc_config().await?.token_endpoint;

View File

@@ -162,8 +162,8 @@ impl PaymentProcessorKey {
}
}
/// Secs wuotes are valid
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, Default)]
/// Seconds quotes are valid
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub struct QuoteTTL {
/// Seconds mint quote is valid
pub mint_ttl: u64,
@@ -178,6 +178,15 @@ impl QuoteTTL {
}
}
impl Default for QuoteTTL {
fn default() -> Self {
Self {
mint_ttl: 60 * 60, // 1 hour
melt_ttl: 60, // 1 minute
}
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;

View File

@@ -31,14 +31,10 @@ where
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
let mut mint_builder = MintBuilder::new();
let mut mint_builder = MintBuilder::new(Arc::new(database));
mint_builder = mint_builder
.with_localstore(Arc::new(database))
.with_keystore(Arc::new(key_store));
mint_builder = mint_builder
.add_ln_backend(
mint_builder
.add_payment_processor(
CurrencyUnit::Sat,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 300),
@@ -46,10 +42,14 @@ where
)
.await?;
mint_builder =
mint_builder.set_clear_auth_settings(openid_discovery, "cashu-client".to_string());
let auth_database = Arc::new(auth_database);
mint_builder = mint_builder.set_blind_auth_settings(50);
mint_builder = mint_builder.with_auth(
auth_database.clone(),
openid_discovery,
"cashu-client".to_string(),
vec![],
);
let blind_auth_endpoints = vec![
ProtectedEndpoint::new(Method::Post, RoutePath::MintQuoteBolt11),
@@ -71,6 +71,8 @@ where
acc
});
mint_builder = mint_builder.with_blind_auth(50, blind_auth_endpoints.keys().cloned().collect());
let mut tx = auth_database.begin_transaction().await?;
tx.add_protected_endpoints(blind_auth_endpoints).await?;
@@ -85,15 +87,13 @@ where
tx.commit().await?;
mint_builder = mint_builder.with_auth_localstore(Arc::new(auth_database));
let mnemonic = Mnemonic::generate(12)?;
mint_builder = mint_builder
.with_description("fake test mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
mint_builder = mint_builder.with_description("fake test mint".to_string());
let _mint = mint_builder.build().await?;
let _mint = mint_builder
.build_with_seed(Arc::new(key_store), &mnemonic.to_seed_normalized(""))
.await?;
todo!("Need to start this a cdk mintd keeping as ref for now");
}

View File

@@ -10,7 +10,7 @@ use async_trait::async_trait;
use bip39::Mnemonic;
use cashu::{MeltQuoteBolt12Request, MintQuoteBolt12Request, MintQuoteBolt12Response};
use cdk::amount::SplitTarget;
use cdk::cdk_database::{self, WalletDatabase};
use cdk::cdk_database::{self, MintDatabase, WalletDatabase};
use cdk::mint::{MintBuilder, MintMeltLimits};
use cdk::nuts::nut00::ProofsMethods;
use cdk::nuts::{
@@ -227,25 +227,22 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
// Read environment variable to determine database type
let db_type = env::var("CDK_TEST_DB_TYPE").expect("Database type set");
let mut mint_builder = match db_type.to_lowercase().as_str() {
"memory" => MintBuilder::new()
.with_localstore(Arc::new(cdk_sqlite::mint::memory::empty().await?))
.with_keystore(Arc::new(cdk_sqlite::mint::memory::empty().await?)),
let localstore = match db_type.to_lowercase().as_str() {
"memory" => Arc::new(cdk_sqlite::mint::memory::empty().await?),
_ => {
// Create a temporary directory for SQLite database
let temp_dir = create_temp_dir("cdk-test-sqlite-mint")?;
let path = temp_dir.join("mint.db").to_str().unwrap().to_string();
let database = Arc::new(
Arc::new(
cdk_sqlite::MintSqliteDatabase::new(&path)
.await
.expect("Could not create sqlite db"),
);
MintBuilder::new()
.with_localstore(database.clone())
.with_keystore(database)
)
}
};
let mut mint_builder = MintBuilder::new(localstore.clone());
let fee_reserve = FeeReserve {
min_fee_reserve: 1.into(),
percent_fee_reserve: 1.0,
@@ -258,8 +255,8 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
0,
);
mint_builder = mint_builder
.add_ln_backend(
mint_builder
.add_payment_processor(
CurrencyUnit::Sat,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 10_000),
@@ -272,23 +269,18 @@ pub async fn create_and_start_test_mint() -> Result<Mint> {
mint_builder = mint_builder
.with_name("pure test mint".to_string())
.with_description("pure test mint".to_string())
.with_urls(vec!["https://aaa".to_string()])
.with_seed(mnemonic.to_seed_normalized("").to_vec());
.with_urls(vec!["https://aaa".to_string()]);
let localstore = mint_builder
.localstore
.as_ref()
.map(|x| x.clone())
.expect("localstore");
let mut tx = localstore.begin_transaction().await?;
tx.set_mint_info(mint_builder.mint_info.clone()).await?;
let tx_localstore = localstore.clone();
let mut tx = tx_localstore.begin_transaction().await?;
let quote_ttl = QuoteTTL::new(10000, 10000);
tx.set_quote_ttl(quote_ttl).await?;
tx.commit().await?;
let mint = mint_builder.build().await?;
let mint = mint_builder
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
.await?;
let mint_clone = mint.clone();
let shutdown = Arc::new(Notify::new());

View File

@@ -753,7 +753,7 @@ async fn get_access_token(mint_info: &MintInfo) -> (String, String) {
.expect("Nutxx defined")
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration
let token_url = oidc_client
@@ -811,7 +811,7 @@ async fn get_custom_access_token(
.expect("Nutxx defined")
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration
let token_url = oidc_client

View File

@@ -443,7 +443,7 @@ pub async fn test_p2pk_swap() {
.collect();
let mut listener = mint_bob
.pubsub_manager
.pubsub_manager()
.try_subscribe::<IndexableParams>(
Params {
kind: cdk::nuts::nut17::Kind::ProofState,
@@ -773,7 +773,7 @@ async fn test_concurrent_double_spend_swap() {
// Verify that all proofs are marked as spent in the mint
let states = mint_bob
.localstore
.localstore()
.get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
.await
.expect("Failed to get proof state");
@@ -870,7 +870,7 @@ async fn test_concurrent_double_spend_melt() {
// Verify that all proofs are marked as spent in the mint
let states = mint_bob
.localstore
.localstore()
.get_proofs_states(&proofs.iter().map(|p| p.y().unwrap()).collect::<Vec<_>>())
.await
.expect("Failed to get proof state");

View File

@@ -29,14 +29,15 @@ async fn test_correct_keyset() {
let fake_wallet = FakeWallet::new(fee_reserve, HashMap::default(), HashSet::default(), 0);
let mut mint_builder = MintBuilder::new();
let localstore = Arc::new(database);
mint_builder = mint_builder
.with_localstore(localstore.clone())
.with_keystore(localstore.clone());
let mut mint_builder = MintBuilder::new(localstore.clone());
mint_builder = mint_builder
.add_ln_backend(
.with_name("regtest mint".to_string())
.with_description("regtest mint".to_string());
mint_builder
.add_payment_processor(
CurrencyUnit::Sat,
PaymentMethod::Bolt11,
MintMeltLimits::new(1, 5_000),
@@ -44,18 +45,14 @@ async fn test_correct_keyset() {
)
.await
.unwrap();
// .with_seed(mnemonic.to_seed_normalized("").to_vec());
mint_builder = mint_builder
.with_name("regtest mint".to_string())
.with_description("regtest mint".to_string())
.with_seed(mnemonic.to_seed_normalized("").to_vec());
let mint = mint_builder.build().await.unwrap();
let mut tx = localstore.begin_transaction().await.unwrap();
tx.set_mint_info(mint_builder.mint_info.clone())
let mint = mint_builder
.build_with_seed(localstore.clone(), &mnemonic.to_seed_normalized(""))
.await
.unwrap();
let mut tx = localstore.begin_transaction().await.unwrap();
let quote_ttl = QuoteTTL::new(10000, 10000);
tx.set_quote_ttl(quote_ttl).await.unwrap();
@@ -81,7 +78,6 @@ async fn test_correct_keyset() {
assert_ne!(keyset_info.id, old_keyset_info.id);
mint.rotate_keyset(CurrencyUnit::Sat, 32, 0).await.unwrap();
let mint = mint_builder.build().await.unwrap();
let active = mint.get_active_keysets();

View File

@@ -643,7 +643,7 @@ impl CdkMint for MintRPCServer {
let mint_quote = self
.mint
.localstore
.localstore()
.get_mint_quote(&quote_id)
.await
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?
@@ -659,9 +659,8 @@ impl CdkMint for MintRPCServer {
payment_identifier: mint_quote.request_lookup_id.clone(),
};
let mut tx = self
.mint
.localstore
let localstore = self.mint.localstore();
let mut tx = localstore
.begin_transaction()
.await
.map_err(|_| Status::internal("Could not start db transaction".to_string()))?;
@@ -693,9 +692,8 @@ impl CdkMint for MintRPCServer {
vec![], // payment_ids
);
let mut tx = self
.mint
.localstore
let mint_store = self.mint.localstore();
let mut tx = mint_store
.begin_transaction()
.await
.map_err(|_| Status::internal("Could not update quote".to_string()))?;
@@ -710,7 +708,7 @@ impl CdkMint for MintRPCServer {
let mint_quote = self
.mint
.localstore
.localstore()
.get_mint_quote(&quote_id)
.await
.map_err(|_| Status::invalid_argument("Could not find quote".to_string()))?

View File

@@ -19,7 +19,7 @@ use bip39::Mnemonic;
use cdk::cdk_database::{self, MintDatabase, MintKeysDatabase};
use cdk::cdk_payment;
use cdk::cdk_payment::MintPayment;
use cdk::mint::{MintBuilder, MintMeltLimits};
use cdk::mint::{Mint, MintBuilder, MintMeltLimits};
#[cfg(any(
feature = "cln",
feature = "lnbits",
@@ -84,16 +84,13 @@ compile_error!(
async fn main() -> Result<()> {
let (work_dir, settings, localstore, keystore) = initial_setup().await?;
let mint_builder = MintBuilder::new()
.with_localstore(localstore)
.with_keystore(keystore);
let mint_builder = MintBuilder::new(localstore);
let (mint_builder, ln_routers) = configure_mint_builder(&settings, mint_builder).await?;
#[cfg(feature = "auth")]
let mint_builder = setup_authentication(&settings, &work_dir, mint_builder).await?;
let mint_builder_info = mint_builder.mint_info.clone();
let mint = mint_builder.build().await?;
let mint = build_mint(&settings, keystore, mint_builder).await?;
tracing::debug!("Mint built from builder.");
@@ -109,7 +106,7 @@ async fn main() -> Result<()> {
&settings,
ln_routers,
&work_dir,
mint_builder_info,
mint.mint_info().await?,
)
.await?;
@@ -236,9 +233,6 @@ async fn configure_mint_builder(
// Configure lightning backend
let mint_builder = configure_lightning_backend(settings, mint_builder, &mut ln_routers).await?;
// Configure signatory or seed
let mint_builder = configure_signing_method(settings, mint_builder).await?;
// Configure caching
let mint_builder = configure_cache(settings, mint_builder);
@@ -274,7 +268,7 @@ fn configure_basic_info(settings: &config::Settings, mint_builder: MintBuilder)
}
for contact in contacts {
builder = builder.add_contact_info(contact);
builder = builder.with_contact_info(contact);
}
if let Some(pubkey) = settings.mint_info.pubkey {
@@ -435,8 +429,8 @@ async fn configure_backend_for_unit(
if let Some(bolt12) = payment_settings.get("bolt12") {
if bolt12.as_bool().unwrap_or_default() {
mint_builder = mint_builder
.add_ln_backend(
mint_builder
.add_payment_processor(
unit.clone(),
PaymentMethod::Bolt12,
mint_melt_limits,
@@ -446,8 +440,8 @@ async fn configure_backend_for_unit(
}
}
mint_builder = mint_builder
.add_ln_backend(
mint_builder
.add_payment_processor(
unit.clone(),
PaymentMethod::Bolt11,
mint_melt_limits,
@@ -456,47 +450,15 @@ async fn configure_backend_for_unit(
.await?;
if let Some(input_fee) = settings.info.input_fee_ppk {
mint_builder = mint_builder.set_unit_fee(&unit, input_fee)?;
mint_builder.set_unit_fee(&unit, input_fee)?;
}
let nut17_supported = SupportedMethods::default_bolt11(unit);
mint_builder = mint_builder.add_supported_websockets(nut17_supported);
mint_builder = mint_builder.with_supported_websockets(nut17_supported);
Ok(mint_builder)
}
/// Configures the signing method (remote signatory or local seed)
async fn configure_signing_method(
settings: &config::Settings,
mint_builder: MintBuilder,
) -> Result<MintBuilder> {
if let Some(signatory_url) = settings.info.signatory_url.clone() {
tracing::info!(
"Connecting to remote signatory to {} with certs {:?}",
signatory_url,
settings.info.signatory_certs.clone()
);
Ok(mint_builder.with_signatory(Arc::new(
cdk_signatory::SignatoryRpcClient::new(
signatory_url,
settings.info.signatory_certs.clone(),
)
.await?,
)))
} else if let Some(mnemonic) = settings
.info
.mnemonic
.clone()
.map(|s| Mnemonic::from_str(&s))
.transpose()?
{
Ok(mint_builder.with_seed(mnemonic.to_seed_normalized("").to_vec()))
} else {
bail!("No seed nor remote signatory set");
}
}
/// Configures cache settings
fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> MintBuilder {
let cached_endpoints = vec![
@@ -506,7 +468,7 @@ fn configure_cache(settings: &config::Settings, mint_builder: MintBuilder) -> Mi
];
let cache: HttpCache = settings.info.http_cache.clone().into();
mint_builder.add_cache(Some(cache.ttl.as_secs()), cached_endpoints)
mint_builder.with_cache(Some(cache.ttl.as_secs()), cached_endpoints)
}
#[cfg(feature = "auth")]
@@ -532,16 +494,9 @@ async fn setup_authentication(
}
};
mint_builder = mint_builder.with_auth_localstore(auth_localstore.clone());
let mint_blind_auth_endpoint =
ProtectedEndpoint::new(Method::Post, RoutePath::MintBlindAuth);
mint_builder = mint_builder.set_clear_auth_settings(
auth_settings.openid_discovery,
auth_settings.openid_client_id,
);
let mut protected_endpoints = HashMap::new();
protected_endpoints.insert(mint_blind_auth_endpoint, AuthRequired::Clear);
@@ -644,7 +599,14 @@ async fn setup_authentication(
}
}
mint_builder = mint_builder.set_blind_auth_settings(auth_settings.mint_max_bat);
mint_builder = mint_builder.with_auth(
auth_localstore.clone(),
auth_settings.openid_discovery,
auth_settings.openid_client_id,
vec![mint_blind_auth_endpoint],
);
mint_builder =
mint_builder.with_blind_auth(auth_settings.mint_max_bat, blind_auth_endpoints);
let mut tx = auth_localstore.begin_transaction().await?;
@@ -655,6 +617,43 @@ async fn setup_authentication(
Ok(mint_builder)
}
/// Build mints with the configured the signing method (remote signatory or local seed)
async fn build_mint(
settings: &config::Settings,
keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
mint_builder: MintBuilder,
) -> Result<Mint> {
if let Some(signatory_url) = settings.info.signatory_url.clone() {
tracing::info!(
"Connecting to remote signatory to {} with certs {:?}",
signatory_url,
settings.info.signatory_certs.clone()
);
Ok(mint_builder
.build_with_signatory(Arc::new(
cdk_signatory::SignatoryRpcClient::new(
signatory_url,
settings.info.signatory_certs.clone(),
)
.await?,
))
.await?)
} else if let Some(mnemonic) = settings
.info
.mnemonic
.clone()
.map(|s| Mnemonic::from_str(&s))
.transpose()?
{
Ok(mint_builder
.build_with_seed(keystore, &mnemonic.to_seed_normalized(""))
.await?)
} else {
bail!("No seed nor remote signatory set");
}
}
async fn start_services(
mint: Arc<cdk::mint::Mint>,
settings: &config::Settings,
@@ -704,7 +703,7 @@ async fn start_services(
mint.set_mint_info(mint_builder_info).await?;
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
} else {
if mint.localstore.get_quote_ttl().await.is_err() {
if mint.localstore().get_quote_ttl().await.is_err() {
mint.set_quote_ttl(QuoteTTL::new(10_000, 10_000)).await?;
}
// Add version information

View File

@@ -112,7 +112,7 @@ async fn get_access_token(mint_info: &MintInfo) -> String {
.expect("Nut21 defined")
.openid_discovery;
let oidc_client = OidcClient::new(openid_discovery);
let oidc_client = OidcClient::new(openid_discovery, None);
// Get the token endpoint from the OIDC configuration
let token_url = oidc_client

View File

@@ -3,13 +3,13 @@
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::anyhow;
use bitcoin::bip32::DerivationPath;
use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
use cdk_common::error::Error;
use cdk_common::nut04::MintMethodOptions;
use cdk_common::nut05::MeltMethodOptions;
use cdk_common::payment::Bolt11Settings;
#[cfg(feature = "auth")]
use cdk_common::{nut21, nut22};
use cdk_signatory::signatory::Signatory;
@@ -19,103 +19,91 @@ use super::nut19::{self, CachedEndpoint};
use super::MintAuthDatabase;
use super::Nuts;
use crate::amount::Amount;
#[cfg(feature = "auth")]
use crate::cdk_database;
use crate::cdk_payment::{self, MintPayment};
use crate::mint::Mint;
#[cfg(feature = "auth")]
use crate::nuts::ProtectedEndpoint;
use crate::nuts::{
ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, MintVersion,
MppMethodSettings, PaymentMethod,
};
use crate::types::PaymentProcessorKey;
/// Cashu Mint
#[derive(Default)]
/// Cashu Mint Builder
pub struct MintBuilder {
/// Mint Info
pub mint_info: MintInfo,
/// Mint Storage backend
pub localstore: Option<Arc<dyn MintDatabase<database::Error> + Send + Sync>>,
/// Database for the Signatory
keystore: Option<Arc<dyn MintKeysDatabase<Err = database::Error> + Send + Sync>>,
/// Mint Storage backend
mint_info: MintInfo,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
#[cfg(feature = "auth")]
auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>>,
/// Ln backends for mint
ln: Option<
payment_processors:
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
>,
seed: Option<Vec<u8>>,
supported_units: HashMap<CurrencyUnit, (u64, u8)>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
// protected_endpoints: HashMap<ProtectedEndpoint, AuthRequired>,
openid_discovery: Option<String>,
signatory: Option<Arc<dyn Signatory + Sync + Send + 'static>>,
}
impl MintBuilder {
/// New mint builder
pub fn new() -> MintBuilder {
let mut builder = MintBuilder::default();
let nuts = Nuts::new()
/// New [`MintBuilder`]
pub fn new(localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>) -> MintBuilder {
let mint_info = MintInfo {
nuts: Nuts::new()
.nut07(true)
.nut08(true)
.nut09(true)
.nut10(true)
.nut11(true)
.nut12(true)
.nut14(true)
.nut20(true);
.nut12(true),
..Default::default()
};
builder.mint_info.nuts = nuts;
builder
}
/// Set signatory service
pub fn with_signatory(mut self, signatory: Arc<dyn Signatory + Sync + Send + 'static>) -> Self {
self.signatory = Some(signatory);
self
}
/// Set seed
pub fn with_seed(mut self, seed: Vec<u8>) -> Self {
self.seed = Some(seed);
self
}
/// Set localstore
pub fn with_localstore(
mut self,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
) -> MintBuilder {
self.localstore = Some(localstore);
self
}
/// Set keystore database
pub fn with_keystore(
mut self,
keystore: Arc<dyn MintKeysDatabase<Err = database::Error> + Send + Sync>,
) -> MintBuilder {
self.keystore = Some(keystore);
self
}
/// Set auth localstore
MintBuilder {
mint_info,
localstore,
#[cfg(feature = "auth")]
pub fn with_auth_localstore(
auth_localstore: None,
payment_processors: HashMap::new(),
supported_units: HashMap::new(),
custom_paths: HashMap::new(),
}
}
/// Set clear auth settings
#[cfg(feature = "auth")]
pub fn with_auth(
mut self,
localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>,
) -> MintBuilder {
self.auth_localstore = Some(localstore);
auth_localstore: Arc<dyn MintAuthDatabase<Err = cdk_database::Error> + Send + Sync>,
openid_discovery: String,
client_id: String,
protected_endpoints: Vec<ProtectedEndpoint>,
) -> Self {
self.auth_localstore = Some(auth_localstore);
self.mint_info.nuts.nut21 = Some(nut21::Settings::new(
openid_discovery,
client_id,
protected_endpoints,
));
self
}
/// Set Openid discovery url
pub fn with_openid_discovery(mut self, openid_discovery: String) -> Self {
self.openid_discovery = Some(openid_discovery);
/// Set blind auth settings
#[cfg(feature = "auth")]
pub fn with_blind_auth(
mut self,
bat_max_mint: u64,
protected_endpoints: Vec<ProtectedEndpoint>,
) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut22 = Some(nut22::Settings::new(bat_max_mint, protected_endpoints));
self.mint_info.nuts = nuts;
self
}
/// Set mint info
pub fn with_mint_info(mut self, mint_info: MintInfo) -> Self {
self.mint_info = mint_info;
self
}
@@ -168,32 +156,68 @@ impl MintBuilder {
}
/// Set contact info
pub fn add_contact_info(mut self, contact_info: ContactInfo) -> Self {
pub fn with_contact_info(mut self, contact_info: ContactInfo) -> Self {
let mut contacts = self.mint_info.contact.clone().unwrap_or_default();
contacts.push(contact_info);
self.mint_info.contact = Some(contacts);
self
}
/// Add ln backend
pub async fn add_ln_backend(
/// Set pubkey
pub fn with_pubkey(mut self, pubkey: crate::nuts::PublicKey) -> Self {
self.mint_info.pubkey = Some(pubkey);
self
}
/// Support websockets
pub fn with_supported_websockets(mut self, supported_method: SupportedMethods) -> Self {
let mut supported_settings = self.mint_info.nuts.nut17.supported.clone();
if !supported_settings.contains(&supported_method) {
supported_settings.push(supported_method);
self.mint_info.nuts = self.mint_info.nuts.nut17(supported_settings);
}
self
}
/// Add support for NUT19
pub fn with_cache(mut self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
let nut19_settings = nut19::Settings {
ttl,
cached_endpoints,
};
self.mint_info.nuts.nut19 = nut19_settings;
self
}
/// Set custom derivation paths for mint units
pub fn with_custom_derivation_paths(
mut self,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Self {
self.custom_paths = custom_paths;
self
}
/// Add payment processor
pub async fn add_payment_processor(
&mut self,
unit: CurrencyUnit,
method: PaymentMethod,
limits: MintMeltLimits,
ln_backend: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
) -> Result<Self, Error> {
let ln_key = PaymentProcessorKey {
payment_processor: Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
) -> Result<(), Error> {
let key = PaymentProcessorKey {
unit: unit.clone(),
method: method.clone(),
};
tracing::debug!("Adding ln backed for {}, {}", unit, method);
tracing::debug!("with limits {:?}", limits);
let mut ln = self.ln.unwrap_or_default();
let settings = ln_backend.get_settings().await?;
let settings = payment_processor.get_settings().await?;
let settings: Bolt11Settings = settings.try_into()?;
@@ -235,91 +259,19 @@ impl MintBuilder {
self.mint_info.nuts.nut05.methods.push(melt_method_settings);
self.mint_info.nuts.nut05.disabled = false;
ln.insert(ln_key.clone(), ln_backend);
let mut supported_units = self.supported_units.clone();
supported_units.insert(ln_key.unit, (0, 32));
supported_units.insert(key.unit.clone(), (0, 32));
self.supported_units = supported_units;
self.ln = Some(ln);
Ok(self)
}
/// Set pubkey
pub fn with_pubkey(mut self, pubkey: crate::nuts::PublicKey) -> Self {
self.mint_info.pubkey = Some(pubkey);
self
}
/// Support websockets
pub fn add_supported_websockets(mut self, supported_method: SupportedMethods) -> Self {
let mut supported_settings = self.mint_info.nuts.nut17.supported.clone();
if !supported_settings.contains(&supported_method) {
supported_settings.push(supported_method);
self.mint_info.nuts = self.mint_info.nuts.nut17(supported_settings);
}
self
}
/// Add support for NUT19
pub fn add_cache(mut self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
let nut19_settings = nut19::Settings {
ttl,
cached_endpoints,
};
self.mint_info.nuts.nut19 = nut19_settings;
self
}
/// Set custom derivation paths for mint units
pub fn add_custom_derivation_paths(
mut self,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Self {
self.custom_paths = custom_paths;
self
}
/// Set clear auth settings
pub fn set_clear_auth_settings(mut self, openid_discovery: String, client_id: String) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut21 = Some(nut21::Settings::new(
openid_discovery.clone(),
client_id,
vec![],
));
self.openid_discovery = Some(openid_discovery);
self.mint_info.nuts = nuts;
self
}
/// Set blind auth settings
pub fn set_blind_auth_settings(mut self, bat_max_mint: u64) -> Self {
let mut nuts = self.mint_info.nuts;
nuts.nut22 = Some(nut22::Settings::new(bat_max_mint, vec![]));
self.mint_info.nuts = nuts;
self
self.payment_processors.insert(key, payment_processor);
Ok(())
}
/// 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> {
pub fn set_unit_fee(&mut self, unit: &CurrencyUnit, input_fee_ppk: u64) -> Result<(), Error> {
let (input_fee, _max_order) = self
.supported_units
.get_mut(unit)
@@ -327,59 +279,53 @@ impl MintBuilder {
*input_fee = input_fee_ppk;
Ok(self)
Ok(())
}
/// Build mint
pub async fn build(&self) -> anyhow::Result<Mint> {
let localstore = self
.localstore
.clone()
.ok_or(anyhow!("Localstore not set"))?;
let ln = self.ln.clone().ok_or(anyhow!("Ln backends not set"))?;
/// Build the mint with the provided signatory
pub async fn build_with_signatory(
self,
signatory: Arc<dyn Signatory + Send + Sync>,
) -> Result<Mint, Error> {
#[cfg(feature = "auth")]
if let Some(auth_localstore) = self.auth_localstore {
return Mint::new_with_auth(
self.mint_info,
signatory,
self.localstore,
auth_localstore,
self.payment_processors,
)
.await;
}
Mint::new(
self.mint_info,
signatory,
self.localstore,
self.payment_processors,
)
.await
}
let signatory = if let Some(signatory) = self.signatory.as_ref() {
signatory.clone()
} else {
let seed = self.seed.as_ref().ok_or(anyhow!("Mint seed not set"))?;
/// Build the mint with the provided keystore and seed
pub async fn build_with_seed(
self,
keystore: Arc<dyn MintKeysDatabase<Err = cdk_database::Error> + Send + Sync>,
seed: &[u8],
) -> Result<Mint, Error> {
let in_memory_signatory = cdk_signatory::db_signatory::DbSignatory::new(
self.keystore.clone().ok_or(anyhow!("keystore not set"))?,
keystore,
seed,
self.supported_units.clone(),
HashMap::new(),
)
.await?;
Arc::new(cdk_signatory::embedded::Service::new(Arc::new(
let signatory = Arc::new(cdk_signatory::embedded::Service::new(Arc::new(
in_memory_signatory,
)))
};
)));
#[cfg(feature = "auth")]
if let Some(openid_discovery) = &self.openid_discovery {
let auth_localstore = self
.auth_localstore
.clone()
.ok_or(anyhow!("Auth localstore not set"))?;
return Ok(Mint::new_with_auth(
signatory,
localstore,
auth_localstore,
ln,
openid_discovery.clone(),
)
.await?);
}
#[cfg(not(feature = "auth"))]
if self.openid_discovery.is_some() {
return Err(anyhow!(
"OpenID discovery URL provided but auth feature is not enabled"
));
}
Ok(Mint::new(signatory, localstore, ln).await?)
self.build_with_signatory(signatory).await
}
}

View File

@@ -23,7 +23,7 @@ impl Mint {
return Ok(());
}
let ln = match self.ln.get(&PaymentProcessorKey::new(
let ln = match self.payment_processors.get(&PaymentProcessorKey::new(
quote.unit.clone(),
quote.payment_method.clone(),
)) {

View File

@@ -152,7 +152,7 @@ impl Mint {
.await?;
let ln = self
.ln
.payment_processors
.get(&PaymentProcessorKey::new(
unit.clone(),
PaymentMethod::Bolt11,
@@ -250,7 +250,7 @@ impl Mint {
.await?;
let ln = self
.ln
.payment_processors
.get(&PaymentProcessorKey::new(
unit.clone(),
PaymentMethod::Bolt12,
@@ -586,7 +586,7 @@ impl Mint {
_ => None,
};
tracing::debug!("partial_amount: {:?}", partial_amount);
let ln = match self.ln.get(&PaymentProcessorKey::new(
let ln = match self.payment_processors.get(&PaymentProcessorKey::new(
quote.unit.clone(),
PaymentMethod::Bolt11,
)) {

View File

@@ -53,17 +53,17 @@ pub struct Mint {
///
/// It is implemented in the cdk-signatory crate, and it can be embedded in the mint or it can
/// be a gRPC client to a remote signatory server.
pub signatory: Arc<dyn Signatory + Send + Sync>,
signatory: Arc<dyn Signatory + Send + Sync>,
/// Mint Storage backend
pub localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
/// Auth Storage backend (only available with auth feature)
#[cfg(feature = "auth")]
pub auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>,
/// Ln backends for mint
pub ln:
auth_localstore: Option<Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>>,
/// Payment processors for mint
payment_processors:
HashMap<PaymentProcessorKey, Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>>,
/// Subscription manager
pub pubsub_manager: Arc<PubSubManager>,
pubsub_manager: Arc<PubSubManager>,
#[cfg(feature = "auth")]
oidc_client: Option<OidcClient>,
/// In-memory keyset
@@ -71,40 +71,23 @@ pub struct Mint {
}
impl Mint {
/// Get the payment processor for the given unit and payment method
pub fn get_payment_processor(
&self,
unit: CurrencyUnit,
payment_method: PaymentMethod,
) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
self.ln.get(&key).cloned().ok_or_else(|| {
tracing::info!(
"No payment processor set for pair {}, {}",
unit,
payment_method
);
Error::UnsupportedUnit
})
}
/// Create new [`Mint`] without authentication
pub async fn new(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
ln: HashMap<
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
) -> Result<Self, Error> {
Self::new_internal(
mint_info,
signatory,
localstore,
#[cfg(feature = "auth")]
None,
ln,
#[cfg(feature = "auth")]
None,
payment_processors,
)
.await
}
@@ -112,21 +95,21 @@ impl Mint {
/// Create new [`Mint`] with authentication support
#[cfg(feature = "auth")]
pub async fn new_with_auth(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
auth_localstore: Arc<dyn MintAuthDatabase<Err = database::Error> + Send + Sync>,
ln: HashMap<
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
open_id_discovery: String,
) -> Result<Self, Error> {
Self::new_internal(
mint_info,
signatory,
localstore,
Some(auth_localstore),
ln,
Some(open_id_discovery),
payment_processors,
)
.await
}
@@ -134,21 +117,17 @@ impl Mint {
/// Internal function to create a new [`Mint`] with shared logic
#[inline]
async fn new_internal(
mint_info: MintInfo,
signatory: Arc<dyn Signatory + Send + Sync>,
localstore: Arc<dyn MintDatabase<database::Error> + Send + Sync>,
#[cfg(feature = "auth")] auth_localstore: Option<
Arc<dyn database::MintAuthDatabase<Err = database::Error> + Send + Sync>,
>,
ln: HashMap<
payment_processors: HashMap<
PaymentProcessorKey,
Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>,
>,
#[cfg(feature = "auth")] open_id_discovery: Option<String>,
) -> Result<Self, Error> {
#[cfg(feature = "auth")]
let oidc_client =
open_id_discovery.map(|openid_discovery| OidcClient::new(openid_discovery.clone()));
let keysets = signatory.keysets().await?;
if !keysets
.keysets
@@ -168,19 +147,57 @@ impl Mint {
.count()
);
let mint_store = localstore.clone();
let mut tx = mint_store.begin_transaction().await?;
tx.set_mint_info(mint_info.clone()).await?;
tx.set_quote_ttl(QuoteTTL::default()).await?;
tx.commit().await?;
Ok(Self {
signatory,
pubsub_manager: Arc::new(localstore.clone().into()),
localstore,
#[cfg(feature = "auth")]
oidc_client,
ln,
oidc_client: mint_info.nuts.nut21.as_ref().map(|nut21| {
OidcClient::new(
nut21.openid_discovery.clone(),
Some(nut21.client_id.clone()),
)
}),
payment_processors,
#[cfg(feature = "auth")]
auth_localstore,
keysets: Arc::new(ArcSwap::new(keysets.keysets.into())),
})
}
/// Get the payment processor for the given unit and payment method
pub fn get_payment_processor(
&self,
unit: CurrencyUnit,
payment_method: PaymentMethod,
) -> Result<Arc<dyn MintPayment<Err = cdk_payment::Error> + Send + Sync>, Error> {
let key = PaymentProcessorKey::new(unit.clone(), payment_method.clone());
self.payment_processors.get(&key).cloned().ok_or_else(|| {
tracing::info!(
"No payment processor set for pair {}, {}",
unit,
payment_method
);
Error::UnsupportedUnit
})
}
/// Localstore
pub fn localstore(&self) -> Arc<dyn MintDatabase<database::Error> + Send + Sync> {
Arc::clone(&self.localstore)
}
/// Pub Sub manager
pub fn pubsub_manager(&self) -> Arc<PubSubManager> {
Arc::clone(&self.pubsub_manager)
}
/// Get mint info
#[instrument(skip_all)]
pub async fn mint_info(&self) -> Result<MintInfo, Error> {
@@ -259,7 +276,7 @@ impl Mint {
Vec<PaymentProcessorKey>,
)> = Vec::new();
for (key, ln) in self.ln.iter() {
for (key, ln) in self.payment_processors.iter() {
// Check if we already have this processor
let found = processor_groups.iter_mut().find(|(proc_ref, _)| {
// Compare Arc pointer equality using ptr_eq
@@ -609,7 +626,7 @@ mod tests {
.expect("Failed to create signatory"),
);
Mint::new(signatory, localstore, HashMap::new())
Mint::new(MintInfo::default(), signatory, localstore, HashMap::new())
.await
.unwrap()
}

View File

@@ -27,7 +27,7 @@ impl Mint {
method: PaymentMethod::Bolt11,
};
let ln_backend = match self.ln.get(&ln_key) {
let ln_backend = match self.payment_processors.get(&ln_key) {
Some(ln_backend) => ln_backend,
None => {
tracing::warn!("No backend for ln key: {:?}", ln_key);

View File

@@ -32,9 +32,9 @@ pub enum Error {
/// Unsupported Algo
#[error("Unsupported signing algo")]
UnsupportedSigningAlgo,
/// Access token not returned
#[error("Error getting access token")]
AccessTokenMissing,
/// Invalid Client ID
#[error("Invalid Client ID")]
InvalidClientId,
}
impl From<Error> for cdk_common::error::Error {
@@ -58,6 +58,7 @@ pub struct OidcConfig {
pub struct OidcClient {
client: Client,
openid_discovery: String,
client_id: Option<String>,
oidc_config: Arc<RwLock<Option<OidcConfig>>>,
jwks_set: Arc<RwLock<Option<JwkSet>>>,
}
@@ -88,10 +89,11 @@ pub struct TokenResponse {
impl OidcClient {
/// Create new [`OidcClient`]
pub fn new(openid_discovery: String) -> Self {
pub fn new(openid_discovery: String, client_id: Option<String>) -> Self {
Self {
client: Client::new(),
openid_discovery,
client_id,
oidc_config: Arc::new(RwLock::new(None)),
jwks_set: Arc::new(RwLock::new(None)),
}
@@ -192,12 +194,41 @@ impl OidcClient {
validation
};
if let Err(err) =
decode::<HashMap<String, serde_json::Value>>(cat_jwt, &decoding_key, &validation)
{
match decode::<HashMap<String, serde_json::Value>>(cat_jwt, &decoding_key, &validation) {
Ok(claims) => {
tracing::debug!("Successfully verified cat");
tracing::debug!("Claims: {:?}", claims.claims);
if let Some(client_id) = &self.client_id {
if let Some(token_client_id) = claims.claims.get("client_id") {
if let Some(token_client_id_value) = token_client_id.as_str() {
if token_client_id_value != client_id {
tracing::warn!(
"Client ID mismatch: expected {}, got {}",
client_id,
token_client_id_value
);
return Err(Error::InvalidClientId);
}
}
} else if let Some(azp) = claims.claims.get("azp") {
if let Some(azp_value) = azp.as_str() {
if azp_value != client_id {
tracing::warn!(
"Client ID (azp) mismatch: expected {}, got {}",
client_id,
azp_value
);
return Err(Error::InvalidClientId);
}
}
}
}
}
Err(err) => {
tracing::debug!("Could not verify cat: {}", err);
return Err(err.into());
}
}
Ok(())
}

View File

@@ -268,8 +268,9 @@ impl Wallet {
auth_wallet.protected_endpoints.write().await;
*protected_endpoints = mint_info.protected_endpoints();
if let Some(oidc_client) =
mint_info.openid_discovery().map(OidcClient::new)
if let Some(oidc_client) = mint_info
.openid_discovery()
.map(|url| OidcClient::new(url, None))
{
auth_wallet.set_oidc_client(Some(oidc_client)).await;
}
@@ -277,7 +278,9 @@ impl Wallet {
None => {
tracing::info!("Mint has auth enabled creating auth wallet");
let oidc_client = mint_info.openid_discovery().map(OidcClient::new);
let oidc_client = mint_info
.openid_discovery()
.map(|url| OidcClient::new(url, None));
let new_auth_wallet = AuthWallet::new(
self.mint_url.clone(),
None,