Skip to content

Commit

Permalink
feat(pay): add preliminary support for currencies on entrypoint
Browse files Browse the repository at this point in the history
  • Loading branch information
lsunsi committed Dec 16, 2023
1 parent 304ff22 commit d187345
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. ™

Expand Down
21 changes: 21 additions & 0 deletions src/core/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
65 changes: 64 additions & 1 deletion src/core/pay/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct Entrypoint {
pub comment_size: Option<u64>,
pub min: u64,
pub max: u64,
pub currencies: Option<Vec<super::Currency>>,
}

impl TryFrom<&[u8]> for Entrypoint {
Expand All @@ -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::<Vec<(String, Value)>>(&p.metadata)
.map_err(|_| "deserialize metadata failed")?;

Expand Down Expand Up @@ -86,6 +100,7 @@ impl TryFrom<&[u8]> for Entrypoint {
email,
jpeg,
png,
currencies,
})
}
}
Expand Down Expand Up @@ -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")]
Expand All @@ -174,6 +190,8 @@ mod de {
pub max_sendable: u64,
#[serde(rename = "commentAllowed")]
pub comment_allowed: Option<u64>,
#[serde(borrow)]
pub currencies: Option<Vec<Currency<'a>>>,
}

#[derive(Deserialize)]
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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#"{
Expand Down
61 changes: 61 additions & 0 deletions src/core/pay/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct Entrypoint {
pub comment_size: Option<u64>,
pub min: u64,
pub max: u64,
pub currencies: Option<Vec<super::Currency>>,
}

impl TryFrom<Entrypoint> for Vec<u8> {
Expand Down Expand Up @@ -48,6 +49,18 @@ impl TryFrom<Entrypoint> for Vec<u8> {
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")
}
Expand Down Expand Up @@ -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;
Expand All @@ -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<Vec<Currency<'a>>>,
}

#[derive(Serialize)]
Expand All @@ -156,6 +172,7 @@ mod tests {
max: 315,
identifier: None,
email: None,
currencies: None,
};

assert_eq!(
Expand All @@ -177,6 +194,7 @@ mod tests {
max: 315,
identifier: None,
email: None,
currencies: None,
};

assert_eq!(
Expand All @@ -198,6 +216,7 @@ mod tests {
max: 315,
identifier: None,
email: None,
currencies: None,
};

assert_eq!(
Expand All @@ -219,6 +238,7 @@ mod tests {
max: 315,
identifier: None,
email: None,
currencies: None,
};

assert_eq!(
Expand All @@ -240,6 +260,7 @@ mod tests {
max: 315,
identifier: Some(String::from("steve@magal.brutal")),
email: None,
currencies: None,
};

assert_eq!(
Expand All @@ -261,6 +282,7 @@ mod tests {
max: 315,
identifier: None,
email: Some(String::from("steve@magal.brutal")),
currencies: None,
};

assert_eq!(
Expand All @@ -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::<u8>::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";
Expand Down
1 change: 1 addition & 0 deletions tests/lud06.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn test() {
max: 315,
identifier: None,
email: None,
currencies: None,
})
}
},
Expand Down
1 change: 1 addition & 0 deletions tests/lud09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn test() {
max: 315,
identifier: None,
email: None,
currencies: None,
})
}
},
Expand Down
1 change: 1 addition & 0 deletions tests/lud11.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn test() {
max: 315,
identifier: None,
email: None,
currencies: None,
})
}
},
Expand Down
1 change: 1 addition & 0 deletions tests/lud12.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn test() {
max: 315,
identifier: None,
email: None,
currencies: None,
})
}
},
Expand Down
1 change: 1 addition & 0 deletions tests/lud16.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}
},
Expand Down

0 comments on commit d187345

Please sign in to comment.