From d187345afc6c1f5f5353358804146e6ca72ed95a Mon Sep 17 00:00:00 2001 From: Lucas Sunsi Abreu Date: Sat, 16 Dec 2023 10:00:16 -0300 Subject: [PATCH] feat(pay): add preliminary support for currencies on entrypoint --- README.md | 1 + src/core/pay.rs | 21 ++++++++++++++ src/core/pay/client.rs | 65 +++++++++++++++++++++++++++++++++++++++++- src/core/pay/server.rs | 61 +++++++++++++++++++++++++++++++++++++++ tests/lud06.rs | 1 + tests/lud09.rs | 1 + tests/lud11.rs | 1 + tests/lud12.rs | 1 + tests/lud16.rs | 1 + 9 files changed, 152 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18b73bc..df0e3fc 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ This library works as a toolkit so you can serve and make your LNURL requests wi - [LUD-18](https://github.com/lnurl/luds/blob/luds/18.md): πŸ†˜ core πŸ†˜ client πŸ†˜ server πŸ†˜ tests - [LUD-19](https://github.com/lnurl/luds/blob/luds/19.md): πŸ†˜ core πŸ†˜ client πŸ†˜ server πŸ†˜ tests - [LUD-20](https://github.com/lnurl/luds/blob/luds/20.md): βœ… core βœ… client βœ… server ⚠️ tests +- [LUD-21 *proposal*](https://github.com/lnurl/luds/blob/8580e3c8cbfd8fc95a6c0e5f7fcb5b048a0d5b61/21.md): ⚠️ core πŸ†˜ client πŸ†˜ server πŸ†˜ tests - ###### Soon. β„’ diff --git a/src/core/pay.rs b/src/core/pay.rs index 7d79270..8939902 100644 --- a/src/core/pay.rs +++ b/src/core/pay.rs @@ -2,9 +2,30 @@ pub const TAG: &str = "payRequest"; pub mod client; pub mod server; +#[derive(Clone, Debug)] +pub struct Currency { + pub code: String, + pub name: String, + pub symbol: String, + pub decimals: u8, + pub multiplier: f64, + pub convertible: bool, +} + mod serde { use serde::{Deserialize, Serialize}; + #[derive(Deserialize, Serialize)] + pub(super) struct Currency<'a> { + pub code: &'a str, + pub name: &'a str, + pub symbol: &'a str, + pub decimals: u8, + pub multiplier: f64, + #[serde(default)] + pub convertible: bool, + } + #[derive(Deserialize, Serialize)] pub(super) struct Callback<'a> { pub comment: Option<&'a str>, diff --git a/src/core/pay/client.rs b/src/core/pay/client.rs index c95b173..4926e37 100644 --- a/src/core/pay/client.rs +++ b/src/core/pay/client.rs @@ -11,6 +11,7 @@ pub struct Entrypoint { pub comment_size: Option, pub min: u64, pub max: u64, + pub currencies: Option>, } impl TryFrom<&[u8]> for Entrypoint { @@ -22,6 +23,19 @@ impl TryFrom<&[u8]> for Entrypoint { let p: de::Entrypoint = serde_json::from_slice(s).map_err(|_| "deserialize failed")?; + let currencies = p.currencies.map(|cs| { + cs.into_iter() + .map(|c| super::Currency { + code: String::from(c.code), + name: String::from(c.name), + symbol: String::from(c.symbol), + decimals: c.decimals, + multiplier: c.multiplier, + convertible: c.convertible, + }) + .collect() + }); + let metadata = serde_json::from_str::>(&p.metadata) .map_err(|_| "deserialize metadata failed")?; @@ -86,6 +100,7 @@ impl TryFrom<&[u8]> for Entrypoint { email, jpeg, png, + currencies, }) } } @@ -160,12 +175,13 @@ impl std::str::FromStr for CallbackResponse { } mod de { + use super::super::serde::Currency; use serde::Deserialize; use std::collections::BTreeMap; use url::Url; #[derive(Deserialize)] - pub(super) struct Entrypoint { + pub(super) struct Entrypoint<'a> { pub metadata: String, pub callback: Url, #[serde(rename = "minSendable")] @@ -174,6 +190,8 @@ mod de { pub max_sendable: u64, #[serde(rename = "commentAllowed")] pub comment_allowed: Option, + #[serde(borrow)] + pub currencies: Option>>, } #[derive(Deserialize)] @@ -213,6 +231,7 @@ mod tests { assert!(parsed.png.is_none()); assert!(parsed.identifier.is_none()); assert!(parsed.email.is_none()); + assert!(parsed.currencies.is_none()); } #[test] @@ -285,6 +304,50 @@ mod tests { assert_eq!(parsed.email.unwrap(), "steve@magal.brutal"); } + #[test] + fn entrypoint_parse_currencies() { + let input = r#"{ + "callback": "https://yuri?o=callback", + "metadata": "[[\"text/plain\", \"boneco do steve magal\"],[\"text/crazy\", \"πŸ‘‹πŸ‡§πŸ‡΄πŸ’Ύ\"]]", + "maxSendable": 315, + "minSendable": 314, + "currencies": [ + { + "code": "BRL", + "name": "Reais", + "symbol": "R$", + "multiplier": 314.15, + "decimals": 2, + "convertible": true + }, + { + "code": "USD", + "name": "DΓ³lar", + "symbol": "$", + "decimals": 6, + "multiplier": 14.5 + } + ] + }"#; + + let parsed: super::Entrypoint = input.as_bytes().try_into().expect("parse"); + let currencies = parsed.currencies.unwrap(); + + assert_eq!(currencies[0].code, "BRL"); + assert_eq!(currencies[0].name, "Reais"); + assert_eq!(currencies[0].symbol, "R$"); + assert_eq!(currencies[0].decimals, 2); + assert!((currencies[0].multiplier - 314.15).abs() < f64::EPSILON); + assert!(currencies[0].convertible); + + assert_eq!(currencies[1].code, "USD"); + assert_eq!(currencies[1].name, "DΓ³lar"); + assert_eq!(currencies[1].symbol, "$"); + assert_eq!(currencies[1].decimals, 6); + assert!((currencies[1].multiplier - 14.5).abs() < f64::EPSILON); + assert!(!currencies[1].convertible); + } + #[test] fn callback_render_base() { let input = r#"{ diff --git a/src/core/pay/server.rs b/src/core/pay/server.rs index 360ab7b..16dade5 100644 --- a/src/core/pay/server.rs +++ b/src/core/pay/server.rs @@ -10,6 +10,7 @@ pub struct Entrypoint { pub comment_size: Option, pub min: u64, pub max: u64, + pub currencies: Option>, } impl TryFrom for Vec { @@ -48,6 +49,18 @@ impl TryFrom for Vec { min_sendable: r.min, max_sendable: r.max, comment_allowed: r.comment_size.unwrap_or(0), + currencies: r.currencies.as_ref().map(|cs| { + cs.iter() + .map(|c| super::serde::Currency { + code: &c.code, + name: &c.name, + symbol: &c.symbol, + decimals: c.decimals, + multiplier: c.multiplier, + convertible: c.convertible, + }) + .collect() + }), }) .map_err(|_| "serialize failed") } @@ -115,6 +128,7 @@ impl std::fmt::Display for CallbackResponse { } mod ser { + use super::super::serde::Currency; use serde::Serialize; use std::collections::BTreeMap; use url::Url; @@ -130,6 +144,8 @@ mod ser { pub max_sendable: u64, #[serde(rename = "commentAllowed")] pub comment_allowed: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub currencies: Option>>, } #[derive(Serialize)] @@ -156,6 +172,7 @@ mod tests { max: 315, identifier: None, email: None, + currencies: None, }; assert_eq!( @@ -177,6 +194,7 @@ mod tests { max: 315, identifier: None, email: None, + currencies: None, }; assert_eq!( @@ -198,6 +216,7 @@ mod tests { max: 315, identifier: None, email: None, + currencies: None, }; assert_eq!( @@ -219,6 +238,7 @@ mod tests { max: 315, identifier: None, email: None, + currencies: None, }; assert_eq!( @@ -240,6 +260,7 @@ mod tests { max: 315, identifier: Some(String::from("steve@magal.brutal")), email: None, + currencies: None, }; assert_eq!( @@ -261,6 +282,7 @@ mod tests { max: 315, identifier: None, email: Some(String::from("steve@magal.brutal")), + currencies: None, }; assert_eq!( @@ -269,6 +291,45 @@ mod tests { ); } + #[test] + fn entrypoint_render_currencies() { + let query = super::Entrypoint { + callback: url::Url::parse("https://yuri?o=callback").expect("url"), + short_description: String::from("boneco do steve magal"), + long_description: None, + jpeg: None, + png: None, + comment_size: None, + min: 314, + max: 315, + identifier: None, + email: None, + currencies: Some(vec![ + super::super::Currency { + code: String::from("BRL"), + name: String::from("Reais"), + symbol: String::from("R$"), + decimals: 2, + multiplier: 314.15, + convertible: true, + }, + super::super::Currency { + code: String::from("USD"), + name: String::from("Dolar"), + symbol: String::from("$"), + decimals: 6, + multiplier: 123.321, + convertible: false, + }, + ]), + }; + + assert_eq!( + Vec::::try_from(query).unwrap(), + br#"{"tag":"payRequest","metadata":"[[\"text/plain\",\"boneco do steve magal\"]]","callback":"https://yuri/?o=callback","minSendable":314,"maxSendable":315,"commentAllowed":0,"currencies":[{"code":"BRL","name":"Reais","symbol":"R$","decimals":2,"multiplier":314.15,"convertible":true},{"code":"USD","name":"Dolar","symbol":"$","decimals":6,"multiplier":123.321,"convertible":false}]}"# + ); + } + #[test] fn callback_parse_base() { let input = "amount=314"; diff --git a/tests/lud06.rs b/tests/lud06.rs index d174446..9574870 100644 --- a/tests/lud06.rs +++ b/tests/lud06.rs @@ -25,6 +25,7 @@ async fn test() { max: 315, identifier: None, email: None, + currencies: None, }) } }, diff --git a/tests/lud09.rs b/tests/lud09.rs index 0b5aaad..97a6cbc 100644 --- a/tests/lud09.rs +++ b/tests/lud09.rs @@ -25,6 +25,7 @@ async fn test() { max: 315, identifier: None, email: None, + currencies: None, }) } }, diff --git a/tests/lud11.rs b/tests/lud11.rs index 95997c2..8f92163 100644 --- a/tests/lud11.rs +++ b/tests/lud11.rs @@ -25,6 +25,7 @@ async fn test() { max: 315, identifier: None, email: None, + currencies: None, }) } }, diff --git a/tests/lud12.rs b/tests/lud12.rs index c4950fd..78755d2 100644 --- a/tests/lud12.rs +++ b/tests/lud12.rs @@ -25,6 +25,7 @@ async fn test() { max: 315, identifier: None, email: None, + currencies: None, }) } }, diff --git a/tests/lud16.rs b/tests/lud16.rs index 585d549..cff5773 100644 --- a/tests/lud16.rs +++ b/tests/lud16.rs @@ -24,6 +24,7 @@ async fn test() { max: 315, identifier: identifier.clone().filter(|i| i.starts_with('n')), email: identifier.filter(|i| i.starts_with('j')), + currencies: None, }) } },