mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-19 05:35:18 +01:00
feat: bolt12
This commit is contained in:
@@ -25,6 +25,7 @@ lnd = ["dep:cdk-lnd"]
|
||||
anyhow.workspace = true
|
||||
async-trait.workspace = true
|
||||
bitcoin.workspace = true
|
||||
cashu.workspace = true
|
||||
cdk-common = { workspace = true, features = ["mint"] }
|
||||
cdk-cln = { workspace = true, optional = true }
|
||||
cdk-lnd = { workspace = true, optional = true }
|
||||
@@ -34,7 +35,7 @@ thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
lightning-invoice.workspace = true
|
||||
uuid = { workspace = true, optional = true }
|
||||
uuid = { workspace = true }
|
||||
utoipa = { workspace = true, optional = true }
|
||||
futures.workspace = true
|
||||
serde_json.workspace = true
|
||||
@@ -43,6 +44,8 @@ tonic = { workspace = true, features = ["router"] }
|
||||
prost.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
tokio-util = { workspace = true, default-features = false }
|
||||
hex = "0.4"
|
||||
lightning = { workspace = true }
|
||||
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Errors
|
||||
//! Error for payment processor
|
||||
|
||||
use thiserror::Error;
|
||||
use tonic::Status;
|
||||
|
||||
/// CDK Payment processor error
|
||||
#[derive(Debug, Error)]
|
||||
@@ -8,13 +9,73 @@ pub enum Error {
|
||||
/// Invalid ID
|
||||
#[error("Invalid id")]
|
||||
InvalidId,
|
||||
/// Invalid payment identifier
|
||||
#[error("Invalid payment identifier")]
|
||||
InvalidPaymentIdentifier,
|
||||
/// Invalid hash
|
||||
#[error("Invalid hash")]
|
||||
InvalidHash,
|
||||
/// Invalid currency unit
|
||||
#[error("Invalid currency unit: {0}")]
|
||||
InvalidCurrencyUnit(String),
|
||||
/// Parse invoice error
|
||||
#[error(transparent)]
|
||||
Invoice(#[from] lightning_invoice::ParseOrSemanticError),
|
||||
/// Hex decode error
|
||||
#[error(transparent)]
|
||||
Hex(#[from] hex::FromHexError),
|
||||
/// BOLT12 parse error
|
||||
#[error("BOLT12 parse error")]
|
||||
Bolt12Parse,
|
||||
/// NUT00 Error
|
||||
#[error(transparent)]
|
||||
NUT00(#[from] cdk_common::nuts::nut00::Error),
|
||||
/// NUT05 error
|
||||
#[error(transparent)]
|
||||
NUT05(#[from] cdk_common::nuts::nut05::Error),
|
||||
/// Parse invoice error
|
||||
/// Payment error
|
||||
#[error(transparent)]
|
||||
Invoice(#[from] lightning_invoice::ParseOrSemanticError),
|
||||
Payment(#[from] cdk_common::payment::Error),
|
||||
}
|
||||
|
||||
impl From<Error> for Status {
|
||||
fn from(error: Error) -> Self {
|
||||
match error {
|
||||
Error::InvalidId => Status::invalid_argument("Invalid ID"),
|
||||
Error::InvalidPaymentIdentifier => {
|
||||
Status::invalid_argument("Invalid payment identifier")
|
||||
}
|
||||
Error::InvalidHash => Status::invalid_argument("Invalid hash"),
|
||||
Error::InvalidCurrencyUnit(unit) => {
|
||||
Status::invalid_argument(format!("Invalid currency unit: {unit}"))
|
||||
}
|
||||
Error::Invoice(err) => Status::invalid_argument(format!("Invoice error: {err}")),
|
||||
Error::Hex(err) => Status::invalid_argument(format!("Hex decode error: {err}")),
|
||||
Error::Bolt12Parse => Status::invalid_argument("BOLT12 parse error"),
|
||||
Error::NUT00(err) => Status::internal(format!("NUT00 error: {err}")),
|
||||
Error::NUT05(err) => Status::internal(format!("NUT05 error: {err}")),
|
||||
Error::Payment(err) => Status::internal(format!("Payment error: {err}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for cdk_common::payment::Error {
|
||||
fn from(error: Error) -> Self {
|
||||
match error {
|
||||
Error::InvalidId => Self::Custom("Invalid ID".to_string()),
|
||||
Error::InvalidPaymentIdentifier => {
|
||||
Self::Custom("Invalid payment identifier".to_string())
|
||||
}
|
||||
Error::InvalidHash => Self::Custom("Invalid hash".to_string()),
|
||||
Error::InvalidCurrencyUnit(unit) => {
|
||||
Self::Custom(format!("Invalid currency unit: {unit}"))
|
||||
}
|
||||
Error::Invoice(err) => Self::Custom(format!("Invoice error: {err}")),
|
||||
Error::Hex(err) => Self::Custom(format!("Hex decode error: {err}")),
|
||||
Error::Bolt12Parse => Self::Custom("BOLT12 parse error".to_string()),
|
||||
Error::NUT00(err) => Self::Custom(format!("NUT00 error: {err}")),
|
||||
Error::NUT05(err) => err.into(),
|
||||
Error::Payment(err) => err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#![warn(rustdoc::bare_urls)]
|
||||
|
||||
pub mod error;
|
||||
/// Protocol types and functionality for the CDK payment processor
|
||||
pub mod proto;
|
||||
|
||||
pub use proto::cdk_payment_processor_client::CdkPaymentProcessorClient;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use cdk_common::payment::{
|
||||
CreateIncomingPaymentResponse, MakePaymentResponse as CdkMakePaymentResponse, MintPayment,
|
||||
PaymentQuoteResponse,
|
||||
CreateIncomingPaymentResponse, IncomingPaymentOptions as CdkIncomingPaymentOptions,
|
||||
MakePaymentResponse as CdkMakePaymentResponse, MintPayment,
|
||||
PaymentQuoteResponse as CdkPaymentQuoteResponse, WaitPaymentResponse,
|
||||
};
|
||||
use cdk_common::{mint, Amount, CurrencyUnit, MeltOptions, MintQuoteState};
|
||||
use futures::{Stream, StreamExt};
|
||||
use serde_json::Value;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
@@ -17,10 +16,10 @@ use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||
use tonic::{async_trait, Request};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::cdk_payment_processor_client::CdkPaymentProcessorClient;
|
||||
use super::{
|
||||
CheckIncomingPaymentRequest, CheckOutgoingPaymentRequest, CreatePaymentRequest,
|
||||
MakePaymentRequest, SettingsRequest, WaitIncomingPaymentRequest,
|
||||
use crate::proto::cdk_payment_processor_client::CdkPaymentProcessorClient;
|
||||
use crate::proto::{
|
||||
CheckIncomingPaymentRequest, CheckOutgoingPaymentRequest, CreatePaymentRequest, EmptyRequest,
|
||||
IncomingPaymentOptions, MakePaymentRequest, OutgoingPaymentRequestType, PaymentQuoteRequest,
|
||||
};
|
||||
|
||||
/// Payment Processor
|
||||
@@ -100,7 +99,7 @@ impl MintPayment for PaymentProcessorClient {
|
||||
async fn get_settings(&self) -> Result<Value, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
let response = inner
|
||||
.get_settings(Request::new(SettingsRequest {}))
|
||||
.get_settings(Request::new(EmptyRequest {}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not get settings: {}", err);
|
||||
@@ -115,18 +114,36 @@ impl MintPayment for PaymentProcessorClient {
|
||||
/// Create a new invoice
|
||||
async fn create_incoming_payment_request(
|
||||
&self,
|
||||
amount: Amount,
|
||||
unit: &CurrencyUnit,
|
||||
description: String,
|
||||
unix_expiry: Option<u64>,
|
||||
unit: &cdk_common::CurrencyUnit,
|
||||
options: CdkIncomingPaymentOptions,
|
||||
) -> Result<CreateIncomingPaymentResponse, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
|
||||
let proto_options = match options {
|
||||
CdkIncomingPaymentOptions::Bolt11(opts) => IncomingPaymentOptions {
|
||||
options: Some(super::incoming_payment_options::Options::Bolt11(
|
||||
super::Bolt11IncomingPaymentOptions {
|
||||
description: opts.description,
|
||||
amount: opts.amount.into(),
|
||||
unix_expiry: opts.unix_expiry,
|
||||
},
|
||||
)),
|
||||
},
|
||||
CdkIncomingPaymentOptions::Bolt12(opts) => IncomingPaymentOptions {
|
||||
options: Some(super::incoming_payment_options::Options::Bolt12(
|
||||
super::Bolt12IncomingPaymentOptions {
|
||||
description: opts.description,
|
||||
amount: opts.amount.map(Into::into),
|
||||
unix_expiry: opts.unix_expiry,
|
||||
},
|
||||
)),
|
||||
},
|
||||
};
|
||||
|
||||
let response = inner
|
||||
.create_payment(Request::new(CreatePaymentRequest {
|
||||
amount: amount.into(),
|
||||
unit: unit.to_string(),
|
||||
description,
|
||||
unix_expiry,
|
||||
options: Some(proto_options),
|
||||
}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -143,16 +160,36 @@ impl MintPayment for PaymentProcessorClient {
|
||||
|
||||
async fn get_payment_quote(
|
||||
&self,
|
||||
request: &str,
|
||||
unit: &CurrencyUnit,
|
||||
options: Option<MeltOptions>,
|
||||
) -> Result<PaymentQuoteResponse, Self::Err> {
|
||||
unit: &cdk_common::CurrencyUnit,
|
||||
options: cdk_common::payment::OutgoingPaymentOptions,
|
||||
) -> Result<CdkPaymentQuoteResponse, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
|
||||
let request_type = match &options {
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt11(_) => {
|
||||
OutgoingPaymentRequestType::Bolt11Invoice
|
||||
}
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt12(_) => {
|
||||
OutgoingPaymentRequestType::Bolt12Offer
|
||||
}
|
||||
};
|
||||
|
||||
let proto_request = match &options {
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt11(opts) => opts.bolt11.to_string(),
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt12(opts) => opts.offer.to_string(),
|
||||
};
|
||||
|
||||
let proto_options = match &options {
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt11(opts) => opts.melt_options,
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt12(opts) => opts.melt_options,
|
||||
};
|
||||
|
||||
let response = inner
|
||||
.get_payment_quote(Request::new(super::PaymentQuoteRequest {
|
||||
request: request.to_string(),
|
||||
.get_payment_quote(Request::new(PaymentQuoteRequest {
|
||||
request: proto_request,
|
||||
unit: unit.to_string(),
|
||||
options: options.map(|o| o.into()),
|
||||
options: proto_options.map(Into::into),
|
||||
request_type: request_type.into(),
|
||||
}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -167,16 +204,44 @@ impl MintPayment for PaymentProcessorClient {
|
||||
|
||||
async fn make_payment(
|
||||
&self,
|
||||
melt_quote: mint::MeltQuote,
|
||||
partial_amount: Option<Amount>,
|
||||
max_fee_amount: Option<Amount>,
|
||||
_unit: &cdk_common::CurrencyUnit,
|
||||
options: cdk_common::payment::OutgoingPaymentOptions,
|
||||
) -> Result<CdkMakePaymentResponse, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
|
||||
let payment_options = match options {
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt11(opts) => {
|
||||
super::OutgoingPaymentVariant {
|
||||
options: Some(super::outgoing_payment_variant::Options::Bolt11(
|
||||
super::Bolt11OutgoingPaymentOptions {
|
||||
bolt11: opts.bolt11.to_string(),
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt12(opts) => {
|
||||
super::OutgoingPaymentVariant {
|
||||
options: Some(super::outgoing_payment_variant::Options::Bolt12(
|
||||
super::Bolt12OutgoingPaymentOptions {
|
||||
offer: opts.offer.to_string(),
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
invoice: opts.invoice,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let response = inner
|
||||
.make_payment(Request::new(MakePaymentRequest {
|
||||
melt_quote: Some(melt_quote.into()),
|
||||
partial_amount: partial_amount.map(|a| a.into()),
|
||||
max_fee_amount: max_fee_amount.map(|a| a.into()),
|
||||
payment_options: Some(payment_options),
|
||||
partial_amount: None,
|
||||
max_fee_amount: None,
|
||||
}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -198,17 +263,16 @@ impl MintPayment for PaymentProcessorClient {
|
||||
})?)
|
||||
}
|
||||
|
||||
/// Listen for invoices to be paid to the mint
|
||||
#[instrument(skip_all)]
|
||||
async fn wait_any_incoming_payment(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
) -> Result<Pin<Box<dyn Stream<Item = WaitPaymentResponse> + Send>>, Self::Err> {
|
||||
self.wait_incoming_payment_stream_is_active
|
||||
.store(true, Ordering::SeqCst);
|
||||
tracing::debug!("Client waiting for payment");
|
||||
let mut inner = self.inner.clone();
|
||||
let stream = inner
|
||||
.wait_incoming_payment(WaitIncomingPaymentRequest {})
|
||||
.wait_incoming_payment(EmptyRequest {})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not check incoming payment stream: {}", err);
|
||||
@@ -222,15 +286,18 @@ impl MintPayment for PaymentProcessorClient {
|
||||
|
||||
let transformed_stream = stream
|
||||
.take_until(cancel_fut)
|
||||
.filter_map(|item| async move {
|
||||
.filter_map(|item| async {
|
||||
match item {
|
||||
Ok(value) => {
|
||||
tracing::warn!("{}", value.lookup_id);
|
||||
Some(value.lookup_id)
|
||||
}
|
||||
Ok(value) => match value.try_into() {
|
||||
Ok(payment_response) => Some(payment_response),
|
||||
Err(e) => {
|
||||
tracing::error!("Error converting payment response: {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Error in payment stream: {}", e);
|
||||
None // Skip this item and continue with the stream
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -255,12 +322,12 @@ impl MintPayment for PaymentProcessorClient {
|
||||
|
||||
async fn check_incoming_payment_status(
|
||||
&self,
|
||||
request_lookup_id: &str,
|
||||
) -> Result<MintQuoteState, Self::Err> {
|
||||
payment_identifier: &cdk_common::payment::PaymentIdentifier,
|
||||
) -> Result<Vec<WaitPaymentResponse>, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
let response = inner
|
||||
.check_incoming_payment(Request::new(CheckIncomingPaymentRequest {
|
||||
request_lookup_id: request_lookup_id.to_string(),
|
||||
request_identifier: Some(payment_identifier.clone().into()),
|
||||
}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
@@ -269,20 +336,21 @@ impl MintPayment for PaymentProcessorClient {
|
||||
})?;
|
||||
|
||||
let check_incoming = response.into_inner();
|
||||
|
||||
let status = check_incoming.status().as_str_name();
|
||||
|
||||
Ok(MintQuoteState::from_str(status)?)
|
||||
check_incoming
|
||||
.payments
|
||||
.into_iter()
|
||||
.map(|resp| resp.try_into().map_err(Self::Err::from))
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn check_outgoing_payment(
|
||||
&self,
|
||||
request_lookup_id: &str,
|
||||
payment_identifier: &cdk_common::payment::PaymentIdentifier,
|
||||
) -> Result<CdkMakePaymentResponse, Self::Err> {
|
||||
let mut inner = self.inner.clone();
|
||||
let response = inner
|
||||
.check_outgoing_payment(Request::new(CheckOutgoingPaymentRequest {
|
||||
request_lookup_id: request_lookup_id.to_string(),
|
||||
request_identifier: Some(payment_identifier.clone().into()),
|
||||
}))
|
||||
.await
|
||||
.map_err(|err| {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! Proto types for payment processor
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use cdk_common::payment::{
|
||||
CreateIncomingPaymentResponse, MakePaymentResponse as CdkMakePaymentResponse,
|
||||
PaymentIdentifier as CdkPaymentIdentifier, WaitPaymentResponse,
|
||||
};
|
||||
use cdk_common::{Bolt11Invoice, CurrencyUnit, MeltQuoteBolt11Request};
|
||||
use melt_options::Options;
|
||||
use cdk_common::{CurrencyUnit, MeltOptions as CdkMeltOptions};
|
||||
|
||||
mod client;
|
||||
mod server;
|
||||
|
||||
@@ -15,15 +14,85 @@ pub use server::PaymentProcessorServer;
|
||||
|
||||
tonic::include_proto!("cdk_payment_processor");
|
||||
|
||||
impl From<CdkPaymentIdentifier> for PaymentIdentifier {
|
||||
fn from(value: CdkPaymentIdentifier) -> Self {
|
||||
match value {
|
||||
CdkPaymentIdentifier::Label(id) => Self {
|
||||
r#type: PaymentIdentifierType::Label.into(),
|
||||
value: Some(payment_identifier::Value::Id(id)),
|
||||
},
|
||||
CdkPaymentIdentifier::OfferId(id) => Self {
|
||||
r#type: PaymentIdentifierType::OfferId.into(),
|
||||
value: Some(payment_identifier::Value::Id(id)),
|
||||
},
|
||||
CdkPaymentIdentifier::PaymentHash(hash) => Self {
|
||||
r#type: PaymentIdentifierType::PaymentHash.into(),
|
||||
value: Some(payment_identifier::Value::Hash(hex::encode(hash))),
|
||||
},
|
||||
CdkPaymentIdentifier::Bolt12PaymentHash(hash) => Self {
|
||||
r#type: PaymentIdentifierType::Bolt12PaymentHash.into(),
|
||||
value: Some(payment_identifier::Value::Hash(hex::encode(hash))),
|
||||
},
|
||||
CdkPaymentIdentifier::CustomId(id) => Self {
|
||||
r#type: PaymentIdentifierType::CustomId.into(),
|
||||
value: Some(payment_identifier::Value::Id(id)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PaymentIdentifier> for CdkPaymentIdentifier {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_from(value: PaymentIdentifier) -> Result<Self, Self::Error> {
|
||||
match (value.r#type(), value.value) {
|
||||
(PaymentIdentifierType::Label, Some(payment_identifier::Value::Id(id))) => {
|
||||
Ok(CdkPaymentIdentifier::Label(id))
|
||||
}
|
||||
(PaymentIdentifierType::OfferId, Some(payment_identifier::Value::Id(id))) => {
|
||||
Ok(CdkPaymentIdentifier::OfferId(id))
|
||||
}
|
||||
(PaymentIdentifierType::PaymentHash, Some(payment_identifier::Value::Hash(hash))) => {
|
||||
let decoded = hex::decode(hash)?;
|
||||
let hash_array: [u8; 32] = decoded
|
||||
.try_into()
|
||||
.map_err(|_| crate::error::Error::InvalidHash)?;
|
||||
Ok(CdkPaymentIdentifier::PaymentHash(hash_array))
|
||||
}
|
||||
(
|
||||
PaymentIdentifierType::Bolt12PaymentHash,
|
||||
Some(payment_identifier::Value::Hash(hash)),
|
||||
) => {
|
||||
let decoded = hex::decode(hash)?;
|
||||
let hash_array: [u8; 32] = decoded
|
||||
.try_into()
|
||||
.map_err(|_| crate::error::Error::InvalidHash)?;
|
||||
Ok(CdkPaymentIdentifier::Bolt12PaymentHash(hash_array))
|
||||
}
|
||||
(PaymentIdentifierType::CustomId, Some(payment_identifier::Value::Id(id))) => {
|
||||
Ok(CdkPaymentIdentifier::CustomId(id))
|
||||
}
|
||||
_ => Err(crate::error::Error::InvalidPaymentIdentifier),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MakePaymentResponse> for CdkMakePaymentResponse {
|
||||
type Error = crate::error::Error;
|
||||
fn try_from(value: MakePaymentResponse) -> Result<Self, Self::Error> {
|
||||
let status = value.status().as_str_name().parse()?;
|
||||
let payment_proof = value.payment_proof;
|
||||
let total_spent = value.total_spent.into();
|
||||
let unit = CurrencyUnit::from_str(&value.unit)?;
|
||||
let payment_identifier = value
|
||||
.payment_identifier
|
||||
.ok_or(crate::error::Error::InvalidPaymentIdentifier)?;
|
||||
Ok(Self {
|
||||
payment_lookup_id: value.payment_lookup_id.clone(),
|
||||
payment_proof: value.payment_proof.clone(),
|
||||
status: value.status().as_str_name().parse()?,
|
||||
total_spent: value.total_spent.into(),
|
||||
unit: value.unit.parse()?,
|
||||
payment_lookup_id: payment_identifier.try_into()?,
|
||||
payment_proof,
|
||||
status,
|
||||
total_spent,
|
||||
unit,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -31,8 +100,8 @@ impl TryFrom<MakePaymentResponse> for CdkMakePaymentResponse {
|
||||
impl From<CdkMakePaymentResponse> for MakePaymentResponse {
|
||||
fn from(value: CdkMakePaymentResponse) -> Self {
|
||||
Self {
|
||||
payment_lookup_id: value.payment_lookup_id.clone(),
|
||||
payment_proof: value.payment_proof.clone(),
|
||||
payment_identifier: Some(value.payment_lookup_id.into()),
|
||||
payment_proof: value.payment_proof,
|
||||
status: QuoteState::from(value.status).into(),
|
||||
total_spent: value.total_spent.into(),
|
||||
unit: value.unit.to_string(),
|
||||
@@ -43,8 +112,8 @@ impl From<CdkMakePaymentResponse> for MakePaymentResponse {
|
||||
impl From<CreateIncomingPaymentResponse> for CreatePaymentResponse {
|
||||
fn from(value: CreateIncomingPaymentResponse) -> Self {
|
||||
Self {
|
||||
request_lookup_id: value.request_lookup_id,
|
||||
request: value.request.to_string(),
|
||||
request_identifier: Some(value.request_lookup_id.into()),
|
||||
request: value.request,
|
||||
expiry: value.expiry,
|
||||
}
|
||||
}
|
||||
@@ -54,82 +123,102 @@ impl TryFrom<CreatePaymentResponse> for CreateIncomingPaymentResponse {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_from(value: CreatePaymentResponse) -> Result<Self, Self::Error> {
|
||||
let request_identifier = value
|
||||
.request_identifier
|
||||
.ok_or(crate::error::Error::InvalidPaymentIdentifier)?;
|
||||
Ok(Self {
|
||||
request_lookup_id: value.request_lookup_id,
|
||||
request_lookup_id: request_identifier.try_into()?,
|
||||
request: value.request,
|
||||
expiry: value.expiry,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MeltQuoteBolt11Request> for PaymentQuoteRequest {
|
||||
fn from(value: &MeltQuoteBolt11Request) -> Self {
|
||||
Self {
|
||||
request: value.request.to_string(),
|
||||
unit: value.unit.to_string(),
|
||||
options: value.options.map(|o| o.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::payment::PaymentQuoteResponse> for PaymentQuoteResponse {
|
||||
fn from(value: cdk_common::payment::PaymentQuoteResponse) -> Self {
|
||||
Self {
|
||||
request_lookup_id: value.request_lookup_id,
|
||||
request_identifier: Some(value.request_lookup_id.into()),
|
||||
amount: value.amount.into(),
|
||||
fee: value.fee.into(),
|
||||
state: QuoteState::from(value.state).into(),
|
||||
unit: value.unit.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::nut23::MeltOptions> for MeltOptions {
|
||||
fn from(value: cdk_common::nut23::MeltOptions) -> Self {
|
||||
Self {
|
||||
options: Some(value.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::nut23::MeltOptions> for Options {
|
||||
fn from(value: cdk_common::nut23::MeltOptions) -> Self {
|
||||
match value {
|
||||
cdk_common::MeltOptions::Mpp { mpp } => Self::Mpp(Mpp {
|
||||
amount: mpp.amount.into(),
|
||||
state: QuoteState::from(value.state).into(),
|
||||
melt_options: value.options.map(|opt| match opt {
|
||||
cdk_common::payment::PaymentQuoteOptions::Bolt12 { invoice } => {
|
||||
PaymentQuoteOptions {
|
||||
melt_options: Some(payment_quote_options::MeltOptions::Bolt12(
|
||||
Bolt12Options {
|
||||
invoice: invoice.map(String::from_utf8).and_then(|r| r.ok()),
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
}),
|
||||
cdk_common::MeltOptions::Amountless { amountless } => Self::Amountless(Amountless {
|
||||
amount_msat: amountless.amount_msat.into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MeltOptions> for cdk_common::nut23::MeltOptions {
|
||||
fn from(value: MeltOptions) -> Self {
|
||||
let options = value.options.expect("option defined");
|
||||
match options {
|
||||
Options::Mpp(mpp) => cdk_common::MeltOptions::new_mpp(mpp.amount),
|
||||
Options::Amountless(amountless) => {
|
||||
cdk_common::MeltOptions::new_amountless(amountless.amount_msat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PaymentQuoteResponse> for cdk_common::payment::PaymentQuoteResponse {
|
||||
fn from(value: PaymentQuoteResponse) -> Self {
|
||||
let state_val = value.state();
|
||||
let request_identifier = value
|
||||
.request_identifier
|
||||
.expect("request identifier required");
|
||||
|
||||
Self {
|
||||
request_lookup_id: value.request_lookup_id.clone(),
|
||||
request_lookup_id: request_identifier
|
||||
.try_into()
|
||||
.expect("valid request identifier"),
|
||||
amount: value.amount.into(),
|
||||
unit: CurrencyUnit::from_str(&value.unit).unwrap_or_default(),
|
||||
fee: value.fee.into(),
|
||||
state: value.state().into(),
|
||||
unit: CurrencyUnit::from_str(&value.unit).unwrap_or_default(),
|
||||
state: state_val.into(),
|
||||
options: value.melt_options.map(|opt| match opt.melt_options {
|
||||
Some(payment_quote_options::MeltOptions::Bolt12(bolt12)) => {
|
||||
cdk_common::payment::PaymentQuoteOptions::Bolt12 {
|
||||
invoice: bolt12.invoice.as_deref().map(str::as_bytes).map(Vec::from),
|
||||
}
|
||||
}
|
||||
None => unreachable!(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuoteState> for cdk_common::nut05::QuoteState {
|
||||
impl From<MeltOptions> for CdkMeltOptions {
|
||||
fn from(value: MeltOptions) -> Self {
|
||||
match value.options.expect("option defined") {
|
||||
melt_options::Options::Mpp(mpp) => Self::Mpp {
|
||||
mpp: cashu::nuts::nut15::Mpp {
|
||||
amount: mpp.amount.into(),
|
||||
},
|
||||
},
|
||||
melt_options::Options::Amountless(amountless) => Self::Amountless {
|
||||
amountless: cashu::nuts::nut23::Amountless {
|
||||
amount_msat: amountless.amount_msat.into(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CdkMeltOptions> for MeltOptions {
|
||||
fn from(value: CdkMeltOptions) -> Self {
|
||||
match value {
|
||||
CdkMeltOptions::Mpp { mpp } => Self {
|
||||
options: Some(melt_options::Options::Mpp(Mpp {
|
||||
amount: mpp.amount.into(),
|
||||
})),
|
||||
},
|
||||
CdkMeltOptions::Amountless { amountless } => Self {
|
||||
options: Some(melt_options::Options::Amountless(Amountless {
|
||||
amount_msat: amountless.amount_msat.into(),
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QuoteState> for cdk_common::nuts::MeltQuoteState {
|
||||
fn from(value: QuoteState) -> Self {
|
||||
match value {
|
||||
QuoteState::Unpaid => Self::Unpaid,
|
||||
@@ -142,80 +231,53 @@ impl From<QuoteState> for cdk_common::nut05::QuoteState {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::nut05::QuoteState> for QuoteState {
|
||||
fn from(value: cdk_common::nut05::QuoteState) -> Self {
|
||||
impl From<cdk_common::nuts::MeltQuoteState> for QuoteState {
|
||||
fn from(value: cdk_common::nuts::MeltQuoteState) -> Self {
|
||||
match value {
|
||||
cdk_common::MeltQuoteState::Unpaid => Self::Unpaid,
|
||||
cdk_common::MeltQuoteState::Paid => Self::Paid,
|
||||
cdk_common::MeltQuoteState::Pending => Self::Pending,
|
||||
cdk_common::MeltQuoteState::Unknown => Self::Unknown,
|
||||
cdk_common::MeltQuoteState::Failed => Self::Failed,
|
||||
cdk_common::nuts::MeltQuoteState::Unpaid => Self::Unpaid,
|
||||
cdk_common::nuts::MeltQuoteState::Paid => Self::Paid,
|
||||
cdk_common::nuts::MeltQuoteState::Pending => Self::Pending,
|
||||
cdk_common::nuts::MeltQuoteState::Unknown => Self::Unknown,
|
||||
cdk_common::nuts::MeltQuoteState::Failed => Self::Failed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::nut23::QuoteState> for QuoteState {
|
||||
fn from(value: cdk_common::nut23::QuoteState) -> Self {
|
||||
impl From<cdk_common::nuts::MintQuoteState> for QuoteState {
|
||||
fn from(value: cdk_common::nuts::MintQuoteState) -> Self {
|
||||
match value {
|
||||
cdk_common::MintQuoteState::Unpaid => Self::Unpaid,
|
||||
cdk_common::MintQuoteState::Paid => Self::Paid,
|
||||
cdk_common::MintQuoteState::Pending => Self::Pending,
|
||||
cdk_common::MintQuoteState::Issued => Self::Issued,
|
||||
cdk_common::nuts::MintQuoteState::Unpaid => Self::Unpaid,
|
||||
cdk_common::nuts::MintQuoteState::Paid => Self::Paid,
|
||||
cdk_common::nuts::MintQuoteState::Issued => Self::Issued,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<cdk_common::mint::MeltQuote> for MeltQuote {
|
||||
fn from(value: cdk_common::mint::MeltQuote) -> Self {
|
||||
impl From<WaitPaymentResponse> for WaitIncomingPaymentResponse {
|
||||
fn from(value: WaitPaymentResponse) -> Self {
|
||||
Self {
|
||||
id: value.id.to_string(),
|
||||
payment_identifier: Some(value.payment_identifier.into()),
|
||||
payment_amount: value.payment_amount.into(),
|
||||
unit: value.unit.to_string(),
|
||||
amount: value.amount.into(),
|
||||
request: value.request,
|
||||
fee_reserve: value.fee_reserve.into(),
|
||||
state: QuoteState::from(value.state).into(),
|
||||
expiry: value.expiry,
|
||||
payment_preimage: value.payment_preimage,
|
||||
request_lookup_id: value.request_lookup_id,
|
||||
msat_to_pay: value.msat_to_pay.map(|a| a.into()),
|
||||
created_time: value.created_time,
|
||||
paid_time: value.paid_time,
|
||||
payment_id: value.payment_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MeltQuote> for cdk_common::mint::MeltQuote {
|
||||
impl TryFrom<WaitIncomingPaymentResponse> for WaitPaymentResponse {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_from(value: MeltQuote) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
id: value
|
||||
.id
|
||||
.parse()
|
||||
.map_err(|_| crate::error::Error::InvalidId)?,
|
||||
unit: value.unit.parse()?,
|
||||
amount: value.amount.into(),
|
||||
request: value.request.clone(),
|
||||
fee_reserve: value.fee_reserve.into(),
|
||||
state: cdk_common::nut05::QuoteState::from(value.state()),
|
||||
expiry: value.expiry,
|
||||
payment_preimage: value.payment_preimage,
|
||||
request_lookup_id: value.request_lookup_id,
|
||||
msat_to_pay: value.msat_to_pay.map(|a| a.into()),
|
||||
created_time: value.created_time,
|
||||
paid_time: value.paid_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
fn try_from(value: WaitIncomingPaymentResponse) -> Result<Self, Self::Error> {
|
||||
let payment_identifier = value
|
||||
.payment_identifier
|
||||
.ok_or(crate::error::Error::InvalidPaymentIdentifier)?
|
||||
.try_into()?;
|
||||
|
||||
impl TryFrom<PaymentQuoteRequest> for MeltQuoteBolt11Request {
|
||||
type Error = crate::error::Error;
|
||||
|
||||
fn try_from(value: PaymentQuoteRequest) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
request: Bolt11Invoice::from_str(&value.request)?,
|
||||
payment_identifier,
|
||||
payment_amount: value.payment_amount.into(),
|
||||
unit: CurrencyUnit::from_str(&value.unit)?,
|
||||
options: value.options.map(|o| o.into()),
|
||||
payment_id: value.payment_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,30 +3,73 @@ syntax = "proto3";
|
||||
package cdk_payment_processor;
|
||||
|
||||
service CdkPaymentProcessor {
|
||||
rpc GetSettings(SettingsRequest) returns (SettingsResponse) {}
|
||||
rpc GetSettings(EmptyRequest) returns (SettingsResponse) {}
|
||||
rpc CreatePayment(CreatePaymentRequest) returns (CreatePaymentResponse) {}
|
||||
rpc GetPaymentQuote(PaymentQuoteRequest) returns (PaymentQuoteResponse) {}
|
||||
rpc MakePayment(MakePaymentRequest) returns (MakePaymentResponse) {}
|
||||
rpc CheckIncomingPayment(CheckIncomingPaymentRequest) returns (CheckIncomingPaymentResponse) {}
|
||||
rpc CheckOutgoingPayment(CheckOutgoingPaymentRequest) returns (MakePaymentResponse) {}
|
||||
rpc WaitIncomingPayment(WaitIncomingPaymentRequest) returns (stream WaitIncomingPaymentResponse) {}
|
||||
rpc WaitIncomingPayment(EmptyRequest) returns (stream WaitIncomingPaymentResponse) {}
|
||||
}
|
||||
|
||||
message SettingsRequest {}
|
||||
message EmptyRequest {}
|
||||
|
||||
message SettingsResponse {
|
||||
string inner = 1;
|
||||
}
|
||||
|
||||
message Bolt11IncomingPaymentOptions {
|
||||
optional string description = 1;
|
||||
uint64 amount = 2;
|
||||
optional uint64 unix_expiry = 3;
|
||||
}
|
||||
|
||||
message Bolt12IncomingPaymentOptions {
|
||||
optional string description = 1;
|
||||
optional uint64 amount = 2;
|
||||
optional uint64 unix_expiry = 3;
|
||||
}
|
||||
|
||||
enum PaymentMethodType {
|
||||
BOLT11 = 0;
|
||||
BOLT12 = 1;
|
||||
}
|
||||
|
||||
enum OutgoingPaymentRequestType {
|
||||
BOLT11_INVOICE = 0;
|
||||
BOLT12_OFFER = 1;
|
||||
}
|
||||
|
||||
enum PaymentIdentifierType {
|
||||
PAYMENT_HASH = 0;
|
||||
OFFER_ID = 1;
|
||||
LABEL = 2;
|
||||
BOLT12_PAYMENT_HASH = 3;
|
||||
CUSTOM_ID = 4;
|
||||
}
|
||||
|
||||
message PaymentIdentifier {
|
||||
PaymentIdentifierType type = 1;
|
||||
oneof value {
|
||||
string hash = 2; // Used for PAYMENT_HASH and BOLT12_PAYMENT_HASH
|
||||
string id = 3; // Used for OFFER_ID, LABEL, and CUSTOM_ID
|
||||
}
|
||||
}
|
||||
|
||||
message IncomingPaymentOptions {
|
||||
oneof options {
|
||||
Bolt11IncomingPaymentOptions bolt11 = 1;
|
||||
Bolt12IncomingPaymentOptions bolt12 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message CreatePaymentRequest {
|
||||
uint64 amount = 1;
|
||||
string unit = 2;
|
||||
string description = 3;
|
||||
optional uint64 unix_expiry = 4;
|
||||
string unit = 1;
|
||||
IncomingPaymentOptions options = 2;
|
||||
}
|
||||
|
||||
message CreatePaymentResponse {
|
||||
string request_lookup_id = 1;
|
||||
PaymentIdentifier request_identifier = 1;
|
||||
string request = 2;
|
||||
optional uint64 expiry = 3;
|
||||
}
|
||||
@@ -35,7 +78,6 @@ message Mpp {
|
||||
uint64 amount = 1;
|
||||
}
|
||||
|
||||
|
||||
message Amountless {
|
||||
uint64 amount_msat = 1;
|
||||
}
|
||||
@@ -51,6 +93,7 @@ message PaymentQuoteRequest {
|
||||
string request = 1;
|
||||
string unit = 2;
|
||||
optional MeltOptions options = 3;
|
||||
OutgoingPaymentRequestType request_type = 4;
|
||||
}
|
||||
|
||||
enum QuoteState {
|
||||
@@ -62,38 +105,60 @@ enum QuoteState {
|
||||
ISSUED = 5;
|
||||
}
|
||||
|
||||
message Bolt12Options {
|
||||
optional string invoice = 1;
|
||||
}
|
||||
|
||||
message PaymentQuoteOptions {
|
||||
oneof melt_options {
|
||||
Bolt12Options bolt12 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
message PaymentQuoteResponse {
|
||||
string request_lookup_id = 1;
|
||||
PaymentIdentifier request_identifier = 1;
|
||||
uint64 amount = 2;
|
||||
uint64 fee = 3;
|
||||
QuoteState state = 4;
|
||||
string unit = 5;
|
||||
optional PaymentQuoteOptions melt_options = 5;
|
||||
string unit = 6;
|
||||
}
|
||||
|
||||
message MeltQuote {
|
||||
string id = 1;
|
||||
string unit = 2;
|
||||
uint64 amount = 3;
|
||||
string request = 4;
|
||||
uint64 fee_reserve = 5;
|
||||
QuoteState state = 6;
|
||||
uint64 expiry = 7;
|
||||
optional string payment_preimage = 8;
|
||||
string request_lookup_id = 9;
|
||||
optional uint64 msat_to_pay = 10;
|
||||
uint64 created_time = 11;
|
||||
optional uint64 paid_time = 12;
|
||||
message Bolt11OutgoingPaymentOptions {
|
||||
string bolt11 = 1;
|
||||
optional uint64 max_fee_amount = 2;
|
||||
optional uint64 timeout_secs = 3;
|
||||
optional MeltOptions melt_options = 4;
|
||||
}
|
||||
|
||||
message Bolt12OutgoingPaymentOptions {
|
||||
string offer = 1;
|
||||
optional uint64 max_fee_amount = 2;
|
||||
optional uint64 timeout_secs = 3;
|
||||
optional bytes invoice = 4;
|
||||
optional MeltOptions melt_options = 5;
|
||||
}
|
||||
|
||||
enum OutgoingPaymentOptionsType {
|
||||
OUTGOING_BOLT11 = 0;
|
||||
OUTGOING_BOLT12 = 1;
|
||||
}
|
||||
|
||||
message OutgoingPaymentVariant {
|
||||
oneof options {
|
||||
Bolt11OutgoingPaymentOptions bolt11 = 1;
|
||||
Bolt12OutgoingPaymentOptions bolt12 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message MakePaymentRequest {
|
||||
MeltQuote melt_quote = 1;
|
||||
OutgoingPaymentVariant payment_options = 1;
|
||||
optional uint64 partial_amount = 2;
|
||||
optional uint64 max_fee_amount = 3;
|
||||
}
|
||||
|
||||
message MakePaymentResponse {
|
||||
string payment_lookup_id = 1;
|
||||
PaymentIdentifier payment_identifier = 1;
|
||||
optional string payment_proof = 2;
|
||||
QuoteState status = 3;
|
||||
uint64 total_spent = 4;
|
||||
@@ -101,22 +166,20 @@ message MakePaymentResponse {
|
||||
}
|
||||
|
||||
message CheckIncomingPaymentRequest {
|
||||
string request_lookup_id = 1;
|
||||
PaymentIdentifier request_identifier = 1;
|
||||
}
|
||||
|
||||
message CheckIncomingPaymentResponse {
|
||||
QuoteState status = 1;
|
||||
repeated WaitIncomingPaymentResponse payments = 1;
|
||||
}
|
||||
|
||||
message CheckOutgoingPaymentRequest {
|
||||
string request_lookup_id = 1;
|
||||
PaymentIdentifier request_identifier = 1;
|
||||
}
|
||||
|
||||
|
||||
message WaitIncomingPaymentRequest {
|
||||
}
|
||||
|
||||
|
||||
message WaitIncomingPaymentResponse {
|
||||
string lookup_id = 1;
|
||||
PaymentIdentifier payment_identifier = 1;
|
||||
uint64 payment_amount = 2;
|
||||
string unit = 3;
|
||||
string payment_id = 4;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use cdk_common::payment::MintPayment;
|
||||
use cdk_common::payment::{IncomingPaymentOptions, MintPayment};
|
||||
use cdk_common::CurrencyUnit;
|
||||
use futures::{Stream, StreamExt};
|
||||
use lightning::offers::offer::Offer;
|
||||
use serde_json::Value;
|
||||
use tokio::sync::{mpsc, Notify};
|
||||
use tokio::task::JoinHandle;
|
||||
@@ -17,6 +19,7 @@ use tonic::{async_trait, Request, Response, Status};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::cdk_payment_processor_server::{CdkPaymentProcessor, CdkPaymentProcessorServer};
|
||||
use crate::error::Error;
|
||||
use crate::proto::*;
|
||||
|
||||
type ResponseStream =
|
||||
@@ -162,7 +165,7 @@ impl Drop for PaymentProcessorServer {
|
||||
impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
async fn get_settings(
|
||||
&self,
|
||||
_request: Request<SettingsRequest>,
|
||||
_request: Request<EmptyRequest>,
|
||||
) -> Result<Response<SettingsResponse>, Status> {
|
||||
let settings: Value = self
|
||||
.inner
|
||||
@@ -179,18 +182,36 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
&self,
|
||||
request: Request<CreatePaymentRequest>,
|
||||
) -> Result<Response<CreatePaymentResponse>, Status> {
|
||||
let CreatePaymentRequest {
|
||||
amount,
|
||||
unit,
|
||||
description,
|
||||
unix_expiry,
|
||||
} = request.into_inner();
|
||||
let CreatePaymentRequest { unit, options } = request.into_inner();
|
||||
|
||||
let unit = CurrencyUnit::from_str(&unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid currency unit"))?;
|
||||
|
||||
let options = options.ok_or_else(|| Status::invalid_argument("Missing payment options"))?;
|
||||
|
||||
let proto_options = match options
|
||||
.options
|
||||
.ok_or_else(|| Status::invalid_argument("Missing options"))?
|
||||
{
|
||||
incoming_payment_options::Options::Bolt11(opts) => {
|
||||
IncomingPaymentOptions::Bolt11(cdk_common::payment::Bolt11IncomingPaymentOptions {
|
||||
description: opts.description,
|
||||
amount: opts.amount.into(),
|
||||
unix_expiry: opts.unix_expiry,
|
||||
})
|
||||
}
|
||||
incoming_payment_options::Options::Bolt12(opts) => IncomingPaymentOptions::Bolt12(
|
||||
Box::new(cdk_common::payment::Bolt12IncomingPaymentOptions {
|
||||
description: opts.description,
|
||||
amount: opts.amount.map(Into::into),
|
||||
unix_expiry: opts.unix_expiry,
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
let unit =
|
||||
CurrencyUnit::from_str(&unit).map_err(|_| Status::invalid_argument("Invalid unit"))?;
|
||||
let invoice_response = self
|
||||
.inner
|
||||
.create_incoming_payment_request(amount.into(), &unit, description, unix_expiry)
|
||||
.create_incoming_payment_request(&unit, proto_options)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Could not create invoice"))?;
|
||||
|
||||
@@ -203,21 +224,46 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
) -> Result<Response<PaymentQuoteResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let options: Option<cdk_common::MeltOptions> =
|
||||
request.options.as_ref().map(|options| (*options).into());
|
||||
let unit = CurrencyUnit::from_str(&request.unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid currency unit"))?;
|
||||
|
||||
let options = match request.request_type() {
|
||||
OutgoingPaymentRequestType::Bolt11Invoice => {
|
||||
let bolt11: cdk_common::Bolt11Invoice =
|
||||
request.request.parse().map_err(Error::Invoice)?;
|
||||
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt11(Box::new(
|
||||
cdk_common::payment::Bolt11OutgoingPaymentOptions {
|
||||
bolt11,
|
||||
max_fee_amount: None,
|
||||
timeout_secs: None,
|
||||
melt_options: request.options.map(Into::into),
|
||||
},
|
||||
))
|
||||
}
|
||||
OutgoingPaymentRequestType::Bolt12Offer => {
|
||||
// Parse offer to verify it's valid, but store as string
|
||||
let _: Offer = request.request.parse().map_err(|_| Error::Bolt12Parse)?;
|
||||
|
||||
cdk_common::payment::OutgoingPaymentOptions::Bolt12(Box::new(
|
||||
cdk_common::payment::Bolt12OutgoingPaymentOptions {
|
||||
offer: Offer::from_str(&request.request).unwrap(),
|
||||
max_fee_amount: None,
|
||||
timeout_secs: None,
|
||||
invoice: None,
|
||||
melt_options: request.options.map(Into::into),
|
||||
},
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let payment_quote = self
|
||||
.inner
|
||||
.get_payment_quote(
|
||||
&request.request,
|
||||
&CurrencyUnit::from_str(&request.unit)
|
||||
.map_err(|_| Status::invalid_argument("Invalid currency unit"))?,
|
||||
options,
|
||||
)
|
||||
.get_payment_quote(&unit, options)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not get bolt11 melt quote: {}", err);
|
||||
Status::internal("Could not get melt quote")
|
||||
tracing::error!("Could not get payment quote: {}", err);
|
||||
Status::internal("Could not get quote")
|
||||
})?;
|
||||
|
||||
Ok(Response::new(payment_quote.into()))
|
||||
@@ -229,17 +275,51 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
) -> Result<Response<MakePaymentResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let pay_invoice = self
|
||||
let options = request
|
||||
.payment_options
|
||||
.ok_or_else(|| Status::invalid_argument("Missing payment options"))?;
|
||||
|
||||
let (unit, payment_options) = match options
|
||||
.options
|
||||
.ok_or_else(|| Status::invalid_argument("Missing options"))?
|
||||
{
|
||||
outgoing_payment_variant::Options::Bolt11(opts) => {
|
||||
let bolt11: cdk_common::Bolt11Invoice =
|
||||
opts.bolt11.parse().map_err(Error::Invoice)?;
|
||||
|
||||
let payment_options = cdk_common::payment::OutgoingPaymentOptions::Bolt11(
|
||||
Box::new(cdk_common::payment::Bolt11OutgoingPaymentOptions {
|
||||
bolt11,
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
}),
|
||||
);
|
||||
|
||||
(CurrencyUnit::Msat, payment_options)
|
||||
}
|
||||
outgoing_payment_variant::Options::Bolt12(opts) => {
|
||||
let offer = Offer::from_str(&opts.offer)
|
||||
.map_err(|_| Error::Bolt12Parse)
|
||||
.unwrap();
|
||||
|
||||
let payment_options = cdk_common::payment::OutgoingPaymentOptions::Bolt12(
|
||||
Box::new(cdk_common::payment::Bolt12OutgoingPaymentOptions {
|
||||
offer,
|
||||
max_fee_amount: opts.max_fee_amount.map(Into::into),
|
||||
timeout_secs: opts.timeout_secs,
|
||||
invoice: opts.invoice,
|
||||
melt_options: opts.melt_options.map(Into::into),
|
||||
}),
|
||||
);
|
||||
|
||||
(CurrencyUnit::Msat, payment_options)
|
||||
}
|
||||
};
|
||||
|
||||
let pay_response = self
|
||||
.inner
|
||||
.make_payment(
|
||||
request
|
||||
.melt_quote
|
||||
.ok_or(Status::invalid_argument("Meltquote is required"))?
|
||||
.try_into()
|
||||
.map_err(|_err| Status::invalid_argument("Invalid melt quote"))?,
|
||||
request.partial_amount.map(|a| a.into()),
|
||||
request.max_fee_amount.map(|a| a.into()),
|
||||
)
|
||||
.make_payment(&unit, payment_options)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Could not make payment: {}", err);
|
||||
@@ -255,7 +335,7 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Response::new(pay_invoice.into()))
|
||||
Ok(Response::new(pay_response.into()))
|
||||
}
|
||||
|
||||
async fn check_incoming_payment(
|
||||
@@ -264,14 +344,20 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
) -> Result<Response<CheckIncomingPaymentResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let check_response = self
|
||||
let payment_identifier = request
|
||||
.request_identifier
|
||||
.ok_or_else(|| Status::invalid_argument("Missing request identifier"))?
|
||||
.try_into()
|
||||
.map_err(|_| Status::invalid_argument("Invalid request identifier"))?;
|
||||
|
||||
let check_responses = self
|
||||
.inner
|
||||
.check_incoming_payment_status(&request.request_lookup_id)
|
||||
.check_incoming_payment_status(&payment_identifier)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Could not check incoming payment status"))?;
|
||||
|
||||
Ok(Response::new(CheckIncomingPaymentResponse {
|
||||
status: QuoteState::from(check_response).into(),
|
||||
payments: check_responses.into_iter().map(|r| r.into()).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -281,23 +367,28 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
) -> Result<Response<MakePaymentResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let payment_identifier = request
|
||||
.request_identifier
|
||||
.ok_or_else(|| Status::invalid_argument("Missing request identifier"))?
|
||||
.try_into()
|
||||
.map_err(|_| Status::invalid_argument("Invalid request identifier"))?;
|
||||
|
||||
let check_response = self
|
||||
.inner
|
||||
.check_outgoing_payment(&request.request_lookup_id)
|
||||
.check_outgoing_payment(&payment_identifier)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Could not check incoming payment status"))?;
|
||||
.map_err(|_| Status::internal("Could not check outgoing payment status"))?;
|
||||
|
||||
Ok(Response::new(check_response.into()))
|
||||
}
|
||||
|
||||
type WaitIncomingPaymentStream = ResponseStream;
|
||||
|
||||
// Clippy thinks select is not stable but it compiles fine on MSRV (1.63.0)
|
||||
#[allow(clippy::incompatible_msrv)]
|
||||
#[instrument(skip_all)]
|
||||
async fn wait_incoming_payment(
|
||||
&self,
|
||||
_request: Request<WaitIncomingPaymentRequest>,
|
||||
_request: Request<EmptyRequest>,
|
||||
) -> Result<Response<Self::WaitIncomingPaymentStream>, Status> {
|
||||
tracing::debug!("Server waiting for payment stream");
|
||||
let (tx, rx) = mpsc::channel(128);
|
||||
@@ -307,34 +398,35 @@ impl CdkPaymentProcessor for PaymentProcessorServer {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = shutdown_clone.notified() => {
|
||||
tracing::info!("Shutdown signal received, stopping task for ");
|
||||
ln.cancel_wait_invoice();
|
||||
break;
|
||||
}
|
||||
result = ln.wait_any_incoming_payment() => {
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(request_lookup_id) = stream.next().await {
|
||||
match tx.send(Result::<_, Status>::Ok(WaitIncomingPaymentResponse{lookup_id: request_lookup_id} )).await {
|
||||
Ok(_) => {
|
||||
// item (server response) was queued to be send to client
|
||||
}
|
||||
Err(item) => {
|
||||
tracing::error!("Error adding incoming payment to stream: {}", item);
|
||||
_ = shutdown_clone.notified() => {
|
||||
tracing::info!("Shutdown signal received, stopping task");
|
||||
ln.cancel_wait_invoice();
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = ln.wait_any_incoming_payment() => {
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
while let Some(payment_response) = stream.next().await {
|
||||
match tx.send(Result::<_, Status>::Ok(payment_response.into()))
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Response was queued to be sent to client
|
||||
}
|
||||
Err(item) => {
|
||||
tracing::error!("Error adding incoming payment to stream: {}", item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Could not get invoice stream: {}", err);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!("Could not get invoice stream for {}", err);
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user