use std::collections::BTreeSet;
use bitcoin::{address::NetworkUnchecked, bip32::Xpub};
#[cfg(doc)]
use lexe_common::root_seed::RootSeed;
#[cfg(any(test, feature = "test-utils"))]
use lexe_common::test_utils::arbitrary;
use lexe_common::{
api::user::{NodePk, UserPk},
ln::{
amount::Amount,
balance::{LightningBalance, OnchainBalance},
channel::{LxChannelDetails, LxChannelId, LxUserChannelId},
hashes::Txid,
priority::ConfirmationPriority,
route::LxRoute,
},
time::TimestampMs,
};
use lexe_enclave::enclave::Measurement;
use lexe_serde::hexstr_or_bytes;
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use serde::{Deserialize, Serialize};
use crate::types::{
bounded_note::BoundedNote,
invoice::Invoice,
offer::{MaxQuantity, Offer},
payments::{
ClientPaymentId, PaymentCreatedIndex, PaymentId, PaymentUpdatedIndex,
},
username::Username,
};
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeInfoV1 {
pub version: semver::Version,
pub measurement: Measurement,
pub user_pk: UserPk,
pub node_pk: NodePk,
pub num_peers: usize,
pub num_usable_channels: usize,
pub num_channels: usize,
pub lightning_balance: LightningBalance,
pub onchain_balance: OnchainBalance,
pub num_utxos: usize,
pub num_confirmed_utxos: usize,
pub num_unconfirmed_utxos: usize,
pub best_block_height: u32,
pub pending_monitor_updates: usize,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct NodeInfo {
pub version: semver::Version,
pub measurement: Measurement,
pub user_pk: UserPk,
pub node_pk: NodePk,
pub num_peers: usize,
pub num_usable_channels: usize,
pub num_channels: usize,
pub lightning_balance: LightningBalance,
pub onchain_balance: OnchainBalance,
pub best_block_height: u32,
}
impl From<NodeInfoV1> for NodeInfo {
fn from(v1: NodeInfoV1) -> Self {
Self {
version: v1.version,
measurement: v1.measurement,
user_pk: v1.user_pk,
node_pk: v1.node_pk,
num_peers: v1.num_peers,
num_usable_channels: v1.num_usable_channels,
num_channels: v1.num_channels,
lightning_balance: v1.lightning_balance,
onchain_balance: v1.onchain_balance,
best_block_height: v1.best_block_height,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DebugInfo {
pub descriptors: OnchainDescriptors,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_descriptors: Option<OnchainDescriptors>,
pub num_utxos: usize,
pub num_confirmed_utxos: usize,
pub num_unconfirmed_utxos: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pending_monitor_updates: Option<usize>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OnchainDescriptors {
pub multipath_descriptor: String,
pub external_descriptor: String,
pub internal_descriptor: String,
pub account_xpub: Xpub,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum GDriveStatus {
Ok,
Error(String),
Disabled,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct BackupInfo {
pub gdrive_status: GDriveStatus,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct EnclavesToProvisionRequest {
pub trusted_measurements: BTreeSet<Measurement>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(test, derive(Arbitrary))]
pub struct SetupGDrive {
#[cfg_attr(test, proptest(strategy = "arbitrary::any_string()"))]
pub google_auth_code: String,
#[serde(with = "hexstr_or_bytes")]
pub encrypted_seed: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
pub struct ListChannelsResponse {
pub channels: Vec<LxChannelDetails>,
}
#[derive(Serialize, Deserialize)]
pub struct OpenChannelRequest {
pub user_channel_id: LxUserChannelId,
pub value: Amount,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OpenChannelResponse {
pub channel_id: LxChannelId,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightOpenChannelRequest {
pub value: Amount,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PreflightOpenChannelResponse {
pub fee_estimate: Amount,
}
#[derive(Serialize, Deserialize)]
pub struct CloseChannelRequest {
pub channel_id: LxChannelId,
pub force_close: bool,
pub maybe_counterparty: Option<NodePk>,
}
pub type PreflightCloseChannelRequest = CloseChannelRequest;
#[derive(Serialize, Deserialize)]
pub struct PreflightCloseChannelResponse {
pub fee_estimate: Amount,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct PaymentIdStruct {
pub id: PaymentId,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct VecPaymentId {
pub ids: Vec<PaymentId>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct PaymentCreatedIndexStruct {
pub index: PaymentCreatedIndex,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetNewPayments {
pub start_index: Option<PaymentCreatedIndex>,
pub limit: Option<u16>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetUpdatedPayments {
pub start_index: Option<PaymentUpdatedIndex>,
pub limit: Option<u16>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetUpdatedPaymentMetadata {
pub start_index: Option<PaymentUpdatedIndex>,
pub limit: Option<u16>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct PaymentCreatedIndexes {
pub indexes: Vec<PaymentCreatedIndex>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct UpdatePaymentNote {
pub index: PaymentCreatedIndex,
pub note: Option<BoundedNote>,
}
#[derive(Default, Serialize, Deserialize)]
pub struct CreateInvoiceRequest {
pub expiry_secs: u32,
pub amount: Option<Amount>,
pub description: Option<String>,
pub description_hash: Option<[u8; 32]>,
pub payer_note: Option<BoundedNote>,
}
#[derive(Serialize, Deserialize)]
pub struct CreateInvoiceResponse {
pub invoice: Invoice,
pub created_index: Option<PaymentCreatedIndex>,
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceRequest {
pub invoice: Invoice,
pub fallback_amount: Option<Amount>,
pub note: Option<BoundedNote>,
pub payer_note: Option<BoundedNote>,
}
#[derive(Serialize, Deserialize)]
pub struct PayInvoiceResponse {
pub created_at: TimestampMs,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightPayInvoiceRequest {
pub invoice: Invoice,
pub fallback_amount: Option<Amount>,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightPayInvoiceResponse {
pub amount: Amount,
pub fees: Amount,
pub route: LxRoute,
}
#[derive(Default, Serialize, Deserialize)]
pub struct CreateOfferRequest {
pub expiry_secs: Option<u32>,
pub amount: Option<Amount>,
pub description: Option<String>,
pub max_quantity: Option<MaxQuantity>,
pub issuer: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct CreateOfferResponse {
pub offer: Offer,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightPayOfferRequest {
pub cid: ClientPaymentId,
pub offer: Offer,
pub fallback_amount: Option<Amount>,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightPayOfferResponse {
pub amount: Amount,
pub fees: Amount,
pub route: LxRoute,
}
#[derive(Serialize, Deserialize)]
pub struct PayOfferRequest {
pub cid: ClientPaymentId,
pub offer: Offer,
pub fallback_amount: Option<Amount>,
pub note: Option<BoundedNote>,
pub payer_note: Option<BoundedNote>,
}
#[derive(Serialize, Deserialize)]
pub struct PayOfferResponse {
pub created_at: TimestampMs,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetAddressResponse {
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_mainnet_addr_unchecked()")
)]
pub addr: bitcoin::Address<NetworkUnchecked>,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary, Debug))]
pub struct PayOnchainRequest {
pub cid: ClientPaymentId,
#[cfg_attr(
any(test, feature = "test-utils"),
proptest(strategy = "arbitrary::any_mainnet_addr_unchecked()")
)]
pub address: bitcoin::Address<NetworkUnchecked>,
pub amount: Amount,
pub priority: ConfirmationPriority,
pub note: Option<BoundedNote>,
}
#[derive(Serialize, Deserialize)]
pub struct PayOnchainResponse {
pub created_at: TimestampMs,
pub txid: Txid,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct PreflightPayOnchainRequest {
pub address: bitcoin::Address<NetworkUnchecked>,
pub amount: Amount,
}
#[derive(Serialize, Deserialize)]
pub struct PreflightPayOnchainResponse {
pub high: Option<FeeEstimate>,
pub normal: FeeEstimate,
pub background: FeeEstimate,
}
#[derive(Serialize, Deserialize)]
pub struct FeeEstimate {
pub amount: Amount,
}
#[derive(Serialize, Deserialize)]
pub struct ResyncRequest {
pub full_sync: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct UpdateHumanBitcoinAddress {
pub username: Username,
pub offer: Offer,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct ClaimGeneratedHumanBitcoinAddress {
pub offer: Offer,
pub username: Username,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct GetGeneratedUsernameResponse {
pub username: Username,
pub already_claimed: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub struct HumanBitcoinAddress {
pub username: Option<Username>,
pub offer: Option<Offer>,
pub updated_at: Option<TimestampMs>,
pub updatable: bool,
}
#[cfg(any(test, feature = "test-utils"))]
mod arbitrary_impl {
use proptest::{
arbitrary::{Arbitrary, any},
strategy::{BoxedStrategy, Strategy},
};
use super::*;
impl Arbitrary for PreflightPayOnchainRequest {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
(arbitrary::any_mainnet_addr_unchecked(), any::<Amount>())
.prop_map(|(address, amount)| Self { address, amount })
.boxed()
}
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use lexe_common::test_utils::roundtrip;
use super::*;
#[test]
fn preflight_pay_onchain_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<PreflightPayOnchainRequest>(
);
}
#[test]
fn payment_id_struct_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<PaymentIdStruct>();
}
#[test]
fn payment_index_struct_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<PaymentCreatedIndexStruct>(
);
}
#[test]
fn get_new_payments_roundtrip() {
roundtrip::query_string_roundtrip_proptest::<GetNewPayments>();
}
#[test]
fn payment_indexes_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<PaymentCreatedIndexes>();
}
#[test]
fn get_address_response_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<GetAddressResponse>();
}
#[test]
fn setup_gdrive_request_roundtrip() {
roundtrip::json_string_roundtrip_proptest::<SetupGDrive>();
}
#[test]
fn human_bitcoin_address_request_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<UpdateHumanBitcoinAddress>();
}
#[test]
fn claim_generated_human_bitcoin_address_request_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<
ClaimGeneratedHumanBitcoinAddress,
>();
}
#[test]
fn get_generated_username_response_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<GetGeneratedUsernameResponse>(
);
}
#[test]
fn human_bitcoin_address_response_roundtrip() {
roundtrip::json_value_roundtrip_proptest::<HumanBitcoinAddress>();
}
#[test]
fn debug_info_serialization() {
let account_xpub = Xpub::from_str(
"xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA"
).unwrap();
let descriptor = "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/<0;1>/*)#c8v4zjyh".to_owned();
let external_descriptor = "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#dwvchw0k".to_owned();
let internal_descriptor = "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/1/*)#u6fe2mlw".to_owned();
let debug_info = DebugInfo {
descriptors: OnchainDescriptors {
multipath_descriptor: descriptor.clone(),
external_descriptor: external_descriptor.clone(),
internal_descriptor: internal_descriptor.clone(),
account_xpub,
},
legacy_descriptors: None,
num_utxos: 5,
num_confirmed_utxos: 3,
num_unconfirmed_utxos: 2,
pending_monitor_updates: Some(0),
};
let json = serde_json::to_string_pretty(&debug_info).unwrap();
let expected = r#"{
"descriptors": {
"multipath_descriptor": "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/<0;1>/*)#c8v4zjyh",
"external_descriptor": "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/0/*)#dwvchw0k",
"internal_descriptor": "wpkh([be83839f/84'/0'/0']xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA/1/*)#u6fe2mlw",
"account_xpub": "xpub6DCQ1YcqvZtSwGWMrwHELPehjWV3f2MGZ69yBADTxFEUAoLwb5Mp5GniQK6tTp3AgbngVz9zEFbBJUPVnkG7LFYt8QMTfbrNqs6FNEwAPKA"
},
"num_utxos": 5,
"num_confirmed_utxos": 3,
"num_unconfirmed_utxos": 2,
"pending_monitor_updates": 0
}"#;
assert_eq!(json, expected);
let back: DebugInfo = serde_json::from_str(&json).unwrap();
assert_eq!(back.num_utxos, 5);
assert_eq!(back.num_confirmed_utxos, 3);
assert_eq!(back.num_unconfirmed_utxos, 2);
assert_eq!(back.descriptors.multipath_descriptor, descriptor);
assert_eq!(back.descriptors.external_descriptor, external_descriptor);
assert_eq!(back.descriptors.internal_descriptor, internal_descriptor);
assert_eq!(
back.descriptors.account_xpub,
debug_info.descriptors.account_xpub
);
assert!(back.legacy_descriptors.is_none());
assert_eq!(back.pending_monitor_updates, Some(0));
}
}