123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629 |
- //! NUT-06: Mint Information
- //!
- //! <https://github.com/cashubtc/nuts/blob/main/06.md>
- #[cfg(feature = "auth")]
- use std::collections::HashMap;
- use serde::{Deserialize, Deserializer, Serialize, Serializer};
- use super::nut01::PublicKey;
- use super::nut17::SupportedMethods;
- use super::nut19::CachedEndpoint;
- use super::{nut04, nut05, nut15, nut19, MppMethodSettings};
- #[cfg(feature = "auth")]
- use super::{AuthRequired, BlindAuthSettings, ClearAuthSettings, ProtectedEndpoint};
- /// Mint Version
- #[derive(Debug, Clone, PartialEq, Eq, Hash)]
- #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
- pub struct MintVersion {
- /// Mint Software name
- pub name: String,
- /// Mint Version
- pub version: String,
- }
- impl MintVersion {
- /// Create new [`MintVersion`]
- pub fn new(name: String, version: String) -> Self {
- Self { name, version }
- }
- }
- impl std::fmt::Display for MintVersion {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}/{}", self.name, self.version)
- }
- }
- impl Serialize for MintVersion {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- let combined = format!("{}/{}", self.name, self.version);
- serializer.serialize_str(&combined)
- }
- }
- impl<'de> Deserialize<'de> for MintVersion {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let combined = String::deserialize(deserializer)?;
- let parts: Vec<&str> = combined.split('/').collect();
- if parts.len() != 2 {
- return Err(serde::de::Error::custom("Invalid input string"));
- }
- Ok(MintVersion {
- name: parts[0].to_string(),
- version: parts[1].to_string(),
- })
- }
- }
- /// Mint Info [NUT-06]
- #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
- #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
- pub struct MintInfo {
- /// name of the mint and should be recognizable
- #[serde(skip_serializing_if = "Option::is_none")]
- pub name: Option<String>,
- /// hex pubkey of the mint
- #[serde(skip_serializing_if = "Option::is_none")]
- pub pubkey: Option<PublicKey>,
- /// implementation name and the version running
- #[serde(skip_serializing_if = "Option::is_none")]
- pub version: Option<MintVersion>,
- /// short description of the mint
- #[serde(skip_serializing_if = "Option::is_none")]
- pub description: Option<String>,
- /// long description
- #[serde(skip_serializing_if = "Option::is_none")]
- pub description_long: Option<String>,
- /// Contact info
- #[serde(skip_serializing_if = "Option::is_none")]
- pub contact: Option<Vec<ContactInfo>>,
- /// shows which NUTs the mint supports
- pub nuts: Nuts,
- /// Mint's icon URL
- #[serde(skip_serializing_if = "Option::is_none")]
- pub icon_url: Option<String>,
- /// Mint's endpoint URLs
- #[serde(skip_serializing_if = "Option::is_none")]
- pub urls: Option<Vec<String>>,
- /// message of the day that the wallet must display to the user
- #[serde(skip_serializing_if = "Option::is_none")]
- pub motd: Option<String>,
- /// server unix timestamp
- #[serde(skip_serializing_if = "Option::is_none")]
- pub time: Option<u64>,
- /// terms of url service of the mint
- #[serde(skip_serializing_if = "Option::is_none")]
- pub tos_url: Option<String>,
- }
- impl MintInfo {
- /// Create new [`MintInfo`]
- pub fn new() -> Self {
- Self::default()
- }
- /// Set name
- pub fn name<S>(self, name: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- name: Some(name.into()),
- ..self
- }
- }
- /// Set pubkey
- pub fn pubkey(self, pubkey: PublicKey) -> Self {
- Self {
- pubkey: Some(pubkey),
- ..self
- }
- }
- /// Set [`MintVersion`]
- pub fn version(self, mint_version: MintVersion) -> Self {
- Self {
- version: Some(mint_version),
- ..self
- }
- }
- /// Set description
- pub fn description<S>(self, description: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- description: Some(description.into()),
- ..self
- }
- }
- /// Set long description
- pub fn long_description<S>(self, description_long: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- description_long: Some(description_long.into()),
- ..self
- }
- }
- /// Set contact info
- pub fn contact_info(self, contact_info: Vec<ContactInfo>) -> Self {
- Self {
- contact: Some(contact_info),
- ..self
- }
- }
- /// Set nuts
- pub fn nuts(self, nuts: Nuts) -> Self {
- Self { nuts, ..self }
- }
- /// Set mint icon url
- pub fn icon_url<S>(self, icon_url: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- icon_url: Some(icon_url.into()),
- ..self
- }
- }
- /// Set motd
- pub fn motd<S>(self, motd: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- motd: Some(motd.into()),
- ..self
- }
- }
- /// Set time
- pub fn time<S>(self, time: S) -> Self
- where
- S: Into<u64>,
- {
- Self {
- time: Some(time.into()),
- ..self
- }
- }
- /// Set tos_url
- pub fn tos_url<S>(self, tos_url: S) -> Self
- where
- S: Into<String>,
- {
- Self {
- tos_url: Some(tos_url.into()),
- ..self
- }
- }
- /// Get protected endpoints
- #[cfg(feature = "auth")]
- pub fn protected_endpoints(&self) -> HashMap<ProtectedEndpoint, AuthRequired> {
- let mut protected_endpoints = HashMap::new();
- if let Some(nut21_settings) = &self.nuts.nut21 {
- for endpoint in nut21_settings.protected_endpoints.iter() {
- protected_endpoints.insert(*endpoint, AuthRequired::Clear);
- }
- }
- if let Some(nut22_settings) = &self.nuts.nut22 {
- for endpoint in nut22_settings.protected_endpoints.iter() {
- protected_endpoints.insert(*endpoint, AuthRequired::Blind);
- }
- }
- protected_endpoints
- }
- /// Get Openid discovery of the mint if it is set
- #[cfg(feature = "auth")]
- pub fn openid_discovery(&self) -> Option<String> {
- self.nuts
- .nut21
- .as_ref()
- .map(|s| s.openid_discovery.to_string())
- }
- /// Get Openid discovery of the mint if it is set
- #[cfg(feature = "auth")]
- pub fn client_id(&self) -> Option<String> {
- self.nuts.nut21.as_ref().map(|s| s.client_id.clone())
- }
- /// Max bat mint
- #[cfg(feature = "auth")]
- pub fn bat_max_mint(&self) -> Option<u64> {
- self.nuts.nut22.as_ref().map(|s| s.bat_max_mint)
- }
- }
- /// Supported nuts and settings
- #[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
- #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
- pub struct Nuts {
- /// NUT04 Settings
- #[serde(default)]
- #[serde(rename = "4")]
- pub nut04: nut04::Settings,
- /// NUT05 Settings
- #[serde(default)]
- #[serde(rename = "5")]
- pub nut05: nut05::Settings,
- /// NUT07 Settings
- #[serde(default)]
- #[serde(rename = "7")]
- pub nut07: SupportedSettings,
- /// NUT08 Settings
- #[serde(default)]
- #[serde(rename = "8")]
- pub nut08: SupportedSettings,
- /// NUT09 Settings
- #[serde(default)]
- #[serde(rename = "9")]
- pub nut09: SupportedSettings,
- /// NUT10 Settings
- #[serde(rename = "10")]
- #[serde(default)]
- pub nut10: SupportedSettings,
- /// NUT11 Settings
- #[serde(rename = "11")]
- #[serde(default)]
- pub nut11: SupportedSettings,
- /// NUT12 Settings
- #[serde(default)]
- #[serde(rename = "12")]
- pub nut12: SupportedSettings,
- /// NUT14 Settings
- #[serde(default)]
- #[serde(rename = "14")]
- pub nut14: SupportedSettings,
- /// NUT15 Settings
- #[serde(default)]
- #[serde(rename = "15")]
- pub nut15: nut15::Settings,
- /// NUT17 Settings
- #[serde(default)]
- #[serde(rename = "17")]
- pub nut17: super::nut17::SupportedSettings,
- /// NUT19 Settings
- #[serde(default)]
- #[serde(rename = "19")]
- pub nut19: nut19::Settings,
- /// NUT20 Settings
- #[serde(default)]
- #[serde(rename = "20")]
- pub nut20: SupportedSettings,
- /// NUT21 Settings
- #[serde(rename = "21")]
- #[serde(skip_serializing_if = "Option::is_none")]
- #[cfg(feature = "auth")]
- pub nut21: Option<ClearAuthSettings>,
- /// NUT22 Settings
- #[serde(rename = "22")]
- #[serde(skip_serializing_if = "Option::is_none")]
- #[cfg(feature = "auth")]
- pub nut22: Option<BlindAuthSettings>,
- }
- impl Nuts {
- /// Create new [`Nuts`]
- pub fn new() -> Self {
- Self::default()
- }
- /// Nut04 settings
- pub fn nut04(self, nut04_settings: nut04::Settings) -> Self {
- Self {
- nut04: nut04_settings,
- ..self
- }
- }
- /// Nut05 settings
- pub fn nut05(self, nut05_settings: nut05::Settings) -> Self {
- Self {
- nut05: nut05_settings,
- ..self
- }
- }
- /// Nut07 settings
- pub fn nut07(self, supported: bool) -> Self {
- Self {
- nut07: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut08 settings
- pub fn nut08(self, supported: bool) -> Self {
- Self {
- nut08: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut09 settings
- pub fn nut09(self, supported: bool) -> Self {
- Self {
- nut09: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut10 settings
- pub fn nut10(self, supported: bool) -> Self {
- Self {
- nut10: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut11 settings
- pub fn nut11(self, supported: bool) -> Self {
- Self {
- nut11: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut12 settings
- pub fn nut12(self, supported: bool) -> Self {
- Self {
- nut12: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut14 settings
- pub fn nut14(self, supported: bool) -> Self {
- Self {
- nut14: SupportedSettings { supported },
- ..self
- }
- }
- /// Nut15 settings
- pub fn nut15(self, mpp_settings: Vec<MppMethodSettings>) -> Self {
- Self {
- nut15: nut15::Settings {
- methods: mpp_settings,
- },
- ..self
- }
- }
- /// Nut17 settings
- pub fn nut17(self, supported: Vec<SupportedMethods>) -> Self {
- Self {
- nut17: super::nut17::SupportedSettings { supported },
- ..self
- }
- }
- /// Nut19 settings
- pub fn nut19(self, ttl: Option<u64>, cached_endpoints: Vec<CachedEndpoint>) -> Self {
- Self {
- nut19: nut19::Settings {
- ttl,
- cached_endpoints,
- },
- ..self
- }
- }
- /// Nut20 settings
- pub fn nut20(self, supported: bool) -> Self {
- Self {
- nut20: SupportedSettings { supported },
- ..self
- }
- }
- }
- /// Check state Settings
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
- #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
- pub struct SupportedSettings {
- supported: bool,
- }
- /// Contact Info
- #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
- #[cfg_attr(feature = "swagger", derive(utoipa::ToSchema))]
- pub struct ContactInfo {
- /// Contact Method i.e. nostr
- pub method: String,
- /// Contact info i.e. npub...
- pub info: String,
- }
- impl ContactInfo {
- /// Create new [`ContactInfo`]
- pub fn new(method: String, info: String) -> Self {
- Self { method, info }
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- #[test]
- fn test_des_mint_into() {
- let mint_info_str = r#"{
- "name": "Cashu mint",
- "pubkey": "0296d0aa13b6a31cf0cd974249f28c7b7176d7274712c95a41c7d8066d3f29d679",
- "version": "Nutshell/0.15.3",
- "contact": [
- ["", ""],
- ["", ""]
- ],
- "nuts": {
- "4": {
- "methods": [
- {"method": "bolt11", "unit": "sat", "description": true},
- {"method": "bolt11", "unit": "usd", "description": true}
- ],
- "disabled": false
- },
- "5": {
- "methods": [
- {"method": "bolt11", "unit": "sat"},
- {"method": "bolt11", "unit": "usd"}
- ],
- "disabled": false
- },
- "7": {"supported": true},
- "8": {"supported": true},
- "9": {"supported": true},
- "10": {"supported": true},
- "11": {"supported": true}
- },
- "tos_url": "https://cashu.mint/tos"
- }"#;
- let _mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
- }
- #[test]
- fn test_ser_mint_info() {
- /*
- let mint_info = serde_json::to_string(&MintInfo {
- name: Some("Cashu-crab".to_string()),
- pubkey: None,
- version: None,
- description: Some("A mint".to_string()),
- description_long: Some("Some longer test".to_string()),
- contact: None,
- nuts: Nuts::default(),
- motd: None,
- })
- .unwrap();
- println!("{}", mint_info);
- */
- let mint_info_str = r#"
- {
- "name": "Bob's Cashu mint",
- "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
- "version": "Nutshell/0.15.0",
- "description": "The short mint description",
- "description_long": "A description that can be a long piece of text.",
- "contact": [
- {
- "method": "nostr",
- "info": "xxxxx"
- },
- {
- "method": "email",
- "info": "contact@me.com"
- }
- ],
- "motd": "Message to display to users.",
- "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
- "nuts": {
- "4": {
- "methods": [
- {
- "method": "bolt11",
- "unit": "sat",
- "min_amount": 0,
- "max_amount": 10000,
- "description": true
- }
- ],
- "disabled": false
- },
- "5": {
- "methods": [
- {
- "method": "bolt11",
- "unit": "sat",
- "min_amount": 0,
- "max_amount": 10000
- }
- ],
- "disabled": false
- },
- "7": {"supported": true},
- "8": {"supported": true},
- "9": {"supported": true},
- "10": {"supported": true},
- "12": {"supported": true}
- },
- "tos_url": "https://cashu.mint/tos"
- }"#;
- let info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
- let mint_info_str = r#"
- {
- "name": "Bob's Cashu mint",
- "pubkey": "0283bf290884eed3a7ca2663fc0260de2e2064d6b355ea13f98dec004b7a7ead99",
- "version": "Nutshell/0.15.0",
- "description": "The short mint description",
- "description_long": "A description that can be a long piece of text.",
- "contact": [
- ["nostr", "xxxxx"],
- ["email", "contact@me.com"]
- ],
- "motd": "Message to display to users.",
- "icon_url": "https://this-is-a-mint-icon-url.com/icon.png",
- "nuts": {
- "4": {
- "methods": [
- {
- "method": "bolt11",
- "unit": "sat",
- "min_amount": 0,
- "max_amount": 10000,
- "description": true
- }
- ],
- "disabled": false
- },
- "5": {
- "methods": [
- {
- "method": "bolt11",
- "unit": "sat",
- "min_amount": 0,
- "max_amount": 10000
- }
- ],
- "disabled": false
- },
- "7": {"supported": true},
- "8": {"supported": true},
- "9": {"supported": true},
- "10": {"supported": true},
- "12": {"supported": true}
- },
- "tos_url": "https://cashu.mint/tos"
- }"#;
- let mint_info: MintInfo = serde_json::from_str(mint_info_str).unwrap();
- assert_eq!(info, mint_info);
- }
- }
|