Skip to content

Commit

Permalink
feat: support custom unit
Browse files Browse the repository at this point in the history
  • Loading branch information
thesimplekid committed Oct 27, 2024
1 parent 58e7226 commit 557208c
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 30 deletions.
1 change: 1 addition & 0 deletions crates/cdk-integration-tests/src/init_regtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ where
Arc::new(database),
ln_backends,
supported_units,
HashMap::new(),
)
.await?;

Expand Down
1 change: 1 addition & 0 deletions crates/cdk-integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ pub async fn start_mint(
Arc::new(MintMemoryDatabase::default()),
ln_backends.clone(),
supported_units,
HashMap::new(),
)
.await?;
let cache_time_to_live = 3600;
Expand Down
4 changes: 3 additions & 1 deletion crates/cdk-integration-tests/tests/mint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ async fn new_mint(fee: u64) -> Mint {
Arc::new(MintMemoryDatabase::default()),
HashMap::new(),
supported_units,
HashMap::new(),
)
.await
.unwrap()
Expand Down Expand Up @@ -270,7 +271,8 @@ async fn test_swap_unbalanced() -> Result<()> {
async fn test_swap_overpay_underpay_fee() -> Result<()> {
let mint = new_mint(1).await;

mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1).await?;
mint.rotate_keyset(CurrencyUnit::Sat, 1, 32, 1, HashMap::new())
.await?;

let keys = mint.pubkeys().await?.keysets.first().unwrap().clone().keys;
let keyset_id = Id::from(&keys);
Expand Down
1 change: 1 addition & 0 deletions crates/cdk-mintd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ async fn main() -> anyhow::Result<()> {
localstore,
ln_backends.clone(),
supported_units,
HashMap::new(),
)
.await?;

Expand Down
11 changes: 9 additions & 2 deletions crates/cdk/src/mint/keysets.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};

use bitcoin::bip32::DerivationPath;
use tracing::instrument;

