Skip to content

Commit

Permalink
feat(pay): add support for lud16 (pay to identifier)
Browse files Browse the repository at this point in the history
  • Loading branch information
lsunsi committed Dec 6, 2023
1 parent 1299a8b commit 909971b
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 34 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ required-features = ["client", "server"]
[[test]]
name = "lud12"
required-features = ["client", "server"]

[[test]]
name = "lud16"
required-features = ["client", "server"]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This library works as a toolkit so you can serve and make your LNURL requests wi

## Current support

- [LUD-01](https://github.com/lnurl/luds/blob/luds/01.md): ✅ core ✅ client ✅ server ⚠️ tests
- [LUD-01](https://github.com/lnurl/luds/blob/luds/01.md): ✅ core ✅ client ✅ server tests
- [LUD-02](https://github.com/lnurl/luds/blob/luds/02.md): ✅ core ✅ client ✅ server ⚠️ tests
- [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md): ✅ core ✅ client ✅ server ⚠️ tests
- [LUD-04](https://github.com/lnurl/luds/blob/luds/04.md): 🆘 core 🆘 client 🆘 server 🆘 tests
Expand All @@ -24,7 +24,7 @@ This library works as a toolkit so you can serve and make your LNURL requests wi
- [LUD-13](https://github.com/lnurl/luds/blob/luds/13.md): 🆘 core 🆘 client 🆘 server 🆘 tests
- [LUD-14](https://github.com/lnurl/luds/blob/luds/14.md): 🆘 core 🆘 client 🆘 server 🆘 tests
- [LUD-15](https://github.com/lnurl/luds/blob/luds/15.md): 🆘 core 🆘 client 🆘 server 🆘 tests
- [LUD-16](https://github.com/lnurl/luds/blob/luds/16.md): ✅ core ⚠️ client 🆘 server 🆘 tests
- [LUD-16](https://github.com/lnurl/luds/blob/luds/16.md): ✅ core client server tests
- [LUD-17](https://github.com/lnurl/luds/blob/luds/17.md): ✅ core ⚠️ client ⚠️ server ⚠️ tests
- [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
Expand Down
106 changes: 106 additions & 0 deletions src/core/pay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub struct Query {
pub callback: url::Url,
pub short_description: String,
pub long_description: Option<String>,
pub identifier: Option<String>,
pub email: Option<String>,
pub jpeg: Option<Vec<u8>>,
pub png: Option<Vec<u8>>,
pub comment_size: u64,
Expand Down Expand Up @@ -59,13 +61,31 @@ impl std::str::FromStr for Query {
_ => None,
});

let identifier = metadata
.iter()
.find_map(|(k, v)| (k == "text/identifier").then_some(v))
.and_then(|v| match v {
Value::String(s) => Some(String::from(s)),
_ => None,
});

let email = metadata
.iter()
.find_map(|(k, v)| (k == "text/email").then_some(v))
.and_then(|v| match v {
Value::String(s) => Some(String::from(s)),
_ => None,
});

Ok(Query {
callback: p.callback.0.into_owned(),
min: p.min_sendable,
max: p.max_sendable,
short_description,
long_description,
comment_size,
identifier,
email,
jpeg,
png,
})
Expand All @@ -88,6 +108,10 @@ impl std::fmt::Display for Query {
self.png
.as_ref()
.map(|s| ("image/png;base64", BASE64_STANDARD.encode(s))),
self.identifier
.as_ref()
.map(|s| ("text/identifier", s.clone())),
self.email.as_ref().map(|s| ("text/email", s.clone())),
]
.into_iter()
.flatten()
Expand Down Expand Up @@ -270,6 +294,8 @@ mod tests {
assert!(parsed.long_description.is_none());
assert!(parsed.jpeg.is_none());
assert!(parsed.png.is_none());
assert!(parsed.identifier.is_none());
assert!(parsed.email.is_none());
}

#[test]
Expand Down Expand Up @@ -322,6 +348,36 @@ mod tests {
assert_eq!(parsed.png.unwrap(), b"fotobrutal");
}

#[test]
fn query_parse_identifier() {
let input = r#"
{
"callback": "https://yuri?o=callback",
"metadata": "[[\"text/plain\", \"boneco do steve magal\"],[\"text/identifier\", \"steve@magal.brutal\"]]",
"maxSendable": 315,
"minSendable": 314
}
"#;

let parsed = input.parse::<super::Query>().expect("parse");
assert_eq!(parsed.identifier.unwrap(), "steve@magal.brutal");
}

#[test]
fn query_parse_email() {
let input = r#"
{
"callback": "https://yuri?o=callback",
"metadata": "[[\"text/plain\", \"boneco do steve magal\"],[\"text/email\", \"steve@magal.brutal\"]]",
"maxSendable": 315,
"minSendable": 314
}
"#;

let parsed = input.parse::<super::Query>().expect("parse");
assert_eq!(parsed.email.unwrap(), "steve@magal.brutal");
}

#[test]
fn query_render_base() {
let query = super::Query {
Expand All @@ -333,6 +389,8 @@ mod tests {
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: None,
};

assert_eq!(
Expand All @@ -352,6 +410,8 @@ mod tests {
comment_size: 140,
min: 314,
max: 315,
identifier: None,
email: None,
};

assert_eq!(
Expand All @@ -371,6 +431,8 @@ mod tests {
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: None,
};

assert_eq!(
Expand All @@ -390,6 +452,8 @@ mod tests {
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: None,
};

assert_eq!(
Expand All @@ -398,6 +462,48 @@ mod tests {
);
}

#[test]
fn query_render_identifier() {
let query = super::Query {
callback: url::Url::parse("https://yuri?o=callback").expect("url"),
short_description: String::from("boneco do steve magal"),
long_description: None,
jpeg: Some(b"imagembrutal".to_vec()),
png: Some(b"fotobrutal".to_vec()),
comment_size: 0,
min: 314,
max: 315,
identifier: Some(String::from("steve@magal.brutal")),
email: None,
};

assert_eq!(
query.to_string(),
r#"{"tag":"payRequest","metadata":"[[\"text/plain\",\"boneco do steve magal\"],[\"image/jpeg;base64\",\"aW1hZ2VtYnJ1dGFs\"],[\"image/png;base64\",\"Zm90b2JydXRhbA==\"],[\"text/identifier\",\"steve@magal.brutal\"]]","callback":"https://yuri/?o=callback","minSendable":314,"maxSendable":315,"commentAllowed":0}"#
);
}

#[test]
fn query_render_email() {
let query = super::Query {
callback: url::Url::parse("https://yuri?o=callback").expect("url"),
short_description: String::from("boneco do steve magal"),
long_description: None,
jpeg: Some(b"imagembrutal".to_vec()),
png: Some(b"fotobrutal".to_vec()),
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: Some(String::from("steve@magal.brutal")),
};

assert_eq!(
query.to_string(),
r#"{"tag":"payRequest","metadata":"[[\"text/plain\",\"boneco do steve magal\"],[\"image/jpeg;base64\",\"aW1hZ2VtYnJ1dGFs\"],[\"image/png;base64\",\"Zm90b2JydXRhbA==\"],[\"text/email\",\"steve@magal.brutal\"]]","callback":"https://yuri/?o=callback","minSendable":314,"maxSendable":315,"commentAllowed":0}"#
);
}

#[test]
fn callback() {
let input = r#"
Expand Down
58 changes: 32 additions & 26 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use axum::{extract::RawQuery, http::StatusCode, routing::get, Router};
use axum::{extract::Path, extract::RawQuery, http::StatusCode, routing::get, Router};
use std::future::Future;

pub struct Server<CQ, CC, PQ, PC, WQ, WC> {
Expand All @@ -13,29 +13,29 @@ pub struct Server<CQ, CC, PQ, PC, WQ, WC> {
impl Default
for Server<
// Channel Request
unimplemented::Query<crate::core::channel::Query>,
unimplemented::Callback<
unimplemented::Handler<(), crate::core::channel::Query>,
unimplemented::Handler<
(String, String, crate::core::channel::CallbackAction),
crate::core::channel::CallbackResponse,
>,
// Pay Request
unimplemented::Query<crate::core::pay::Query>,
unimplemented::Callback<(u64, Option<String>), crate::core::pay::CallbackResponse>,
unimplemented::Handler<Option<String>, crate::core::pay::Query>,
unimplemented::Handler<(u64, Option<String>), crate::core::pay::CallbackResponse>,
// Withdraw Request
unimplemented::Query<crate::core::withdraw::Query>,
unimplemented::Callback<(String, String), crate::core::withdraw::CallbackResponse>,
unimplemented::Handler<(), crate::core::withdraw::Query>,
unimplemented::Handler<(String, String), crate::core::withdraw::CallbackResponse>,
>
{
fn default() -> Self {
Server {
channel_query: unimplemented::query,
channel_callback: unimplemented::callback,
channel_query: unimplemented::handler,
channel_callback: unimplemented::handler,

pay_query: unimplemented::query,
pay_callback: unimplemented::callback,
pay_query: unimplemented::handler,
pay_callback: unimplemented::handler,

withdraw_query: unimplemented::query,
withdraw_callback: unimplemented::callback,
withdraw_query: unimplemented::handler,
withdraw_callback: unimplemented::handler,
}
}
}
Expand Down Expand Up @@ -90,7 +90,7 @@ impl<CQ, CC, PQ, PC, WQ, WC> Server<CQ, CC, PQ, PC, WQ, WC> {
impl<CQ, CQFut, CC, CCFut, PQ, PQFut, PC, PCFut, WQ, WQFut, WC, WCFut>
Server<CQ, CC, PQ, PC, WQ, WC>
where
CQ: 'static + Send + Clone + Fn() -> CQFut,
CQ: 'static + Send + Clone + Fn(()) -> CQFut,
CQFut: Send + Future<Output = Result<crate::core::channel::Query, StatusCode>>,

CC: 'static
Expand All @@ -99,25 +99,26 @@ where
+ Fn((String, String, crate::core::channel::CallbackAction)) -> CCFut,
CCFut: Send + Future<Output = Result<crate::core::channel::CallbackResponse, StatusCode>>,

PQ: 'static + Send + Clone + Fn() -> PQFut,
PQ: 'static + Send + Clone + Fn(Option<String>) -> PQFut,
PQFut: Send + Future<Output = Result<crate::core::pay::Query, StatusCode>>,

PC: 'static + Send + Clone + Fn((u64, Option<String>)) -> PCFut,
PCFut: Send + Future<Output = Result<crate::core::pay::CallbackResponse, StatusCode>>,

WQ: 'static + Send + Clone + Fn() -> WQFut,
WQ: 'static + Send + Clone + Fn(()) -> WQFut,
WQFut: Send + Future<Output = Result<crate::core::withdraw::Query, StatusCode>>,

WC: 'static + Send + Clone + Fn((String, String)) -> WCFut,
WCFut: Send + Future<Output = Result<crate::core::withdraw::CallbackResponse, StatusCode>>,
{
#[allow(clippy::too_many_lines)]
pub fn build(self) -> Router<()> {
Router::new()
.route(
"/lnurlc",
get(move || {
let cq = self.channel_query.clone();
async move { cq().await.map(|a| a.to_string()) }
async move { cq(()).await.map(|a| a.to_string()) }
}),
)
.route(
Expand Down Expand Up @@ -155,11 +156,21 @@ where
}
}),
)
.route(
"/.well-known/lnurlp/:identifier",
get({
let pq = self.pay_query.clone();
move |Path(identifier): Path<String>| {
let pq = pq.clone();
async move { pq(Some(identifier)).await.map(|a| a.to_string()) }
}
}),
)
.route(
"/lnurlp",
get(move || {
let pq = self.pay_query.clone();
async move { pq().await.map(|a| a.to_string()) }
async move { pq(None).await.map(|a| a.to_string()) }
}),
)
.route(
Expand Down Expand Up @@ -188,7 +199,7 @@ where
"/lnurlw",
get(move || {
let wq = self.withdraw_query.clone();
async move { wq().await.map(|a| a.to_string()) }
async move { wq(()).await.map(|a| a.to_string()) }
}),
)
.route(
Expand Down Expand Up @@ -222,13 +233,8 @@ mod unimplemented {
task::{Context, Poll},
};

pub(super) type Query<Ret> = fn() -> Unimplemented<Ret>;
pub(super) fn query<T>() -> Unimplemented<T> {
Unimplemented(PhantomData)
}

pub(super) type Callback<Param, Ret> = fn(Param) -> Unimplemented<Ret>;
pub(super) fn callback<T, T1>(_: T1) -> Unimplemented<T> {
pub(super) type Handler<Param, Ret> = fn(Param) -> Unimplemented<Ret>;
pub(super) fn handler<Param, Ret>(_: Param) -> Unimplemented<Ret> {
Unimplemented(PhantomData)
}

Expand Down
2 changes: 1 addition & 1 deletion tests/lud02.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn test() {

let router = lnurlkit::Server::default()
.channel_request(
move || {
move |()| {
let callback = callback_url.clone();
async {
Ok(lnurlkit::channel::Query {
Expand Down
2 changes: 1 addition & 1 deletion tests/lud03.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn test() {

let router = lnurlkit::Server::default()
.withdraw_request(
move || {
move |()| {
let callback = callback_url.clone();
async {
Ok(lnurlkit::withdraw::Query {
Expand Down
4 changes: 3 additions & 1 deletion tests/lud06.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn test() {

let router = lnurlkit::Server::default()
.pay_request(
move || {
move |_| {
let callback = callback_url.clone();
async {
Ok(lnurlkit::pay::Query {
Expand All @@ -23,6 +23,8 @@ async fn test() {
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: None,
})
}
},
Expand Down
4 changes: 3 additions & 1 deletion tests/lud09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async fn test() {

let router = lnurlkit::Server::default()
.pay_request(
move || {
move |_| {
let callback = callback_url.clone();
async {
Ok(lnurlkit::pay::Query {
Expand All @@ -23,6 +23,8 @@ async fn test() {
comment_size: 0,
min: 314,
max: 315,
identifier: None,
email: None,
})
}
},
Expand Down
Loading

0 comments on commit 909971b

Please sign in to comment.