Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement handle_client_payment_confirm #11

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/uma-demo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ lightspark = { path = "../../lightspark" }
reqwest = "0.11.22"
serde = "1.0.190"
serde_json = "1.0.107"
tokio = "1.33.0"
uma = "0.1.2"
url = "2.4.1"
uuid = { version = "1.5.0", features = ["v4"] }
4 changes: 3 additions & 1 deletion examples/uma-demo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct PayReqParam {
}

#[get("/api/umapayreq/{callback_uuid}")]
#[warn(clippy::await_holding_lock)]
#[allow(clippy::await_holding_lock)]
async fn client_payreq(
vasp: web::Data<Arc<Mutex<SendingVASP<InMemoryPublicKeyCache>>>>,
callback_uuid: web::Path<String>,
Expand All @@ -46,12 +46,14 @@ async fn client_payreq(
}

#[get("/api/sendpayment/{callback_uuid}")]
#[allow(clippy::await_holding_lock)]
async fn send_payment(
vasp: web::Data<Arc<Mutex<SendingVASP<InMemoryPublicKeyCache>>>>,
callback_uuid: web::Path<String>,
) -> impl Responder {
let mut vasp = vasp.lock().unwrap();
vasp.handle_client_payment_confirm(callback_uuid.as_str())
.await
}

#[get("/.well-known/lnurlp/{username}")]
Expand Down
110 changes: 107 additions & 3 deletions examples/uma-demo/src/vasp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ use lightspark::{
objects::{
lightspark_node::LightsparkNode,
lightspark_node_with_remote_signing::LightsparkNodeWithRemoteSigning,
outgoing_payment::OutgoingPayment, transaction_status::TransactionStatus,
},
utils::value_millisatoshi,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use uma::{
payer_data::PayerDataOptions,
protocol::PubKeyResponse,
protocol::{PubKeyResponse, UtxoWithAmount},
public_key_cache::PublicKeyCache,
uma::{
fetch_public_key_for_vasp, get_pay_request, get_signed_lnurlp_request_url,
Expand All @@ -27,12 +29,16 @@ use crate::{config::Config, vasp_request_cache::Vasp1PayReqCache};

pub enum Error {
SigningKeyParseError,
PaymentTimeOut,
LightsparkError(lightspark::error::Error),
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let msg = match self {
Error::SigningKeyParseError => "Error parsing signing key".to_string(),
Error::PaymentTimeOut => "Payment timed out".to_string(),
Error::LightsparkError(e) => format!("Lightspark error: {}", e),
};
write!(f, "{}", msg)
}
Expand Down Expand Up @@ -325,7 +331,7 @@ impl<T: PublicKeyCache> SendingVASP<T> {
unimplemented!()
}

pub fn handle_client_payment_confirm(&mut self, callback_uuid: &str) -> impl Responder {
pub async fn handle_client_payment_confirm(&mut self, callback_uuid: &str) -> impl Responder {
let pay_req_data = match self.request_cache.get_pay_req_data(callback_uuid) {
Some(data) => data,
None => {
Expand Down Expand Up @@ -367,7 +373,105 @@ impl<T: PublicKeyCache> SendingVASP<T> {
}
};

unimplemented!()
let payment = match self
.client
.pay_uma_invoice(
&self.config.node_id,
&pay_req_data.encoded_invoice,
60,
1_000_000,
None,
)
.await
{
Ok(payment) => payment,
Err(_) => {
return HttpResponse::InternalServerError().json(json!({
"status": "ERROR",
"reason": "Failed to pay invoice",
}))
}
};

let payment = match self.wait_for_payment_completion(payment).await {
Ok(payment) => payment,
Err(e) => {
return HttpResponse::InternalServerError().json(json!({
"status": "ERROR",
"reason": format!("Failed to wait for payment completion: {}", e),
}))
}
};

println!(
"Payment {} completed: {:?}",
payment.id,
payment.status.to_string()
);

let mut utxos_with_amounts = Vec::<UtxoWithAmount>::new();
for utxo in payment.uma_post_transaction_data.iter().flatten() {
let millsatoshi_amount = match value_millisatoshi(&utxo.amount) {
Ok(amount) => amount,
Err(_) => {
return HttpResponse::InternalServerError().json(json!({
"status": "ERROR",
"reason": "Error converting amount to millisatoshi",
}))
}
};
utxos_with_amounts.push(UtxoWithAmount {
utxo: utxo.utxo.clone(),
amount: millsatoshi_amount,
});
}

let utxo_with_amounts_bytes = match serde_json::to_vec(&utxos_with_amounts) {
Ok(bytes) => bytes,
Err(_) => {
return HttpResponse::InternalServerError().json(json!({
"status": "ERROR",
"reason": "Error serializing utxos",
}))
}
};

println!(
"Sending UTXOs to {}: {}",
pay_req_data.utxo_callback,
String::from_utf8_lossy(&utxo_with_amounts_bytes)
);

HttpResponse::Ok().json(json!({
"didSucceed": payment.status.to_string() == TransactionStatus::Success.to_string(),
"paymentId": payment.id,
}))
}

async fn wait_for_payment_completion(
&self,
payment: OutgoingPayment,
) -> Result<OutgoingPayment, Error> {
let mut attemp_limit = 200;
let mut payment = payment;
while payment.status.to_string() != TransactionStatus::Success.to_string()
&& payment.status.to_string() != TransactionStatus::Failed.to_string()
&& payment.status.to_string() != TransactionStatus::Cancelled.to_string()
{
if attemp_limit == 0 {
return Err(Error::PaymentTimeOut);
}

attemp_limit -= 1;
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

payment = match self.client.get_entity::<OutgoingPayment>(&payment.id).await {
Ok(p) => p,
Err(e) => return Err(Error::LightsparkError(e)),
};
}

Ok(payment)
}

pub fn handle_well_known_lnurlp(
Expand Down
2 changes: 1 addition & 1 deletion lightspark/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{error::Error, objects::currency_amount::CurrencyAmount};

pub fn value_millisatoshi(amount: CurrencyAmount) -> Result<i64, Error> {
pub fn value_millisatoshi(amount: &CurrencyAmount) -> Result<i64, Error> {
match amount.original_unit {
crate::objects::currency_unit::CurrencyUnit::Bitcoin => {
Ok(amount.original_value * 100_000_000_000)
Expand Down