use crate::Error;
Expand Down Expand Up @@ -89,8 +90,14 @@ impl Mint {
derivation_path_index: u32,
max_order: u8,
input_fee_ppk: u64,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Result<(), Error> {
let derivation_path = derivation_path_from_unit(unit, derivation_path_index);
let derivation_path = match custom_paths.get(&unit) {
Some(path) => path.clone(),
None => derivation_path_from_unit(unit, derivation_path_index)
.ok_or(Error::UnsupportedUnit)?,
};

let (keyset, keyset_info) = create_new_keyset(
&self.secp_ctx,
self.xpriv,
Expand Down
37 changes: 27 additions & 10 deletions crates/cdk/src/mint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct Mint {

impl Mint {
/// Create new [`Mint`]
#[allow(clippy::too_many_arguments)]
pub async fn new(
mint_url: &str,
seed: &[u8],
Expand All @@ -63,6 +64,7 @@ impl Mint {
ln: HashMap<LnKey, Arc<dyn MintLightning<Err = cdk_lightning::Error> + Send + Sync>>,
// Hashmap where the key is the unit and value is (input fee ppk, max_order)
supported_units: HashMap<CurrencyUnit, (u64, u8)>,
custom_paths: HashMap<CurrencyUnit, DerivationPath>,
) -> Result<Self, Error> {
let secp_ctx = Secp256k1::new();
let xpriv = Xpriv::new_master(bitcoin::Network::Bitcoin, seed).expect("RNG busted");
Expand Down Expand Up @@ -125,7 +127,11 @@ impl Mint {
highest_index_keyset.derivation_path_index.unwrap_or(0) + 1
};

let derivation_path = derivation_path_from_unit(unit, derivation_path_index);
let derivation_path = match custom_paths.get(&unit) {
Some(path) => path.clone(),
None => derivation_path_from_unit(unit, derivation_path_index)
.ok_or(Error::UnsupportedUnit)?,
};

let (keyset, keyset_info) = create_new_keyset(
&secp_ctx,
Expand All @@ -148,7 +154,10 @@ impl Mint {

for (unit, (fee, max_order)) in supported_units {
if !active_keyset_units.contains(&unit) {
let derivation_path = derivation_path_from_unit(unit, 0);
let derivation_path = match custom_paths.get(&unit) {
Some(path) => path.clone(),
None => derivation_path_from_unit(unit, 0).ok_or(Error::UnsupportedUnit)?,
};

let (keyset, keyset_info) = create_new_keyset(
&secp_ctx,
Expand Down Expand Up @@ -571,12 +580,17 @@ fn create_new_keyset<C: secp256k1::Signing>(
(keyset, keyset_info)
}

fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> DerivationPath {
DerivationPath::from(vec![
fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> Option<DerivationPath> {
let unit_index = match unit.derivation_index() {
Some(index) => index,
None => return None,
};

Some(DerivationPath::from(vec![
ChildNumber::from_hardened_idx(0).expect("0 is a valid index"),
ChildNumber::from_hardened_idx(unit.derivation_index()).expect("0 is a valid index"),
ChildNumber::from_hardened_idx(unit_index).expect("0 is a valid index"),
ChildNumber::from_hardened_idx(index).expect("0 is a valid index"),
])
]))
}

#[cfg(test)]
Expand All @@ -598,7 +612,7 @@ mod tests {
seed,
2,
CurrencyUnit::Sat,
derivation_path_from_unit(CurrencyUnit::Sat, 0),
derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
);

assert_eq!(keyset.unit, CurrencyUnit::Sat);
Expand Down Expand Up @@ -642,7 +656,7 @@ mod tests {
xpriv,
2,
CurrencyUnit::Sat,
derivation_path_from_unit(CurrencyUnit::Sat, 0),
derivation_path_from_unit(CurrencyUnit::Sat, 0).unwrap(),
);

assert_eq!(keyset.unit, CurrencyUnit::Sat);
Expand Down Expand Up @@ -722,6 +736,7 @@ mod tests {
localstore,
HashMap::new(),
config.supported_units,
HashMap::new(),
)
.await
}
Expand Down Expand Up @@ -777,15 +792,17 @@ mod tests {
assert!(keysets.keysets.is_empty());

// generate the first keyset and set it to active
mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1).await?;
mint.rotate_keyset(CurrencyUnit::default(), 0, 1, 1, HashMap::new())
.await?;

let keysets = mint.keysets().await.unwrap();
assert!(keysets.keysets.len().eq(&1));
assert!(keysets.keysets[0].active);
let first_keyset_id = keysets.keysets[0].id;

// set the first keyset to inactive and generate a new keyset
mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1).await?;
mint.rotate_keyset(CurrencyUnit::default(), 1, 1, 1, HashMap::new())
.await?;

let keysets = mint.keysets().await.unwrap();

Expand Down
59 changes: 42 additions & 17 deletions crates/cdk/src/nuts/nut00/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,46 +373,71 @@ pub enum CurrencyUnit {
Usd,
/// Euro
Eur,
/// Custom currency unit (3 characters)
Custom([u8; 3]),
}

#[cfg(feature = "mint")]
impl CurrencyUnit {
/// Derivation index mint will use for unit
pub fn derivation_index(&self) -> u32 {
pub fn derivation_index(&self) -> Option<u32> {
match self {
Self::Sat => 0,
Self::Msat => 1,
Self::Usd => 2,
Self::Eur => 3,
Self::Sat => Some(0),
Self::Msat => Some(1),
Self::Usd => Some(2),
Self::Eur => Some(3),
_ => None,
}
}
}

impl FromStr for CurrencyUnit {
type Err = Error;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"sat" => Ok(Self::Sat),
"msat" => Ok(Self::Msat),
"usd" => Ok(Self::Usd),
"eur" => Ok(Self::Eur),
_ => Err(Error::UnsupportedUnit),
let value = &value.to_uppercase();
match value.as_str() {
"SAT" => Ok(Self::Sat),
"MSAT" => Ok(Self::Msat),
"USD" => Ok(Self::Usd),
"EUR" => Ok(Self::Eur),
c => {
if c.len() != 3 {
return Err(Error::UnsupportedUnit);
}
// Convert to ASCII uppercase bytes
let bytes: [u8; 3] = c
.as_bytes()
.try_into()
.map_err(|_| Error::UnsupportedUnit)?;

// Validate that all characters are ASCII uppercase letters
if bytes.iter().any(|&b| !b.is_ascii_uppercase()) {
return Err(Error::UnsupportedUnit);
}

Ok(Self::Custom(bytes))
}
}
}
}

impl fmt::Display for CurrencyUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
CurrencyUnit::Sat => "sat",
CurrencyUnit::Msat => "msat",
CurrencyUnit::Usd => "usd",
CurrencyUnit::Eur => "eur",
CurrencyUnit::Sat => "SAT",
CurrencyUnit::Msat => "MSAT",
CurrencyUnit::Usd => "USD",
CurrencyUnit::Eur => "EUR",
CurrencyUnit::Custom(code) => {
let s =
std::str::from_utf8(code).expect("It is checked when enum created to be utf8");
s
}
};
if let Some(width) = f.width() {
write!(f, "{:width$}", s, width = width)
write!(f, "{:width$}", s.to_lowercase(), width = width)
} else {
write!(f, "{}", s)
write!(f, "{}", s.to_lowercase())
}
}
}
Expand Down

0 comments on commit 557208c

Please sign in to comment.