use anyhow::Context;
use lexe_api::{
models::command,
types::{
bounded_string::BoundedString,
invoice::Invoice,
payments::{
ClientPaymentId, PaymentCreatedIndex, PaymentHash, PaymentSecret,
},
},
};
use lexe_common::{ln::amount::Amount, ppm::Ppm, time::TimestampMs};
use lexe_payment_uri::PaymentMethod;
use serde::{Deserialize, Serialize};
use crate::types::{
auth::{Measurement, NodePk, UserPk},
bitcoin::Offer,
payment::Payment,
};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NodeInfo {
pub version: semver::Version,
pub measurement: Measurement,
pub user_pk: UserPk,
pub node_pk: NodePk,
pub balance: Amount,
pub lightning_balance: Amount,
pub lightning_sendable_balance: Amount,
pub lightning_max_sendable_balance: Amount,
pub onchain_balance: Amount,
pub onchain_trusted_balance: Amount,
pub num_channels: usize,
pub num_usable_channels: usize,
}
impl From<command::NodeInfo> for NodeInfo {
fn from(info: command::NodeInfo) -> Self {
let lightning_balance = info.lightning_balance.total();
let onchain_balance = Amount::try_from(info.onchain_balance.total())
.expect("We're unreasonably rich!");
let onchain_trusted_balance =
Amount::try_from(info.onchain_balance.trusted_spendable())
.expect("We're unreasonably rich!");
let balance = lightning_balance.saturating_add(onchain_balance);
Self {
version: info.version,
measurement: Measurement::from_unstable(info.measurement),
user_pk: UserPk::from_unstable(info.user_pk),
node_pk: NodePk::from_unstable(info.node_pk),
balance,
lightning_balance,
lightning_sendable_balance: info.lightning_balance.sendable,
lightning_max_sendable_balance: info.lightning_balance.max_sendable,
onchain_balance,
onchain_trusted_balance,
num_channels: info.num_channels,
num_usable_channels: info.num_usable_channels,
}
}
}
pub struct AnalyzeRequest {
pub payable: String,
}
pub struct PayableDetails {
pub payable: String,
pub method: PaymentMethod,
pub description: Option<String>,
pub amount: Option<Amount>,
pub min_amount: Option<Amount>,
pub max_amount: Option<Amount>,
pub expires_at: Option<TimestampMs>,
}
pub struct AnalyzeResponse {
pub payables: Vec<PayableDetails>,
}
pub struct PayRequest {
pub payable: String,
pub amount: Option<Amount>,
pub payer_note: Option<String>,
pub note: Option<String>,
}
pub struct PayResponse {
pub index: PaymentCreatedIndex,
pub created_at: TimestampMs,
}
#[derive(Default, Serialize, Deserialize)]
pub struct CreateInvoiceRequest {
pub expiration_secs: Option<u32>,
pub amount: Option<Amount>,
pub description: Option<String>,
#[serde(default)]
pub partner_pk: Option<UserPk>,
#[serde(default)]
pub partner_prop_fee: Option<Ppm>,
#[serde(default)]
pub partner_base_fee: Option<Amount>,
}
#[derive(Serialize, Deserialize)]
pub struct CreateInvoiceResponse {
pub index: PaymentCreatedIndex,
pub invoice: Invoice,
pub description: Option<String>,
pub amount: Option<Amount>,
pub created_at: TimestampMs,
pub expires_at: TimestampMs,
pub payment_hash: PaymentHash,
pub payment_secret: PaymentSecret,
}
impl CreateInvoiceResponse {
pub fn new(index: PaymentCreatedIndex, invoice: Invoice) -> Self {
let description = invoice.description_str().map(|s| s.to_owned());
let amount_sats = invoice.amount();
let created_at = invoice.saturating_created_at();
let expires_at = invoice.saturating_expires_at();
let payment_hash = invoice.payment_hash();
let payment_secret = invoice.payment_secret();
Self {
index,
invoice,
description,
amount: amount_sats,
created_at,
expires_at,
payment_hash,
payment_secret,
}
}
}
impl TryFrom<CreateInvoiceRequest> for command::CreateInvoiceRequest {
type Error = anyhow::Error;
fn try_from(req: CreateInvoiceRequest) -> anyhow::Result<Self> {
const DEFAULT_EXPIRATION_SECS: u32 = 60 * 60 * 24;
Ok(Self {
expiry_secs: req.expiration_secs.unwrap_or(DEFAULT_EXPIRATION_SECS),
amount: req.amount,
description: req.description,
description_hash: None,
payer_note: None,
partner_pk: req.partner_pk.map(|pk| pk.unstable()),
partner_prop_fee: req.partner_prop_fee,
partner_base_fee: req.partner_base_fee,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceRequest {
pub invoice: Invoice,
pub fallback_amount: Option<Amount>,
pub note: Option<String>,
}
impl TryFrom<PayInvoiceRequest> for command::PayInvoiceRequest {
type Error = anyhow::Error;
fn try_from(req: PayInvoiceRequest) -> anyhow::Result<Self> {
Ok(Self {
invoice: req.invoice,
fallback_amount: req.fallback_amount,
note: req
.note
.map(BoundedString::new)
.transpose()
.context("Invalid personal note")?,
payer_note: None,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceResponse {
pub index: PaymentCreatedIndex,
pub created_at: TimestampMs,
}
#[derive(Default, Serialize, Deserialize)]
pub struct CreateOfferRequest {
pub description: Option<String>,
pub min_amount: Option<Amount>,
pub expiration_secs: Option<u32>,
}
impl TryFrom<CreateOfferRequest> for command::CreateOfferRequest {
type Error = anyhow::Error;
fn try_from(req: CreateOfferRequest) -> anyhow::Result<Self> {
let description = req
.description
.map(BoundedString::new)
.transpose()
.context("Invalid description")?;
Ok(Self {
description,
min_amount: req.min_amount,
expiry_secs: req.expiration_secs,
max_quantity: None,
issuer: None,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct CreateOfferResponse {
pub offer: Offer,
}
#[derive(Serialize, Deserialize)]
pub struct PayOfferRequest {
pub offer: Offer,
pub amount: Amount,
pub note: Option<String>,
pub payer_note: Option<String>,
}
impl PayOfferRequest {
pub(crate) fn into_unstable(
self,
cid: ClientPaymentId,
) -> anyhow::Result<command::PayOfferRequest> {
Ok(command::PayOfferRequest {
cid,
offer: self.offer,
amount: self.amount,
note: self
.note
.map(BoundedString::new)
.transpose()
.context("Invalid personal note")?,
payer_note: self
.payer_note
.map(BoundedString::new)
.transpose()
.context("Invalid payer note")?,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct PayOfferResponse {
pub index: PaymentCreatedIndex,
pub created_at: TimestampMs,
}
#[derive(Serialize, Deserialize)]
pub struct UpdatePaymentNoteRequest {
pub index: PaymentCreatedIndex,
pub note: Option<String>,
}
impl TryFrom<UpdatePaymentNoteRequest> for command::UpdatePaymentNote {
type Error = anyhow::Error;
fn try_from(sdk: UpdatePaymentNoteRequest) -> anyhow::Result<Self> {
Ok(Self {
index: sdk.index,
note: sdk
.note
.map(BoundedString::new)
.transpose()
.context("Invalid note")?,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct GetPaymentRequest {
pub index: PaymentCreatedIndex,
}
#[derive(Serialize, Deserialize)]
pub struct GetPaymentResponse {
pub payment: Option<Payment>,
}
#[derive(Serialize, Deserialize)]
pub struct ListPaymentsResponse {
pub payments: Vec<Payment>,
pub next_index: Option<PaymentCreatedIndex>,
}
#[derive(Debug)]
pub struct PaymentSyncSummary {
pub num_new: usize,
pub num_updated: usize,
}