use anyhow::Context;
use lexe_api::{
models::command,
types::{
bounded_note::BoundedNote,
invoice::Invoice,
payments::{PaymentCreatedIndex, PaymentHash, PaymentSecret},
},
};
use lexe_common::{ln::amount::Amount, time::TimestampMs};
use serde::{Deserialize, Serialize};
use crate::types::{
auth::{Measurement, NodePk, UserPk},
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,
}
}
}
#[derive(Default, Serialize, Deserialize)]
pub struct CreateInvoiceRequest {
pub expiration_secs: Option<u32>,
pub amount: Option<Amount>,
pub description: Option<String>,
#[serde(default)]
pub payer_note: Option<String>,
}
#[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: req
.payer_note
.map(BoundedNote::new)
.transpose()
.context(
"Invalid payer_note (must be non-empty and <=200 chars / \
<=512 UTF-8 bytes)",
)?,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceRequest {
pub invoice: Invoice,
pub fallback_amount: Option<Amount>,
pub note: Option<String>,
pub payer_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(BoundedNote::new).transpose().context(
"Invalid note (must be non-empty and <=200 chars / \
<=512 UTF-8 bytes)",
)?,
payer_note: req
.payer_note
.map(BoundedNote::new)
.transpose()
.context(
"Invalid payer_note (must be non-empty and <=200 chars / \
<=512 UTF-8 bytes)",
)?,
})
}
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceResponse {
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(BoundedNote::new).transpose().context(
"Invalid note (must be non-empty and <=200 chars / \
<=512 UTF-8 bytes)",
)?,
})
}
}
#[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,
}