diff --git a/.github/workflows/check-protos.yml b/.github/workflows/check-protos.yml deleted file mode 100644 index 629bc0bb..00000000 --- a/.github/workflows/check-protos.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Check Generated Protos - -on: - push: - paths: - - 'ldk-server-protos/**' - pull_request: - paths: - - 'ldk-server-protos/**' - workflow_dispatch: - -jobs: - check-protos: - runs-on: ubuntu-latest - steps: - - name: Checkout source code - uses: actions/checkout@v3 - - name: Install Rust stable toolchain - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile=minimal --default-toolchain stable - - name: Install protoc - run: sudo apt-get install -y protobuf-compiler - - name: Generate protos - run: RUSTFLAGS="--cfg genproto" cargo build -p ldk-server-protos - - name: Format generated code - run: rustup component add rustfmt && cargo fmt --all - - name: Check for differences - run: | - if ! git diff --exit-code; then - echo "error: Generated protobuf files are out of date. Run: RUSTFLAGS=\"--cfg genproto\" cargo build -p ldk-server-protos && cargo fmt --all" - exit 1 - fi diff --git a/CLAUDE.md b/CLAUDE.md index aa1da645..f4bd6434 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,7 +9,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for build commands, testing, code style, - **ldk-server** - Main daemon server (entry point: `src/main.rs`) - **ldk-server-cli** - CLI client using clap - **ldk-server-client** - Reqwest-based client library -- **ldk-server-protos** - Protocol buffer definitions and generated Rust code +- **ldk-server-json-models** - Shared JSON request/response types with serde ## Development Rules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a783dfb..76fae4fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,18 +37,11 @@ cargo clippy --all-features -- -D warnings -A clippy::drop_non_drop # Lint (CI - Hard tabs, max width 100 chars - Imports grouped: std, external crates, local crates -## Protocol Buffer Generation - -```bash -RUSTFLAGS="--cfg genproto" cargo build -p ldk-server-protos -cargo fmt --all -``` - ## Adding a New API Endpoint -1. Define request/response messages in `ldk-server-protos/src/proto/api.proto` -2. Regenerate protos (see above) -3. Create handler in `ldk-server/src/api/` (follow existing patterns) +1. Define request/response types in `ldk-server-json-models/src/api.rs` +2. Create handler in `ldk-server/src/api/` (follow existing patterns) +3. Add endpoint constant in `ldk-server-json-models/src/endpoints.rs` 4. Add route in `ldk-server/src/service.rs` 5. Add CLI command in `ldk-server-cli/src/main.rs` diff --git a/Cargo.lock b/Cargo.lock index 99a74a7d..7a9496a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -573,9 +573,6 @@ name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -dependencies = [ - "serde", -] [[package]] name = "cbc" @@ -1753,7 +1750,6 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.21.7", - "bytes", "chrono", "clap", "futures-util", @@ -1764,12 +1760,12 @@ dependencies = [ "hyper-util", "lapin", "ldk-node", - "ldk-server-protos", + "ldk-server-json-models", "log", - "prost", "ring", "rusqlite", "serde", + "serde_json", "tokio", "tokio-rustls 0.26.4", "toml", @@ -1794,19 +1790,18 @@ name = "ldk-server-client" version = "0.1.0" dependencies = [ "bitcoin_hashes 0.14.0", - "ldk-server-protos", - "prost", + "ldk-server-json-models", "reqwest 0.11.27", + "serde", + "serde_json", ] [[package]] -name = "ldk-server-protos" +name = "ldk-server-json-models" version = "0.1.0" dependencies = [ - "bytes", - "prost", - "prost-build", "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2a8c8b9b..aaa6637f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["ldk-server-cli", "ldk-server-client", "ldk-server-protos", "ldk-server"] +members = ["ldk-server-cli", "ldk-server-client", "ldk-server-json-models", "ldk-server"] exclude = ["e2e-tests"] [profile.release] diff --git a/README.md b/README.md index f14d2881..04ddfc34 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ The primary goal of LDK Server is to provide an efficient, stable, and API-first solution for deploying and managing a Lightning Network node. With its streamlined setup, LDK Server enables users to easily set up, configure, and run -a Lightning node while exposing a robust, language-agnostic API via [Protocol Buffers (Protobuf)](https://protobuf.dev/). +a Lightning node while exposing a robust, language-agnostic REST API with JSON. ### Features @@ -15,7 +15,7 @@ a Lightning node while exposing a robust, language-agnostic API via [Protocol Bu - Deploy a Lightning Network node with minimal configuration, no coding required. - **API-First Design**: - - Exposes a well-defined API using Protobuf, allowing seamless integration with HTTP-clients or applications. + - Exposes a well-defined JSON REST API, allowing seamless integration with HTTP-clients or applications. - **Powered by LDK**: - Built on top of LDK-Node, leveraging the modular, reliable, and high-performance architecture of LDK. diff --git a/e2e-tests/Cargo.lock b/e2e-tests/Cargo.lock index 13326fd6..11b6c65e 100644 --- a/e2e-tests/Cargo.lock +++ b/e2e-tests/Cargo.lock @@ -548,9 +548,6 @@ name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] [[package]] name = "bzip2" @@ -874,8 +871,7 @@ dependencies = [ "lapin", "ldk-node", "ldk-server-client", - "ldk-server-protos", - "prost", + "ldk-server-json-models", "serde_json", "tempfile", "tokio", @@ -1834,18 +1830,16 @@ name = "ldk-server-client" version = "0.1.0" dependencies = [ "bitcoin_hashes", - "ldk-server-protos", - "prost", + "ldk-server-json-models", "reqwest 0.11.27", + "serde", + "serde_json", ] [[package]] -name = "ldk-server-protos" +name = "ldk-server-json-models" version = "0.1.0" dependencies = [ - "bytes", - "prost", - "prost-build", "serde", ] diff --git a/e2e-tests/Cargo.toml b/e2e-tests/Cargo.toml index 5576b7d2..e0f42f61 100644 --- a/e2e-tests/Cargo.toml +++ b/e2e-tests/Cargo.toml @@ -8,10 +8,9 @@ corepc-node = { version = "0.10", features = ["download", "29_0"] } tempfile = "3" tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros", "time"] } ldk-server-client = { path = "../ldk-server-client" } -ldk-server-protos = { path = "../ldk-server-protos", features = ["serde"] } +ldk-server-json-models = { path = "../ldk-server-json-models" } serde_json = "1.0" hex-conservative = { version = "0.2", features = ["std"] } lapin = { version = "2.4.0", features = ["rustls"], default-features = false } -prost = { version = "0.11.6", default-features = false, features = ["std"] } futures-util = "0.3" ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "d1bbf978c8b7abe87ae2e40793556c1fe4e7ea49" } diff --git a/e2e-tests/build.rs b/e2e-tests/build.rs index 6701b3d0..0741cbe8 100644 --- a/e2e-tests/build.rs +++ b/e2e-tests/build.rs @@ -45,6 +45,6 @@ fn main() { println!("cargo:rerun-if-changed=../ldk-server/Cargo.toml"); println!("cargo:rerun-if-changed=../ldk-server-cli/src"); println!("cargo:rerun-if-changed=../ldk-server-cli/Cargo.toml"); - println!("cargo:rerun-if-changed=../ldk-server-protos/src"); - println!("cargo:rerun-if-changed=../ldk-server-protos/Cargo.toml"); + println!("cargo:rerun-if-changed=../ldk-server-json-models/src"); + println!("cargo:rerun-if-changed=../ldk-server-json-models/Cargo.toml"); } diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 083c08e7..41f4c49f 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -16,9 +16,9 @@ use std::time::Duration; use corepc_node::Node; use hex_conservative::DisplayHex; use ldk_server_client::client::LdkServerClient; -use ldk_server_client::ldk_server_protos::api::{GetNodeInfoRequest, GetNodeInfoResponse}; -use ldk_server_protos::api::{ - GetBalancesRequest, ListChannelsRequest, OnchainReceiveRequest, OpenChannelRequest, +use ldk_server_json_models::api::{ + GetBalancesRequest, GetNodeInfoRequest, GetNodeInfoResponse, ListChannelsRequest, + OnchainReceiveRequest, OpenChannelRequest, }; /// Wrapper around a managed bitcoind process for regtest. @@ -92,7 +92,7 @@ pub struct LdkServerHandle { pub storage_dir: PathBuf, pub api_key: String, pub tls_cert_path: PathBuf, - pub node_id: String, + pub node_id: [u8; 33], pub exchange_name: String, client: LdkServerClient, } @@ -201,7 +201,7 @@ client_trusts_lsp = true storage_dir, api_key, tls_cert_path, - node_id: String::new(), + node_id: [0u8; 33], exchange_name, client, }; @@ -217,7 +217,7 @@ client_trusts_lsp = true &self.client } - pub fn node_id(&self) -> &str { + pub fn node_id(&self) -> &[u8; 33] { &self.node_id } @@ -325,16 +325,14 @@ pub async fn mine_and_sync( let start = std::time::Instant::now(); loop { if let Ok(info) = client.get_node_info(GetNodeInfoRequest {}).await { - if info.current_best_block.as_ref().map(|b| b.height).unwrap_or(0) - >= expected_height as u32 - { + if info.current_best_block.height >= expected_height as u32 { break; } } if start.elapsed() > timeout { panic!( "Timed out waiting for server {} to sync to height {}", - server.node_id(), + server.node_id().to_lower_hex_string(), expected_height ); } @@ -409,7 +407,7 @@ pub async fn setup_funded_channel( let open_resp = server_a .client() .open_channel(OpenChannelRequest { - node_pubkey: server_b.node_id().to_string(), + node_pubkey: *server_b.node_id(), address: format!("127.0.0.1:{}", server_b.p2p_port), channel_amount_sats, push_to_counterparty_msat: None, @@ -502,17 +500,16 @@ impl RabbitMqEventConsumer { /// Consume up to `count` events, waiting up to `timeout` for each. pub async fn consume_events( &mut self, count: usize, timeout: Duration, - ) -> Vec { + ) -> Vec { use futures_util::StreamExt; use lapin::options::BasicAckOptions; - use prost::Message; let mut events = Vec::new(); for _ in 0..count { match tokio::time::timeout(timeout, self.consumer.next()).await { Ok(Some(Ok(delivery))) => { - let event = ldk_server_protos::events::EventEnvelope::decode(&*delivery.data) - .expect("Failed to decode event"); + let event: ldk_server_json_models::events::Event = + serde_json::from_slice(&delivery.data).expect("Failed to decode event"); self.channel .basic_ack(delivery.delivery_tag, BasicAckOptions::default()) .await diff --git a/e2e-tests/tests/e2e.rs b/e2e-tests/tests/e2e.rs index 577b74c1..9e08de08 100644 --- a/e2e-tests/tests/e2e.rs +++ b/e2e-tests/tests/e2e.rs @@ -16,16 +16,16 @@ use e2e_tests::{ }; use hex_conservative::{DisplayHex, FromHex}; use ldk_node::bitcoin::hashes::{sha256, Hash}; +use ldk_node::bitcoin::secp256k1::PublicKey; +use ldk_node::bitcoin::Network; use ldk_node::lightning::ln::msgs::SocketAddress; use ldk_node::lightning::offers::offer::Offer; use ldk_node::lightning_invoice::Bolt11Invoice; -use ldk_server_client::ldk_server_protos::api::{ +use ldk_server_json_models::api::{ Bolt11ReceiveRequest, Bolt12ReceiveRequest, OnchainReceiveRequest, }; -use ldk_server_client::ldk_server_protos::types::{ - bolt11_invoice_description, Bolt11InvoiceDescription, -}; -use ldk_server_protos::events::event_envelope::Event; +use ldk_server_json_models::events::Event; +use ldk_server_json_models::types::Bolt11InvoiceDescription; #[tokio::test] async fn test_cli_get_node_info() { @@ -33,8 +33,18 @@ async fn test_cli_get_node_info() { let server = LdkServerHandle::start(&bitcoind).await; let output = run_cli(&server, &["get-node-info"]); - assert!(output.get("node_id").is_some()); - assert_eq!(output["node_id"], server.node_id()); + assert_eq!(output["node_id"], server.node_id().to_lower_hex_string()); + + // Cross-check block hash against bitcoind's RPC at the same height. + // This verifies byte order is correct (Bitcoin hashes display reversed). + let height = output["current_best_block"]["height"].as_u64().unwrap(); + assert!(height > 0); + let api_block_hash = output["current_best_block"]["block_hash"].as_str().unwrap(); + let rpc_block_hash = bitcoind.bitcoind.client.get_block_hash(height).unwrap().0; + assert_eq!( + api_block_hash, rpc_block_hash, + "block_hash from API should match bitcoind RPC (display byte order)" + ); } #[tokio::test] @@ -73,7 +83,7 @@ async fn test_cli_list_payments_empty() { let server = LdkServerHandle::start(&bitcoind).await; let output = run_cli(&server, &["list-payments"]); - assert!(output["list"].as_array().unwrap().is_empty()); + assert!(output["payments"].as_array().unwrap().is_empty()); } #[tokio::test] @@ -82,7 +92,7 @@ async fn test_cli_list_forwarded_payments_empty() { let server = LdkServerHandle::start(&bitcoind).await; let output = run_cli(&server, &["list-forwarded-payments"]); - assert!(output["list"].as_array().unwrap().is_empty()); + assert!(output["forwarded_payments"].as_array().unwrap().is_empty()); } #[tokio::test] @@ -102,7 +112,8 @@ async fn test_cli_verify_signature() { let sign_output = run_cli(&server, &["sign-message", "hello"]); let signature = sign_output["signature"].as_str().unwrap(); - let output = run_cli(&server, &["verify-signature", "hello", signature, server.node_id()]); + let node_id_hex = server.node_id().to_lower_hex_string(); + let output = run_cli(&server, &["verify-signature", "hello", signature, &node_id_hex]); assert_eq!(output["valid"], true); } @@ -118,9 +129,7 @@ async fn test_cli_export_pathfinding_scores() { .client() .bolt11_receive(Bolt11ReceiveRequest { amount_msat: Some(10_000_000), - description: Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct("test".to_string())), - }), + description: Some(Bolt11InvoiceDescription::Direct("test".to_string())), expiry_secs: 3600, }) .await @@ -142,10 +151,18 @@ async fn test_cli_bolt11_receive() { assert!(invoice_str.starts_with("lnbcrt"), "Expected lnbcrt prefix, got: {}", invoice_str); let invoice: Bolt11Invoice = invoice_str.parse().unwrap(); - let payment_hash = sha256::Hash::from_str(output["payment_hash"].as_str().unwrap()).unwrap(); - assert_eq!(*invoice.payment_hash(), payment_hash); - let payment_secret = <[u8; 32]>::from_hex(output["payment_secret"].as_str().unwrap()).unwrap(); - assert_eq!(invoice.payment_secret().0, payment_secret); + + // Cross-check payment_hash bytes: API response vs parsed invoice + let api_hash = <[u8; 32]>::from_hex(output["payment_hash"].as_str().unwrap()).unwrap(); + assert_eq!( + api_hash, + *invoice.payment_hash().as_byte_array(), + "payment_hash bytes should match invoice" + ); + + // Cross-check payment_secret bytes: API response vs parsed invoice + let api_secret = <[u8; 32]>::from_hex(output["payment_secret"].as_str().unwrap()).unwrap(); + assert_eq!(api_secret, invoice.payment_secret().0, "payment_secret bytes should match invoice"); } #[tokio::test] @@ -181,7 +198,13 @@ async fn test_cli_onchain_send() { let dest_addr = recv_output["address"].as_str().unwrap(); let output = run_cli(&server, &["onchain-send", dest_addr, "50000sat"]); - assert!(!output["txid"].as_str().unwrap().is_empty()); + let txid_hex = output["txid"].as_str().unwrap(); + + // Verify the txid is in display byte order by round-tripping through + // bitcoin::Txid::from_str (which expects display/reversed hex) + let txid = ldk_node::bitcoin::Txid::from_str(txid_hex) + .expect("API txid should parse as a Bitcoin Txid (display byte order)"); + assert_eq!(txid.to_string(), txid_hex, "Txid round-trip should preserve hex"); } #[tokio::test] @@ -191,7 +214,8 @@ async fn test_cli_connect_peer() { let server_b = LdkServerHandle::start(&bitcoind).await; let addr = format!("127.0.0.1:{}", server_b.p2p_port); - let output = run_cli(&server_a, &["connect-peer", server_b.node_id(), &addr]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["connect-peer", &node_id_hex, &addr]); // ConnectPeerResponse is empty assert!(output.is_object()); } @@ -208,12 +232,13 @@ async fn test_cli_list_peers() { assert!(output["peers"].as_array().unwrap().is_empty()); let addr = format!("127.0.0.1:{}", server_b.p2p_port); - run_cli(&server_a, &["connect-peer", server_b.node_id(), &addr]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + run_cli(&server_a, &["connect-peer", &node_id_hex, &addr]); let output = run_cli(&server_a, &["list-peers"]); let peers = output["peers"].as_array().unwrap(); assert_eq!(peers.len(), 1); - assert_eq!(peers[0]["node_id"], server_b.node_id()); + assert_eq!(peers[0]["node_id"], node_id_hex); assert_eq!(peers[0]["address"], addr); assert_eq!(peers[0]["is_persisted"], false); assert_eq!(peers[0]["is_connected"], true); @@ -238,9 +263,10 @@ async fn test_cli_open_channel() { // Open channel via CLI let addr = format!("127.0.0.1:{}", server_b.p2p_port); + let node_id_hex = server_b.node_id().to_lower_hex_string(); let output = run_cli( &server_a, - &["open-channel", server_b.node_id(), &addr, "100000sat", "--announce-channel"], + &["open-channel", &node_id_hex, &addr, "100000sat", "--announce-channel"], ); assert!(!output["user_channel_id"].as_str().unwrap().is_empty()); } @@ -255,7 +281,33 @@ async fn test_cli_list_channels() { let output = run_cli(&server_a, &["list-channels"]); let channels = output["channels"].as_array().unwrap(); assert!(!channels.is_empty()); - assert_eq!(channels[0]["counterparty_node_id"], server_b.node_id()); + + let ch = &channels[0]; + // Verify counterparty_node_id is server_b's actual pubkey + let cp_id = <[u8; 33]>::from_hex(ch["counterparty_node_id"].as_str().unwrap()).unwrap(); + assert_eq!(cp_id, *server_b.node_id(), "counterparty should be server_b"); + + // Verify channel_id is a valid 32-byte value + let channel_id = <[u8; 32]>::from_hex(ch["channel_id"].as_str().unwrap()).unwrap(); + assert_ne!(channel_id, [0u8; 32], "channel_id should not be all zeros"); + + // Verify funding txid is in correct display byte order + let funding_txid_hex = ch["funding_txo"]["txid"].as_str().unwrap(); + let funding_txid = ldk_node::bitcoin::Txid::from_str(funding_txid_hex) + .expect("funding txid should parse as a Bitcoin Txid (display byte order)"); + assert_eq!(funding_txid.to_string(), funding_txid_hex); + + // Both sides should see the same channel — cross-check from server_b + let output_b = run_cli(&server_b, &["list-channels"]); + let channels_b = output_b["channels"].as_array().unwrap(); + assert!(!channels_b.is_empty()); + let ch_b = &channels_b[0]; + let cp_id_b = <[u8; 33]>::from_hex(ch_b["counterparty_node_id"].as_str().unwrap()).unwrap(); + assert_eq!(cp_id_b, *server_a.node_id(), "server_b's counterparty should be server_a"); + assert_eq!( + ch_b["funding_txo"]["txid"], ch["funding_txo"]["txid"], + "both sides should report same funding txid" + ); } #[tokio::test] @@ -265,12 +317,13 @@ async fn test_cli_update_channel_config() { let server_b = LdkServerHandle::start(&bitcoind).await; let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; + let node_id_hex = server_b.node_id().to_lower_hex_string(); let output = run_cli( &server_a, &[ "update-channel-config", &user_channel_id, - server_b.node_id(), + &node_id_hex, "--forwarding-fee-base-msat", "100", ], @@ -295,9 +348,7 @@ async fn test_cli_bolt11_send() { .client() .bolt11_receive(Bolt11ReceiveRequest { amount_msat: Some(10_000_000), - description: Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct("test".to_string())), - }), + description: Some(Bolt11InvoiceDescription::Direct("test".to_string())), expiry_secs: 3600, }) .await @@ -312,13 +363,13 @@ async fn test_cli_bolt11_send() { let events_a = consumer_a.consume_events(5, Duration::from_secs(10)).await; assert!( - events_a.iter().any(|e| matches!(&e.event, Some(Event::PaymentSuccessful(_)))), + events_a.iter().any(|e| matches!(e, Event::PaymentSuccessful(_))), "Expected PaymentSuccessful on sender" ); let events_b = consumer_b.consume_events(5, Duration::from_secs(10)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentReceived(_)))), + events_b.iter().any(|e| matches!(e, Event::PaymentReceived(_))), "Expected PaymentReceived on receiver" ); } @@ -335,15 +386,16 @@ async fn test_cli_pay() { .client() .bolt11_receive(Bolt11ReceiveRequest { amount_msat: Some(10_000_000), - description: Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct("test".to_string())), - }), + description: Some(Bolt11InvoiceDescription::Direct("test".to_string())), expiry_secs: 3600, }) .await .unwrap(); let output = run_cli(&server_a, &["pay", &invoice_resp.invoice]); - assert!(output.get("bolt11_payment_id").is_some()); + assert!( + output.get("bolt11").unwrap().get("payment_id").is_some(), + "Expected bolt11.payment_id in output, got {output:?}" + ); // Pay a BOLT12 offer via unified `pay` command let offer_resp = server_b @@ -357,7 +409,10 @@ async fn test_cli_pay() { .await .unwrap(); let output = run_cli(&server_a, &["pay", &offer_resp.offer, "10000sat"]); - assert!(output.get("bolt12_payment_id").is_some()); + assert!( + output.get("bolt12").unwrap().get("payment_id").is_some(), + "Expected bolt12.payment_id in output, got {output:?}" + ); } #[tokio::test] @@ -395,7 +450,8 @@ async fn test_cli_spontaneous_send() { setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; - let output = run_cli(&server_a, &["spontaneous-send", server_b.node_id(), "10000sat"]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["spontaneous-send", &node_id_hex, "10000sat"]); assert!(!output["payment_id"].as_str().unwrap().is_empty()); // Verify events @@ -403,13 +459,13 @@ async fn test_cli_spontaneous_send() { let events_a = consumer_a.consume_events(5, Duration::from_secs(10)).await; assert!( - events_a.iter().any(|e| matches!(&e.event, Some(Event::PaymentSuccessful(_)))), + events_a.iter().any(|e| matches!(e, Event::PaymentSuccessful(_))), "Expected PaymentSuccessful on sender" ); let events_b = consumer_b.consume_events(5, Duration::from_secs(10)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentReceived(_)))), + events_b.iter().any(|e| matches!(e, Event::PaymentReceived(_))), "Expected PaymentReceived on receiver" ); } @@ -426,14 +482,14 @@ async fn test_cli_get_payment_details() { .client() .bolt11_receive(Bolt11ReceiveRequest { amount_msat: Some(10_000_000), - description: Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct("test".to_string())), - }), + description: Some(Bolt11InvoiceDescription::Direct("test".to_string())), expiry_secs: 3600, }) .await .unwrap(); + let invoice: Bolt11Invoice = invoice_resp.invoice.parse().unwrap(); + let send_output = run_cli(&server_a, &["bolt11-send", &invoice_resp.invoice]); let payment_id = send_output["payment_id"].as_str().unwrap(); @@ -441,8 +497,19 @@ async fn test_cli_get_payment_details() { tokio::time::sleep(Duration::from_secs(3)).await; let output = run_cli(&server_a, &["get-payment-details", payment_id]); - assert!(output.get("payment").is_some()); - assert_eq!(output["payment"]["id"], payment_id); + let payment = &output["payment"]; + assert_eq!(payment["id"], payment_id); + assert_eq!(payment["status"], "succeeded"); + assert_eq!(payment["direction"], "outbound"); + + // Verify the payment hash in the details matches the invoice + let details_hash_hex = payment["kind"]["bolt11"]["hash"].as_str().unwrap(); + let details_hash = <[u8; 32]>::from_hex(details_hash_hex).unwrap(); + assert_eq!( + details_hash, + *invoice.payment_hash().as_byte_array(), + "payment hash in details should match invoice" + ); } #[tokio::test] @@ -457,9 +524,7 @@ async fn test_cli_list_payments() { .client() .bolt11_receive(Bolt11ReceiveRequest { amount_msat: Some(10_000_000), - description: Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct("test".to_string())), - }), + description: Some(Bolt11InvoiceDescription::Direct("test".to_string())), expiry_secs: 3600, }) .await @@ -469,7 +534,7 @@ async fn test_cli_list_payments() { tokio::time::sleep(Duration::from_secs(3)).await; let output = run_cli(&server_a, &["list-payments"]); - assert!(!output["list"].as_array().unwrap().is_empty()); + assert!(!output["payments"].as_array().unwrap().is_empty()); } #[tokio::test] @@ -479,7 +544,8 @@ async fn test_cli_close_channel() { let server_b = LdkServerHandle::start(&bitcoind).await; let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; - let output = run_cli(&server_a, &["close-channel", &user_channel_id, server_b.node_id()]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["close-channel", &user_channel_id, &node_id_hex]); assert!(output.is_object()); mine_and_sync(&bitcoind, &[&server_a, &server_b], 6).await; @@ -496,7 +562,8 @@ async fn test_cli_force_close_channel() { let server_b = LdkServerHandle::start(&bitcoind).await; let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; - let output = run_cli(&server_a, &["force-close-channel", &user_channel_id, server_b.node_id()]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["force-close-channel", &user_channel_id, &node_id_hex]); assert!(output.is_object()); mine_and_sync(&bitcoind, &[&server_a, &server_b], 6).await; @@ -513,8 +580,8 @@ async fn test_cli_splice_in() { let server_b = LdkServerHandle::start(&bitcoind).await; let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; - let output = - run_cli(&server_a, &["splice-in", &user_channel_id, server_b.node_id(), "50000sat"]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["splice-in", &user_channel_id, &node_id_hex, "50000sat"]); assert!(output.is_object()); } @@ -525,8 +592,8 @@ async fn test_cli_splice_out() { let server_b = LdkServerHandle::start(&bitcoind).await; let user_channel_id = setup_funded_channel(&bitcoind, &server_a, &server_b, 100_000).await; - let output = - run_cli(&server_a, &["splice-out", &user_channel_id, server_b.node_id(), "10000sat"]); + let node_id_hex = server_b.node_id().to_lower_hex_string(); + let output = run_cli(&server_a, &["splice-out", &user_channel_id, &node_id_hex, "10000sat"]); let address = output["address"].as_str().unwrap(); assert!(address.starts_with("bcrt1"), "Expected regtest address, got: {}", address); } @@ -577,7 +644,9 @@ async fn test_cli_graph_with_channel() { let channel = &output["channel"]; let node_one = channel["node_one"].as_str().unwrap(); let node_two = channel["node_two"].as_str().unwrap(); - let nodes = [server_a.node_id(), server_b.node_id()]; + let node_a_hex = server_a.node_id().to_lower_hex_string(); + let node_b_hex = server_b.node_id().to_lower_hex_string(); + let nodes = [node_a_hex.as_str(), node_b_hex.as_str()]; assert!(nodes.contains(&node_one), "node_one {} not one of our nodes", node_one); assert!(nodes.contains(&node_two), "node_two {} not one of our nodes", node_two); @@ -585,11 +654,11 @@ async fn test_cli_graph_with_channel() { let output = run_cli(&server_a, &["graph-list-nodes"]); let node_ids: Vec<&str> = output["node_ids"].as_array().unwrap().iter().map(|n| n.as_str().unwrap()).collect(); - assert!(node_ids.contains(&server_a.node_id()), "Expected server_a in graph nodes"); - assert!(node_ids.contains(&server_b.node_id()), "Expected server_b in graph nodes"); + assert!(node_ids.contains(&node_a_hex.as_str()), "Expected server_a in graph nodes"); + assert!(node_ids.contains(&node_b_hex.as_str()), "Expected server_b in graph nodes"); // Test GraphGetNode: should return node info with at least one channel. - let output = run_cli(&server_a, &["graph-get-node", server_b.node_id()]); + let output = run_cli(&server_a, &["graph-get-node", &node_b_hex]); let node = &output["node"]; let channels = node["channels"].as_array().unwrap(); assert!(!channels.is_empty(), "Expected node to have at least one channel"); @@ -630,7 +699,7 @@ async fn test_forwarded_payment_event() { let storage_dir_c = tempfile::tempdir().unwrap().into_path(); let p2p_port_c = find_available_port(); let config_c = ldk_node::config::Config { - network: ldk_node::bitcoin::Network::Regtest, + network: Network::Regtest, storage_dir_path: storage_dir_c.to_str().unwrap().to_string(), listening_addresses: Some(vec![SocketAddress::from_str(&format!( "127.0.0.1:{p2p_port_c}" @@ -644,7 +713,8 @@ async fn test_forwarded_payment_event() { builder_c.set_chain_source_bitcoind_rpc(rpc_host, rpc_port, rpc_user, rpc_password); // Set B as LSPS2 LSP for C - let b_node_id = ldk_node::bitcoin::secp256k1::PublicKey::from_str(server_b.node_id()).unwrap(); + let b_node_id_hex = server_b.node_id().to_lower_hex_string(); + let b_node_id = PublicKey::from_str(&b_node_id_hex).unwrap(); let b_addr = SocketAddress::from_str(&format!("127.0.0.1:{}", server_b.p2p_port)).unwrap(); builder_c.set_liquidity_source_lsps2(b_node_id, b_addr, None); @@ -678,9 +748,8 @@ async fn test_forwarded_payment_event() { // Verify PaymentForwarded event on B let events_b = consumer_b.consume_events(10, Duration::from_secs(15)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentForwarded(_)))), - "Expected PaymentForwarded event on LSP node B, got events: {:?}", - events_b.iter().map(|e| &e.event).collect::>() + events_b.iter().any(|e| matches!(e, Event::PaymentForwarded(_))), + "Expected PaymentForwarded event on LSP node B, got events: {events_b:?}" ); node_c.stop().unwrap(); @@ -731,9 +800,8 @@ async fn test_hodl_invoice_claim() { // Verify PaymentClaimable event on B let events_b = consumer_b.consume_events(1, Duration::from_secs(10)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentClaimable(_)))), - "Expected PaymentClaimable on receiver, got events: {:?}", - events_b.iter().map(|e| &e.event).collect::>() + events_b.iter().any(|e| matches!(e, Event::PaymentClaimable(_))), + "Expected PaymentClaimable on receiver, got events: {events_b:?}", ); // Claim the payment on B @@ -749,26 +817,21 @@ async fn test_hodl_invoice_claim() { // Verify PaymentReceived event on B let events_b = consumer_b.consume_events(1, Duration::from_secs(10)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentReceived(_)))), - "Expected PaymentReceived on receiver after claim, got events: {:?}", - events_b.iter().map(|e| &e.event).collect::>() + events_b.iter().any(|e| matches!(e, Event::PaymentReceived(_))), + "Expected PaymentReceived on receiver after claim, got events: {events_b:?}", ); // Verify PaymentSuccessful on A let events_a = consumer_a.consume_events(1, Duration::from_secs(10)).await; assert!( - events_a.iter().any(|e| matches!(&e.event, Some(Event::PaymentSuccessful(_)))), - "Expected PaymentSuccessful on sender, got events: {:?}", - events_a.iter().map(|e| &e.event).collect::>() + events_a.iter().any(|e| matches!(e, Event::PaymentSuccessful(_))), + "Expected PaymentSuccessful on sender, got events: {events_a:?}", ); } } #[tokio::test] async fn test_hodl_invoice_fail() { - use hex_conservative::DisplayHex; - use ldk_node::bitcoin::hashes::{sha256, Hash}; - let bitcoind = TestBitcoind::new(); let server_a = LdkServerHandle::start(&bitcoind).await; let server_b = LdkServerHandle::start(&bitcoind).await; @@ -808,9 +871,8 @@ async fn test_hodl_invoice_fail() { // Verify PaymentClaimable event on B let events_b = consumer_b.consume_events(5, Duration::from_secs(10)).await; assert!( - events_b.iter().any(|e| matches!(&e.event, Some(Event::PaymentClaimable(_)))), - "Expected PaymentClaimable on receiver, got events: {:?}", - events_b.iter().map(|e| &e.event).collect::>() + events_b.iter().any(|e| matches!(e, Event::PaymentClaimable(_))), + "Expected PaymentClaimable on receiver, got events: {events_b:?}", ); // Fail the payment on B using CLI @@ -822,8 +884,7 @@ async fn test_hodl_invoice_fail() { // Verify PaymentFailed on A let events_a = consumer_a.consume_events(10, Duration::from_secs(10)).await; assert!( - events_a.iter().any(|e| matches!(&e.event, Some(Event::PaymentFailed(_)))), - "Expected PaymentFailed on sender after hodl rejection, got events: {:?}", - events_a.iter().map(|e| &e.event).collect::>() + events_a.iter().any(|e| matches!(e, Event::PaymentFailed(_))), + "Expected PaymentFailed on sender after hodl rejection, got events: {events_a:?}", ); } diff --git a/ldk-server-cli/Cargo.toml b/ldk-server-cli/Cargo.toml index 5d006001..4b839146 100644 --- a/ldk-server-cli/Cargo.toml +++ b/ldk-server-cli/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -ldk-server-client = { path = "../ldk-server-client", features = ["serde"] } +ldk-server-client = { path = "../ldk-server-client" } clap = { version = "4.0.5", default-features = false, features = ["derive", "std", "error-context", "suggestions", "help"] } clap_complete = { version = "4.0", default-features = false } hex-conservative = { version = "0.2", default-features = false, features = ["std"] } diff --git a/ldk-server-cli/src/main.rs b/ldk-server-cli/src/main.rs index d960f087..f5253462 100644 --- a/ldk-server-cli/src/main.rs +++ b/ldk-server-cli/src/main.rs @@ -15,49 +15,36 @@ use config::{ api_key_path_for_storage_dir, cert_path_for_storage_dir, get_default_api_key_path, get_default_cert_path, get_default_config_path, load_config, }; -use hex_conservative::DisplayHex; +use hex_conservative::{DisplayHex, FromHex}; use ldk_server_client::client::LdkServerClient; use ldk_server_client::error::LdkServerError; use ldk_server_client::error::LdkServerErrorCode::{ - AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError, + AuthError, InternalError, InternalServerError, InvalidRequestError, JsonParseError, + LightningError, }; -use ldk_server_client::ldk_server_protos::api::{ - Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse, Bolt11FailForHashRequest, - Bolt11FailForHashResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse, - Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, - Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, - Bolt11ReceiveViaJitChannelResponse, Bolt11SendRequest, Bolt11SendResponse, - Bolt12ReceiveRequest, Bolt12ReceiveResponse, Bolt12SendRequest, Bolt12SendResponse, - CloseChannelRequest, CloseChannelResponse, ConnectPeerRequest, ConnectPeerResponse, - DisconnectPeerRequest, DisconnectPeerResponse, ExportPathfindingScoresRequest, - ForceCloseChannelRequest, ForceCloseChannelResponse, GetBalancesRequest, GetBalancesResponse, - GetNodeInfoRequest, GetNodeInfoResponse, GetPaymentDetailsRequest, GetPaymentDetailsResponse, - GraphGetChannelRequest, GraphGetChannelResponse, GraphGetNodeRequest, GraphGetNodeResponse, - GraphListChannelsRequest, GraphListChannelsResponse, GraphListNodesRequest, - GraphListNodesResponse, ListChannelsRequest, ListChannelsResponse, - ListForwardedPaymentsRequest, ListPaymentsRequest, ListPeersRequest, ListPeersResponse, - OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse, - OpenChannelRequest, OpenChannelResponse, SignMessageRequest, SignMessageResponse, - SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, SpontaneousSendRequest, - SpontaneousSendResponse, UnifiedSendRequest, UnifiedSendResponse, UpdateChannelConfigRequest, - UpdateChannelConfigResponse, VerifySignatureRequest, VerifySignatureResponse, +use ldk_server_client::ldk_server_json_models::api::{ + Bolt11ClaimForHashRequest, Bolt11FailForHashRequest, Bolt11ReceiveForHashRequest, + Bolt11ReceiveRequest, Bolt11ReceiveVariableAmountViaJitChannelRequest, + Bolt11ReceiveViaJitChannelRequest, Bolt11SendRequest, Bolt12ReceiveRequest, Bolt12SendRequest, + CloseChannelRequest, ConnectPeerRequest, DisconnectPeerRequest, ExportPathfindingScoresRequest, + ForceCloseChannelRequest, GetBalancesRequest, GetNodeInfoRequest, GetPaymentDetailsRequest, + GraphGetChannelRequest, GraphGetNodeRequest, GraphListChannelsRequest, GraphListNodesRequest, + ListChannelsRequest, ListForwardedPaymentsRequest, ListPaymentsRequest, ListPeersRequest, + OnchainReceiveRequest, OnchainSendRequest, OpenChannelRequest, SignMessageRequest, + SpliceInRequest, SpliceOutRequest, SpontaneousSendRequest, UnifiedSendRequest, + UpdateChannelConfigRequest, VerifySignatureRequest, }; -use ldk_server_client::ldk_server_protos::types::{ - bolt11_invoice_description, Bolt11InvoiceDescription, ChannelConfig, PageToken, - RouteParametersConfig, +use ldk_server_client::ldk_server_json_models::types::{ + Bolt11InvoiceDescription, ChannelConfig, RouteParametersConfig, }; use serde::Serialize; -use serde_json::{json, Value}; -use types::{ - Amount, CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse, -}; +use serde_json::json; +use types::Amount; mod config; mod types; -// Having these default values as constants in the Proto file and -// importing/reusing them here might be better, but Proto3 removed -// the ability to set default values. +// Default values for route parameters configuration. const DEFAULT_MAX_TOTAL_CLTV_EXPIRY_DELTA: u32 = 1008; const DEFAULT_MAX_PATH_COUNT: u32 = 10; const DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF: u32 = 2; @@ -421,13 +408,7 @@ enum Commands { ListChannels, #[command(about = "Retrieve list of all payments")] ListPayments { - #[arg(short, long)] - #[arg( - help = "Fetch at least this many payments by iterating through multiple pages. Returns combined results with the last page token. If not provided, returns only a single page." - )] - number_of_payments: Option, - #[arg(long)] - #[arg(help = "Page token to continue from a previous page (format: token:index)")] + #[arg(help = "Page token to continue from a previous page")] page_token: Option, }, #[command(about = "Get details of a specific payment by its payment ID")] @@ -437,13 +418,7 @@ enum Commands { }, #[command(about = "Retrieves list of all forwarded payments")] ListForwardedPayments { - #[arg( - short, - long, - help = "Fetch at least this many forwarded payments by iterating through multiple pages. Returns combined results with the last page token. If not provided, returns only a single page." - )] - number_of_payments: Option, - #[arg(long, help = "Page token to continue from a previous page (format: token:index)")] + #[arg(help = "Page token to continue from a previous page")] page_token: Option, }, #[command(about = "Update the forwarding fees and CLTV expiry delta for an existing channel")] @@ -605,23 +580,17 @@ async fn main() { match cli.command { Commands::GetNodeInfo => { - handle_response_result::<_, GetNodeInfoResponse>( - client.get_node_info(GetNodeInfoRequest {}).await, - ); + handle_response_result(client.get_node_info(GetNodeInfoRequest {}).await); }, Commands::GetBalances => { - handle_response_result::<_, GetBalancesResponse>( - client.get_balances(GetBalancesRequest {}).await, - ); + handle_response_result(client.get_balances(GetBalancesRequest {}).await); }, Commands::OnchainReceive => { - handle_response_result::<_, OnchainReceiveResponse>( - client.onchain_receive(OnchainReceiveRequest {}).await, - ); + handle_response_result(client.onchain_receive(OnchainReceiveRequest {}).await); }, Commands::OnchainSend { address, amount, send_all, fee_rate_sat_per_vb } => { let amount_sats = amount.map(|a| a.to_sat().unwrap_or_else(|e| handle_error_msg(&e))); - handle_response_result::<_, OnchainSendResponse>( + handle_response_result( client .onchain_send(OnchainSendRequest { address, @@ -641,9 +610,7 @@ async fn main() { let request = Bolt11ReceiveRequest { description: invoice_description, expiry_secs, amount_msat }; - handle_response_result::<_, Bolt11ReceiveResponse>( - client.bolt11_receive(request).await, - ); + handle_response_result(client.bolt11_receive(request).await); }, Commands::Bolt11ReceiveForHash { payment_hash, @@ -654,12 +621,8 @@ async fn main() { } => { let amount_msat = amount.map(|a| a.to_msat()); let invoice_description = match (description, description_hash) { - (Some(desc), None) => Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct(desc)), - }), - (None, Some(hash)) => Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Hash(hash)), - }), + (Some(desc), None) => Some(Bolt11InvoiceDescription::Direct(desc)), + (None, Some(hash)) => Some(Bolt11InvoiceDescription::Hash(hash)), (Some(_), Some(_)) => { handle_error(LdkServerError::new( InternalError, @@ -674,27 +637,29 @@ async fn main() { description: invoice_description, expiry_secs, amount_msat, - payment_hash, + payment_hash: parse_hex_32(&payment_hash, "payment_hash"), }; - handle_response_result::<_, Bolt11ReceiveForHashResponse>( - client.bolt11_receive_for_hash(request).await, - ); + handle_response_result(client.bolt11_receive_for_hash(request).await); }, Commands::Bolt11ClaimForHash { preimage, claimable_amount, payment_hash } => { - handle_response_result::<_, Bolt11ClaimForHashResponse>( + handle_response_result( client .bolt11_claim_for_hash(Bolt11ClaimForHashRequest { - payment_hash, + payment_hash: payment_hash.map(|h| parse_hex_32(&h, "payment_hash")), claimable_amount_msat: claimable_amount.map(|a| a.to_msat()), - preimage, + preimage: parse_hex_32(&preimage, "preimage"), }) .await, ); }, Commands::Bolt11FailForHash { payment_hash } => { - handle_response_result::<_, Bolt11FailForHashResponse>( - client.bolt11_fail_for_hash(Bolt11FailForHashRequest { payment_hash }).await, + handle_response_result( + client + .bolt11_fail_for_hash(Bolt11FailForHashRequest { + payment_hash: parse_hex_32(&payment_hash, "payment_hash"), + }) + .await, ); }, Commands::Bolt11ReceiveViaJitChannel { @@ -711,9 +676,7 @@ async fn main() { max_total_lsp_fee_limit_msat: max_total_lsp_fee_limit.map(|a| a.to_msat()), }; - handle_response_result::<_, Bolt11ReceiveViaJitChannelResponse>( - client.bolt11_receive_via_jit_channel(request).await, - ); + handle_response_result(client.bolt11_receive_via_jit_channel(request).await); }, Commands::Bolt11ReceiveVariableAmountViaJitChannel { description, @@ -727,7 +690,7 @@ async fn main() { max_proportional_lsp_fee_limit_ppm_msat, }; - handle_response_result::<_, Bolt11ReceiveVariableAmountViaJitChannelResponse>( + handle_response_result( client.bolt11_receive_variable_amount_via_jit_channel(request).await, ); }, @@ -749,7 +712,7 @@ async fn main() { max_channel_saturation_power_of_half: max_channel_saturation_power_of_half .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), }; - handle_response_result::<_, Bolt11SendResponse>( + handle_response_result( client .bolt11_send(Bolt11SendRequest { invoice, @@ -761,7 +724,7 @@ async fn main() { }, Commands::Bolt12Receive { description, amount, expiry_secs, quantity } => { let amount_msat = amount.map(|a| a.to_msat()); - handle_response_result::<_, Bolt12ReceiveResponse>( + handle_response_result( client .bolt12_receive(Bolt12ReceiveRequest { description, @@ -793,7 +756,7 @@ async fn main() { .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), }; - handle_response_result::<_, Bolt12SendResponse>( + handle_response_result( client .bolt12_send(Bolt12SendRequest { offer, @@ -824,11 +787,11 @@ async fn main() { .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), }; - handle_response_result::<_, SpontaneousSendResponse>( + handle_response_result( client .spontaneous_send(SpontaneousSendRequest { amount_msat, - node_id, + node_id: parse_hex_33(&node_id, "node_id"), route_parameters: Some(route_parameters), }) .await, @@ -852,7 +815,7 @@ async fn main() { max_channel_saturation_power_of_half: max_channel_saturation_power_of_half .unwrap_or(DEFAULT_MAX_CHANNEL_SATURATION_POWER_OF_HALF), }; - handle_response_result::<_, UnifiedSendResponse>( + handle_response_result( client .unified_send(UnifiedSendRequest { uri, @@ -863,9 +826,15 @@ async fn main() { ); }, Commands::CloseChannel { user_channel_id, counterparty_node_id } => { - handle_response_result::<_, CloseChannelResponse>( + handle_response_result( client - .close_channel(CloseChannelRequest { user_channel_id, counterparty_node_id }) + .close_channel(CloseChannelRequest { + user_channel_id, + counterparty_node_id: parse_hex_33( + &counterparty_node_id, + "counterparty_node_id", + ), + }) .await, ); }, @@ -874,11 +843,14 @@ async fn main() { counterparty_node_id, force_close_reason, } => { - handle_response_result::<_, ForceCloseChannelResponse>( + handle_response_result( client .force_close_channel(ForceCloseChannelRequest { user_channel_id, - counterparty_node_id, + counterparty_node_id: parse_hex_33( + &counterparty_node_id, + "counterparty_node_id", + ), force_close_reason, }) .await, @@ -903,10 +875,10 @@ async fn main() { cltv_expiry_delta, ); - handle_response_result::<_, OpenChannelResponse>( + handle_response_result( client .open_channel(OpenChannelRequest { - node_pubkey, + node_pubkey: parse_hex_33(&node_pubkey, "node_pubkey"), address, channel_amount_sats, push_to_counterparty_msat, @@ -919,11 +891,14 @@ async fn main() { Commands::SpliceIn { user_channel_id, counterparty_node_id, splice_amount } => { let splice_amount_sats = splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(&e)); - handle_response_result::<_, SpliceInResponse>( + handle_response_result( client .splice_in(SpliceInRequest { user_channel_id, - counterparty_node_id, + counterparty_node_id: parse_hex_33( + &counterparty_node_id, + "counterparty_node_id", + ), splice_amount_sats, }) .await, @@ -932,11 +907,14 @@ async fn main() { Commands::SpliceOut { user_channel_id, counterparty_node_id, address, splice_amount } => { let splice_amount_sats = splice_amount.to_sat().unwrap_or_else(|e| handle_error_msg(&e)); - handle_response_result::<_, SpliceOutResponse>( + handle_response_result( client .splice_out(SpliceOutRequest { user_channel_id, - counterparty_node_id, + counterparty_node_id: parse_hex_33( + &counterparty_node_id, + "counterparty_node_id", + ), address, splice_amount_sats, }) @@ -944,45 +922,23 @@ async fn main() { ); }, Commands::ListChannels => { - handle_response_result::<_, ListChannelsResponse>( - client.list_channels(ListChannelsRequest {}).await, - ); + handle_response_result(client.list_channels(ListChannelsRequest {}).await); }, - Commands::ListPayments { number_of_payments, page_token } => { - let page_token = page_token - .map(|token_str| parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e))); - - handle_response_result::<_, CliListPaymentsResponse>( - fetch_paginated( - number_of_payments, - page_token, - |pt| client.list_payments(ListPaymentsRequest { page_token: pt }), - |r| (r.payments, r.next_page_token), - ) - .await, - ); + Commands::ListPayments { page_token } => { + handle_response_result(client.list_payments(ListPaymentsRequest { page_token }).await); }, Commands::GetPaymentDetails { payment_id } => { - handle_response_result::<_, GetPaymentDetailsResponse>( - client.get_payment_details(GetPaymentDetailsRequest { payment_id }).await, + handle_response_result( + client + .get_payment_details(GetPaymentDetailsRequest { + payment_id: parse_hex_32(&payment_id, "payment_id"), + }) + .await, ); }, - Commands::ListForwardedPayments { number_of_payments, page_token } => { - let page_token = page_token - .map(|token_str| parse_page_token(&token_str).unwrap_or_else(|e| handle_error(e))); - - handle_response_result::<_, CliListForwardedPaymentsResponse>( - fetch_paginated( - number_of_payments, - page_token, - |pt| { - client.list_forwarded_payments(ListForwardedPaymentsRequest { - page_token: pt, - }) - }, - |r| (r.forwarded_payments, r.next_page_token), - ) - .await, + Commands::ListForwardedPayments { page_token } => { + handle_response_result( + client.list_forwarded_payments(ListForwardedPaymentsRequest { page_token }).await, ); }, Commands::UpdateChannelConfig { @@ -1001,11 +957,14 @@ async fn main() { max_dust_htlc_exposure: None, }; - handle_response_result::<_, UpdateChannelConfigResponse>( + handle_response_result( client .update_channel_config(UpdateChannelConfigRequest { user_channel_id, - counterparty_node_id, + counterparty_node_id: parse_hex_33( + &counterparty_node_id, + "counterparty_node_id", + ), channel_config: Some(channel_config), }) .await, @@ -1020,40 +979,46 @@ async fn main() { eprintln!("Error: address is required. Provide it as pubkey@address or as a separate argument."); std::process::exit(1); }; - handle_response_result::<_, ConnectPeerResponse>( - client.connect_peer(ConnectPeerRequest { node_pubkey, address, persist }).await, + handle_response_result( + client + .connect_peer(ConnectPeerRequest { + node_pubkey: parse_hex_33(&node_pubkey, "node_pubkey"), + address, + persist, + }) + .await, ); }, Commands::DisconnectPeer { node_pubkey } => { - handle_response_result::<_, DisconnectPeerResponse>( - client.disconnect_peer(DisconnectPeerRequest { node_pubkey }).await, + handle_response_result( + client + .disconnect_peer(DisconnectPeerRequest { + node_pubkey: parse_hex_33(&node_pubkey, "node_pubkey"), + }) + .await, ); }, Commands::ListPeers => { - handle_response_result::<_, ListPeersResponse>( - client.list_peers(ListPeersRequest {}).await, - ); + handle_response_result(client.list_peers(ListPeersRequest {}).await); }, Commands::SignMessage { message } => { - handle_response_result::<_, SignMessageResponse>( - client - .sign_message(SignMessageRequest { message: message.into_bytes().into() }) - .await, + handle_response_result( + client.sign_message(SignMessageRequest { message: message.into_bytes() }).await, ); }, Commands::VerifySignature { message, signature, public_key } => { - handle_response_result::<_, VerifySignatureResponse>( + handle_response_result( client .verify_signature(VerifySignatureRequest { - message: message.into_bytes().into(), + message: message.into_bytes(), signature, - public_key, + public_key: parse_hex_33(&public_key, "public_key"), }) .await, ); }, Commands::ExportPathfindingScores => { - handle_response_result::<_, Value>( + handle_response_result( client.export_pathfinding_scores(ExportPathfindingScoresRequest {}).await.map( |s| { let scores_hex = s.scores.as_hex().to_string(); @@ -1063,23 +1028,23 @@ async fn main() { ); }, Commands::GraphListChannels => { - handle_response_result::<_, GraphListChannelsResponse>( - client.graph_list_channels(GraphListChannelsRequest {}).await, - ); + handle_response_result(client.graph_list_channels(GraphListChannelsRequest {}).await); }, Commands::GraphGetChannel { short_channel_id } => { - handle_response_result::<_, GraphGetChannelResponse>( + handle_response_result( client.graph_get_channel(GraphGetChannelRequest { short_channel_id }).await, ); }, Commands::GraphListNodes => { - handle_response_result::<_, GraphListNodesResponse>( - client.graph_list_nodes(GraphListNodesRequest {}).await, - ); + handle_response_result(client.graph_list_nodes(GraphListNodesRequest {}).await); }, Commands::GraphGetNode { node_id } => { - handle_response_result::<_, GraphGetNodeResponse>( - client.graph_get_node(GraphGetNodeRequest { node_id }).await, + handle_response_result( + client + .graph_get_node(GraphGetNodeRequest { + node_id: parse_hex_33(&node_id, "node_id"), + }) + .await, ); }, Commands::Completions { .. } => unreachable!("Handled above"), @@ -1108,74 +1073,43 @@ fn build_open_channel_config( }) } -async fn fetch_paginated( - target_count: Option, initial_page_token: Option, - fetch_page: impl Fn(Option) -> Fut, - extract: impl Fn(R) -> (Vec, Option), -) -> Result, LdkServerError> -where - Fut: std::future::Future>, -{ - match target_count { - Some(count) => { - let mut items = Vec::with_capacity(count as usize); - let mut page_token = initial_page_token; - let mut next_page_token; - - loop { - let response = fetch_page(page_token).await?; - let (new_items, new_next_page_token) = extract(response); - items.extend(new_items); - next_page_token = new_next_page_token; - - if items.len() >= count as usize || next_page_token.is_none() { - break; - } - page_token = next_page_token; - } - - Ok(CliPaginatedResponse::new(items, next_page_token)) - }, - None => { - let response = fetch_page(initial_page_token).await?; - let (items, next_page_token) = extract(response); - Ok(CliPaginatedResponse::new(items, next_page_token)) +fn handle_response_result(response: Result) { + match response { + Ok(response) => match serde_json::to_string_pretty(&response) { + Ok(json) => println!("{json}"), + Err(e) => { + eprintln!("Error serializing response ({response:?}) to JSON: {e}"); + std::process::exit(1); + }, }, + Err(e) => handle_error(e), } } -fn handle_response_result(response: Result) -where - Rs: Into, - Js: Serialize + std::fmt::Debug, -{ - match response { - Ok(response) => { - let json_response: Js = response.into(); - match serde_json::to_string_pretty(&json_response) { - Ok(json) => println!("{json}"), - Err(e) => { - eprintln!("Error serializing response ({json_response:?}) to JSON: {e}"); - std::process::exit(1); - }, - } - }, - Err(e) => { - handle_error(e); - }, - } +fn parse_hex_32(hex: &str, field_name: &str) -> [u8; 32] { + <[u8; 32]>::from_hex(hex).unwrap_or_else(|_| { + handle_error(LdkServerError::new( + InvalidRequestError, + format!("Invalid {field_name}, must be a 32-byte hex string."), + )) + }) +} + +fn parse_hex_33(hex: &str, field_name: &str) -> [u8; 33] { + <[u8; 33]>::from_hex(hex).unwrap_or_else(|_| { + handle_error(LdkServerError::new( + InvalidRequestError, + format!("Invalid {field_name}, must be a 33-byte hex string."), + )) + }) } fn parse_bolt11_invoice_description( description: Option, description_hash: Option, ) -> Option { match (description, description_hash) { - (Some(desc), None) => Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Direct(desc)), - }), - (None, Some(hash)) => Some(Bolt11InvoiceDescription { - kind: Some(bolt11_invoice_description::Kind::Hash(hash)), - }), + (Some(desc), None) => Some(Bolt11InvoiceDescription::Direct(desc)), + (None, Some(hash)) => Some(Bolt11InvoiceDescription::Hash(hash)), (Some(_), Some(_)) => { handle_error(LdkServerError::new( InternalError, @@ -1186,20 +1120,6 @@ fn parse_bolt11_invoice_description( } } -fn parse_page_token(token_str: &str) -> Result { - let parts: Vec<&str> = token_str.split(':').collect(); - if parts.len() != 2 { - return Err(LdkServerError::new( - InternalError, - "Page token must be in format 'token:index'".to_string(), - )); - } - let index = parts[1] - .parse::() - .map_err(|_| LdkServerError::new(InternalError, "Invalid page token index".to_string()))?; - Ok(PageToken { token: parts[0].to_string(), index }) -} - fn handle_error_msg(msg: &str) -> ! { eprintln!("Error: {msg}"); std::process::exit(1); @@ -1211,6 +1131,7 @@ fn handle_error(e: LdkServerError) -> ! { AuthError => "Authentication Error", LightningError => "Lightning Error", InternalServerError => "Internal Server Error", + JsonParseError => "JSON Parse Error", InternalError => "Internal Error", }; eprintln!("Error ({}): {}", error_type, e.message); diff --git a/ldk-server-cli/src/types.rs b/ldk-server-cli/src/types.rs index 92f778ad..2a6a2cb8 100644 --- a/ldk-server-cli/src/types.rs +++ b/ldk-server-cli/src/types.rs @@ -16,33 +16,6 @@ use std::fmt; use std::str::FromStr; -use ldk_server_client::ldk_server_protos::types::{ForwardedPayment, PageToken, Payment}; -use serde::Serialize; - -/// CLI-specific wrapper for paginated responses that formats the page token -/// as "token:idx" instead of a JSON object. -#[derive(Debug, Clone, Serialize)] -pub struct CliPaginatedResponse { - /// List of items. - pub list: Vec, - /// Next page token formatted as "token:idx", or None if no more pages. - #[serde(skip_serializing_if = "Option::is_none")] - pub next_page_token: Option, -} - -impl CliPaginatedResponse { - pub fn new(list: Vec, next_page_token: Option) -> Self { - Self { list, next_page_token: next_page_token.map(format_page_token) } - } -} - -pub type CliListPaymentsResponse = CliPaginatedResponse; -pub type CliListForwardedPaymentsResponse = CliPaginatedResponse; - -fn format_page_token(token: PageToken) -> String { - format!("{}:{}", token.token, token.index) -} - /// A denomination-aware amount that stores its value internally in millisatoshis. /// /// Accepts the following formats when parsed from a string: diff --git a/ldk-server-client/Cargo.toml b/ldk-server-client/Cargo.toml index 13916fa3..c1560fad 100644 --- a/ldk-server-client/Cargo.toml +++ b/ldk-server-client/Cargo.toml @@ -3,12 +3,9 @@ name = "ldk-server-client" version = "0.1.0" edition = "2021" -[features] -default = [] -serde = ["ldk-server-protos/serde"] - [dependencies] -ldk-server-protos = { path = "../ldk-server-protos" } +ldk-server-json-models = { path = "../ldk-server-json-models" } reqwest = { version = "0.11.13", default-features = false, features = ["rustls-tls"] } -prost = { version = "0.11.6", default-features = false, features = ["std", "prost-derive"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" bitcoin_hashes = "0.14" diff --git a/ldk-server-client/src/client.rs b/ldk-server-client/src/client.rs index 75459a45..a087950f 100644 --- a/ldk-server-client/src/client.rs +++ b/ldk-server-client/src/client.rs @@ -11,7 +11,7 @@ use std::time::{SystemTime, UNIX_EPOCH}; use bitcoin_hashes::hmac::{Hmac, HmacEngine}; use bitcoin_hashes::{sha256, Hash, HashEngine}; -use ldk_server_protos::api::{ +use ldk_server_json_models::api::{ Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse, Bolt11FailForHashRequest, Bolt11FailForHashResponse, Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse, Bolt11ReceiveRequest, Bolt11ReceiveResponse, Bolt11ReceiveVariableAmountViaJitChannelRequest, @@ -33,7 +33,7 @@ use ldk_server_protos::api::{ SpontaneousSendResponse, UnifiedSendRequest, UnifiedSendResponse, UpdateChannelConfigRequest, UpdateChannelConfigResponse, VerifySignatureRequest, VerifySignatureResponse, }; -use ldk_server_protos::endpoints::{ +use ldk_server_json_models::endpoints::{ BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_PATH, BOLT11_RECEIVE_FOR_HASH_PATH, BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, @@ -45,17 +45,19 @@ use ldk_server_protos::endpoints::{ SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; -use ldk_server_protos::error::{ErrorCode, ErrorResponse}; -use prost::Message; +use ldk_server_json_models::error::{ErrorCode, ErrorResponse}; use reqwest::header::CONTENT_TYPE; use reqwest::{Certificate, Client}; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::error::LdkServerError; use crate::error::LdkServerErrorCode::{ - AuthError, InternalError, InternalServerError, InvalidRequestError, LightningError, + AuthError, InternalError, InternalServerError, InvalidRequestError, JsonParseError, + LightningError, }; -const APPLICATION_OCTET_STREAM: &str = "application/octet-stream"; +const APPLICATION_JSON: &str = "application/json"; /// Client to access a hosted instance of LDK Server. /// @@ -427,15 +429,17 @@ impl LdkServerClient { self.post_request(&request, &url).await } - async fn post_request( + async fn post_request( &self, request: &Rq, url: &str, ) -> Result { - let request_body = request.encode_to_vec(); + let request_body = serde_json::to_vec(request).map_err(|e| { + LdkServerError::new(JsonParseError, format!("Failed to serialize request: {}", e)) + })?; let auth_header = self.compute_auth_header(&request_body); let response_raw = self .client .post(url) - .header(CONTENT_TYPE, APPLICATION_OCTET_STREAM) + .header(CONTENT_TYPE, APPLICATION_JSON) .header("X-Auth", auth_header) .body(request_body) .send() @@ -450,26 +454,27 @@ impl LdkServerClient { })?; if status.is_success() { - Ok(Rs::decode(&payload[..]).map_err(|e| { + Ok(serde_json::from_slice::(&payload).map_err(|e| { LdkServerError::new( - InternalError, + JsonParseError, format!("Failed to decode success response: {}", e), ) })?) } else { - let error_response = ErrorResponse::decode(&payload[..]).map_err(|e| { - LdkServerError::new( - InternalError, - format!("Failed to decode error response (status {}): {}", status, e), - ) - })?; - - let error_code = match ErrorCode::from_i32(error_response.error_code) { - Some(ErrorCode::InvalidRequestError) => InvalidRequestError, - Some(ErrorCode::AuthError) => AuthError, - Some(ErrorCode::LightningError) => LightningError, - Some(ErrorCode::InternalServerError) => InternalServerError, - Some(ErrorCode::UnknownError) | None => InternalError, + let error_response = + serde_json::from_slice::(&payload).map_err(|e| { + LdkServerError::new( + JsonParseError, + format!("Failed to decode error response (status {}): {}", status, e), + ) + })?; + + let error_code = match error_response.error_code { + ErrorCode::InvalidRequestError => InvalidRequestError, + ErrorCode::AuthError => AuthError, + ErrorCode::LightningError => LightningError, + ErrorCode::InternalServerError => InternalServerError, + ErrorCode::UnknownError => InternalError, }; Err(LdkServerError::new(error_code, error_response.message)) diff --git a/ldk-server-client/src/error.rs b/ldk-server-client/src/error.rs index 67cba37d..06bc867a 100644 --- a/ldk-server-client/src/error.rs +++ b/ldk-server-client/src/error.rs @@ -41,18 +41,21 @@ impl fmt::Display for LdkServerError { /// Defines error codes for categorizing LDK server errors. #[derive(Clone, Debug, PartialEq, Eq)] pub enum LdkServerErrorCode { - /// Please refer to [`ldk_server_protos::error::ErrorCode::InvalidRequestError`]. + /// Please refer to [`ldk_server_json_models::error::ErrorCode::InvalidRequestError`]. InvalidRequestError, - /// Please refer to [`ldk_server_protos::error::ErrorCode::AuthError`]. + /// Please refer to [`ldk_server_json_models::error::ErrorCode::AuthError`]. AuthError, - /// Please refer to [`ldk_server_protos::error::ErrorCode::LightningError`]. + /// Please refer to [`ldk_server_json_models::error::ErrorCode::LightningError`]. LightningError, - /// Please refer to [`ldk_server_protos::error::ErrorCode::InternalServerError`]. + /// Please refer to [`ldk_server_json_models::error::ErrorCode::InternalServerError`]. InternalServerError, + /// A JSON serialization or deserialization error occurred. + JsonParseError, + /// There is an unknown error, it could be a client-side bug, unrecognized error-code, network error /// or something else. InternalError, @@ -65,6 +68,7 @@ impl fmt::Display for LdkServerErrorCode { LdkServerErrorCode::AuthError => write!(f, "AuthError"), LdkServerErrorCode::LightningError => write!(f, "LightningError"), LdkServerErrorCode::InternalServerError => write!(f, "InternalServerError"), + LdkServerErrorCode::JsonParseError => write!(f, "JsonParseError"), LdkServerErrorCode::InternalError => write!(f, "InternalError"), } } diff --git a/ldk-server-client/src/lib.rs b/ldk-server-client/src/lib.rs index 098c7087..708f23a3 100644 --- a/ldk-server-client/src/lib.rs +++ b/ldk-server-client/src/lib.rs @@ -20,4 +20,4 @@ pub mod client; pub mod error; /// Request/Response structs required for interacting with the ldk-ldk-server-client. -pub use ldk_server_protos; +pub use ldk_server_json_models; diff --git a/ldk-server-json-models/Cargo.toml b/ldk-server-json-models/Cargo.toml new file mode 100644 index 00000000..1d25ed2e --- /dev/null +++ b/ldk-server-json-models/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "ldk-server-json-models" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +serde_json = "1.0" diff --git a/ldk-server-protos/src/api.rs b/ldk-server-json-models/src/api.rs similarity index 50% rename from ldk-server-protos/src/api.rs rename to ldk-server-json-models/src/api.rs index f72357d3..b3c5f849 100644 --- a/ldk-server-protos/src/api.rs +++ b/ldk-server-json-models/src/api.rs @@ -7,113 +7,85 @@ // You may not use this file except in accordance with one or both of these // licenses. +use serde::{Deserialize, Serialize}; + /// Retrieve the latest node info like `node_id`, `current_best_block` etc. /// See more: /// - /// - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetNodeInfoRequest {} /// The response `content` for the `GetNodeInfo` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetNodeInfoResponse { /// The hex-encoded `node-id` or public key for our own lightning node. - #[prost(string, tag = "1")] - pub node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_id: [u8; 33], /// The best block to which our Lightning wallet is currently synced. - /// - /// Should be always set, will never be `None`. - #[prost(message, optional, tag = "3")] - pub current_best_block: ::core::option::Option, + pub current_best_block: super::types::BestBlock, /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced our Lightning wallet to /// the chain tip. /// /// Will be `None` if the wallet hasn't been synced yet. - #[prost(uint64, optional, tag = "4")] - pub latest_lightning_wallet_sync_timestamp: ::core::option::Option, + pub latest_lightning_wallet_sync_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced our on-chain /// wallet to the chain tip. /// - /// Will be `None` if the wallet hasn’t been synced since the node was initialized. - #[prost(uint64, optional, tag = "5")] - pub latest_onchain_wallet_sync_timestamp: ::core::option::Option, + /// Will be `None` if the wallet hasn't been synced since the node was initialized. + pub latest_onchain_wallet_sync_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last successfully update our fee rate cache. /// - /// Will be `None` if the cache hasn’t been updated since the node was initialized. - #[prost(uint64, optional, tag = "6")] - pub latest_fee_rate_cache_update_timestamp: ::core::option::Option, + /// Will be `None` if the cache hasn't been updated since the node was initialized. + pub latest_fee_rate_cache_update_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when the last rapid gossip sync (RGS) snapshot we /// successfully applied was generated. /// - /// Will be `None` if RGS isn’t configured or the snapshot hasn’t been updated since the node was initialized. - #[prost(uint64, optional, tag = "7")] - pub latest_rgs_snapshot_timestamp: ::core::option::Option, + /// Will be `None` if RGS isn't configured or the snapshot hasn't been updated since the node was initialized. + pub latest_rgs_snapshot_timestamp: Option, /// The timestamp, in seconds since start of the UNIX epoch, when we last broadcasted a node announcement. /// - /// Will be `None` if we have no public channels or we haven’t broadcasted since the node was initialized. - #[prost(uint64, optional, tag = "8")] - pub latest_node_announcement_broadcast_timestamp: ::core::option::Option, + /// Will be `None` if we have no public channels or we haven't broadcasted since the node was initialized. + pub latest_node_announcement_broadcast_timestamp: Option, /// The addresses the node is currently listening on for incoming connections. /// /// Will be empty if the node is not listening on any addresses. - #[prost(string, repeated, tag = "9")] - pub listening_addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + pub listening_addresses: Vec, /// The addresses the node announces to the network. /// /// Will be empty if no announcement addresses are configured. - #[prost(string, repeated, tag = "10")] - pub announcement_addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + pub announcement_addresses: Vec, /// The node alias, if configured. /// /// Will be `None` if no alias is configured. - #[prost(string, optional, tag = "11")] - pub node_alias: ::core::option::Option<::prost::alloc::string::String>, + pub node_alias: Option, /// The node URIs that can be used to connect to this node, in the format `node_id@address`. /// /// These are constructed from the announcement addresses and the node's public key. /// Will be empty if no announcement addresses are configured. - #[prost(string, repeated, tag = "12")] - pub node_uris: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + pub node_uris: Vec, } /// Retrieve a new on-chain funding address. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OnchainReceiveRequest {} /// The response `content` for the `OnchainReceive` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`.. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OnchainReceiveResponse { /// A Bitcoin on-chain address. - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, + pub address: String, } /// Send an on-chain payment to the given address. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OnchainSendRequest { /// The address to send coins to. - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, + pub address: String, /// The amount in satoshis to send. /// While sending the specified amount, we will respect any on-chain reserve we need to keep, /// i.e., won't allow to cut into `total_anchor_channels_reserve_sats`. /// See more: - #[prost(uint64, optional, tag = "2")] - pub amount_sats: ::core::option::Option, + pub amount_sats: Option, /// If set, the amount_sats field should be unset. /// It indicates that node will send full balance to the specified address. /// @@ -121,23 +93,21 @@ pub struct OnchainSendRequest { /// which might be potentially dangerous if you have open Anchor channels for which you can't trust /// the counterparty to spend the Anchor output after channel closure. /// See more: - #[prost(bool, optional, tag = "3")] - pub send_all: ::core::option::Option, + pub send_all: Option, /// If `fee_rate_sat_per_vb` is set it will be used on the resulting transaction. Otherwise we'll retrieve /// a reasonable estimate from BitcoinD. - #[prost(uint64, optional, tag = "4")] - pub fee_rate_sat_per_vb: ::core::option::Option, + pub fee_rate_sat_per_vb: Option, } /// The response `content` for the `OnchainSend` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OnchainSendResponse { /// The transaction ID of the broadcasted transaction. - #[prost(string, tag = "1")] - pub txid: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub txid: [u8; 32], + /// The payment ID for this on-chain payment, usable with `GetPaymentDetails`. + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_id: [u8; 32], } /// Return a BOLT11 payable invoice that can be used to request and receive a payment /// for the given amount, if specified. @@ -145,40 +115,30 @@ pub struct OnchainSendResponse { /// See more: /// - /// - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ReceiveRequest { /// The amount in millisatoshi to send. If unset, a "zero-amount" or variable-amount invoice is returned. - #[prost(uint64, optional, tag = "1")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// An optional description to attach along with the invoice. /// Will be set in the description field of the encoded payment request. - #[prost(message, optional, tag = "2")] - pub description: ::core::option::Option, + pub description: Option, /// Invoice expiry time in seconds. - #[prost(uint32, tag = "3")] pub expiry_secs: u32, } /// The response `content` for the `Bolt11Receive` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ReceiveResponse { /// An invoice for a payment within the Lightning Network. /// With the details of the invoice, the sender has all the data necessary to send a payment /// to the recipient. - #[prost(string, tag = "1")] - pub invoice: ::prost::alloc::string::String, + pub invoice: String, /// The hex-encoded 32-byte payment hash. - #[prost(string, tag = "2")] - pub payment_hash: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], /// The hex-encoded 32-byte payment secret. - #[prost(string, tag = "3")] - pub payment_secret: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_secret: [u8; 32], } /// Return a BOLT11 payable invoice for a given payment hash. /// The inbound payment will NOT be automatically claimed upon arrival. @@ -187,495 +147,325 @@ pub struct Bolt11ReceiveResponse { /// See more: /// - /// - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ReceiveForHashRequest { /// The amount in millisatoshi to receive. If unset, a "zero-amount" or variable-amount invoice is returned. - #[prost(uint64, optional, tag = "1")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// An optional description to attach along with the invoice. /// Will be set in the description field of the encoded payment request. - #[prost(message, optional, tag = "2")] - pub description: ::core::option::Option, + pub description: Option, /// Invoice expiry time in seconds. - #[prost(uint32, tag = "3")] pub expiry_secs: u32, /// The hex-encoded 32-byte payment hash to use for the invoice. - #[prost(string, tag = "4")] - pub payment_hash: ::prost::alloc::string::String, -} -/// The response `content` for the `Bolt11ReceiveForHash` API, when HttpStatusCode is OK (200). -/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11ReceiveForHashResponse { - /// An invoice for a payment within the Lightning Network. - /// With the details of the invoice, the sender has all the data necessary to send a payment - /// to the recipient. - #[prost(string, tag = "1")] - pub invoice: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], } +/// The response for the `Bolt11ReceiveForHash` API. Same shape as [`Bolt11ReceiveResponse`]. +pub type Bolt11ReceiveForHashResponse = Bolt11ReceiveResponse; /// Manually claim a payment for a given payment hash with the corresponding preimage. /// This should be used to claim payments created via `Bolt11ReceiveForHash`. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ClaimForHashRequest { /// The hex-encoded 32-byte payment hash. /// If provided, it will be used to verify that the preimage matches. - #[prost(string, optional, tag = "1")] - pub payment_hash: ::core::option::Option<::prost::alloc::string::String>, + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub payment_hash: Option<[u8; 32]>, /// The amount in millisatoshi that is claimable. /// If not provided, skips amount verification. - #[prost(uint64, optional, tag = "2")] - pub claimable_amount_msat: ::core::option::Option, + pub claimable_amount_msat: Option, /// The hex-encoded 32-byte payment preimage. - #[prost(string, tag = "3")] - pub preimage: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub preimage: [u8; 32], } /// The response `content` for the `Bolt11ClaimForHash` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ClaimForHashResponse {} /// Manually fail a payment for a given payment hash. /// This should be used to reject payments created via `Bolt11ReceiveForHash`. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11FailForHashRequest { /// The hex-encoded 32-byte payment hash. - #[prost(string, tag = "1")] - pub payment_hash: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], } /// The response `content` for the `Bolt11FailForHash` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11FailForHashResponse {} /// Return a BOLT11 payable invoice that can be used to request and receive a payment via an /// LSPS2 just-in-time channel. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ReceiveViaJitChannelRequest { /// The amount in millisatoshi to request. - #[prost(uint64, tag = "1")] pub amount_msat: u64, /// An optional description to attach along with the invoice. /// Will be set in the description field of the encoded payment request. - #[prost(message, optional, tag = "2")] - pub description: ::core::option::Option, + pub description: Option, /// Invoice expiry time in seconds. - #[prost(uint32, tag = "3")] pub expiry_secs: u32, /// Optional upper bound for the total fee an LSP may deduct when opening the JIT channel. - #[prost(uint64, optional, tag = "4")] - pub max_total_lsp_fee_limit_msat: ::core::option::Option, -} -/// The response `content` for the `Bolt11ReceiveViaJitChannel` API, when HttpStatusCode is OK (200). -/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11ReceiveViaJitChannelResponse { - /// An invoice for a payment within the Lightning Network. - #[prost(string, tag = "1")] - pub invoice: ::prost::alloc::string::String, + pub max_total_lsp_fee_limit_msat: Option, } +/// The response for the `Bolt11ReceiveViaJitChannel` API. Same shape as [`Bolt11ReceiveResponse`]. +pub type Bolt11ReceiveViaJitChannelResponse = Bolt11ReceiveResponse; /// Return a variable-amount BOLT11 invoice that can be used to receive a payment via an LSPS2 /// just-in-time channel. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11ReceiveVariableAmountViaJitChannelRequest { /// An optional description to attach along with the invoice. /// Will be set in the description field of the encoded payment request. - #[prost(message, optional, tag = "1")] - pub description: ::core::option::Option, + pub description: Option, /// Invoice expiry time in seconds. - #[prost(uint32, tag = "2")] pub expiry_secs: u32, /// Optional upper bound for the proportional fee, in parts-per-million millisatoshis, that an /// LSP may deduct when opening the JIT channel. - #[prost(uint64, optional, tag = "3")] - pub max_proportional_lsp_fee_limit_ppm_msat: ::core::option::Option, -} -/// The response `content` for the `Bolt11ReceiveVariableAmountViaJitChannel` API, when HttpStatusCode is OK (200). -/// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11ReceiveVariableAmountViaJitChannelResponse { - /// An invoice for a payment within the Lightning Network. - #[prost(string, tag = "1")] - pub invoice: ::prost::alloc::string::String, + pub max_proportional_lsp_fee_limit_ppm_msat: Option, } +/// The response for the `Bolt11ReceiveVariableAmountViaJitChannel` API. Same shape as [`Bolt11ReceiveResponse`]. +pub type Bolt11ReceiveVariableAmountViaJitChannelResponse = Bolt11ReceiveResponse; /// Send a payment for a BOLT11 invoice. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11SendRequest { /// An invoice for a payment within the Lightning Network. - #[prost(string, tag = "1")] - pub invoice: ::prost::alloc::string::String, + pub invoice: String, /// Set this field when paying a so-called "zero-amount" invoice, i.e., an invoice that leaves the /// amount paid to be determined by the user. /// This operation will fail if the amount specified is less than the value required by the given invoice. - #[prost(uint64, optional, tag = "2")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// Configuration options for payment routing and pathfinding. - #[prost(message, optional, tag = "3")] - pub route_parameters: ::core::option::Option, + pub route_parameters: Option, } /// The response `content` for the `Bolt11Send` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt11SendResponse { /// An identifier used to uniquely identify a payment in hex-encoded form. - #[prost(string, tag = "1")] - pub payment_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_id: [u8; 32], } /// Returns a BOLT12 offer for the given amount, if specified. /// /// See more: /// - /// - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt12ReceiveRequest { /// An optional description to attach along with the offer. /// Will be set in the description field of the encoded offer. - #[prost(string, tag = "1")] - pub description: ::prost::alloc::string::String, + pub description: String, /// The amount in millisatoshi to send. If unset, a "zero-amount" or variable-amount offer is returned. - #[prost(uint64, optional, tag = "2")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// Offer expiry time in seconds. - #[prost(uint32, optional, tag = "3")] - pub expiry_secs: ::core::option::Option, + pub expiry_secs: Option, /// If set, it represents the number of items requested, can only be set for fixed-amount offers. - #[prost(uint64, optional, tag = "4")] - pub quantity: ::core::option::Option, + pub quantity: Option, } /// The response `content` for the `Bolt12Receive` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt12ReceiveResponse { /// An offer for a payment within the Lightning Network. /// With the details of the offer, the sender has all the data necessary to send a payment /// to the recipient. - #[prost(string, tag = "1")] - pub offer: ::prost::alloc::string::String, + pub offer: String, /// The hex-encoded offer id. - #[prost(string, tag = "2")] - pub offer_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub offer_id: [u8; 32], } /// Send a payment for a BOLT12 offer. /// See more: /// - /// - -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt12SendRequest { /// An offer for a payment within the Lightning Network. - #[prost(string, tag = "1")] - pub offer: ::prost::alloc::string::String, + pub offer: String, /// Set this field when paying a so-called "zero-amount" offer, i.e., an offer that leaves the /// amount paid to be determined by the user. /// This operation will fail if the amount specified is less than the value required by the given offer. - #[prost(uint64, optional, tag = "2")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// If set, it represents the number of items requested. - #[prost(uint64, optional, tag = "3")] - pub quantity: ::core::option::Option, + pub quantity: Option, /// If set, it will be seen by the recipient and reflected back in the invoice. - #[prost(string, optional, tag = "4")] - pub payer_note: ::core::option::Option<::prost::alloc::string::String>, + pub payer_note: Option, /// Configuration options for payment routing and pathfinding. - #[prost(message, optional, tag = "5")] - pub route_parameters: ::core::option::Option, + pub route_parameters: Option, } /// The response `content` for the `Bolt12Send` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Bolt12SendResponse { /// An identifier used to uniquely identify a payment in hex-encoded form. - #[prost(string, tag = "1")] - pub payment_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_id: [u8; 32], } /// Send a spontaneous payment, also known as "keysend", to a node. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpontaneousSendRequest { /// The amount in millisatoshis to send. - #[prost(uint64, tag = "1")] pub amount_msat: u64, /// The hex-encoded public key of the node to send the payment to. - #[prost(string, tag = "2")] - pub node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_id: [u8; 33], /// Configuration options for payment routing and pathfinding. - #[prost(message, optional, tag = "3")] - pub route_parameters: ::core::option::Option, + pub route_parameters: Option, } /// The response `content` for the `SpontaneousSend` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpontaneousSendResponse { /// An identifier used to uniquely identify a payment in hex-encoded form. - #[prost(string, tag = "1")] - pub payment_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_id: [u8; 32], } /// Creates a new outbound channel to the given remote node. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OpenChannelRequest { /// The hex-encoded public key of the node to open a channel with. - #[prost(string, tag = "1")] - pub node_pubkey: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_pubkey: [u8; 33], /// An address which can be used to connect to a remote peer. /// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port - #[prost(string, tag = "2")] - pub address: ::prost::alloc::string::String, + pub address: String, /// The amount of satoshis the caller is willing to commit to the channel. - #[prost(uint64, tag = "3")] pub channel_amount_sats: u64, /// The amount of satoshis to push to the remote side as part of the initial commitment state. - #[prost(uint64, optional, tag = "4")] - pub push_to_counterparty_msat: ::core::option::Option, + pub push_to_counterparty_msat: Option, /// The channel configuration to be used for opening this channel. If unset, default ChannelConfig is used. - #[prost(message, optional, tag = "5")] - pub channel_config: ::core::option::Option, + pub channel_config: Option, /// Whether the channel should be public. - #[prost(bool, tag = "6")] pub announce_channel: bool, } /// The response `content` for the `OpenChannel` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct OpenChannelResponse { /// The local channel id of the created channel that user can use to refer to channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, } /// Increases the channel balance by the given amount. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpliceInRequest { /// The local `user_channel_id` of the channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, /// The hex-encoded public key of the channel's counterparty node. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], /// The amount of sats to splice into the channel. - #[prost(uint64, tag = "3")] pub splice_amount_sats: u64, } /// The response `content` for the `SpliceIn` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpliceInResponse {} /// Decreases the channel balance by the given amount. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpliceOutRequest { /// The local `user_channel_id` of this channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, /// The hex-encoded public key of the channel's counterparty node. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], /// A Bitcoin on-chain address to send the spliced-out funds. /// /// If not set, an address from the node's on-chain wallet will be used. - #[prost(string, optional, tag = "3")] - pub address: ::core::option::Option<::prost::alloc::string::String>, + pub address: Option, /// The amount of sats to splice out of the channel. - #[prost(uint64, tag = "4")] pub splice_amount_sats: u64, } /// The response `content` for the `SpliceOut` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SpliceOutResponse { /// The Bitcoin on-chain address where the funds will be sent. - #[prost(string, tag = "1")] - pub address: ::prost::alloc::string::String, + pub address: String, } /// Update the config for a previously opened channel. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateChannelConfigRequest { /// The local `user_channel_id` of this channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, /// The hex-encoded public key of the counterparty node to update channel config with. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], /// The updated channel configuration settings for a channel. - #[prost(message, optional, tag = "3")] - pub channel_config: ::core::option::Option, + pub channel_config: Option, } /// The response `content` for the `UpdateChannelConfig` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UpdateChannelConfigResponse {} /// Closes the channel specified by given request. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CloseChannelRequest { /// The local `user_channel_id` of this channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, /// The hex-encoded public key of the node to close a channel with. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], } /// The response `content` for the `CloseChannel` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct CloseChannelResponse {} /// Force closes the channel specified by given request. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ForceCloseChannelRequest { /// The local `user_channel_id` of this channel. - #[prost(string, tag = "1")] - pub user_channel_id: ::prost::alloc::string::String, + pub user_channel_id: String, /// The hex-encoded public key of the node to close a channel with. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], /// The reason for force-closing. - #[prost(string, optional, tag = "3")] - pub force_close_reason: ::core::option::Option<::prost::alloc::string::String>, + pub force_close_reason: Option, } /// The response `content` for the `ForceCloseChannel` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ForceCloseChannelResponse {} /// Returns a list of known channels. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListChannelsRequest {} /// The response `content` for the `ListChannels` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListChannelsResponse { /// List of channels. - #[prost(message, repeated, tag = "1")] - pub channels: ::prost::alloc::vec::Vec, + pub channels: Vec, } /// Returns payment details for a given payment_id. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetPaymentDetailsRequest { /// An identifier used to uniquely identify a payment in hex-encoded form. - #[prost(string, tag = "1")] - pub payment_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_id: [u8; 32], } /// The response `content` for the `GetPaymentDetails` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetPaymentDetailsResponse { /// Represents a payment. /// Will be `None` if payment doesn't exist. - #[prost(message, optional, tag = "1")] - pub payment: ::core::option::Option, + pub payment: Option, } /// Retrieves list of all payments. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListPaymentsRequest { /// `page_token` is a pagination token. /// @@ -683,19 +473,14 @@ pub struct ListPaymentsRequest { /// /// For subsequent pages, use the value that was returned as `next_page_token` in the previous /// page's response. - #[prost(message, optional, tag = "1")] - pub page_token: ::core::option::Option, + pub page_token: Option, } /// The response `content` for the `ListPayments` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListPaymentsResponse { /// List of payments. - #[prost(message, repeated, tag = "1")] - pub payments: ::prost::alloc::vec::Vec, + pub payments: Vec, /// `next_page_token` is a pagination token, used to retrieve the next page of results. /// Use this value to query for next-page of paginated operation, by specifying /// this value as the `page_token` in the next request. @@ -709,15 +494,11 @@ pub struct ListPaymentsResponse { /// /// **Caution**: Clients must not assume a specific number of records to be present in a page for /// paginated response. - #[prost(message, optional, tag = "2")] - pub next_page_token: ::core::option::Option, + pub next_page_token: Option, } /// Retrieves list of all forwarded payments. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListForwardedPaymentsRequest { /// `page_token` is a pagination token. /// @@ -725,19 +506,14 @@ pub struct ListForwardedPaymentsRequest { /// /// For subsequent pages, use the value that was returned as `next_page_token` in the previous /// page's response. - #[prost(message, optional, tag = "1")] - pub page_token: ::core::option::Option, + pub page_token: Option, } /// The response `content` for the `ListForwardedPayments` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListForwardedPaymentsResponse { /// List of forwarded payments. - #[prost(message, repeated, tag = "1")] - pub forwarded_payments: ::prost::alloc::vec::Vec, + pub forwarded_payments: Vec, /// `next_page_token` is a pagination token, used to retrieve the next page of results. /// Use this value to query for next-page of paginated operation, by specifying /// this value as the `page_token` in the next request. @@ -751,109 +527,77 @@ pub struct ListForwardedPaymentsResponse { /// /// **Caution**: Clients must not assume a specific number of records to be present in a page for /// paginated response. - #[prost(message, optional, tag = "2")] - pub next_page_token: ::core::option::Option, + pub next_page_token: Option, } /// Sign a message with the node's secret key. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SignMessageRequest { /// The message to sign, as raw bytes. - #[prost(bytes = "bytes", tag = "1")] - pub message: ::prost::bytes::Bytes, + #[serde(with = "crate::serde_utils::bytes_hex")] + pub message: Vec, } /// The response `content` for the `SignMessage` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct SignMessageResponse { /// The signature of the message, as a zbase32-encoded string. - #[prost(string, tag = "1")] - pub signature: ::prost::alloc::string::String, + pub signature: String, } /// Verify a signature against a message and public key. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VerifySignatureRequest { /// The message that was signed, as raw bytes. - #[prost(bytes = "bytes", tag = "1")] - pub message: ::prost::bytes::Bytes, + #[serde(with = "crate::serde_utils::bytes_hex")] + pub message: Vec, /// The signature to verify, as a zbase32-encoded string. - #[prost(string, tag = "2")] - pub signature: ::prost::alloc::string::String, + pub signature: String, /// The hex-encoded public key of the signer. - #[prost(string, tag = "3")] - pub public_key: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub public_key: [u8; 33], } /// The response `content` for the `VerifySignature` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct VerifySignatureResponse { /// Whether the signature is valid. - #[prost(bool, tag = "1")] pub valid: bool, } /// Export the pathfinding scores used by the router. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ExportPathfindingScoresRequest {} /// The response `content` for the `ExportPathfindingScores` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ExportPathfindingScoresResponse { /// The serialized pathfinding scores data. - #[prost(bytes = "bytes", tag = "1")] - pub scores: ::prost::bytes::Bytes, + #[serde(with = "crate::serde_utils::bytes_hex")] + pub scores: Vec, } /// Retrieves an overview of all known balances. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetBalancesRequest {} /// The response `content` for the `GetBalances` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GetBalancesResponse { /// The total balance of our on-chain wallet. - #[prost(uint64, tag = "1")] pub total_onchain_balance_sats: u64, /// The currently spendable balance of our on-chain wallet. /// /// This includes any sufficiently confirmed funds, minus `total_anchor_channels_reserve_sats`. - #[prost(uint64, tag = "2")] pub spendable_onchain_balance_sats: u64, /// The share of our total balance that we retain as an emergency reserve to (hopefully) be /// able to spend the Anchor outputs when one of our channels is closed. - #[prost(uint64, tag = "3")] pub total_anchor_channels_reserve_sats: u64, /// The total balance that we would be able to claim across all our Lightning channels. /// /// Note this excludes balances that we are unsure if we are able to claim (e.g., as we are /// waiting for a preimage or for a timeout to expire). These balances will however be included /// as `MaybePreimageClaimableHTLC` and `MaybeTimeoutClaimableHTLC` in `lightning_balances`. - #[prost(uint64, tag = "4")] pub total_lightning_balance_sats: u64, /// A detailed list of all known Lightning balances that would be claimable on channel closure. /// @@ -861,8 +605,7 @@ pub struct GetBalancesResponse { /// restrictions apply. Please refer to `Channel::outbound_capacity_msat` and /// Channel::next_outbound_htlc_limit_msat as returned by `ListChannels` /// for a better approximation of the spendable amounts. - #[prost(message, repeated, tag = "5")] - pub lightning_balances: ::prost::alloc::vec::Vec, + pub lightning_balances: Vec, /// A detailed list of balances currently being swept from the Lightning to the on-chain /// wallet. /// @@ -871,129 +614,84 @@ pub struct GetBalancesResponse { /// /// Note that, depending on the sync status of the wallets, swept balances listed here might or /// might not already be accounted for in `total_onchain_balance_sats`. - #[prost(message, repeated, tag = "6")] - pub pending_balances_from_channel_closures: - ::prost::alloc::vec::Vec, + pub pending_balances_from_channel_closures: Vec, } /// Connect to a peer on the Lightning Network. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConnectPeerRequest { /// The hex-encoded public key of the node to connect to. - #[prost(string, tag = "1")] - pub node_pubkey: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_pubkey: [u8; 33], /// An address which can be used to connect to a remote peer. /// It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port - #[prost(string, tag = "2")] - pub address: ::prost::alloc::string::String, + pub address: String, /// Whether to persist the peer connection, i.e., whether the peer will be re-connected on /// restart. - #[prost(bool, tag = "3")] pub persist: bool, } /// The response `content` for the `ConnectPeer` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConnectPeerResponse {} /// Disconnect from a peer and remove it from the peer store. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DisconnectPeerRequest { /// The hex-encoded public key of the node to disconnect from. - #[prost(string, tag = "1")] - pub node_pubkey: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_pubkey: [u8; 33], } /// The response `content` for the `DisconnectPeer` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct DisconnectPeerResponse {} /// Returns a list of peers. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListPeersRequest {} /// The response `content` for the `ListPeers` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ListPeersResponse { /// List of peers. - #[prost(message, repeated, tag = "1")] - pub peers: ::prost::alloc::vec::Vec, + pub peers: Vec, } /// Returns a list of all known short channel IDs in the network graph. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphListChannelsRequest {} /// The response `content` for the `GraphListChannels` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphListChannelsResponse { /// List of short channel IDs known to the network graph. - #[prost(uint64, repeated, tag = "1")] - pub short_channel_ids: ::prost::alloc::vec::Vec, + pub short_channel_ids: Vec, } /// Returns information on a channel with the given short channel ID from the network graph. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphGetChannelRequest { /// The short channel ID to look up. - #[prost(uint64, tag = "1")] pub short_channel_id: u64, } /// The response `content` for the `GraphGetChannel` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphGetChannelResponse { /// The channel information. - #[prost(message, optional, tag = "1")] - pub channel: ::core::option::Option, + pub channel: Option, } /// Returns a list of all known node IDs in the network graph. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphListNodesRequest {} /// The response `content` for the `GraphListNodes` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphListNodesResponse { /// List of hex-encoded node IDs known to the network graph. - #[prost(string, repeated, tag = "1")] - pub node_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + pub node_ids: Vec, } /// Send a payment given a BIP 21 URI or BIP 353 Human-Readable Name. /// @@ -1001,69 +699,54 @@ pub struct GraphListNodesResponse { /// has an offer and/or invoice, it will try to pay the offer first followed by the invoice. /// If they both fail, the on-chain payment will be paid. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct UnifiedSendRequest { /// A BIP 21 URI or BIP 353 Human-Readable Name to pay. - #[prost(string, tag = "1")] - pub uri: ::prost::alloc::string::String, + pub uri: String, /// The amount in millisatoshis to send. Required for "zero-amount" or variable-amount URIs. - #[prost(uint64, optional, tag = "2")] - pub amount_msat: ::core::option::Option, + pub amount_msat: Option, /// Configuration options for payment routing and pathfinding. - #[prost(message, optional, tag = "3")] - pub route_parameters: ::core::option::Option, + pub route_parameters: Option, } /// The response `content` for the `UnifiedSend` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct UnifiedSendResponse { - #[prost(oneof = "unified_send_response::PaymentResult", tags = "1, 2, 3")] - #[cfg_attr(feature = "serde", serde(flatten))] - pub payment_result: ::core::option::Option, -} -/// Nested message and enum types in `UnifiedSendResponse`. -pub mod unified_send_response { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum PaymentResult { - /// An on-chain payment was made. Contains the transaction ID. - #[prost(string, tag = "1")] - Txid(::prost::alloc::string::String), - /// A BOLT11 payment was made. Contains the payment ID in hex-encoded form. - #[prost(string, tag = "2")] - Bolt11PaymentId(::prost::alloc::string::String), - /// A BOLT12 payment was made. Contains the payment ID in hex-encoded form. - #[prost(string, tag = "3")] - Bolt12PaymentId(::prost::alloc::string::String), - } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum UnifiedSendResponse { + /// An on-chain payment was made. + Onchain { + /// The transaction ID of the broadcasted transaction. + #[serde(with = "crate::serde_utils::hex_32")] + txid: [u8; 32], + /// The payment ID for this on-chain payment, usable with `GetPaymentDetails`. + #[serde(with = "crate::serde_utils::hex_32")] + payment_id: [u8; 32], + }, + /// A BOLT11 payment was made. + Bolt11 { + /// An identifier used to uniquely identify a payment in hex-encoded form. + #[serde(with = "crate::serde_utils::hex_32")] + payment_id: [u8; 32], + }, + /// A BOLT12 payment was made. + Bolt12 { + /// An identifier used to uniquely identify a payment in hex-encoded form. + #[serde(with = "crate::serde_utils::hex_32")] + payment_id: [u8; 32], + }, } /// Returns information on a node with the given ID from the network graph. /// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphGetNodeRequest { /// The hex-encoded node ID to look up. - #[prost(string, tag = "1")] - pub node_id: ::prost::alloc::string::String, + #[serde(with = "crate::serde_utils::hex_33")] + pub node_id: [u8; 33], } /// The response `content` for the `GraphGetNode` API, when HttpStatusCode is OK (200). /// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct GraphGetNodeResponse { /// The node information. - #[prost(message, optional, tag = "1")] - pub node: ::core::option::Option, + pub node: Option, } diff --git a/ldk-server-protos/src/endpoints.rs b/ldk-server-json-models/src/endpoints.rs similarity index 100% rename from ldk-server-protos/src/endpoints.rs rename to ldk-server-json-models/src/endpoints.rs diff --git a/ldk-server-json-models/src/error.rs b/ldk-server-json-models/src/error.rs new file mode 100644 index 00000000..44cd4d93 --- /dev/null +++ b/ldk-server-json-models/src/error.rs @@ -0,0 +1,88 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use serde::{Deserialize, Serialize}; + +/// When HttpStatusCode is not ok (200), the response `content` contains a serialized `ErrorResponse` +/// with the relevant ErrorCode and `message` +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ErrorResponse { + /// The error message containing a generic description of the error condition in English. + /// It is intended for a human audience only and should not be parsed to extract any information + /// programmatically. Client-side code may use it for logging only. + pub message: String, + /// The error code uniquely identifying an error condition. + /// It is meant to be read and understood programmatically by code that detects/handles errors by + /// type. + pub error_code: ErrorCode, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ErrorCode { + /// Will never be used as `error_code` by server. + UnknownError, + /// Used in the following cases: + /// - The request was missing a required argument. + /// - The specified argument was invalid, incomplete or in the wrong format. + /// - The request body of api cannot be deserialized. + /// - The request does not follow api contract. + InvalidRequestError, + /// Used when authentication fails or in case of an unauthorized request. + AuthError, + /// Used to represent an error while doing a Lightning operation. + LightningError, + /// Used when an internal server error occurred. The client is probably at no fault. + InternalServerError, +} + +#[cfg(test)] +mod tests { + use super::{ErrorCode, ErrorResponse}; + + #[test] + fn error_response_serializes_error_code_in_snake_case() { + let response = ErrorResponse { + message: "bad request".to_string(), + error_code: ErrorCode::InvalidRequestError, + }; + + let value = serde_json::to_value(response).unwrap(); + + assert_eq!(value["error_code"], "invalid_request_error"); + } + + #[test] + fn error_response_roundtrip() { + let err = ErrorResponse { + message: "something went wrong".into(), + error_code: ErrorCode::InternalServerError, + }; + let json = serde_json::to_value(&err).unwrap(); + assert_eq!(json["error_code"], "internal_server_error"); + let back: ErrorResponse = serde_json::from_value(json).unwrap(); + assert_eq!(back, err); + } + + #[test] + fn error_code_all_variants() { + for (variant, expected) in [ + (ErrorCode::UnknownError, "unknown_error"), + (ErrorCode::InvalidRequestError, "invalid_request_error"), + (ErrorCode::AuthError, "auth_error"), + (ErrorCode::LightningError, "lightning_error"), + (ErrorCode::InternalServerError, "internal_server_error"), + ] { + let json = serde_json::to_value(&variant).unwrap(); + assert_eq!(json, expected); + let back: ErrorCode = serde_json::from_value(json).unwrap(); + assert_eq!(back, variant); + } + } +} diff --git a/ldk-server-json-models/src/events.rs b/ldk-server-json-models/src/events.rs new file mode 100644 index 00000000..713951f7 --- /dev/null +++ b/ldk-server-json-models/src/events.rs @@ -0,0 +1,59 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use serde::{Deserialize, Serialize}; + +/// An event emitted by the LDK Server to notify consumers of payment lifecycle changes. +/// +/// Events are published to the configured messaging system (e.g., RabbitMQ) as JSON. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Event { + PaymentReceived(PaymentReceived), + PaymentSuccessful(PaymentSuccessful), + PaymentFailed(PaymentFailed), + PaymentForwarded(PaymentForwarded), + PaymentClaimable(PaymentClaimable), +} + +/// PaymentReceived indicates a payment has been received. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentReceived { + /// The payment details for the received payment. + pub payment: super::types::Payment, +} + +/// PaymentSuccessful indicates a sent payment was successful. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentSuccessful { + /// The payment details for the successful payment. + pub payment: super::types::Payment, +} + +/// PaymentFailed indicates a sent payment has failed. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentFailed { + /// The payment details for the failed payment. + pub payment: super::types::Payment, +} + +/// PaymentClaimable indicates a payment has arrived and is waiting to be manually claimed or failed. +/// This event is only emitted for payments created via `Bolt11ReceiveForHash`. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentClaimable { + /// The payment details for the claimable payment. + pub payment: super::types::Payment, +} + +/// PaymentForwarded indicates a payment was forwarded through the node. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PaymentForwarded { + /// The forwarded payment details. + pub forwarded_payment: super::types::ForwardedPayment, +} diff --git a/ldk-server-protos/src/lib.rs b/ldk-server-json-models/src/lib.rs similarity index 95% rename from ldk-server-protos/src/lib.rs rename to ldk-server-json-models/src/lib.rs index f76f2f73..9b8bed3d 100644 --- a/ldk-server-protos/src/lib.rs +++ b/ldk-server-json-models/src/lib.rs @@ -11,6 +11,5 @@ pub mod api; pub mod endpoints; pub mod error; pub mod events; -#[cfg(feature = "serde")] pub mod serde_utils; pub mod types; diff --git a/ldk-server-json-models/src/serde_utils.rs b/ldk-server-json-models/src/serde_utils.rs new file mode 100644 index 00000000..47defcf8 --- /dev/null +++ b/ldk-server-json-models/src/serde_utils.rs @@ -0,0 +1,139 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Custom serde serializers/deserializers for byte fields. +//! +//! These are used via `#[serde(with = "...")]` attributes on fields to produce +//! human-readable hex output for byte data. + +use std::fmt::Write; + +use serde::{Deserialize, Deserializer, Serializer}; + +/// Module for serializing/deserializing `Vec` as a hex string. +pub mod bytes_hex { + use super::*; + + pub fn serialize(value: &[u8], serializer: S) -> Result + where + S: Serializer, + { + let hex = value.iter().fold(String::with_capacity(value.len() * 2), |mut acc, b| { + let _ = write!(acc, "{b:02x}"); + acc + }); + serializer.serialize_str(&hex) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + hex_to_bytes(&hex).map_err(serde::de::Error::custom) + } +} + +/// Module for serializing/deserializing `[u8; 32]` as a hex string. +pub mod hex_32 { + use super::*; + + pub fn serialize(value: &[u8; 32], serializer: S) -> Result + where + S: Serializer, + { + let hex = value.iter().fold(String::with_capacity(64), |mut acc, b| { + let _ = write!(acc, "{b:02x}"); + acc + }); + serializer.serialize_str(&hex) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + hex_to_fixed::<32>(&hex).map_err(serde::de::Error::custom) + } +} + +/// Module for serializing/deserializing `Option<[u8; 32]>` as a hex string (or null). +pub mod opt_hex_32 { + use super::*; + + pub fn serialize(value: &Option<[u8; 32]>, serializer: S) -> Result + where + S: Serializer, + { + match value { + Some(bytes) => { + let hex = bytes.iter().fold(String::with_capacity(64), |mut acc, b| { + let _ = write!(acc, "{b:02x}"); + acc + }); + serializer.serialize_some(&hex) + }, + None => serializer.serialize_none(), + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let opt: Option = Option::deserialize(deserializer)?; + match opt { + Some(hex) => { + let bytes = hex_to_fixed::<32>(&hex).map_err(serde::de::Error::custom)?; + Ok(Some(bytes)) + }, + None => Ok(None), + } + } +} + +fn hex_to_bytes(hex: &str) -> Result, String> { + if hex.len() % 2 != 0 { + return Err("Hex string must have even length".to_string()); + } + (0..hex.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).map_err(|e| e.to_string())) + .collect() +} + +/// Module for serializing/deserializing `[u8; 33]` as a hex string. +pub mod hex_33 { + use super::*; + + pub fn serialize(value: &[u8; 33], serializer: S) -> Result + where + S: Serializer, + { + let hex = value.iter().fold(String::with_capacity(66), |mut acc, b| { + let _ = write!(acc, "{b:02x}"); + acc + }); + serializer.serialize_str(&hex) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 33], D::Error> + where + D: Deserializer<'de>, + { + let hex = String::deserialize(deserializer)?; + hex_to_fixed::<33>(&hex).map_err(serde::de::Error::custom) + } +} + +fn hex_to_fixed(hex: &str) -> Result<[u8; N], String> { + let bytes = hex_to_bytes(hex)?; + bytes.try_into().map_err(|v: Vec| format!("expected {} bytes, got {}", N, v.len())) +} diff --git a/ldk-server-json-models/src/types.rs b/ldk-server-json-models/src/types.rs new file mode 100644 index 00000000..5d8ddabd --- /dev/null +++ b/ldk-server-json-models/src/types.rs @@ -0,0 +1,750 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +use serde::{Deserialize, Serialize}; + +/// Represents a payment. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Payment { + /// An identifier used to uniquely identify a payment in hex-encoded form. + #[serde(with = "crate::serde_utils::hex_32")] + pub id: [u8; 32], + /// The kind of the payment. + pub kind: PaymentKind, + /// The amount transferred. + pub amount_msat: Option, + /// The fees that were paid for this payment. + /// + /// For Lightning payments, this will only be updated for outbound payments once they + /// succeeded. + pub fee_paid_msat: Option, + /// The direction of the payment. + pub direction: PaymentDirection, + /// The status of the payment. + pub status: PaymentStatus, + /// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. + pub latest_update_timestamp: u64, +} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PaymentKind { + Onchain(Onchain), + Bolt11(Bolt11), + Bolt11Jit(Bolt11Jit), + Bolt12Offer(Bolt12Offer), + Bolt12Refund(Bolt12Refund), + Spontaneous(Spontaneous), +} +/// Represents an on-chain payment. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Onchain { + /// The transaction identifier of this payment. + #[serde(with = "crate::serde_utils::hex_32")] + pub txid: [u8; 32], + /// The confirmation status of this payment. + pub status: ConfirmationStatus, +} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ConfirmationStatus { + Confirmed(Confirmed), + Unconfirmed(Unconfirmed), +} +/// The on-chain transaction is confirmed in the best chain. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Confirmed { + /// The hex representation of hash of the block in which the transaction was confirmed. + #[serde(with = "crate::serde_utils::hex_32")] + pub block_hash: [u8; 32], + /// The height under which the block was confirmed. + pub height: u32, + /// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. + pub timestamp: u64, +} +/// The on-chain transaction is unconfirmed. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Unconfirmed {} +/// Represents a BOLT 11 payment. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Bolt11 { + /// The payment hash, i.e., the hash of the preimage. + #[serde(with = "crate::serde_utils::hex_32")] + pub hash: [u8; 32], + /// The pre-image used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub preimage: Option<[u8; 32]>, + /// The secret used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub secret: Option<[u8; 32]>, +} +/// Represents a BOLT 11 payment intended to open an LSPS 2 just-in-time channel. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Bolt11Jit { + /// The payment hash, i.e., the hash of the preimage. + #[serde(with = "crate::serde_utils::hex_32")] + pub hash: [u8; 32], + /// The pre-image used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub preimage: Option<[u8; 32]>, + /// The secret used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub secret: Option<[u8; 32]>, + /// Limits applying to how much fee we allow an LSP to deduct from the payment amount. + /// + /// Allowing them to deduct this fee from the first inbound payment will pay for the LSP's channel opening fees. + /// + /// See \[`LdkChannelConfig::accept_underpaying_htlcs`\]() + /// for more information. + pub lsp_fee_limits: Option, + /// The value, in thousands of a satoshi, that was deducted from this payment as an extra + /// fee taken by our channel counterparty. + /// + /// Will only be `Some` once we received the payment. + pub counterparty_skimmed_fee_msat: Option, +} +/// Represents a BOLT 12 'offer' payment, i.e., a payment for an Offer. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Bolt12Offer { + /// The payment hash, i.e., the hash of the preimage. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub hash: Option<[u8; 32]>, + /// The pre-image used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub preimage: Option<[u8; 32]>, + /// The secret used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub secret: Option<[u8; 32]>, + /// The hex-encoded ID of the offer this payment is for. + #[serde(with = "crate::serde_utils::hex_32")] + pub offer_id: [u8; 32], + /// The payer's note for the payment. + /// Truncated to \[PAYER_NOTE_LIMIT\](). + /// + /// **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, + /// all non-printable characters will be sanitized and replaced with safe characters. + pub payer_note: Option, + /// The quantity of an item requested in the offer. + pub quantity: Option, +} +/// Represents a BOLT 12 'refund' payment, i.e., a payment for a Refund. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Bolt12Refund { + /// The payment hash, i.e., the hash of the preimage. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub hash: Option<[u8; 32]>, + /// The pre-image used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub preimage: Option<[u8; 32]>, + /// The secret used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub secret: Option<[u8; 32]>, + /// The payer's note for the payment. + /// Truncated to \[PAYER_NOTE_LIMIT\](). + /// + /// **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, + /// all non-printable characters will be sanitized and replaced with safe characters. + pub payer_note: Option, + /// The quantity of an item requested in the offer. + pub quantity: Option, +} +/// Represents a spontaneous ("keysend") payment. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Spontaneous { + /// The payment hash, i.e., the hash of the preimage. + #[serde(with = "crate::serde_utils::hex_32")] + pub hash: [u8; 32], + /// The pre-image used by the payment. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub preimage: Option<[u8; 32]>, +} +/// Limits applying to how much fee we allow an LSP to deduct from the payment amount. +/// See \[`LdkChannelConfig::accept_underpaying_htlcs`\] for more information. +/// +/// \[`LdkChannelConfig::accept_underpaying_htlcs`\]: lightning::util::config::ChannelConfig::accept_underpaying_htlcs +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct LspFeeLimits { + /// The maximal total amount we allow any configured LSP withhold from us when forwarding the + /// payment. + pub max_total_opening_fee_msat: Option, + /// The maximal proportional fee, in parts-per-million millisatoshi, we allow any configured + /// LSP withhold from us when forwarding the payment. + pub max_proportional_opening_fee_ppm_msat: Option, +} +/// A forwarded payment through our node. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ForwardedPayment { + /// The channel id of the incoming channel between the previous node and us. + #[serde(with = "crate::serde_utils::hex_32")] + pub prev_channel_id: [u8; 32], + /// The channel id of the outgoing channel between the next node and us. + #[serde(with = "crate::serde_utils::hex_32")] + pub next_channel_id: [u8; 32], + /// The `user_channel_id` of the incoming channel between the previous node and us. + pub prev_user_channel_id: String, + /// The node id of the previous node. + #[serde(with = "crate::serde_utils::hex_33")] + pub prev_node_id: [u8; 33], + /// The node id of the next node. + #[serde(with = "crate::serde_utils::hex_33")] + pub next_node_id: [u8; 33], + /// The `user_channel_id` of the outgoing channel between the next node and us. + /// This will be `None` if the payment was settled via an on-chain transaction. + /// See the caveat described for the `total_fee_earned_msat` field. + pub next_user_channel_id: Option, + /// The total fee, in milli-satoshis, which was earned as a result of the payment. + /// + /// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC was pending, the amount the + /// next hop claimed will have been rounded down to the nearest whole satoshi. Thus, the fee calculated here may be + /// higher than expected as we still claimed the full value in millisatoshis from the source. + /// In this case, `claim_from_onchain_tx` will be set. + /// + /// If the channel which sent us the payment has been force-closed, we will claim the funds via an on-chain transaction. + /// In that case we do not yet know the on-chain transaction fees which we will spend and will instead set this to `None`. + pub total_fee_earned_msat: Option, + /// The share of the total fee, in milli-satoshis, which was withheld in addition to the forwarding fee. + /// This will only be set if we forwarded an intercepted HTLC with less than the expected amount. This means our + /// counterparty accepted to receive less than the invoice amount. + /// + /// The caveat described above the `total_fee_earned_msat` field applies here as well. + pub skimmed_fee_msat: Option, + /// If this is true, the forwarded HTLC was claimed by our counterparty via an on-chain transaction. + pub claim_from_onchain_tx: bool, + /// The final amount forwarded, in milli-satoshis, after the fee is deducted. + /// + /// The caveat described above the `total_fee_earned_msat` field applies here as well. + pub outbound_amount_forwarded_msat: Option, +} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Channel { + /// The channel ID (prior to funding transaction generation, this is a random 32-byte + /// identifier, afterwards this is the transaction ID of the funding transaction XOR the + /// funding transaction output). + /// + /// Note that this means this value is *not* persistent - it can change once during the + /// lifetime of the channel. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The node ID of our the channel's remote counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The channel's funding transaction output, if we've negotiated the funding transaction with + /// our counterparty already. + pub funding_txo: Option, + /// The hex-encoded local `user_channel_id` of this channel. + pub user_channel_id: String, + /// The value, in satoshis, that must always be held as a reserve in the channel for us. This + /// value ensures that if we broadcast a revoked state, our counterparty can punish us by + /// claiming at least this value on chain. + /// + /// This value is not included in \[`outbound_capacity_msat`\] as it can never be spent. + /// + /// This value will be `None` for outbound channels until the counterparty accepts the channel. + pub unspendable_punishment_reserve: Option, + /// The value, in satoshis, of this channel as it appears in the funding output. + pub channel_value_sats: u64, + /// The currently negotiated fee rate denominated in satoshi per 1000 weight units, + /// which is applied to commitment and HTLC transactions. + pub feerate_sat_per_1000_weight: u32, + /// The available outbound capacity for sending HTLCs to the remote peer. + /// + /// The amount does not include any pending HTLCs which are not yet resolved (and, thus, whose + /// balance is not available for inclusion in new outbound HTLCs). This further does not include + /// any pending outgoing HTLCs which are awaiting some other resolution to be sent. + pub outbound_capacity_msat: u64, + /// The available outbound capacity for sending HTLCs to the remote peer. + /// + /// The amount does not include any pending HTLCs which are not yet resolved + /// (and, thus, whose balance is not available for inclusion in new inbound HTLCs). This further + /// does not include any pending outgoing HTLCs which are awaiting some other resolution to be + /// sent. + pub inbound_capacity_msat: u64, + /// The number of required confirmations on the funding transactions before the funding is + /// considered "locked". The amount is selected by the channel fundee. + /// + /// The value will be `None` for outbound channels until the counterparty accepts the channel. + pub confirmations_required: Option, + /// The current number of confirmations on the funding transaction. + pub confirmations: Option, + /// Is `true` if the channel was initiated (and therefore funded) by us. + pub is_outbound: bool, + /// Is `true` if both parties have exchanged `channel_ready` messages, and the channel is + /// not currently being shut down. Both parties exchange `channel_ready` messages upon + /// independently verifying that the required confirmations count provided by + /// `confirmations_required` has been reached. + pub is_channel_ready: bool, + /// Is `true` if the channel (a) `channel_ready` messages have been exchanged, (b) the + /// peer is connected, and (c) the channel is not currently negotiating shutdown. + /// + /// This is a strict superset of `is_channel_ready`. + pub is_usable: bool, + /// Is `true` if this channel is (or will be) publicly-announced + pub is_announced: bool, + /// Set of configurable parameters set by self that affect channel operation. + pub channel_config: Option, + /// The available outbound capacity for sending a single HTLC to the remote peer. This is + /// similar to `outbound_capacity_msat` but it may be further restricted by + /// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us + /// to use a limit as close as possible to the HTLC limit we can currently send. + pub next_outbound_htlc_limit_msat: u64, + /// The minimum value for sending a single HTLC to the remote peer. This is the equivalent of + /// `next_outbound_htlc_limit_msat` but represents a lower-bound, rather than + /// an upper-bound. This is intended for use when routing, allowing us to ensure we pick a + /// route which is valid. + pub next_outbound_htlc_minimum_msat: u64, + /// The number of blocks (after our commitment transaction confirms) that we will need to wait + /// until we can claim our funds after we force-close the channel. During this time our + /// counterparty is allowed to punish us if we broadcasted a stale state. If our counterparty + /// force-closes the channel and broadcasts a commitment transaction we do not have to wait any + /// time to claim our non-HTLC-encumbered funds. + /// + /// This value will be `None` for outbound channels until the counterparty accepts the channel. + pub force_close_spend_delay: Option, + /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. + /// + /// This field is only `None` before we have received either the `OpenChannel` or + /// `AcceptChannel` message from the remote peer. + pub counterparty_outbound_htlc_minimum_msat: Option, + /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel. + pub counterparty_outbound_htlc_maximum_msat: Option, + /// The value, in satoshis, that must always be held in the channel for our counterparty. This + /// value ensures that if our counterparty broadcasts a revoked state, we can punish them by + /// claiming at least this value on chain. + /// + /// This value is not included in `inbound_capacity_msat` as it can never be spent. + pub counterparty_unspendable_punishment_reserve: u64, + /// Base routing fee in millisatoshis. + pub counterparty_forwarding_info_fee_base_msat: Option, + /// Proportional fee, in millionths of a satoshi the channel will charge per transferred satoshi. + pub counterparty_forwarding_info_fee_proportional_millionths: Option, + /// The minimum difference in CLTV expiry between an ingoing HTLC and its outgoing counterpart, + /// such that the outgoing HTLC is forwardable to this counterparty. + pub counterparty_forwarding_info_cltv_expiry_delta: Option, +} +/// ChannelConfig represents the configuration settings for a channel in a Lightning Network node. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChannelConfig { + /// Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound + /// over the channel. + /// See more: + pub forwarding_fee_proportional_millionths: Option, + /// Amount (in milli-satoshi) charged for payments forwarded outbound over the channel, + /// in excess of forwarding_fee_proportional_millionths. + /// See more: + pub forwarding_fee_base_msat: Option, + /// The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded + /// over the channel this config applies to. + /// See more: + pub cltv_expiry_delta: Option, + /// The maximum additional fee we're willing to pay to avoid waiting for the counterparty's + /// to_self_delay to reclaim funds. + /// See more: + pub force_close_avoidance_max_fee_satoshis: Option, + /// If set, allows this channel's counterparty to skim an additional fee off this node's + /// inbound HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users. + /// See more: + pub accept_underpaying_htlcs: Option, + /// Limit our total exposure to potential loss to on-chain fees on close, including + /// in-flight HTLCs which are burned to fees as they are too small to claim on-chain + /// and fees on commitment transaction(s) broadcasted by our counterparty in excess of + /// our own fee estimate. + /// See more: + pub max_dust_htlc_exposure: Option, +} +/// Limit our total exposure to potential loss to on-chain fees on close, including +/// in-flight HTLCs which are burned to fees as they are too small to claim on-chain +/// and fees on commitment transaction(s) broadcasted by our counterparty in excess of +/// our own fee estimate. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum MaxDustHtlcExposure { + /// This sets a fixed limit on the total dust exposure in millisatoshis. + /// See more: + FixedLimitMsat(u64), + /// This sets a multiplier on the ConfirmationTarget::OnChainSweep feerate (in sats/KW) to determine the maximum allowed dust exposure. + /// See more: + FeeRateMultiplier(u64), +} +/// Represent a transaction outpoint. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct OutPoint { + /// The referenced transaction's txid. + #[serde(with = "crate::serde_utils::hex_32")] + pub txid: [u8; 32], + /// The index of the referenced output in its transaction's vout. + pub vout: u32, +} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BestBlock { + /// The block's hash + #[serde(with = "crate::serde_utils::hex_32")] + pub block_hash: [u8; 32], + /// The height at which the block was confirmed. + pub height: u32, +} +/// Details about the status of a known Lightning balance. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum LightningBalance { + ClaimableOnChannelClose(ClaimableOnChannelClose), + ClaimableAwaitingConfirmations(ClaimableAwaitingConfirmations), + ContentiousClaimable(ContentiousClaimable), + MaybeTimeoutClaimableHtlc(MaybeTimeoutClaimableHtlc), + MaybePreimageClaimableHtlc(MaybePreimageClaimableHtlc), + CounterpartyRevokedOutputClaimable(CounterpartyRevokedOutputClaimable), +} +/// The channel is not yet closed (or the commitment or closing transaction has not yet appeared in a block). +/// The given balance is claimable (less on-chain fees) if the channel is force-closed now. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ClaimableOnChannelClose { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount available to claim, in satoshis, excluding the on-chain fees which will be required to do so. + pub amount_satoshis: u64, + /// The transaction fee we pay for the closing commitment transaction. + /// This amount is not included in the `amount_satoshis` value. + /// + /// Note that if this channel is inbound (and thus our counterparty pays the commitment transaction fee) this value + /// will be zero. + pub transaction_fee_satoshis: u64, + /// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to + /// a payment which was sent by us. This is the sum of the millisatoshis part of all HTLCs which are otherwise + /// represented by `LightningBalance::MaybeTimeoutClaimableHTLC` with their + /// `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` flag set, as well as any dust HTLCs which would + /// otherwise be represented the same. + /// + /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. + pub outbound_payment_htlc_rounded_msat: u64, + /// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to + /// a forwarded HTLC. This is the sum of the millisatoshis part of all HTLCs which are otherwise represented by + /// `LightningBalance::MaybeTimeoutClaimableHTLC` with their `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` + /// flag not set, as well as any dust HTLCs which would otherwise be represented the same. + /// + /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. + pub outbound_forwarded_htlc_rounded_msat: u64, + /// The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we know + /// the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by + /// `LightningBalance::ContentiousClaimable` on channel close, but whose current value is included in `amount_satoshis`, + /// as well as any dust HTLCs which would otherwise be represented the same. + /// + /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. + pub inbound_claiming_htlc_rounded_msat: u64, + /// The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we do + /// not know the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by + /// `LightningBalance::MaybePreimageClaimableHTLC` on channel close, as well as any dust HTLCs which would otherwise be + /// represented the same. + /// + /// This amount (rounded up to a whole satoshi value) will not be included in the counterparty's `amount_satoshis`. + pub inbound_htlc_rounded_msat: u64, +} +/// The channel has been closed, and the given balance is ours but awaiting confirmations until we consider it spendable. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ClaimableAwaitingConfirmations { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount available to claim, in satoshis, possibly excluding the on-chain fees which were spent in broadcasting + /// the transaction. + pub amount_satoshis: u64, + /// The height at which we start tracking it as `SpendableOutput`. + pub confirmation_height: u32, + /// Whether this balance is a result of cooperative close, a force-close, or an HTLC. + pub source: BalanceSource, +} +/// The channel has been closed, and the given balance should be ours but awaiting spending transaction confirmation. +/// If the spending transaction does not confirm in time, it is possible our counterparty can take the funds by +/// broadcasting an HTLC timeout on-chain. +/// +/// Once the spending transaction confirms, before it has reached enough confirmations to be considered safe from chain +/// reorganizations, the balance will instead be provided via `LightningBalance::ClaimableAwaitingConfirmations`. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ContentiousClaimable { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting + /// the transaction. + pub amount_satoshis: u64, + /// The height at which the counterparty may be able to claim the balance if we have not done so. + pub timeout_height: u32, + /// The payment hash that locks this HTLC. + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], + /// The preimage that can be used to claim this HTLC. + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_preimage: [u8; 32], +} +/// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain fees) if the counterparty +/// does not know the preimage for the HTLCs. These are somewhat likely to be claimed by our counterparty before we do. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MaybeTimeoutClaimableHtlc { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting + /// the transaction. + pub amount_satoshis: u64, + /// The height at which we will be able to claim the balance if our counterparty has not done so. + pub claimable_height: u32, + /// The payment hash whose preimage our counterparty needs to claim this HTLC. + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], + /// Indicates whether this HTLC represents a payment which was sent outbound from us. + pub outbound_payment: bool, +} +/// HTLCs which we received from our counterparty which are claimable with a preimage which we do not currently have. +/// This will only be claimable if we receive the preimage from the node to which we forwarded this HTLC before the +/// timeout. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MaybePreimageClaimableHtlc { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting + /// the transaction. + pub amount_satoshis: u64, + /// The height at which our counterparty will be able to claim the balance if we have not yet received the preimage and + /// claimed it ourselves. + pub expiry_height: u32, + /// The payment hash whose preimage we need to claim this HTLC. + #[serde(with = "crate::serde_utils::hex_32")] + pub payment_hash: [u8; 32], +} +/// The channel has been closed, and our counterparty broadcasted a revoked commitment transaction. +/// +/// Thus, we're able to claim all outputs in the commitment transaction, one of which has the following amount. +/// +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CounterpartyRevokedOutputClaimable { + /// The identifier of the channel this balance belongs to. + #[serde(with = "crate::serde_utils::hex_32")] + pub channel_id: [u8; 32], + /// The identifier of our channel counterparty. + #[serde(with = "crate::serde_utils::hex_33")] + pub counterparty_node_id: [u8; 33], + /// The amount, in satoshis, of the output which we can claim. + pub amount_satoshis: u64, +} +/// Details about the status of a known balance currently being swept to our on-chain wallet. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PendingSweepBalance { + PendingBroadcast(PendingBroadcast), + BroadcastAwaitingConfirmation(BroadcastAwaitingConfirmation), + AwaitingThresholdConfirmations(AwaitingThresholdConfirmations), +} +/// The spendable output is about to be swept, but a spending transaction has yet to be generated and broadcast. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct PendingBroadcast { + /// The identifier of the channel this balance belongs to. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub channel_id: Option<[u8; 32]>, + /// The amount, in satoshis, of the output being swept. + pub amount_satoshis: u64, +} +/// A spending transaction has been generated and broadcast and is awaiting confirmation on-chain. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct BroadcastAwaitingConfirmation { + /// The identifier of the channel this balance belongs to. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub channel_id: Option<[u8; 32]>, + /// The best height when we last broadcast a transaction spending the output being swept. + pub latest_broadcast_height: u32, + /// The identifier of the transaction spending the swept output we last broadcast. + #[serde(with = "crate::serde_utils::hex_32")] + pub latest_spending_txid: [u8; 32], + /// The amount, in satoshis, of the output being swept. + pub amount_satoshis: u64, +} +/// A spending transaction has been confirmed on-chain and is awaiting threshold confirmations. +/// +/// It will be considered irrevocably confirmed after reaching `ANTI_REORG_DELAY`. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AwaitingThresholdConfirmations { + /// The identifier of the channel this balance belongs to. + #[serde(default, with = "crate::serde_utils::opt_hex_32")] + pub channel_id: Option<[u8; 32]>, + /// The identifier of the confirmed transaction spending the swept output. + #[serde(with = "crate::serde_utils::hex_32")] + pub latest_spending_txid: [u8; 32], + /// The hash of the block in which the spending transaction was confirmed. + #[serde(with = "crate::serde_utils::hex_32")] + pub confirmation_hash: [u8; 32], + /// The height at which the spending transaction was confirmed. + pub confirmation_height: u32, + /// The amount, in satoshis, of the output being swept. + pub amount_satoshis: u64, +} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Bolt11InvoiceDescription { + Direct(String), + Hash(String), +} +/// Configuration options for payment routing and pathfinding. +/// See for more details on each field. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct RouteParametersConfig { + /// The maximum total fees, in millisatoshi, that may accrue during route finding. + /// Defaults to 1% of the payment amount + 50 sats + pub max_total_routing_fee_msat: Option, + /// The maximum total CLTV delta we accept for the route. + /// Defaults to 1008. + pub max_total_cltv_expiry_delta: u32, + /// The maximum number of paths that may be used by (MPP) payments. + /// Defaults to 10. + pub max_path_count: u32, + /// Selects the maximum share of a channel's total capacity which will be + /// sent over a channel, as a power of 1/2. + /// Default value: 2 + pub max_channel_saturation_power_of_half: u32, +} +/// Routing fees for a channel as part of the network graph. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GraphRoutingFees { + /// Flat routing fee in millisatoshis. + pub base_msat: u32, + /// Liquidity-based routing fee in millionths of a routed amount. + pub proportional_millionths: u32, +} +/// Details about one direction of a channel in the network graph, +/// as received within a `ChannelUpdate`. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GraphChannelUpdate { + /// When the last update to the channel direction was issued. + /// Value is opaque, as set in the announcement. + pub last_update: u32, + /// Whether the channel can be currently used for payments (in this one direction). + pub enabled: bool, + /// The difference in CLTV values that you must have when routing through this channel. + pub cltv_expiry_delta: u32, + /// The minimum value, which must be relayed to the next hop via the channel. + pub htlc_minimum_msat: u64, + /// The maximum value which may be relayed to the next hop via the channel. + pub htlc_maximum_msat: u64, + /// Fees charged when the channel is used for routing. + pub fees: GraphRoutingFees, +} +/// Details about a channel in the network graph (both directions). +/// Received within a channel announcement. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GraphChannel { + /// Source node of the first direction of the channel (hex-encoded public key). + #[serde(with = "crate::serde_utils::hex_33")] + pub node_one: [u8; 33], + /// Source node of the second direction of the channel (hex-encoded public key). + #[serde(with = "crate::serde_utils::hex_33")] + pub node_two: [u8; 33], + /// The channel capacity as seen on-chain, if chain lookup is available. + pub capacity_sats: Option, + /// Details about the first direction of a channel. + pub one_to_two: Option, + /// Details about the second direction of a channel. + pub two_to_one: Option, +} +/// Information received in the latest node_announcement from this node. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GraphNodeAnnouncement { + /// When the last known update to the node state was issued. + /// Value is opaque, as set in the announcement. + pub last_update: u32, + /// Moniker assigned to the node. + /// May be invalid or malicious (eg control chars), should not be exposed to the user. + pub alias: String, + /// Color assigned to the node as a hex-encoded RGB string, e.g. "ff0000". + pub rgb: String, + /// List of addresses on which this node is reachable. + pub addresses: Vec, +} +/// Details of a known Lightning peer. +/// See more: +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Peer { + /// The hex-encoded node ID of the peer. + #[serde(with = "crate::serde_utils::hex_33")] + pub node_id: [u8; 33], + /// The network address of the peer. + pub address: String, + /// Indicates whether we'll try to reconnect to this peer after restarts. + pub is_persisted: bool, + /// Indicates whether we currently have an active connection with the peer. + pub is_connected: bool, +} +/// Details about a node in the network graph, known from the network announcement. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GraphNode { + /// All valid channels a node has announced. + pub channels: Vec, + /// More information about a node from node_announcement. + /// Optional because we store a node entry after learning about it from + /// a channel announcement, but before receiving a node announcement. + pub announcement_info: Option, +} +/// Represents the direction of a payment. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PaymentDirection { + /// The payment is inbound. + Inbound, + /// The payment is outbound. + Outbound, +} +/// Represents the current status of a payment. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PaymentStatus { + /// The payment is still pending. + Pending, + /// The payment succeeded. + Succeeded, + /// The payment failed. + Failed, +} +/// Indicates whether the balance is derived from a cooperative close, a force-close (for holder or counterparty), +/// or whether it is for an HTLC. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum BalanceSource { + /// The channel was force closed by the holder. + HolderForceClosed, + /// The channel was force closed by the counterparty. + CounterpartyForceClosed, + /// The channel was cooperatively closed. + CoopClose, + /// This balance is the result of an HTLC. + Htlc, +} diff --git a/ldk-server-json-models/tests/serialization.rs b/ldk-server-json-models/tests/serialization.rs new file mode 100644 index 00000000..133fe593 --- /dev/null +++ b/ldk-server-json-models/tests/serialization.rs @@ -0,0 +1,1200 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Serialization round-trip tests for all JSON models. +//! +//! Each test constructs a value, serializes it to JSON, deserializes back, +//! and asserts equality. Key serde attributes (hex encoding, rename_all, +//! tagged enums) are spot-checked against the raw JSON. + +use std::fmt::Debug; + +use ldk_server_json_models::api::*; +use ldk_server_json_models::events::*; +use ldk_server_json_models::types::*; +use serde::de::DeserializeOwned; +use serde::Serialize; + +/// Serialize to JSON and back, asserting round-trip equality. +fn roundtrip(value: &T) -> serde_json::Value { + let json = serde_json::to_value(value).expect("serialize"); + let back: T = serde_json::from_value(json.clone()).expect("deserialize"); + assert_eq!(&back, value, "round-trip mismatch"); + json +} + +// --------------------------------------------------------------------------- +// Test data helpers +// --------------------------------------------------------------------------- + +const HASH_32: [u8; 32] = [ + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, + 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, +]; +const HASH_32_HEX: &str = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; + +const HASH_32_B: [u8; 32] = [0x11; 32]; +const HASH_32_B_HEX: &str = "1111111111111111111111111111111111111111111111111111111111111111"; + +const HASH_32_C: [u8; 32] = [0x22; 32]; + +const PUBKEY_33: [u8; 33] = [ + 0x02, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, + 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, + 0x89, +]; +const PUBKEY_33_HEX: &str = "02abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; + +const PUBKEY_33_B: [u8; 33] = [0x03; 33]; + +fn sample_payment() -> Payment { + Payment { + id: HASH_32, + kind: PaymentKind::Bolt11(Bolt11 { + hash: HASH_32_B, + preimage: Some(HASH_32_C), + secret: None, + }), + amount_msat: Some(100_000), + fee_paid_msat: Some(10), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Succeeded, + latest_update_timestamp: 1_700_000_000, + } +} + +fn sample_forwarded_payment() -> ForwardedPayment { + ForwardedPayment { + prev_channel_id: HASH_32, + next_channel_id: HASH_32_B, + prev_user_channel_id: "abc123".into(), + prev_node_id: PUBKEY_33, + next_node_id: PUBKEY_33_B, + next_user_channel_id: Some("def456".into()), + total_fee_earned_msat: Some(500), + skimmed_fee_msat: None, + claim_from_onchain_tx: false, + outbound_amount_forwarded_msat: Some(99_500), + } +} + +fn sample_channel_config() -> ChannelConfig { + ChannelConfig { + forwarding_fee_proportional_millionths: Some(100), + forwarding_fee_base_msat: Some(1000), + cltv_expiry_delta: Some(144), + force_close_avoidance_max_fee_satoshis: Some(1000), + accept_underpaying_htlcs: Some(false), + max_dust_htlc_exposure: Some(MaxDustHtlcExposure::FixedLimitMsat(5_000_000)), + } +} + +fn sample_channel() -> Channel { + Channel { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + funding_txo: Some(OutPoint { txid: HASH_32_B, vout: 0 }), + user_channel_id: "user123".into(), + unspendable_punishment_reserve: Some(1000), + channel_value_sats: 1_000_000, + feerate_sat_per_1000_weight: 253, + outbound_capacity_msat: 500_000_000, + inbound_capacity_msat: 400_000_000, + confirmations_required: Some(3), + confirmations: Some(6), + is_outbound: true, + is_channel_ready: true, + is_usable: true, + is_announced: false, + channel_config: Some(sample_channel_config()), + next_outbound_htlc_limit_msat: 450_000_000, + next_outbound_htlc_minimum_msat: 1000, + force_close_spend_delay: Some(144), + counterparty_outbound_htlc_minimum_msat: Some(1000), + counterparty_outbound_htlc_maximum_msat: Some(450_000_000), + counterparty_unspendable_punishment_reserve: 1000, + counterparty_forwarding_info_fee_base_msat: Some(1000), + counterparty_forwarding_info_fee_proportional_millionths: Some(100), + counterparty_forwarding_info_cltv_expiry_delta: Some(40), + } +} + +fn sample_route_params() -> RouteParametersConfig { + RouteParametersConfig { + max_total_routing_fee_msat: Some(5000), + max_total_cltv_expiry_delta: 1008, + max_path_count: 10, + max_channel_saturation_power_of_half: 2, + } +} + +// =========================================================================== +// types.rs +// =========================================================================== + +#[test] +fn payment_bolt11_roundtrip() { + let p = sample_payment(); + let json = roundtrip(&p); + assert_eq!(json["id"], HASH_32_HEX); + assert_eq!(json["direction"], "outbound"); + assert_eq!(json["status"], "succeeded"); + assert!(json["kind"]["bolt11"].is_object()); + assert_eq!(json["kind"]["bolt11"]["hash"], HASH_32_B_HEX); +} + +#[test] +fn payment_onchain_confirmed_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Onchain(Onchain { + txid: HASH_32_B, + status: ConfirmationStatus::Confirmed(Confirmed { + block_hash: HASH_32_C, + height: 800_000, + timestamp: 1_700_000_000, + }), + }), + amount_msat: Some(50_000_000), + fee_paid_msat: None, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Succeeded, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["onchain"]["status"]["confirmed"].is_object()); + assert_eq!(json["direction"], "inbound"); +} + +#[test] +fn payment_onchain_unconfirmed_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Onchain(Onchain { + txid: HASH_32_B, + status: ConfirmationStatus::Unconfirmed(Unconfirmed {}), + }), + amount_msat: None, + fee_paid_msat: None, + direction: PaymentDirection::Outbound, + status: PaymentStatus::Pending, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["onchain"]["status"]["unconfirmed"].is_object()); + assert_eq!(json["status"], "pending"); +} + +#[test] +fn payment_bolt11_jit_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Bolt11Jit(Bolt11Jit { + hash: HASH_32_B, + preimage: None, + secret: Some(HASH_32_C), + lsp_fee_limits: Some(LspFeeLimits { + max_total_opening_fee_msat: Some(10_000), + max_proportional_opening_fee_ppm_msat: Some(5000), + }), + counterparty_skimmed_fee_msat: Some(100), + }), + amount_msat: Some(100_000), + fee_paid_msat: None, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Pending, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["bolt11_jit"].is_object()); +} + +#[test] +fn payment_bolt12_offer_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Bolt12Offer(Bolt12Offer { + hash: Some(HASH_32_B), + preimage: None, + secret: None, + offer_id: HASH_32_C, + payer_note: Some("thanks".into()), + quantity: Some(2), + }), + amount_msat: Some(200_000), + fee_paid_msat: Some(20), + direction: PaymentDirection::Outbound, + status: PaymentStatus::Succeeded, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["bolt12_offer"].is_object()); + assert_eq!(json["kind"]["bolt12_offer"]["payer_note"], "thanks"); +} + +#[test] +fn payment_bolt12_refund_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Bolt12Refund(Bolt12Refund { + hash: None, + preimage: None, + secret: None, + payer_note: None, + quantity: None, + }), + amount_msat: None, + fee_paid_msat: None, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Failed, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["bolt12_refund"].is_object()); + assert_eq!(json["status"], "failed"); +} + +#[test] +fn payment_spontaneous_roundtrip() { + let p = Payment { + id: HASH_32, + kind: PaymentKind::Spontaneous(Spontaneous { hash: HASH_32_B, preimage: Some(HASH_32_C) }), + amount_msat: Some(50_000), + fee_paid_msat: None, + direction: PaymentDirection::Inbound, + status: PaymentStatus::Succeeded, + latest_update_timestamp: 1_700_000_000, + }; + let json = roundtrip(&p); + assert!(json["kind"]["spontaneous"].is_object()); +} + +#[test] +fn forwarded_payment_roundtrip() { + let fp = sample_forwarded_payment(); + let json = roundtrip(&fp); + assert_eq!(json["prev_channel_id"], HASH_32_HEX); + assert_eq!(json["prev_node_id"], PUBKEY_33_HEX); + assert_eq!(json["claim_from_onchain_tx"], false); +} + +#[test] +fn channel_roundtrip() { + let ch = sample_channel(); + let json = roundtrip(&ch); + assert_eq!(json["channel_id"], HASH_32_HEX); + assert_eq!(json["counterparty_node_id"], PUBKEY_33_HEX); + assert_eq!(json["is_usable"], true); + assert!(json["funding_txo"].is_object()); + assert!(json["channel_config"].is_object()); +} + +#[test] +fn channel_config_roundtrip() { + let cfg = sample_channel_config(); + let json = roundtrip(&cfg); + assert!(json["max_dust_htlc_exposure"]["fixed_limit_msat"].is_number()); +} + +#[test] +fn max_dust_htlc_exposure_variants() { + let fixed = MaxDustHtlcExposure::FixedLimitMsat(5_000_000); + let json = roundtrip(&fixed); + assert_eq!(json["fixed_limit_msat"], 5_000_000); + + let rate = MaxDustHtlcExposure::FeeRateMultiplier(1000); + let json = roundtrip(&rate); + assert_eq!(json["fee_rate_multiplier"], 1000); +} + +#[test] +fn outpoint_roundtrip() { + let op = OutPoint { txid: HASH_32, vout: 1 }; + let json = roundtrip(&op); + assert_eq!(json["txid"], HASH_32_HEX); + assert_eq!(json["vout"], 1); +} + +#[test] +fn best_block_roundtrip() { + let bb = BestBlock { block_hash: HASH_32, height: 800_000 }; + let json = roundtrip(&bb); + assert_eq!(json["block_hash"], HASH_32_HEX); + assert_eq!(json["height"], 800_000); +} + +#[test] +fn confirmation_status_variants() { + let confirmed = ConfirmationStatus::Confirmed(Confirmed { + block_hash: HASH_32, + height: 800_000, + timestamp: 1_700_000_000, + }); + let json = roundtrip(&confirmed); + assert!(json["confirmed"].is_object()); + + let unconfirmed = ConfirmationStatus::Unconfirmed(Unconfirmed {}); + let json = roundtrip(&unconfirmed); + assert!(json["unconfirmed"].is_object()); +} + +#[test] +fn lightning_balance_claimable_on_channel_close() { + let bal = LightningBalance::ClaimableOnChannelClose(ClaimableOnChannelClose { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 500_000, + transaction_fee_satoshis: 300, + outbound_payment_htlc_rounded_msat: 100, + outbound_forwarded_htlc_rounded_msat: 200, + inbound_claiming_htlc_rounded_msat: 50, + inbound_htlc_rounded_msat: 25, + }); + let json = roundtrip(&bal); + assert!(json["claimable_on_channel_close"].is_object()); + assert_eq!(json["claimable_on_channel_close"]["channel_id"], HASH_32_HEX); +} + +#[test] +fn lightning_balance_claimable_awaiting_confirmations() { + let bal = LightningBalance::ClaimableAwaitingConfirmations(ClaimableAwaitingConfirmations { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 100_000, + confirmation_height: 800_100, + source: BalanceSource::HolderForceClosed, + }); + let json = roundtrip(&bal); + assert!(json["claimable_awaiting_confirmations"].is_object()); + assert_eq!(json["claimable_awaiting_confirmations"]["source"], "holder_force_closed"); +} + +#[test] +fn lightning_balance_contentious_claimable() { + let bal = LightningBalance::ContentiousClaimable(ContentiousClaimable { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 50_000, + timeout_height: 800_200, + payment_hash: HASH_32_B, + payment_preimage: HASH_32_C, + }); + let json = roundtrip(&bal); + assert!(json["contentious_claimable"].is_object()); +} + +#[test] +fn lightning_balance_maybe_timeout_claimable() { + let bal = LightningBalance::MaybeTimeoutClaimableHtlc(MaybeTimeoutClaimableHtlc { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 25_000, + claimable_height: 800_300, + payment_hash: HASH_32_B, + outbound_payment: true, + }); + let json = roundtrip(&bal); + assert!(json["maybe_timeout_claimable_htlc"].is_object()); +} + +#[test] +fn lightning_balance_maybe_preimage_claimable() { + let bal = LightningBalance::MaybePreimageClaimableHtlc(MaybePreimageClaimableHtlc { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 10_000, + expiry_height: 800_400, + payment_hash: HASH_32_B, + }); + let json = roundtrip(&bal); + assert!(json["maybe_preimage_claimable_htlc"].is_object()); +} + +#[test] +fn lightning_balance_counterparty_revoked() { + let bal = + LightningBalance::CounterpartyRevokedOutputClaimable(CounterpartyRevokedOutputClaimable { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 75_000, + }); + let json = roundtrip(&bal); + assert!(json["counterparty_revoked_output_claimable"].is_object()); +} + +#[test] +fn balance_source_variants() { + for (variant, expected) in [ + (BalanceSource::HolderForceClosed, "holder_force_closed"), + (BalanceSource::CounterpartyForceClosed, "counterparty_force_closed"), + (BalanceSource::CoopClose, "coop_close"), + (BalanceSource::Htlc, "htlc"), + ] { + let json = serde_json::to_value(&variant).unwrap(); + assert_eq!(json, expected); + let back: BalanceSource = serde_json::from_value(json).unwrap(); + assert_eq!(back, variant); + } +} + +#[test] +fn pending_sweep_balance_pending_broadcast() { + let bal = PendingSweepBalance::PendingBroadcast(PendingBroadcast { + channel_id: Some(HASH_32), + amount_satoshis: 50_000, + }); + let json = roundtrip(&bal); + assert!(json["pending_broadcast"].is_object()); + assert_eq!(json["pending_broadcast"]["channel_id"], HASH_32_HEX); +} + +#[test] +fn pending_sweep_balance_pending_broadcast_no_channel() { + let bal = PendingSweepBalance::PendingBroadcast(PendingBroadcast { + channel_id: None, + amount_satoshis: 50_000, + }); + let json = roundtrip(&bal); + assert!(json["pending_broadcast"]["channel_id"].is_null()); +} + +#[test] +fn pending_sweep_balance_broadcast_awaiting_confirmation() { + let bal = PendingSweepBalance::BroadcastAwaitingConfirmation(BroadcastAwaitingConfirmation { + channel_id: Some(HASH_32), + latest_broadcast_height: 800_000, + latest_spending_txid: HASH_32_B, + amount_satoshis: 50_000, + }); + let json = roundtrip(&bal); + assert!(json["broadcast_awaiting_confirmation"].is_object()); +} + +#[test] +fn pending_sweep_balance_awaiting_threshold() { + let bal = PendingSweepBalance::AwaitingThresholdConfirmations(AwaitingThresholdConfirmations { + channel_id: None, + latest_spending_txid: HASH_32, + confirmation_hash: HASH_32_B, + confirmation_height: 800_010, + amount_satoshis: 50_000, + }); + let json = roundtrip(&bal); + assert!(json["awaiting_threshold_confirmations"].is_object()); +} + +#[test] +fn bolt11_invoice_description_variants() { + let direct = Bolt11InvoiceDescription::Direct("coffee".into()); + let json = roundtrip(&direct); + assert_eq!(json["direct"], "coffee"); + + let hash = Bolt11InvoiceDescription::Hash("abc123".into()); + let json = roundtrip(&hash); + assert_eq!(json["hash"], "abc123"); +} + +#[test] +fn route_parameters_config_roundtrip() { + let rp = sample_route_params(); + let json = roundtrip(&rp); + assert_eq!(json["max_total_cltv_expiry_delta"], 1008); + assert_eq!(json["max_path_count"], 10); +} + +#[test] +fn graph_routing_fees_roundtrip() { + let fees = GraphRoutingFees { base_msat: 1000, proportional_millionths: 100 }; + roundtrip(&fees); +} + +#[test] +fn graph_channel_update_roundtrip() { + let update = GraphChannelUpdate { + last_update: 1_700_000_000, + enabled: true, + cltv_expiry_delta: 144, + htlc_minimum_msat: 1000, + htlc_maximum_msat: 1_000_000_000, + fees: GraphRoutingFees { base_msat: 1000, proportional_millionths: 100 }, + }; + roundtrip(&update); +} + +#[test] +fn graph_channel_roundtrip() { + let ch = GraphChannel { + node_one: PUBKEY_33, + node_two: PUBKEY_33_B, + capacity_sats: Some(1_000_000), + one_to_two: Some(GraphChannelUpdate { + last_update: 1_700_000_000, + enabled: true, + cltv_expiry_delta: 144, + htlc_minimum_msat: 1000, + htlc_maximum_msat: 1_000_000_000, + fees: GraphRoutingFees { base_msat: 1000, proportional_millionths: 100 }, + }), + two_to_one: None, + }; + let json = roundtrip(&ch); + assert_eq!(json["node_one"], PUBKEY_33_HEX); +} + +#[test] +fn graph_node_announcement_roundtrip() { + let ann = GraphNodeAnnouncement { + last_update: 1_700_000_000, + alias: "my-node".into(), + rgb: "ff6600".into(), + addresses: vec!["127.0.0.1:9735".into()], + }; + roundtrip(&ann); +} + +#[test] +fn graph_node_roundtrip() { + let node = GraphNode { + channels: vec![123456789, 987654321], + announcement_info: Some(GraphNodeAnnouncement { + last_update: 1_700_000_000, + alias: "test-node".into(), + rgb: "aabbcc".into(), + addresses: vec![], + }), + }; + roundtrip(&node); +} + +#[test] +fn peer_roundtrip() { + let peer = Peer { + node_id: PUBKEY_33, + address: "127.0.0.1:9735".into(), + is_persisted: true, + is_connected: true, + }; + let json = roundtrip(&peer); + assert_eq!(json["node_id"], PUBKEY_33_HEX); +} + +#[test] +fn payment_direction_variants() { + for (variant, expected) in + [(PaymentDirection::Inbound, "inbound"), (PaymentDirection::Outbound, "outbound")] + { + let json = serde_json::to_value(&variant).unwrap(); + assert_eq!(json, expected); + let back: PaymentDirection = serde_json::from_value(json).unwrap(); + assert_eq!(back, variant); + } +} + +#[test] +fn payment_status_variants() { + for (variant, expected) in [ + (PaymentStatus::Pending, "pending"), + (PaymentStatus::Succeeded, "succeeded"), + (PaymentStatus::Failed, "failed"), + ] { + let json = serde_json::to_value(&variant).unwrap(); + assert_eq!(json, expected); + let back: PaymentStatus = serde_json::from_value(json).unwrap(); + assert_eq!(back, variant); + } +} + +#[test] +fn lsp_fee_limits_roundtrip() { + let limits = LspFeeLimits { + max_total_opening_fee_msat: Some(10_000), + max_proportional_opening_fee_ppm_msat: None, + }; + roundtrip(&limits); +} + +// =========================================================================== +// events.rs +// =========================================================================== + +#[test] +fn event_payment_received() { + let ev = Event::PaymentReceived(PaymentReceived { payment: sample_payment() }); + let json = roundtrip(&ev); + assert!(json["payment_received"].is_object()); + assert!(json["payment_received"]["payment"].is_object()); +} + +#[test] +fn event_payment_successful() { + let ev = Event::PaymentSuccessful(PaymentSuccessful { payment: sample_payment() }); + let json = roundtrip(&ev); + assert!(json["payment_successful"].is_object()); +} + +#[test] +fn event_payment_failed() { + let ev = Event::PaymentFailed(PaymentFailed { payment: sample_payment() }); + let json = roundtrip(&ev); + assert!(json["payment_failed"].is_object()); +} + +#[test] +fn event_payment_claimable() { + let ev = Event::PaymentClaimable(PaymentClaimable { payment: sample_payment() }); + let json = roundtrip(&ev); + assert!(json["payment_claimable"].is_object()); +} + +#[test] +fn event_payment_forwarded() { + let ev = + Event::PaymentForwarded(PaymentForwarded { forwarded_payment: sample_forwarded_payment() }); + let json = roundtrip(&ev); + assert!(json["payment_forwarded"].is_object()); + assert!(json["payment_forwarded"]["forwarded_payment"].is_object()); +} + +// =========================================================================== +// api.rs +// =========================================================================== + +#[test] +fn get_node_info_roundtrip() { + roundtrip(&GetNodeInfoRequest {}); + let resp = GetNodeInfoResponse { + node_id: PUBKEY_33, + current_best_block: BestBlock { block_hash: HASH_32, height: 800_000 }, + latest_lightning_wallet_sync_timestamp: Some(1_700_000_000), + latest_onchain_wallet_sync_timestamp: Some(1_700_000_000), + latest_fee_rate_cache_update_timestamp: None, + latest_rgs_snapshot_timestamp: None, + latest_node_announcement_broadcast_timestamp: None, + listening_addresses: vec!["0.0.0.0:9735".into()], + announcement_addresses: vec![], + node_alias: Some("my-node".into()), + node_uris: vec![], + }; + let json = roundtrip(&resp); + assert_eq!(json["node_id"], PUBKEY_33_HEX); +} + +#[test] +fn onchain_receive_roundtrip() { + roundtrip(&OnchainReceiveRequest {}); + roundtrip(&OnchainReceiveResponse { address: "bc1qtest".into() }); +} + +#[test] +fn onchain_send_roundtrip() { + let req = OnchainSendRequest { + address: "bc1qtest".into(), + amount_sats: Some(100_000), + send_all: None, + fee_rate_sat_per_vb: Some(5), + }; + roundtrip(&req); + + let resp = OnchainSendResponse { txid: HASH_32, payment_id: HASH_32_B }; + let json = roundtrip(&resp); + assert_eq!(json["txid"], HASH_32_HEX); + assert_eq!(json["payment_id"], HASH_32_B_HEX); +} + +#[test] +fn onchain_send_all_roundtrip() { + let req = OnchainSendRequest { + address: "bc1qtest".into(), + amount_sats: None, + send_all: Some(true), + fee_rate_sat_per_vb: None, + }; + roundtrip(&req); +} + +#[test] +fn bolt11_receive_roundtrip() { + let req = Bolt11ReceiveRequest { + amount_msat: Some(100_000), + description: Some(Bolt11InvoiceDescription::Direct("test".into())), + expiry_secs: 3600, + }; + roundtrip(&req); + + let resp = Bolt11ReceiveResponse { + invoice: "lnbc1...".into(), + payment_hash: HASH_32, + payment_secret: HASH_32_B, + }; + let json = roundtrip(&resp); + assert_eq!(json["payment_hash"], HASH_32_HEX); +} + +#[test] +fn bolt11_receive_variable_amount_roundtrip() { + let req = Bolt11ReceiveRequest { amount_msat: None, description: None, expiry_secs: 3600 }; + roundtrip(&req); +} + +#[test] +fn bolt11_receive_for_hash_roundtrip() { + let req = Bolt11ReceiveForHashRequest { + amount_msat: Some(50_000), + description: Some(Bolt11InvoiceDescription::Hash("deadbeef".into())), + expiry_secs: 1800, + payment_hash: HASH_32, + }; + let json = roundtrip(&req); + assert_eq!(json["payment_hash"], HASH_32_HEX); +} + +#[test] +fn bolt11_claim_for_hash_roundtrip() { + let req = Bolt11ClaimForHashRequest { + payment_hash: Some(HASH_32), + claimable_amount_msat: Some(100_000), + preimage: HASH_32_B, + }; + roundtrip(&req); + roundtrip(&Bolt11ClaimForHashResponse {}); +} + +#[test] +fn bolt11_fail_for_hash_roundtrip() { + let req = Bolt11FailForHashRequest { payment_hash: HASH_32 }; + let json = roundtrip(&req); + assert_eq!(json["payment_hash"], HASH_32_HEX); + roundtrip(&Bolt11FailForHashResponse {}); +} + +#[test] +fn bolt11_receive_via_jit_channel_roundtrip() { + let req = Bolt11ReceiveViaJitChannelRequest { + amount_msat: 1_000_000, + description: Some(Bolt11InvoiceDescription::Direct("jit test".into())), + expiry_secs: 3600, + max_total_lsp_fee_limit_msat: Some(5000), + }; + roundtrip(&req); +} + +#[test] +fn bolt11_receive_variable_amount_via_jit_channel_roundtrip() { + let req = Bolt11ReceiveVariableAmountViaJitChannelRequest { + description: None, + expiry_secs: 3600, + max_proportional_lsp_fee_limit_ppm_msat: Some(1000), + }; + roundtrip(&req); +} + +#[test] +fn bolt11_send_roundtrip() { + let req = Bolt11SendRequest { + invoice: "lnbc1...".into(), + amount_msat: Some(100_000), + route_parameters: Some(sample_route_params()), + }; + roundtrip(&req); + + let resp = Bolt11SendResponse { payment_id: HASH_32 }; + let json = roundtrip(&resp); + assert_eq!(json["payment_id"], HASH_32_HEX); +} + +#[test] +fn bolt12_receive_roundtrip() { + let req = Bolt12ReceiveRequest { + description: "test offer".into(), + amount_msat: Some(500_000), + expiry_secs: Some(86400), + quantity: Some(1), + }; + roundtrip(&req); + + let resp = Bolt12ReceiveResponse { offer: "lno1...".into(), offer_id: HASH_32 }; + let json = roundtrip(&resp); + assert_eq!(json["offer_id"], HASH_32_HEX); +} + +#[test] +fn bolt12_send_roundtrip() { + let req = Bolt12SendRequest { + offer: "lno1...".into(), + amount_msat: Some(500_000), + quantity: Some(1), + payer_note: Some("thanks".into()), + route_parameters: None, + }; + roundtrip(&req); + + let resp = Bolt12SendResponse { payment_id: HASH_32 }; + roundtrip(&resp); +} + +#[test] +fn spontaneous_send_roundtrip() { + let req = SpontaneousSendRequest { + amount_msat: 100_000, + node_id: PUBKEY_33, + route_parameters: Some(sample_route_params()), + }; + let json = roundtrip(&req); + assert_eq!(json["node_id"], PUBKEY_33_HEX); + + let resp = SpontaneousSendResponse { payment_id: HASH_32 }; + roundtrip(&resp); +} + +#[test] +fn open_channel_roundtrip() { + let req = OpenChannelRequest { + node_pubkey: PUBKEY_33, + address: "127.0.0.1:9735".into(), + channel_amount_sats: 1_000_000, + push_to_counterparty_msat: Some(10_000), + channel_config: Some(sample_channel_config()), + announce_channel: true, + }; + let json = roundtrip(&req); + assert_eq!(json["node_pubkey"], PUBKEY_33_HEX); + + roundtrip(&OpenChannelResponse { user_channel_id: "abc123".into() }); +} + +#[test] +fn splice_in_roundtrip() { + let req = SpliceInRequest { + user_channel_id: "abc123".into(), + counterparty_node_id: PUBKEY_33, + splice_amount_sats: 500_000, + }; + roundtrip(&req); + roundtrip(&SpliceInResponse {}); +} + +#[test] +fn splice_out_roundtrip() { + let req = SpliceOutRequest { + user_channel_id: "abc123".into(), + counterparty_node_id: PUBKEY_33, + address: Some("bc1qtest".into()), + splice_amount_sats: 200_000, + }; + roundtrip(&req); + roundtrip(&SpliceOutResponse { address: "bc1qtest".into() }); +} + +#[test] +fn update_channel_config_roundtrip() { + let req = UpdateChannelConfigRequest { + user_channel_id: "abc123".into(), + counterparty_node_id: PUBKEY_33, + channel_config: Some(sample_channel_config()), + }; + roundtrip(&req); + roundtrip(&UpdateChannelConfigResponse {}); +} + +#[test] +fn close_channel_roundtrip() { + let req = + CloseChannelRequest { user_channel_id: "abc123".into(), counterparty_node_id: PUBKEY_33 }; + let json = roundtrip(&req); + assert_eq!(json["counterparty_node_id"], PUBKEY_33_HEX); + roundtrip(&CloseChannelResponse {}); +} + +#[test] +fn force_close_channel_roundtrip() { + let req = ForceCloseChannelRequest { + user_channel_id: "abc123".into(), + counterparty_node_id: PUBKEY_33, + force_close_reason: Some("unresponsive".into()), + }; + roundtrip(&req); + roundtrip(&ForceCloseChannelResponse {}); +} + +#[test] +fn list_channels_roundtrip() { + roundtrip(&ListChannelsRequest {}); + let resp = ListChannelsResponse { channels: vec![sample_channel()] }; + roundtrip(&resp); +} + +#[test] +fn get_payment_details_roundtrip() { + let req = GetPaymentDetailsRequest { payment_id: HASH_32 }; + let json = roundtrip(&req); + assert_eq!(json["payment_id"], HASH_32_HEX); + + let resp_some = GetPaymentDetailsResponse { payment: Some(sample_payment()) }; + roundtrip(&resp_some); + + let resp_none = GetPaymentDetailsResponse { payment: None }; + roundtrip(&resp_none); +} + +#[test] +fn list_payments_roundtrip() { + let req = ListPaymentsRequest { page_token: Some("token123".into()) }; + roundtrip(&req); + + let resp = ListPaymentsResponse { + payments: vec![sample_payment()], + next_page_token: Some("next_token".into()), + }; + roundtrip(&resp); +} + +#[test] +fn list_forwarded_payments_roundtrip() { + let req = ListForwardedPaymentsRequest { page_token: None }; + roundtrip(&req); + + let resp = ListForwardedPaymentsResponse { + forwarded_payments: vec![sample_forwarded_payment()], + next_page_token: None, + }; + roundtrip(&resp); +} + +#[test] +fn sign_message_roundtrip() { + let req = SignMessageRequest { message: vec![0x48, 0x65, 0x6c, 0x6c, 0x6f] }; + let json = roundtrip(&req); + assert_eq!(json["message"], "48656c6c6f"); // "Hello" in hex + + let resp = SignMessageResponse { signature: "d2mea3...".into() }; + roundtrip(&resp); +} + +#[test] +fn verify_signature_roundtrip() { + let req = VerifySignatureRequest { + message: vec![0x48, 0x69], + signature: "d2mea3...".into(), + public_key: PUBKEY_33, + }; + let json = roundtrip(&req); + assert_eq!(json["public_key"], PUBKEY_33_HEX); + + let resp = VerifySignatureResponse { valid: true }; + let json = roundtrip(&resp); + assert_eq!(json["valid"], true); +} + +#[test] +fn export_pathfinding_scores_roundtrip() { + roundtrip(&ExportPathfindingScoresRequest {}); + + let resp = ExportPathfindingScoresResponse { scores: vec![0xde, 0xad, 0xbe, 0xef] }; + let json = roundtrip(&resp); + assert_eq!(json["scores"], "deadbeef"); +} + +#[test] +fn get_balances_roundtrip() { + roundtrip(&GetBalancesRequest {}); + + let resp = GetBalancesResponse { + total_onchain_balance_sats: 1_000_000, + spendable_onchain_balance_sats: 900_000, + total_anchor_channels_reserve_sats: 100_000, + total_lightning_balance_sats: 500_000, + lightning_balances: vec![LightningBalance::ClaimableOnChannelClose( + ClaimableOnChannelClose { + channel_id: HASH_32, + counterparty_node_id: PUBKEY_33, + amount_satoshis: 500_000, + transaction_fee_satoshis: 300, + outbound_payment_htlc_rounded_msat: 0, + outbound_forwarded_htlc_rounded_msat: 0, + inbound_claiming_htlc_rounded_msat: 0, + inbound_htlc_rounded_msat: 0, + }, + )], + pending_balances_from_channel_closures: vec![PendingSweepBalance::PendingBroadcast( + PendingBroadcast { channel_id: Some(HASH_32), amount_satoshis: 50_000 }, + )], + }; + roundtrip(&resp); +} + +#[test] +fn connect_peer_roundtrip() { + let req = ConnectPeerRequest { + node_pubkey: PUBKEY_33, + address: "127.0.0.1:9735".into(), + persist: true, + }; + let json = roundtrip(&req); + assert_eq!(json["node_pubkey"], PUBKEY_33_HEX); + roundtrip(&ConnectPeerResponse {}); +} + +#[test] +fn disconnect_peer_roundtrip() { + let req = DisconnectPeerRequest { node_pubkey: PUBKEY_33 }; + let json = roundtrip(&req); + assert_eq!(json["node_pubkey"], PUBKEY_33_HEX); + roundtrip(&DisconnectPeerResponse {}); +} + +#[test] +fn list_peers_roundtrip() { + roundtrip(&ListPeersRequest {}); + + let resp = ListPeersResponse { + peers: vec![Peer { + node_id: PUBKEY_33, + address: "127.0.0.1:9735".into(), + is_persisted: true, + is_connected: false, + }], + }; + roundtrip(&resp); +} + +#[test] +fn graph_list_channels_roundtrip() { + roundtrip(&GraphListChannelsRequest {}); + roundtrip(&GraphListChannelsResponse { short_channel_ids: vec![123456789, 987654321] }); +} + +#[test] +fn graph_get_channel_roundtrip() { + let req = GraphGetChannelRequest { short_channel_id: 123456789 }; + roundtrip(&req); + + let resp = GraphGetChannelResponse { + channel: Some(GraphChannel { + node_one: PUBKEY_33, + node_two: PUBKEY_33_B, + capacity_sats: Some(1_000_000), + one_to_two: None, + two_to_one: None, + }), + }; + roundtrip(&resp); + + roundtrip(&GraphGetChannelResponse { channel: None }); +} + +#[test] +fn graph_list_nodes_roundtrip() { + roundtrip(&GraphListNodesRequest {}); + roundtrip(&GraphListNodesResponse { node_ids: vec!["abc".into(), "def".into()] }); +} + +#[test] +fn graph_get_node_roundtrip() { + let req = GraphGetNodeRequest { node_id: PUBKEY_33 }; + let json = roundtrip(&req); + assert_eq!(json["node_id"], PUBKEY_33_HEX); + + let resp = GraphGetNodeResponse { + node: Some(GraphNode { + channels: vec![123], + announcement_info: Some(GraphNodeAnnouncement { + last_update: 1_700_000_000, + alias: "test".into(), + rgb: "ffffff".into(), + addresses: vec![], + }), + }), + }; + roundtrip(&resp); + + roundtrip(&GraphGetNodeResponse { node: None }); +} + +#[test] +fn unified_send_request_roundtrip() { + let req = UnifiedSendRequest { + uri: "bitcoin:bc1q...?lightning=lnbc1...".into(), + amount_msat: Some(100_000), + route_parameters: Some(sample_route_params()), + }; + roundtrip(&req); +} + +#[test] +fn unified_send_response_onchain() { + let resp = UnifiedSendResponse::Onchain { txid: HASH_32, payment_id: HASH_32_B }; + let json = roundtrip(&resp); + assert!(json["onchain"].is_object()); + assert_eq!(json["onchain"]["txid"], HASH_32_HEX); +} + +#[test] +fn unified_send_response_bolt11() { + let resp = UnifiedSendResponse::Bolt11 { payment_id: HASH_32 }; + let json = roundtrip(&resp); + assert!(json["bolt11"].is_object()); + assert_eq!(json["bolt11"]["payment_id"], HASH_32_HEX); +} + +#[test] +fn unified_send_response_bolt12() { + let resp = UnifiedSendResponse::Bolt12 { payment_id: HASH_32 }; + let json = roundtrip(&resp); + assert!(json["bolt12"].is_object()); +} + +// =========================================================================== +// Cross-cutting: hex serde round-trips from raw JSON strings +// =========================================================================== + +#[test] +fn hex_32_from_json_string() { + let json_str = format!(r#"{{"payment_id":"{}"}}"#, HASH_32_HEX); + let req: GetPaymentDetailsRequest = serde_json::from_str(&json_str).unwrap(); + assert_eq!(req.payment_id, HASH_32); +} + +#[test] +fn hex_33_from_json_string() { + let json_str = format!( + r#"{{"node_pubkey":"{}","address":"127.0.0.1:9735","persist":true}}"#, + PUBKEY_33_HEX + ); + let req: ConnectPeerRequest = serde_json::from_str(&json_str).unwrap(); + assert_eq!(req.node_pubkey, PUBKEY_33); +} + +#[test] +fn opt_hex_32_null_from_json() { + let json_str = r#"{"hash":"1111111111111111111111111111111111111111111111111111111111111111","preimage":null,"secret":null}"#; + let bolt11: Bolt11 = serde_json::from_str(json_str).unwrap(); + assert_eq!(bolt11.hash, HASH_32_B); + assert_eq!(bolt11.preimage, None); + assert_eq!(bolt11.secret, None); +} + +#[test] +fn opt_hex_32_absent_from_json() { + // Fields with #[serde(default)] should work when absent + let json_str = r#"{"hash":"1111111111111111111111111111111111111111111111111111111111111111"}"#; + let bolt11: Bolt11 = serde_json::from_str(json_str).unwrap(); + assert_eq!(bolt11.preimage, None); + assert_eq!(bolt11.secret, None); +} + +#[test] +fn bytes_hex_roundtrip() { + let req = SignMessageRequest { message: vec![] }; + let json = roundtrip(&req); + assert_eq!(json["message"], ""); + + let req = SignMessageRequest { message: vec![0xff, 0x00, 0xab] }; + let json = roundtrip(&req); + assert_eq!(json["message"], "ff00ab"); +} diff --git a/ldk-server-protos/Cargo.toml b/ldk-server-protos/Cargo.toml deleted file mode 100644 index c971d6f5..00000000 --- a/ldk-server-protos/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "ldk-server-protos" -version = "0.1.0" -edition = "2021" - -build = "build.rs" - -# We use a cfg instead of a feature for genproto to prevent it from being -# enabled with --all-features. Proto generation is a developer-only tool that -# requires external dependencies (protoc) and shouldn't be triggered accidentally. -# This lint configuration tells Cargo that genproto is an expected custom cfg. -[lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(genproto)'] } - -[features] -default = [] -serde = ["dep:serde", "dep:bytes"] - -[dependencies] -prost = { version = "0.11.6", default-features = false, features = ["std", "prost-derive"] } -serde = { version = "1.0", features = ["derive"], optional = true } -bytes = { version = "1", features = ["serde"], optional = true } - -[target.'cfg(genproto)'.build-dependencies] -prost-build = { version = "0.11.6", default-features = false } diff --git a/ldk-server-protos/build.rs b/ldk-server-protos/build.rs deleted file mode 100644 index 13e54d53..00000000 --- a/ldk-server-protos/build.rs +++ /dev/null @@ -1,95 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -#[cfg(genproto)] -extern crate prost_build; - -#[cfg(genproto)] -use std::{env, fs, io::Write, path::Path}; - -#[cfg(genproto)] -const COPYRIGHT_HEADER: &str = - "// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -"; - -/// To generate updated proto objects, run `RUSTFLAGS="--cfg genproto" cargo build` -fn main() { - #[cfg(genproto)] - generate_protos(); -} - -#[cfg(genproto)] -fn generate_protos() { - prost_build::Config::new() - .bytes(&["."]) - .type_attribute( - ".", - "#[cfg_attr(feature = \"serde\", derive(serde::Serialize, serde::Deserialize))]", - ) - .type_attribute(".", "#[cfg_attr(feature = \"serde\", serde(rename_all = \"snake_case\"))]") - .field_attribute( - "types.Bolt11.secret", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_opt_bytes_hex\"))]", - ) - .field_attribute( - "types.Bolt11Jit.secret", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_opt_bytes_hex\"))]", - ) - .field_attribute( - "types.Bolt12Offer.secret", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_opt_bytes_hex\"))]", - ) - .field_attribute( - "types.Bolt12Refund.secret", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_opt_bytes_hex\"))]", - ) - .field_attribute( - "types.Payment.direction", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_payment_direction\"))]", - ) - .field_attribute( - "types.Payment.status", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_payment_status\"))]", - ) - .field_attribute( - "types.ClaimableAwaitingConfirmations.source", - "#[cfg_attr(feature = \"serde\", serde(serialize_with = \"crate::serde_utils::serialize_balance_source\"))]", - ) - .field_attribute( - "api.UnifiedSendResponse.payment_result", - "#[cfg_attr(feature = \"serde\", serde(flatten))]", - ) - .compile_protos( - &[ - "src/proto/api.proto", - "src/proto/types.proto", - "src/proto/events.proto", - "src/proto/error.proto", - ], - &["src/proto/"], - ) - .expect("protobuf compilation failed"); - let out_dir = env::var("OUT_DIR").unwrap(); - println!("OUT_DIR: {}", &out_dir); - for file in &["api.rs", "types.rs", "events.rs", "error.rs"] { - let from_path = Path::new(&out_dir).join(file); - let content = fs::read(&from_path).unwrap(); - let mut dest = fs::File::create(Path::new("src").join(file)).unwrap(); - dest.write_all(COPYRIGHT_HEADER.as_bytes()).unwrap(); - dest.write_all(&content).unwrap(); - } -} diff --git a/ldk-server-protos/src/error.rs b/ldk-server-protos/src/error.rs deleted file mode 100644 index 41e73ee4..00000000 --- a/ldk-server-protos/src/error.rs +++ /dev/null @@ -1,79 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -/// When HttpStatusCode is not ok (200), the response `content` contains a serialized `ErrorResponse` -/// with the relevant ErrorCode and `message` -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ErrorResponse { - /// The error message containing a generic description of the error condition in English. - /// It is intended for a human audience only and should not be parsed to extract any information - /// programmatically. Client-side code may use it for logging only. - #[prost(string, tag = "1")] - pub message: ::prost::alloc::string::String, - /// The error code uniquely identifying an error condition. - /// It is meant to be read and understood programmatically by code that detects/handles errors by - /// type. - /// - /// **Caution**: If a new type of `error_code` is introduced in the `ErrorCode` enum, `error_code` field will be set to - /// `UnknownError`. - #[prost(enumeration = "ErrorCode", tag = "2")] - pub error_code: i32, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum ErrorCode { - /// Will never be used as `error_code` by server. - /// - /// **Caution**: If a new type of `error_code` is introduced in the `ErrorCode` enum, `error_code` field will be set to - /// `UnknownError`. - UnknownError = 0, - /// Used in the following cases: - /// - The request was missing a required argument. - /// - The specified argument was invalid, incomplete or in the wrong format. - /// - The request body of api cannot be deserialized into corresponding protobuf object. - /// - The request does not follow api contract. - InvalidRequestError = 1, - /// Used when authentication fails or in case of an unauthorized request. - AuthError = 2, - /// Used to represent an error while doing a Lightning operation. - LightningError = 3, - /// Used when an internal server error occurred. The client is probably at no fault. - InternalServerError = 4, -} -impl ErrorCode { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - ErrorCode::UnknownError => "UNKNOWN_ERROR", - ErrorCode::InvalidRequestError => "INVALID_REQUEST_ERROR", - ErrorCode::AuthError => "AUTH_ERROR", - ErrorCode::LightningError => "LIGHTNING_ERROR", - ErrorCode::InternalServerError => "INTERNAL_SERVER_ERROR", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "UNKNOWN_ERROR" => Some(Self::UnknownError), - "INVALID_REQUEST_ERROR" => Some(Self::InvalidRequestError), - "AUTH_ERROR" => Some(Self::AuthError), - "LIGHTNING_ERROR" => Some(Self::LightningError), - "INTERNAL_SERVER_ERROR" => Some(Self::InternalServerError), - _ => None, - } - } -} diff --git a/ldk-server-protos/src/events.rs b/ldk-server-protos/src/events.rs deleted file mode 100644 index a41446aa..00000000 --- a/ldk-server-protos/src/events.rs +++ /dev/null @@ -1,87 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -/// EventEnvelope wraps different event types in a single message to be used by EventPublisher. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct EventEnvelope { - #[prost(oneof = "event_envelope::Event", tags = "2, 3, 4, 6, 7")] - pub event: ::core::option::Option, -} -/// Nested message and enum types in `EventEnvelope`. -pub mod event_envelope { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Event { - #[prost(message, tag = "2")] - PaymentReceived(super::PaymentReceived), - #[prost(message, tag = "3")] - PaymentSuccessful(super::PaymentSuccessful), - #[prost(message, tag = "4")] - PaymentFailed(super::PaymentFailed), - #[prost(message, tag = "6")] - PaymentForwarded(super::PaymentForwarded), - #[prost(message, tag = "7")] - PaymentClaimable(super::PaymentClaimable), - } -} -/// PaymentReceived indicates a payment has been received. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentReceived { - /// The payment details for the payment in event. - #[prost(message, optional, tag = "1")] - pub payment: ::core::option::Option, -} -/// PaymentSuccessful indicates a sent payment was successful. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentSuccessful { - /// The payment details for the payment in event. - #[prost(message, optional, tag = "1")] - pub payment: ::core::option::Option, -} -/// PaymentFailed indicates a sent payment has failed. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentFailed { - /// The payment details for the payment in event. - #[prost(message, optional, tag = "1")] - pub payment: ::core::option::Option, -} -/// PaymentClaimable indicates a payment has arrived and is waiting to be manually claimed or failed. -/// This event is only emitted for payments created via `Bolt11ReceiveForHash`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentClaimable { - /// The payment details for the claimable payment. - #[prost(message, optional, tag = "1")] - pub payment: ::core::option::Option, -} -/// PaymentForwarded indicates a payment was forwarded through the node. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentForwarded { - #[prost(message, optional, tag = "1")] - pub forwarded_payment: ::core::option::Option, -} diff --git a/ldk-server-protos/src/proto/api.proto b/ldk-server-protos/src/proto/api.proto deleted file mode 100644 index 3eb505d3..00000000 --- a/ldk-server-protos/src/proto/api.proto +++ /dev/null @@ -1,824 +0,0 @@ -syntax = "proto3"; -package api; - -import 'types.proto'; - -// Retrieve the latest node info like `node_id`, `current_best_block` etc. -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.node_id -// - https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.status -message GetNodeInfoRequest { -} - -// The response `content` for the `GetNodeInfo` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GetNodeInfoResponse { - - // The hex-encoded `node-id` or public key for our own lightning node. - string node_id = 1; - - // The best block to which our Lightning wallet is currently synced. - // - // Should be always set, will never be `None`. - types.BestBlock current_best_block = 3; - - // The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced our Lightning wallet to - // the chain tip. - // - // Will be `None` if the wallet hasn't been synced yet. - optional uint64 latest_lightning_wallet_sync_timestamp = 4; - - // The timestamp, in seconds since start of the UNIX epoch, when we last successfully synced our on-chain - // wallet to the chain tip. - // - // Will be `None` if the wallet hasn’t been synced since the node was initialized. - optional uint64 latest_onchain_wallet_sync_timestamp = 5; - - // The timestamp, in seconds since start of the UNIX epoch, when we last successfully update our fee rate cache. - // - // Will be `None` if the cache hasn’t been updated since the node was initialized. - optional uint64 latest_fee_rate_cache_update_timestamp = 6; - - // The timestamp, in seconds since start of the UNIX epoch, when the last rapid gossip sync (RGS) snapshot we - // successfully applied was generated. - // - // Will be `None` if RGS isn’t configured or the snapshot hasn’t been updated since the node was initialized. - optional uint64 latest_rgs_snapshot_timestamp = 7; - - // The timestamp, in seconds since start of the UNIX epoch, when we last broadcasted a node announcement. - // - // Will be `None` if we have no public channels or we haven’t broadcasted since the node was initialized. - optional uint64 latest_node_announcement_broadcast_timestamp = 8; - - // The addresses the node is currently listening on for incoming connections. - // - // Will be empty if the node is not listening on any addresses. - repeated string listening_addresses = 9; - - // The addresses the node announces to the network. - // - // Will be empty if no announcement addresses are configured. - repeated string announcement_addresses = 10; - - // The node alias, if configured. - // - // Will be `None` if no alias is configured. - optional string node_alias = 11; - - // The node URIs that can be used to connect to this node, in the format `node_id@address`. - // - // These are constructed from the announcement addresses and the node's public key. - // Will be empty if no announcement addresses are configured. - repeated string node_uris = 12; -} - -// Retrieve a new on-chain funding address. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.OnchainPayment.html#method.new_address -message OnchainReceiveRequest { -} - -// The response `content` for the `OnchainReceive` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`.. -message OnchainReceiveResponse { - - // A Bitcoin on-chain address. - string address = 1; -} - -// Send an on-chain payment to the given address. -message OnchainSendRequest { - - // The address to send coins to. - string address = 1; - - // The amount in satoshis to send. - // While sending the specified amount, we will respect any on-chain reserve we need to keep, - // i.e., won't allow to cut into `total_anchor_channels_reserve_sats`. - // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.OnchainPayment.html#method.send_to_address - optional uint64 amount_sats = 2; - - // If set, the amount_sats field should be unset. - // It indicates that node will send full balance to the specified address. - // - // Please note that when send_all is used this operation will **not** retain any on-chain reserves, - // which might be potentially dangerous if you have open Anchor channels for which you can't trust - // the counterparty to spend the Anchor output after channel closure. - // See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.OnchainPayment.html#method.send_all_to_address - optional bool send_all = 3; - - // If `fee_rate_sat_per_vb` is set it will be used on the resulting transaction. Otherwise we'll retrieve - // a reasonable estimate from BitcoinD. - optional uint64 fee_rate_sat_per_vb = 4; -} - -// The response `content` for the `OnchainSend` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message OnchainSendResponse { - - // The transaction ID of the broadcasted transaction. - string txid = 1; -} - -// Return a BOLT11 payable invoice that can be used to request and receive a payment -// for the given amount, if specified. -// The inbound payment will be automatically claimed upon arrival. -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_variable_amount -message Bolt11ReceiveRequest { - - // The amount in millisatoshi to send. If unset, a "zero-amount" or variable-amount invoice is returned. - optional uint64 amount_msat = 1; - - // An optional description to attach along with the invoice. - // Will be set in the description field of the encoded payment request. - types.Bolt11InvoiceDescription description = 2; - - // Invoice expiry time in seconds. - uint32 expiry_secs = 3; -} - -// The response `content` for the `Bolt11Receive` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11ReceiveResponse { - - // An invoice for a payment within the Lightning Network. - // With the details of the invoice, the sender has all the data necessary to send a payment - // to the recipient. - string invoice = 1; - - // The hex-encoded 32-byte payment hash. - string payment_hash = 2; - - // The hex-encoded 32-byte payment secret. - string payment_secret = 3; -} - -// Return a BOLT11 payable invoice for a given payment hash. -// The inbound payment will NOT be automatically claimed upon arrival. -// Instead, the payment will need to be manually claimed by calling `Bolt11ClaimForHash` -// or manually failed by calling `Bolt11FailForHash`. -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_for_hash -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_variable_amount_for_hash -message Bolt11ReceiveForHashRequest { - - // The amount in millisatoshi to receive. If unset, a "zero-amount" or variable-amount invoice is returned. - optional uint64 amount_msat = 1; - - // An optional description to attach along with the invoice. - // Will be set in the description field of the encoded payment request. - types.Bolt11InvoiceDescription description = 2; - - // Invoice expiry time in seconds. - uint32 expiry_secs = 3; - - // The hex-encoded 32-byte payment hash to use for the invoice. - string payment_hash = 4; -} - -// The response `content` for the `Bolt11ReceiveForHash` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11ReceiveForHashResponse { - - // An invoice for a payment within the Lightning Network. - // With the details of the invoice, the sender has all the data necessary to send a payment - // to the recipient. - string invoice = 1; -} - -// Manually claim a payment for a given payment hash with the corresponding preimage. -// This should be used to claim payments created via `Bolt11ReceiveForHash`. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.claim_for_hash -message Bolt11ClaimForHashRequest { - - // The hex-encoded 32-byte payment hash. - // If provided, it will be used to verify that the preimage matches. - optional string payment_hash = 1; - - // The amount in millisatoshi that is claimable. - // If not provided, skips amount verification. - optional uint64 claimable_amount_msat = 2; - - // The hex-encoded 32-byte payment preimage. - string preimage = 3; -} - -// The response `content` for the `Bolt11ClaimForHash` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11ClaimForHashResponse {} - -// Manually fail a payment for a given payment hash. -// This should be used to reject payments created via `Bolt11ReceiveForHash`. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.fail_for_hash -message Bolt11FailForHashRequest { - - // The hex-encoded 32-byte payment hash. - string payment_hash = 1; -} - -// The response `content` for the `Bolt11FailForHash` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11FailForHashResponse {} - -// Return a BOLT11 payable invoice that can be used to request and receive a payment via an -// LSPS2 just-in-time channel. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_via_jit_channel -message Bolt11ReceiveViaJitChannelRequest { - - // The amount in millisatoshi to request. - uint64 amount_msat = 1; - - // An optional description to attach along with the invoice. - // Will be set in the description field of the encoded payment request. - types.Bolt11InvoiceDescription description = 2; - - // Invoice expiry time in seconds. - uint32 expiry_secs = 3; - - // Optional upper bound for the total fee an LSP may deduct when opening the JIT channel. - optional uint64 max_total_lsp_fee_limit_msat = 4; -} - -// The response `content` for the `Bolt11ReceiveViaJitChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11ReceiveViaJitChannelResponse { - - // An invoice for a payment within the Lightning Network. - string invoice = 1; -} - -// Return a variable-amount BOLT11 invoice that can be used to receive a payment via an LSPS2 -// just-in-time channel. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.receive_variable_amount_via_jit_channel -message Bolt11ReceiveVariableAmountViaJitChannelRequest { - - // An optional description to attach along with the invoice. - // Will be set in the description field of the encoded payment request. - types.Bolt11InvoiceDescription description = 1; - - // Invoice expiry time in seconds. - uint32 expiry_secs = 2; - - // Optional upper bound for the proportional fee, in parts-per-million millisatoshis, that an - // LSP may deduct when opening the JIT channel. - optional uint64 max_proportional_lsp_fee_limit_ppm_msat = 3; -} - -// The response `content` for the `Bolt11ReceiveVariableAmountViaJitChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11ReceiveVariableAmountViaJitChannelResponse { - - // An invoice for a payment within the Lightning Network. - string invoice = 1; -} - - -// Send a payment for a BOLT11 invoice. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt11Payment.html#method.send -message Bolt11SendRequest { - - // An invoice for a payment within the Lightning Network. - string invoice = 1; - - // Set this field when paying a so-called "zero-amount" invoice, i.e., an invoice that leaves the - // amount paid to be determined by the user. - // This operation will fail if the amount specified is less than the value required by the given invoice. - optional uint64 amount_msat = 2; - - // Configuration options for payment routing and pathfinding. - optional types.RouteParametersConfig route_parameters = 3; - -} - -// The response `content` for the `Bolt11Send` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt11SendResponse { - - // An identifier used to uniquely identify a payment in hex-encoded form. - string payment_id = 1; -} - -// Returns a BOLT12 offer for the given amount, if specified. -// -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt12Payment.html#method.receive -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt12Payment.html#method.receive_variable_amount -message Bolt12ReceiveRequest { - - // An optional description to attach along with the offer. - // Will be set in the description field of the encoded offer. - string description = 1; - - // The amount in millisatoshi to send. If unset, a "zero-amount" or variable-amount offer is returned. - optional uint64 amount_msat = 2; - - // Offer expiry time in seconds. - optional uint32 expiry_secs = 3; - - // If set, it represents the number of items requested, can only be set for fixed-amount offers. - optional uint64 quantity = 4; -} - -// The response `content` for the `Bolt12Receive` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt12ReceiveResponse { - - // An offer for a payment within the Lightning Network. - // With the details of the offer, the sender has all the data necessary to send a payment - // to the recipient. - string offer = 1; - - // The hex-encoded offer id. - string offer_id = 2; -} - -// Send a payment for a BOLT12 offer. -// See more: -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt12Payment.html#method.send -// - https://docs.rs/ldk-node/latest/ldk_node/payment/struct.Bolt12Payment.html#method.send_using_amount -message Bolt12SendRequest { - - // An offer for a payment within the Lightning Network. - string offer = 1; - - // Set this field when paying a so-called "zero-amount" offer, i.e., an offer that leaves the - // amount paid to be determined by the user. - // This operation will fail if the amount specified is less than the value required by the given offer. - optional uint64 amount_msat = 2; - - // If set, it represents the number of items requested. - optional uint64 quantity = 3; - - // If set, it will be seen by the recipient and reflected back in the invoice. - optional string payer_note = 4; - - // Configuration options for payment routing and pathfinding. - optional types.RouteParametersConfig route_parameters = 5; -} - -// The response `content` for the `Bolt12Send` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message Bolt12SendResponse { - - // An identifier used to uniquely identify a payment in hex-encoded form. - string payment_id = 1; -} - -// Send a spontaneous payment, also known as "keysend", to a node. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.SpontaneousPayment.html#method.send -message SpontaneousSendRequest { - // The amount in millisatoshis to send. - uint64 amount_msat = 1; - - // The hex-encoded public key of the node to send the payment to. - string node_id = 2; - - // Configuration options for payment routing and pathfinding. - optional types.RouteParametersConfig route_parameters = 3; -} - -// The response `content` for the `SpontaneousSend` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message SpontaneousSendResponse { - // An identifier used to uniquely identify a payment in hex-encoded form. - string payment_id = 1; -} - -// Creates a new outbound channel to the given remote node. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.connect_open_channel -message OpenChannelRequest { - - // The hex-encoded public key of the node to open a channel with. - string node_pubkey = 1; - - // An address which can be used to connect to a remote peer. - // It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port - string address = 2; - - // The amount of satoshis the caller is willing to commit to the channel. - uint64 channel_amount_sats = 3; - - // The amount of satoshis to push to the remote side as part of the initial commitment state. - optional uint64 push_to_counterparty_msat = 4; - - // The channel configuration to be used for opening this channel. If unset, default ChannelConfig is used. - optional types.ChannelConfig channel_config = 5; - - // Whether the channel should be public. - bool announce_channel = 6; -} - -// The response `content` for the `OpenChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message OpenChannelResponse { - - // The local channel id of the created channel that user can use to refer to channel. - string user_channel_id = 1; -} - -// Increases the channel balance by the given amount. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.splice_in -message SpliceInRequest { - - // The local `user_channel_id` of the channel. - string user_channel_id = 1; - - // The hex-encoded public key of the channel's counterparty node. - string counterparty_node_id = 2; - - // The amount of sats to splice into the channel. - uint64 splice_amount_sats = 3; -} - -// The response `content` for the `SpliceIn` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message SpliceInResponse {} - -// Decreases the channel balance by the given amount. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.splice_out -message SpliceOutRequest { - - // The local `user_channel_id` of this channel. - string user_channel_id = 1; - - // The hex-encoded public key of the channel's counterparty node. - string counterparty_node_id = 2; - - // A Bitcoin on-chain address to send the spliced-out funds. - // - // If not set, an address from the node's on-chain wallet will be used. - optional string address = 3; - - // The amount of sats to splice out of the channel. - uint64 splice_amount_sats = 4; -} - -// The response `content` for the `SpliceOut` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message SpliceOutResponse { - - // The Bitcoin on-chain address where the funds will be sent. - string address = 1; -} - -// Update the config for a previously opened channel. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.update_channel_config -message UpdateChannelConfigRequest { - - // The local `user_channel_id` of this channel. - string user_channel_id = 1; - - // The hex-encoded public key of the counterparty node to update channel config with. - string counterparty_node_id = 2; - - // The updated channel configuration settings for a channel. - types.ChannelConfig channel_config = 3; -} - -// The response `content` for the `UpdateChannelConfig` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message UpdateChannelConfigResponse { -} - -// Closes the channel specified by given request. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.close_channel -message CloseChannelRequest { - - // The local `user_channel_id` of this channel. - string user_channel_id = 1; - - // The hex-encoded public key of the node to close a channel with. - string counterparty_node_id = 2; -} - -// The response `content` for the `CloseChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message CloseChannelResponse {} - -// Force closes the channel specified by given request. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.force_close_channel -message ForceCloseChannelRequest { - // The local `user_channel_id` of this channel. - string user_channel_id = 1; - // The hex-encoded public key of the node to close a channel with. - string counterparty_node_id = 2; - // The reason for force-closing. - optional string force_close_reason = 3; -} - -// The response `content` for the `ForceCloseChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ForceCloseChannelResponse {} - -// Returns a list of known channels. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_channels -message ListChannelsRequest {} - -// The response `content` for the `ListChannels` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ListChannelsResponse { - - // List of channels. - repeated types.Channel channels = 1; -} - -// Returns payment details for a given payment_id. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.payment -message GetPaymentDetailsRequest { - // An identifier used to uniquely identify a payment in hex-encoded form. - string payment_id = 1; -} - -// The response `content` for the `GetPaymentDetails` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GetPaymentDetailsResponse { - // Represents a payment. - // Will be `None` if payment doesn't exist. - types.Payment payment = 1; -} - -// Retrieves list of all payments. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_payments -message ListPaymentsRequest { - // `page_token` is a pagination token. - // - // To query for the first page, `page_token` must not be specified. - // - // For subsequent pages, use the value that was returned as `next_page_token` in the previous - // page's response. - optional types.PageToken page_token = 1; -} - -// The response `content` for the `ListPayments` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ListPaymentsResponse { - // List of payments. - repeated types.Payment payments = 1; - - // `next_page_token` is a pagination token, used to retrieve the next page of results. - // Use this value to query for next-page of paginated operation, by specifying - // this value as the `page_token` in the next request. - // - // If `next_page_token` is `None`, then the "last page" of results has been processed and - // there is no more data to be retrieved. - // - // If `next_page_token` is not `None`, it does not necessarily mean that there is more data in the - // result set. The only way to know when you have reached the end of the result set is when - // `next_page_token` is `None`. - // - // **Caution**: Clients must not assume a specific number of records to be present in a page for - // paginated response. - optional types.PageToken next_page_token = 2; -} - -// Retrieves list of all forwarded payments. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.Event.html#variant.PaymentForwarded -message ListForwardedPaymentsRequest { - // `page_token` is a pagination token. - // - // To query for the first page, `page_token` must not be specified. - // - // For subsequent pages, use the value that was returned as `next_page_token` in the previous - // page's response. - optional types.PageToken page_token = 1; -} - -// The response `content` for the `ListForwardedPayments` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ListForwardedPaymentsResponse { - // List of forwarded payments. - repeated types.ForwardedPayment forwarded_payments = 1; - - // `next_page_token` is a pagination token, used to retrieve the next page of results. - // Use this value to query for next-page of paginated operation, by specifying - // this value as the `page_token` in the next request. - // - // If `next_page_token` is `None`, then the "last page" of results has been processed and - // there is no more data to be retrieved. - // - // If `next_page_token` is not `None`, it does not necessarily mean that there is more data in the - // result set. The only way to know when you have reached the end of the result set is when - // `next_page_token` is `None`. - // - // **Caution**: Clients must not assume a specific number of records to be present in a page for - // paginated response. - optional types.PageToken next_page_token = 2; -} - -// Sign a message with the node's secret key. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.sign_message -message SignMessageRequest { - // The message to sign, as raw bytes. - bytes message = 1; -} - -// The response `content` for the `SignMessage` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message SignMessageResponse { - // The signature of the message, as a zbase32-encoded string. - string signature = 1; -} - -// Verify a signature against a message and public key. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.verify_signature -message VerifySignatureRequest { - // The message that was signed, as raw bytes. - bytes message = 1; - - // The signature to verify, as a zbase32-encoded string. - string signature = 2; - - // The hex-encoded public key of the signer. - string public_key = 3; -} - -// The response `content` for the `VerifySignature` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message VerifySignatureResponse { - // Whether the signature is valid. - bool valid = 1; -} - -// Export the pathfinding scores used by the router. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.export_pathfinding_scores -message ExportPathfindingScoresRequest {} - -// The response `content` for the `ExportPathfindingScores` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ExportPathfindingScoresResponse { - // The serialized pathfinding scores data. - bytes scores = 1; -} - -// Retrieves an overview of all known balances. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_balances -message GetBalancesRequest {} - -// The response `content` for the `GetBalances` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GetBalancesResponse { - // The total balance of our on-chain wallet. - uint64 total_onchain_balance_sats = 1; - - // The currently spendable balance of our on-chain wallet. - // - // This includes any sufficiently confirmed funds, minus `total_anchor_channels_reserve_sats`. - uint64 spendable_onchain_balance_sats = 2; - - // The share of our total balance that we retain as an emergency reserve to (hopefully) be - // able to spend the Anchor outputs when one of our channels is closed. - uint64 total_anchor_channels_reserve_sats = 3; - - // The total balance that we would be able to claim across all our Lightning channels. - // - // Note this excludes balances that we are unsure if we are able to claim (e.g., as we are - // waiting for a preimage or for a timeout to expire). These balances will however be included - // as `MaybePreimageClaimableHTLC` and `MaybeTimeoutClaimableHTLC` in `lightning_balances`. - uint64 total_lightning_balance_sats = 4; - - // A detailed list of all known Lightning balances that would be claimable on channel closure. - // - // Note that less than the listed amounts are spendable over lightning as further reserve - // restrictions apply. Please refer to `Channel::outbound_capacity_msat` and - // Channel::next_outbound_htlc_limit_msat as returned by `ListChannels` - // for a better approximation of the spendable amounts. - repeated types.LightningBalance lightning_balances = 5; - - // A detailed list of balances currently being swept from the Lightning to the on-chain - // wallet. - // - // These are balances resulting from channel closures that may have been encumbered by a - // delay, but are now being claimed and useable once sufficiently confirmed on-chain. - // - // Note that, depending on the sync status of the wallets, swept balances listed here might or - // might not already be accounted for in `total_onchain_balance_sats`. - repeated types.PendingSweepBalance pending_balances_from_channel_closures = 6; -} - -// Connect to a peer on the Lightning Network. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.connect -message ConnectPeerRequest { - // The hex-encoded public key of the node to connect to. - string node_pubkey = 1; - - // An address which can be used to connect to a remote peer. - // It can be of type IPv4:port, IPv6:port, OnionV3:port or hostname:port - string address = 2; - - // Whether to persist the peer connection, i.e., whether the peer will be re-connected on - // restart. - bool persist = 3; -} - -// The response `content` for the `ConnectPeer` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ConnectPeerResponse {} - -// Disconnect from a peer and remove it from the peer store. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.disconnect -message DisconnectPeerRequest { - // The hex-encoded public key of the node to disconnect from. - string node_pubkey = 1; -} - -// The response `content` for the `DisconnectPeer` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message DisconnectPeerResponse {} - -// Returns a list of peers. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_peers -message ListPeersRequest {} - -// The response `content` for the `ListPeers` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message ListPeersResponse { - - // List of peers. - repeated types.Peer peers = 1; -} - -// Returns a list of all known short channel IDs in the network graph. -// See more: https://docs.rs/ldk-node/latest/ldk_node/graph/struct.NetworkGraph.html#method.list_channels -message GraphListChannelsRequest {} - -// The response `content` for the `GraphListChannels` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GraphListChannelsResponse { - // List of short channel IDs known to the network graph. - repeated uint64 short_channel_ids = 1; -} - -// Returns information on a channel with the given short channel ID from the network graph. -// See more: https://docs.rs/ldk-node/latest/ldk_node/graph/struct.NetworkGraph.html#method.channel -message GraphGetChannelRequest { - // The short channel ID to look up. - uint64 short_channel_id = 1; -} - -// The response `content` for the `GraphGetChannel` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GraphGetChannelResponse { - // The channel information. - types.GraphChannel channel = 1; -} - -// Returns a list of all known node IDs in the network graph. -// See more: https://docs.rs/ldk-node/latest/ldk_node/graph/struct.NetworkGraph.html#method.list_nodes -message GraphListNodesRequest {} - -// The response `content` for the `GraphListNodes` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GraphListNodesResponse { - // List of hex-encoded node IDs known to the network graph. - repeated string node_ids = 1; -} - -// Send a payment given a BIP 21 URI or BIP 353 Human-Readable Name. -// -// This method parses the provided URI string and attempts to send the payment. If the URI -// has an offer and/or invoice, it will try to pay the offer first followed by the invoice. -// If they both fail, the on-chain payment will be paid. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.UnifiedPayment.html#method.send -message UnifiedSendRequest { - - // A BIP 21 URI or BIP 353 Human-Readable Name to pay. - string uri = 1; - - // The amount in millisatoshis to send. Required for "zero-amount" or variable-amount URIs. - optional uint64 amount_msat = 2; - - // Configuration options for payment routing and pathfinding. - optional types.RouteParametersConfig route_parameters = 3; -} - -// The response `content` for the `UnifiedSend` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message UnifiedSendResponse { - - oneof payment_result { - - // An on-chain payment was made. Contains the transaction ID. - string txid = 1; - - // A BOLT11 payment was made. Contains the payment ID in hex-encoded form. - string bolt11_payment_id = 2; - - // A BOLT12 payment was made. Contains the payment ID in hex-encoded form. - string bolt12_payment_id = 3; - } -} - -// Returns information on a node with the given ID from the network graph. -// See more: https://docs.rs/ldk-node/latest/ldk_node/graph/struct.NetworkGraph.html#method.node -message GraphGetNodeRequest { - // The hex-encoded node ID to look up. - string node_id = 1; -} - -// The response `content` for the `GraphGetNode` API, when HttpStatusCode is OK (200). -// When HttpStatusCode is not OK (non-200), the response `content` contains a serialized `ErrorResponse`. -message GraphGetNodeResponse { - // The node information. - types.GraphNode node = 1; -} diff --git a/ldk-server-protos/src/proto/error.proto b/ldk-server-protos/src/proto/error.proto deleted file mode 100644 index c5a75d7d..00000000 --- a/ldk-server-protos/src/proto/error.proto +++ /dev/null @@ -1,45 +0,0 @@ -syntax = "proto3"; -package error; - -// When HttpStatusCode is not ok (200), the response `content` contains a serialized `ErrorResponse` -// with the relevant ErrorCode and `message` -message ErrorResponse { - - // The error message containing a generic description of the error condition in English. - // It is intended for a human audience only and should not be parsed to extract any information - // programmatically. Client-side code may use it for logging only. - string message = 1; - - // The error code uniquely identifying an error condition. - // It is meant to be read and understood programmatically by code that detects/handles errors by - // type. - // - // **Caution**: If a new type of `error_code` is introduced in the `ErrorCode` enum, `error_code` field will be set to - // `UnknownError`. - ErrorCode error_code = 2; -} - -enum ErrorCode { - - // Will never be used as `error_code` by server. - // - // **Caution**: If a new type of `error_code` is introduced in the `ErrorCode` enum, `error_code` field will be set to - // `UnknownError`. - UNKNOWN_ERROR = 0; - - // Used in the following cases: - // - The request was missing a required argument. - // - The specified argument was invalid, incomplete or in the wrong format. - // - The request body of api cannot be deserialized into corresponding protobuf object. - // - The request does not follow api contract. - INVALID_REQUEST_ERROR = 1; - - // Used when authentication fails or in case of an unauthorized request. - AUTH_ERROR = 2; - - // Used to represent an error while doing a Lightning operation. - LIGHTNING_ERROR = 3; - - // Used when an internal server error occurred. The client is probably at no fault. - INTERNAL_SERVER_ERROR = 4; -} diff --git a/ldk-server-protos/src/proto/events.proto b/ldk-server-protos/src/proto/events.proto deleted file mode 100644 index 5c5ce2c3..00000000 --- a/ldk-server-protos/src/proto/events.proto +++ /dev/null @@ -1,44 +0,0 @@ -syntax = "proto3"; -import "types.proto"; -package events; - -// EventEnvelope wraps different event types in a single message to be used by EventPublisher. -message EventEnvelope { - oneof event { - PaymentReceived payment_received = 2; - PaymentSuccessful payment_successful = 3; - PaymentFailed payment_failed = 4; - PaymentForwarded payment_forwarded = 6; - PaymentClaimable payment_claimable = 7; - } -} - -// PaymentReceived indicates a payment has been received. -message PaymentReceived { - // The payment details for the payment in event. - types.Payment payment = 1; -} - -// PaymentSuccessful indicates a sent payment was successful. -message PaymentSuccessful { - // The payment details for the payment in event. - types.Payment payment = 1; -} - -// PaymentFailed indicates a sent payment has failed. -message PaymentFailed { - // The payment details for the payment in event. - types.Payment payment = 1; -} - -// PaymentClaimable indicates a payment has arrived and is waiting to be manually claimed or failed. -// This event is only emitted for payments created via `Bolt11ReceiveForHash`. -message PaymentClaimable { - // The payment details for the claimable payment. - types.Payment payment = 1; -} - -// PaymentForwarded indicates a payment was forwarded through the node. -message PaymentForwarded { - types.ForwardedPayment forwarded_payment = 1; -} diff --git a/ldk-server-protos/src/proto/types.proto b/ldk-server-protos/src/proto/types.proto deleted file mode 100644 index e48908ef..00000000 --- a/ldk-server-protos/src/proto/types.proto +++ /dev/null @@ -1,820 +0,0 @@ -syntax = "proto3"; -package types; - -// Represents a payment. -// See more: https://docs.rs/ldk-node/latest/ldk_node/payment/struct.PaymentDetails.html -message Payment { - // An identifier used to uniquely identify a payment in hex-encoded form. - string id = 1; - - // The kind of the payment. - PaymentKind kind = 2; - - // The amount transferred. - optional uint64 amount_msat = 3; - - // The fees that were paid for this payment. - // - // For Lightning payments, this will only be updated for outbound payments once they - // succeeded. - optional uint64 fee_paid_msat = 7; - - // The direction of the payment. - PaymentDirection direction = 4; - - // The status of the payment. - PaymentStatus status = 5; - - // The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. - uint64 latest_update_timestamp = 6; -} - -message PaymentKind { - oneof kind { - Onchain onchain = 1; - Bolt11 bolt11 = 2; - Bolt11Jit bolt11_jit = 3; - Bolt12Offer bolt12_offer = 4; - Bolt12Refund bolt12_refund = 5; - Spontaneous spontaneous = 6; - } -} - -// Represents an on-chain payment. -message Onchain { - // The transaction identifier of this payment. - string txid = 1; - - // The confirmation status of this payment. - ConfirmationStatus status = 2; -} - -message ConfirmationStatus { - oneof status { - Confirmed confirmed = 1; - Unconfirmed unconfirmed = 2; - } -} - -// The on-chain transaction is confirmed in the best chain. -message Confirmed { - // The hex representation of hash of the block in which the transaction was confirmed. - string block_hash = 1; - - // The height under which the block was confirmed. - uint32 height = 2; - - // The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. - uint64 timestamp = 3; -} - -// The on-chain transaction is unconfirmed. -message Unconfirmed {} - -// Represents a BOLT 11 payment. -message Bolt11 { - // The payment hash, i.e., the hash of the preimage. - string hash = 1; - - // The pre-image used by the payment. - optional string preimage = 2; - - // The secret used by the payment. - optional bytes secret = 3; -} - -// Represents a BOLT 11 payment intended to open an LSPS 2 just-in-time channel. -message Bolt11Jit { - // The payment hash, i.e., the hash of the preimage. - string hash = 1; - - // The pre-image used by the payment. - optional string preimage = 2; - - // The secret used by the payment. - optional bytes secret = 3; - - // Limits applying to how much fee we allow an LSP to deduct from the payment amount. - // - // Allowing them to deduct this fee from the first inbound payment will pay for the LSP’s channel opening fees. - // - // See [`LdkChannelConfig::accept_underpaying_htlcs`](https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.accept_underpaying_htlcs) - // for more information. - LSPFeeLimits lsp_fee_limits = 4; - - // The value, in thousands of a satoshi, that was deducted from this payment as an extra - // fee taken by our channel counterparty. - // - // Will only be `Some` once we received the payment. - optional uint64 counterparty_skimmed_fee_msat = 5; -} - -// Represents a BOLT 12 ‘offer’ payment, i.e., a payment for an Offer. -message Bolt12Offer { - // The payment hash, i.e., the hash of the preimage. - optional string hash = 1; - - // The pre-image used by the payment. - optional string preimage = 2; - - // The secret used by the payment. - optional bytes secret = 3; - - // The hex-encoded ID of the offer this payment is for. - string offer_id = 4; - - // The payer's note for the payment. - // Truncated to [PAYER_NOTE_LIMIT](https://docs.rs/lightning/latest/lightning/offers/invoice_request/constant.PAYER_NOTE_LIMIT.html). - // - // **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, - // all non-printable characters will be sanitized and replaced with safe characters. - optional string payer_note = 5; - - // The quantity of an item requested in the offer. - optional uint64 quantity = 6; -} - -// Represents a BOLT 12 ‘refund’ payment, i.e., a payment for a Refund. -message Bolt12Refund { - // The payment hash, i.e., the hash of the preimage. - optional string hash = 1; - - // The pre-image used by the payment. - optional string preimage = 2; - - // The secret used by the payment. - optional bytes secret = 3; - - // The payer's note for the payment. - // Truncated to [PAYER_NOTE_LIMIT](https://docs.rs/lightning/latest/lightning/offers/invoice_request/constant.PAYER_NOTE_LIMIT.html). - // - // **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, - // all non-printable characters will be sanitized and replaced with safe characters. - optional string payer_note = 5; - - // The quantity of an item requested in the offer. - optional uint64 quantity = 6; - -} - -// Represents a spontaneous (“keysend”) payment. -message Spontaneous { - // The payment hash, i.e., the hash of the preimage. - string hash = 1; - - // The pre-image used by the payment. - optional string preimage = 2; -} - -// Limits applying to how much fee we allow an LSP to deduct from the payment amount. -// See [`LdkChannelConfig::accept_underpaying_htlcs`] for more information. -// -// [`LdkChannelConfig::accept_underpaying_htlcs`]: lightning::util::config::ChannelConfig::accept_underpaying_htlcs -message LSPFeeLimits { - // The maximal total amount we allow any configured LSP withhold from us when forwarding the - // payment. - optional uint64 max_total_opening_fee_msat = 1; - - // The maximal proportional fee, in parts-per-million millisatoshi, we allow any configured - // LSP withhold from us when forwarding the payment. - optional uint64 max_proportional_opening_fee_ppm_msat = 2; -} - -// Represents the direction of a payment. -enum PaymentDirection { - // The payment is inbound. - INBOUND = 0; - - // The payment is outbound. - OUTBOUND = 1; -} - -// Represents the current status of a payment. -enum PaymentStatus { - // The payment is still pending. - PENDING = 0; - - // The payment succeeded. - SUCCEEDED = 1; - - // The payment failed. - FAILED = 2; -} - -// A forwarded payment through our node. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.Event.html#variant.PaymentForwarded -message ForwardedPayment{ - // The channel id of the incoming channel between the previous node and us. - string prev_channel_id = 1; - - // The channel id of the outgoing channel between the next node and us. - string next_channel_id = 2; - - // The `user_channel_id` of the incoming channel between the previous node and us. - string prev_user_channel_id = 3; - - // The node id of the previous node. - string prev_node_id = 9; - - // The node id of the next node. - string next_node_id = 10; - - // The `user_channel_id` of the outgoing channel between the next node and us. - // This will be `None` if the payment was settled via an on-chain transaction. - // See the caveat described for the `total_fee_earned_msat` field. - optional string next_user_channel_id = 4; - - // The total fee, in milli-satoshis, which was earned as a result of the payment. - // - // Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC was pending, the amount the - // next hop claimed will have been rounded down to the nearest whole satoshi. Thus, the fee calculated here may be - // higher than expected as we still claimed the full value in millisatoshis from the source. - // In this case, `claim_from_onchain_tx` will be set. - // - // If the channel which sent us the payment has been force-closed, we will claim the funds via an on-chain transaction. - // In that case we do not yet know the on-chain transaction fees which we will spend and will instead set this to `None`. - optional uint64 total_fee_earned_msat = 5; - - // The share of the total fee, in milli-satoshis, which was withheld in addition to the forwarding fee. - // This will only be set if we forwarded an intercepted HTLC with less than the expected amount. This means our - // counterparty accepted to receive less than the invoice amount. - // - // The caveat described above the `total_fee_earned_msat` field applies here as well. - optional uint64 skimmed_fee_msat = 6; - - // If this is true, the forwarded HTLC was claimed by our counterparty via an on-chain transaction. - bool claim_from_onchain_tx = 7; - - // The final amount forwarded, in milli-satoshis, after the fee is deducted. - // - // The caveat described above the `total_fee_earned_msat` field applies here as well. - optional uint64 outbound_amount_forwarded_msat = 8; - -} - -message Channel { - // The channel ID (prior to funding transaction generation, this is a random 32-byte - // identifier, afterwards this is the transaction ID of the funding transaction XOR the - // funding transaction output). - // - // Note that this means this value is *not* persistent - it can change once during the - // lifetime of the channel. - string channel_id = 1; - - // The node ID of our the channel's remote counterparty. - string counterparty_node_id = 2; - - // The channel's funding transaction output, if we've negotiated the funding transaction with - // our counterparty already. - optional OutPoint funding_txo = 3; - - // The hex-encoded local `user_channel_id` of this channel. - string user_channel_id = 4; - - // The value, in satoshis, that must always be held as a reserve in the channel for us. This - // value ensures that if we broadcast a revoked state, our counterparty can punish us by - // claiming at least this value on chain. - // - // This value is not included in [`outbound_capacity_msat`] as it can never be spent. - // - // This value will be `None` for outbound channels until the counterparty accepts the channel. - optional uint64 unspendable_punishment_reserve = 5; - - // The value, in satoshis, of this channel as it appears in the funding output. - uint64 channel_value_sats = 6; - - // The currently negotiated fee rate denominated in satoshi per 1000 weight units, - // which is applied to commitment and HTLC transactions. - uint32 feerate_sat_per_1000_weight = 7; - - // The available outbound capacity for sending HTLCs to the remote peer. - // - // The amount does not include any pending HTLCs which are not yet resolved (and, thus, whose - // balance is not available for inclusion in new outbound HTLCs). This further does not include - // any pending outgoing HTLCs which are awaiting some other resolution to be sent. - uint64 outbound_capacity_msat = 8; - - // The available outbound capacity for sending HTLCs to the remote peer. - // - // The amount does not include any pending HTLCs which are not yet resolved - // (and, thus, whose balance is not available for inclusion in new inbound HTLCs). This further - // does not include any pending outgoing HTLCs which are awaiting some other resolution to be - // sent. - uint64 inbound_capacity_msat = 9; - - // The number of required confirmations on the funding transactions before the funding is - // considered "locked". The amount is selected by the channel fundee. - // - // The value will be `None` for outbound channels until the counterparty accepts the channel. - optional uint32 confirmations_required = 10; - - // The current number of confirmations on the funding transaction. - optional uint32 confirmations = 11; - - // Is `true` if the channel was initiated (and therefore funded) by us. - bool is_outbound = 12; - - // Is `true` if both parties have exchanged `channel_ready` messages, and the channel is - // not currently being shut down. Both parties exchange `channel_ready` messages upon - // independently verifying that the required confirmations count provided by - // `confirmations_required` has been reached. - bool is_channel_ready = 13; - - // Is `true` if the channel (a) `channel_ready` messages have been exchanged, (b) the - // peer is connected, and (c) the channel is not currently negotiating shutdown. - // - // This is a strict superset of `is_channel_ready`. - bool is_usable = 14; - - // Is `true` if this channel is (or will be) publicly-announced - bool is_announced = 15; - - // Set of configurable parameters set by self that affect channel operation. - ChannelConfig channel_config = 16; - - // The available outbound capacity for sending a single HTLC to the remote peer. This is - // similar to `outbound_capacity_msat` but it may be further restricted by - // the current state and per-HTLC limit(s). This is intended for use when routing, allowing us - // to use a limit as close as possible to the HTLC limit we can currently send. - uint64 next_outbound_htlc_limit_msat = 17; - - // The minimum value for sending a single HTLC to the remote peer. This is the equivalent of - // `next_outbound_htlc_limit_msat` but represents a lower-bound, rather than - // an upper-bound. This is intended for use when routing, allowing us to ensure we pick a - // route which is valid. - uint64 next_outbound_htlc_minimum_msat = 18; - - // The number of blocks (after our commitment transaction confirms) that we will need to wait - // until we can claim our funds after we force-close the channel. During this time our - // counterparty is allowed to punish us if we broadcasted a stale state. If our counterparty - // force-closes the channel and broadcasts a commitment transaction we do not have to wait any - // time to claim our non-HTLC-encumbered funds. - // - // This value will be `None` for outbound channels until the counterparty accepts the channel. - optional uint32 force_close_spend_delay = 19; - - // The smallest value HTLC (in msat) the remote peer will accept, for this channel. - // - // This field is only `None` before we have received either the `OpenChannel` or - // `AcceptChannel` message from the remote peer. - optional uint64 counterparty_outbound_htlc_minimum_msat = 20; - - // The largest value HTLC (in msat) the remote peer currently will accept, for this channel. - optional uint64 counterparty_outbound_htlc_maximum_msat = 21; - - // The value, in satoshis, that must always be held in the channel for our counterparty. This - // value ensures that if our counterparty broadcasts a revoked state, we can punish them by - // claiming at least this value on chain. - // - // This value is not included in `inbound_capacity_msat` as it can never be spent. - uint64 counterparty_unspendable_punishment_reserve = 22; - - // Base routing fee in millisatoshis. - optional uint32 counterparty_forwarding_info_fee_base_msat = 23; - - // Proportional fee, in millionths of a satoshi the channel will charge per transferred satoshi. - optional uint32 counterparty_forwarding_info_fee_proportional_millionths = 24; - - // The minimum difference in CLTV expiry between an ingoing HTLC and its outgoing counterpart, - // such that the outgoing HTLC is forwardable to this counterparty. - optional uint32 counterparty_forwarding_info_cltv_expiry_delta = 25; -} - -// ChannelConfig represents the configuration settings for a channel in a Lightning Network node. -// See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html -message ChannelConfig { - // Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound - // over the channel. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.forwarding_fee_proportional_millionths - optional uint32 forwarding_fee_proportional_millionths = 1; - - // Amount (in milli-satoshi) charged for payments forwarded outbound over the channel, - // in excess of forwarding_fee_proportional_millionths. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.forwarding_fee_base_msat - optional uint32 forwarding_fee_base_msat = 2; - - // The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded - // over the channel this config applies to. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.cltv_expiry_delta - optional uint32 cltv_expiry_delta = 3; - - // The maximum additional fee we’re willing to pay to avoid waiting for the counterparty’s - // to_self_delay to reclaim funds. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.force_close_avoidance_max_fee_satoshis - optional uint64 force_close_avoidance_max_fee_satoshis = 4; - - // If set, allows this channel’s counterparty to skim an additional fee off this node’s - // inbound HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.accept_underpaying_htlcs - optional bool accept_underpaying_htlcs = 5; - - // Limit our total exposure to potential loss to on-chain fees on close, including - // in-flight HTLCs which are burned to fees as they are too small to claim on-chain - // and fees on commitment transaction(s) broadcasted by our counterparty in excess of - // our own fee estimate. - // See more: https://docs.rs/lightning/latest/lightning/util/config/struct.ChannelConfig.html#structfield.max_dust_htlc_exposure - oneof max_dust_htlc_exposure { - - // This sets a fixed limit on the total dust exposure in millisatoshis. - // See more: https://docs.rs/lightning/latest/lightning/util/config/enum.MaxDustHTLCExposure.html#variant.FixedLimitMsat - uint64 fixed_limit_msat = 6; - - // This sets a multiplier on the ConfirmationTarget::OnChainSweep feerate (in sats/KW) to determine the maximum allowed dust exposure. - // See more: https://docs.rs/lightning/latest/lightning/util/config/enum.MaxDustHTLCExposure.html#variant.FeeRateMultiplier - uint64 fee_rate_multiplier = 7; - } -} - -// Represent a transaction outpoint. -message OutPoint { - // The referenced transaction's txid. - string txid = 1; - - // The index of the referenced output in its transaction's vout. - uint32 vout = 2; -} - -message BestBlock { - // The block’s hash - string block_hash = 1; - - // The height at which the block was confirmed. - uint32 height = 2; -} - -// Details about the status of a known Lightning balance. -message LightningBalance { - oneof balance_type { - ClaimableOnChannelClose claimable_on_channel_close = 1; - ClaimableAwaitingConfirmations claimable_awaiting_confirmations = 2; - ContentiousClaimable contentious_claimable = 3; - MaybeTimeoutClaimableHTLC maybe_timeout_claimable_htlc = 4; - MaybePreimageClaimableHTLC maybe_preimage_claimable_htlc = 5; - CounterpartyRevokedOutputClaimable counterparty_revoked_output_claimable = 6; - } -} - -// The channel is not yet closed (or the commitment or closing transaction has not yet appeared in a block). -// The given balance is claimable (less on-chain fees) if the channel is force-closed now. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.ClaimableOnChannelClose -message ClaimableOnChannelClose { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount available to claim, in satoshis, excluding the on-chain fees which will be required to do so. - uint64 amount_satoshis = 3; - - // The transaction fee we pay for the closing commitment transaction. - // This amount is not included in the `amount_satoshis` value. - // - // Note that if this channel is inbound (and thus our counterparty pays the commitment transaction fee) this value - // will be zero. - uint64 transaction_fee_satoshis = 4; - - // The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to - // a payment which was sent by us. This is the sum of the millisatoshis part of all HTLCs which are otherwise - // represented by `LightningBalance::MaybeTimeoutClaimableHTLC` with their - // `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` flag set, as well as any dust HTLCs which would - // otherwise be represented the same. - // - // This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - uint64 outbound_payment_htlc_rounded_msat = 5; - - // The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to - // a forwarded HTLC. This is the sum of the millisatoshis part of all HTLCs which are otherwise represented by - // `LightningBalance::MaybeTimeoutClaimableHTLC` with their `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` - // flag not set, as well as any dust HTLCs which would otherwise be represented the same. - // - // This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - uint64 outbound_forwarded_htlc_rounded_msat = 6; - - // The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we know - // the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by - // `LightningBalance::ContentiousClaimable` on channel close, but whose current value is included in `amount_satoshis`, - // as well as any dust HTLCs which would otherwise be represented the same. - // - // This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - uint64 inbound_claiming_htlc_rounded_msat = 7; - - // The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we do - // not know the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by - // `LightningBalance::MaybePreimageClaimableHTLC` on channel close, as well as any dust HTLCs which would otherwise be - // represented the same. - // - // This amount (rounded up to a whole satoshi value) will not be included in the counterparty’s `amount_satoshis`. - uint64 inbound_htlc_rounded_msat = 8; -} - -// The channel has been closed, and the given balance is ours but awaiting confirmations until we consider it spendable. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.ClaimableAwaitingConfirmations -message ClaimableAwaitingConfirmations { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount available to claim, in satoshis, possibly excluding the on-chain fees which were spent in broadcasting - // the transaction. - uint64 amount_satoshis = 3; - - // The height at which we start tracking it as `SpendableOutput`. - uint32 confirmation_height = 4; - - // Whether this balance is a result of cooperative close, a force-close, or an HTLC. - BalanceSource source = 5; -} - -// Indicates whether the balance is derived from a cooperative close, a force-close (for holder or counterparty), -// or whether it is for an HTLC. -enum BalanceSource { - // The channel was force closed by the holder. - HOLDER_FORCE_CLOSED = 0; - - // The channel was force closed by the counterparty. - COUNTERPARTY_FORCE_CLOSED = 1; - - // The channel was cooperatively closed. - COOP_CLOSE = 2; - - // This balance is the result of an HTLC. - HTLC = 3; -} - -// The channel has been closed, and the given balance should be ours but awaiting spending transaction confirmation. -// If the spending transaction does not confirm in time, it is possible our counterparty can take the funds by -// broadcasting an HTLC timeout on-chain. -// -// Once the spending transaction confirms, before it has reached enough confirmations to be considered safe from chain -// reorganizations, the balance will instead be provided via `LightningBalance::ClaimableAwaitingConfirmations`. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.ContentiousClaimable -message ContentiousClaimable { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - // the transaction. - uint64 amount_satoshis = 3; - - // The height at which the counterparty may be able to claim the balance if we have not done so. - uint32 timeout_height = 4; - - // The payment hash that locks this HTLC. - string payment_hash = 5; - - // The preimage that can be used to claim this HTLC. - string payment_preimage = 6; -} - -// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain fees) if the counterparty -// does not know the preimage for the HTLCs. These are somewhat likely to be claimed by our counterparty before we do. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.MaybeTimeoutClaimableHTLC -message MaybeTimeoutClaimableHTLC { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - // the transaction. - uint64 amount_satoshis = 3; - - // The height at which we will be able to claim the balance if our counterparty has not done so. - uint32 claimable_height = 4; - - // The payment hash whose preimage our counterparty needs to claim this HTLC. - string payment_hash = 5; - - // Indicates whether this HTLC represents a payment which was sent outbound from us. - bool outbound_payment = 6; -} - -// HTLCs which we received from our counterparty which are claimable with a preimage which we do not currently have. -// This will only be claimable if we receive the preimage from the node to which we forwarded this HTLC before the -// timeout. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.MaybePreimageClaimableHTLC -message MaybePreimageClaimableHTLC { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - // the transaction. - uint64 amount_satoshis = 3; - - // The height at which our counterparty will be able to claim the balance if we have not yet received the preimage and - // claimed it ourselves. - uint32 expiry_height = 4; - - // The payment hash whose preimage we need to claim this HTLC. - string payment_hash = 5; -} -// The channel has been closed, and our counterparty broadcasted a revoked commitment transaction. -// -// Thus, we’re able to claim all outputs in the commitment transaction, one of which has the following amount. -// -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.LightningBalance.html#variant.CounterpartyRevokedOutputClaimable -message CounterpartyRevokedOutputClaimable { - // The identifier of the channel this balance belongs to. - string channel_id = 1; - - // The identifier of our channel counterparty. - string counterparty_node_id = 2; - - // The amount, in satoshis, of the output which we can claim. - uint64 amount_satoshis = 3; -} - -// Details about the status of a known balance currently being swept to our on-chain wallet. -message PendingSweepBalance { - oneof balance_type { - PendingBroadcast pending_broadcast = 1; - BroadcastAwaitingConfirmation broadcast_awaiting_confirmation = 2; - AwaitingThresholdConfirmations awaiting_threshold_confirmations = 3; - } -} - -// The spendable output is about to be swept, but a spending transaction has yet to be generated and broadcast. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.PendingSweepBalance.html#variant.PendingBroadcast -message PendingBroadcast { - // The identifier of the channel this balance belongs to. - optional string channel_id = 1; - - // The amount, in satoshis, of the output being swept. - uint64 amount_satoshis = 2; -} - -// A spending transaction has been generated and broadcast and is awaiting confirmation on-chain. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.PendingSweepBalance.html#variant.BroadcastAwaitingConfirmation -message BroadcastAwaitingConfirmation { - // The identifier of the channel this balance belongs to. - optional string channel_id = 1; - - // The best height when we last broadcast a transaction spending the output being swept. - uint32 latest_broadcast_height = 2; - - // The identifier of the transaction spending the swept output we last broadcast. - string latest_spending_txid = 3; - - // The amount, in satoshis, of the output being swept. - uint64 amount_satoshis = 4; -} - -// A spending transaction has been confirmed on-chain and is awaiting threshold confirmations. -// -// It will be considered irrevocably confirmed after reaching `ANTI_REORG_DELAY`. -// See more: https://docs.rs/ldk-node/latest/ldk_node/enum.PendingSweepBalance.html#variant.AwaitingThresholdConfirmations -message AwaitingThresholdConfirmations { - // The identifier of the channel this balance belongs to. - optional string channel_id = 1; - - // The identifier of the confirmed transaction spending the swept output. - string latest_spending_txid = 2; - - // The hash of the block in which the spending transaction was confirmed. - string confirmation_hash = 3; - - // The height at which the spending transaction was confirmed. - uint32 confirmation_height = 4; - - // The amount, in satoshis, of the output being swept. - uint64 amount_satoshis = 5; -} - -// Token used to determine start of next page in paginated APIs. -message PageToken { - string token = 1; - int64 index = 2; -} - -message Bolt11InvoiceDescription { - oneof kind { - string direct = 1; - string hash = 2; - } -} - -// Configuration options for payment routing and pathfinding. -// See https://docs.rs/lightning/0.2.0/lightning/routing/router/struct.RouteParametersConfig.html for more details on each field. -message RouteParametersConfig { - // The maximum total fees, in millisatoshi, that may accrue during route finding. - // Defaults to 1% of the payment amount + 50 sats - optional uint64 max_total_routing_fee_msat = 1; - - // The maximum total CLTV delta we accept for the route. - // Defaults to 1008. - uint32 max_total_cltv_expiry_delta = 2; - - // The maximum number of paths that may be used by (MPP) payments. - // Defaults to 10. - uint32 max_path_count = 3; - - // Selects the maximum share of a channel's total capacity which will be - // sent over a channel, as a power of 1/2. - // Default value: 2 - uint32 max_channel_saturation_power_of_half = 4; -} - -// Routing fees for a channel as part of the network graph. -message GraphRoutingFees { - // Flat routing fee in millisatoshis. - uint32 base_msat = 1; - - // Liquidity-based routing fee in millionths of a routed amount. - uint32 proportional_millionths = 2; -} - -// Details about one direction of a channel in the network graph, -// as received within a `ChannelUpdate`. -message GraphChannelUpdate { - // When the last update to the channel direction was issued. - // Value is opaque, as set in the announcement. - uint32 last_update = 1; - - // Whether the channel can be currently used for payments (in this one direction). - bool enabled = 2; - - // The difference in CLTV values that you must have when routing through this channel. - uint32 cltv_expiry_delta = 3; - - // The minimum value, which must be relayed to the next hop via the channel. - uint64 htlc_minimum_msat = 4; - - // The maximum value which may be relayed to the next hop via the channel. - uint64 htlc_maximum_msat = 5; - - // Fees charged when the channel is used for routing. - GraphRoutingFees fees = 6; -} - -// Details about a channel in the network graph (both directions). -// Received within a channel announcement. -message GraphChannel { - // Source node of the first direction of the channel (hex-encoded public key). - string node_one = 1; - - // Source node of the second direction of the channel (hex-encoded public key). - string node_two = 2; - - // The channel capacity as seen on-chain, if chain lookup is available. - optional uint64 capacity_sats = 3; - - // Details about the first direction of a channel. - GraphChannelUpdate one_to_two = 4; - - // Details about the second direction of a channel. - GraphChannelUpdate two_to_one = 5; -} - -// Information received in the latest node_announcement from this node. -message GraphNodeAnnouncement { - // When the last known update to the node state was issued. - // Value is opaque, as set in the announcement. - uint32 last_update = 1; - - // Moniker assigned to the node. - // May be invalid or malicious (eg control chars), should not be exposed to the user. - string alias = 2; - - // Color assigned to the node as a hex-encoded RGB string, e.g. "ff0000". - string rgb = 3; - - // List of addresses on which this node is reachable. - repeated string addresses = 4; -} - -// Details of a known Lightning peer. -// See more: https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.list_peers -message Peer { - // The hex-encoded node ID of the peer. - string node_id = 1; - - // The network address of the peer. - string address = 2; - - // Indicates whether we'll try to reconnect to this peer after restarts. - bool is_persisted = 3; - - // Indicates whether we currently have an active connection with the peer. - bool is_connected = 4; -} - -// Details about a node in the network graph, known from the network announcement. -message GraphNode { - // All valid channels a node has announced. - repeated uint64 channels = 1; - - // More information about a node from node_announcement. - // Optional because we store a node entry after learning about it from - // a channel announcement, but before receiving a node announcement. - GraphNodeAnnouncement announcement_info = 2; -} diff --git a/ldk-server-protos/src/serde_utils.rs b/ldk-server-protos/src/serde_utils.rs deleted file mode 100644 index 5f10d137..00000000 --- a/ldk-server-protos/src/serde_utils.rs +++ /dev/null @@ -1,58 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -//! Custom serde serializers for proto types. -//! -//! These are used via `#[serde(serialize_with = "...")]` attributes on generated -//! proto fields to produce human-readable output (hex strings for bytes, enum -//! names for integer enum fields). - -use std::fmt::Write; - -use serde::Serializer; - -/// Generates a serde serializer that converts an `i32` proto enum field to its -/// string name via `from_i32()` and `as_str_name()`. -macro_rules! stringify_enum_serializer { - ($fn_name:ident, $enum_type:ty) => { - pub fn $fn_name(value: &i32, serializer: S) -> Result - where - S: serde::Serializer, - { - let name = match <$enum_type>::from_i32(*value) { - Some(v) => v.as_str_name(), - None => "UNKNOWN", - }; - serializer.serialize_str(name) - } - }; -} - -stringify_enum_serializer!(serialize_payment_direction, crate::types::PaymentDirection); -stringify_enum_serializer!(serialize_payment_status, crate::types::PaymentStatus); -stringify_enum_serializer!(serialize_balance_source, crate::types::BalanceSource); - -/// Serializes `Option` as a hex string (or null). -pub fn serialize_opt_bytes_hex( - value: &Option, serializer: S, -) -> Result -where - S: Serializer, -{ - match value { - Some(bytes) => { - let hex = bytes.iter().fold(String::with_capacity(bytes.len() * 2), |mut acc, b| { - let _ = write!(acc, "{b:02x}"); - acc - }); - serializer.serialize_some(&hex) - }, - None => serializer.serialize_none(), - } -} diff --git a/ldk-server-protos/src/types.rs b/ldk-server-protos/src/types.rs deleted file mode 100644 index 280e34f4..00000000 --- a/ldk-server-protos/src/types.rs +++ /dev/null @@ -1,1143 +0,0 @@ -// This file is Copyright its original authors, visible in version control -// history. -// -// This file is licensed under the Apache License, Version 2.0 or the MIT license -// , at your option. -// You may not use this file except in accordance with one or both of these -// licenses. - -/// Represents a payment. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Payment { - /// An identifier used to uniquely identify a payment in hex-encoded form. - #[prost(string, tag = "1")] - pub id: ::prost::alloc::string::String, - /// The kind of the payment. - #[prost(message, optional, tag = "2")] - pub kind: ::core::option::Option, - /// The amount transferred. - #[prost(uint64, optional, tag = "3")] - pub amount_msat: ::core::option::Option, - /// The fees that were paid for this payment. - /// - /// For Lightning payments, this will only be updated for outbound payments once they - /// succeeded. - #[prost(uint64, optional, tag = "7")] - pub fee_paid_msat: ::core::option::Option, - /// The direction of the payment. - #[prost(enumeration = "PaymentDirection", tag = "4")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_payment_direction") - )] - pub direction: i32, - /// The status of the payment. - #[prost(enumeration = "PaymentStatus", tag = "5")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_payment_status") - )] - pub status: i32, - /// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. - #[prost(uint64, tag = "6")] - pub latest_update_timestamp: u64, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PaymentKind { - #[prost(oneof = "payment_kind::Kind", tags = "1, 2, 3, 4, 5, 6")] - pub kind: ::core::option::Option, -} -/// Nested message and enum types in `PaymentKind`. -pub mod payment_kind { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Kind { - #[prost(message, tag = "1")] - Onchain(super::Onchain), - #[prost(message, tag = "2")] - Bolt11(super::Bolt11), - #[prost(message, tag = "3")] - Bolt11Jit(super::Bolt11Jit), - #[prost(message, tag = "4")] - Bolt12Offer(super::Bolt12Offer), - #[prost(message, tag = "5")] - Bolt12Refund(super::Bolt12Refund), - #[prost(message, tag = "6")] - Spontaneous(super::Spontaneous), - } -} -/// Represents an on-chain payment. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Onchain { - /// The transaction identifier of this payment. - #[prost(string, tag = "1")] - pub txid: ::prost::alloc::string::String, - /// The confirmation status of this payment. - #[prost(message, optional, tag = "2")] - pub status: ::core::option::Option, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ConfirmationStatus { - #[prost(oneof = "confirmation_status::Status", tags = "1, 2")] - pub status: ::core::option::Option, -} -/// Nested message and enum types in `ConfirmationStatus`. -pub mod confirmation_status { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Status { - #[prost(message, tag = "1")] - Confirmed(super::Confirmed), - #[prost(message, tag = "2")] - Unconfirmed(super::Unconfirmed), - } -} -/// The on-chain transaction is confirmed in the best chain. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Confirmed { - /// The hex representation of hash of the block in which the transaction was confirmed. - #[prost(string, tag = "1")] - pub block_hash: ::prost::alloc::string::String, - /// The height under which the block was confirmed. - #[prost(uint32, tag = "2")] - pub height: u32, - /// The timestamp, in seconds since start of the UNIX epoch, when this entry was last updated. - #[prost(uint64, tag = "3")] - pub timestamp: u64, -} -/// The on-chain transaction is unconfirmed. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Unconfirmed {} -/// Represents a BOLT 11 payment. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11 { - /// The payment hash, i.e., the hash of the preimage. - #[prost(string, tag = "1")] - pub hash: ::prost::alloc::string::String, - /// The pre-image used by the payment. - #[prost(string, optional, tag = "2")] - pub preimage: ::core::option::Option<::prost::alloc::string::String>, - /// The secret used by the payment. - #[prost(bytes = "bytes", optional, tag = "3")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_opt_bytes_hex") - )] - pub secret: ::core::option::Option<::prost::bytes::Bytes>, -} -/// Represents a BOLT 11 payment intended to open an LSPS 2 just-in-time channel. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11Jit { - /// The payment hash, i.e., the hash of the preimage. - #[prost(string, tag = "1")] - pub hash: ::prost::alloc::string::String, - /// The pre-image used by the payment. - #[prost(string, optional, tag = "2")] - pub preimage: ::core::option::Option<::prost::alloc::string::String>, - /// The secret used by the payment. - #[prost(bytes = "bytes", optional, tag = "3")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_opt_bytes_hex") - )] - pub secret: ::core::option::Option<::prost::bytes::Bytes>, - /// Limits applying to how much fee we allow an LSP to deduct from the payment amount. - /// - /// Allowing them to deduct this fee from the first inbound payment will pay for the LSP’s channel opening fees. - /// - /// See \[`LdkChannelConfig::accept_underpaying_htlcs`\]() - /// for more information. - #[prost(message, optional, tag = "4")] - pub lsp_fee_limits: ::core::option::Option, - /// The value, in thousands of a satoshi, that was deducted from this payment as an extra - /// fee taken by our channel counterparty. - /// - /// Will only be `Some` once we received the payment. - #[prost(uint64, optional, tag = "5")] - pub counterparty_skimmed_fee_msat: ::core::option::Option, -} -/// Represents a BOLT 12 ‘offer’ payment, i.e., a payment for an Offer. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt12Offer { - /// The payment hash, i.e., the hash of the preimage. - #[prost(string, optional, tag = "1")] - pub hash: ::core::option::Option<::prost::alloc::string::String>, - /// The pre-image used by the payment. - #[prost(string, optional, tag = "2")] - pub preimage: ::core::option::Option<::prost::alloc::string::String>, - /// The secret used by the payment. - #[prost(bytes = "bytes", optional, tag = "3")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_opt_bytes_hex") - )] - pub secret: ::core::option::Option<::prost::bytes::Bytes>, - /// The hex-encoded ID of the offer this payment is for. - #[prost(string, tag = "4")] - pub offer_id: ::prost::alloc::string::String, - /// The payer's note for the payment. - /// Truncated to \[PAYER_NOTE_LIMIT\](). - /// - /// **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, - /// all non-printable characters will be sanitized and replaced with safe characters. - #[prost(string, optional, tag = "5")] - pub payer_note: ::core::option::Option<::prost::alloc::string::String>, - /// The quantity of an item requested in the offer. - #[prost(uint64, optional, tag = "6")] - pub quantity: ::core::option::Option, -} -/// Represents a BOLT 12 ‘refund’ payment, i.e., a payment for a Refund. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt12Refund { - /// The payment hash, i.e., the hash of the preimage. - #[prost(string, optional, tag = "1")] - pub hash: ::core::option::Option<::prost::alloc::string::String>, - /// The pre-image used by the payment. - #[prost(string, optional, tag = "2")] - pub preimage: ::core::option::Option<::prost::alloc::string::String>, - /// The secret used by the payment. - #[prost(bytes = "bytes", optional, tag = "3")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_opt_bytes_hex") - )] - pub secret: ::core::option::Option<::prost::bytes::Bytes>, - /// The payer's note for the payment. - /// Truncated to \[PAYER_NOTE_LIMIT\](). - /// - /// **Caution**: The `payer_note` field may come from an untrusted source. To prevent potential misuse, - /// all non-printable characters will be sanitized and replaced with safe characters. - #[prost(string, optional, tag = "5")] - pub payer_note: ::core::option::Option<::prost::alloc::string::String>, - /// The quantity of an item requested in the offer. - #[prost(uint64, optional, tag = "6")] - pub quantity: ::core::option::Option, -} -/// Represents a spontaneous (“keysend”) payment. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Spontaneous { - /// The payment hash, i.e., the hash of the preimage. - #[prost(string, tag = "1")] - pub hash: ::prost::alloc::string::String, - /// The pre-image used by the payment. - #[prost(string, optional, tag = "2")] - pub preimage: ::core::option::Option<::prost::alloc::string::String>, -} -/// Limits applying to how much fee we allow an LSP to deduct from the payment amount. -/// See \[`LdkChannelConfig::accept_underpaying_htlcs`\] for more information. -/// -/// \[`LdkChannelConfig::accept_underpaying_htlcs`\]: lightning::util::config::ChannelConfig::accept_underpaying_htlcs -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct LspFeeLimits { - /// The maximal total amount we allow any configured LSP withhold from us when forwarding the - /// payment. - #[prost(uint64, optional, tag = "1")] - pub max_total_opening_fee_msat: ::core::option::Option, - /// The maximal proportional fee, in parts-per-million millisatoshi, we allow any configured - /// LSP withhold from us when forwarding the payment. - #[prost(uint64, optional, tag = "2")] - pub max_proportional_opening_fee_ppm_msat: ::core::option::Option, -} -/// A forwarded payment through our node. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ForwardedPayment { - /// The channel id of the incoming channel between the previous node and us. - #[prost(string, tag = "1")] - pub prev_channel_id: ::prost::alloc::string::String, - /// The channel id of the outgoing channel between the next node and us. - #[prost(string, tag = "2")] - pub next_channel_id: ::prost::alloc::string::String, - /// The `user_channel_id` of the incoming channel between the previous node and us. - #[prost(string, tag = "3")] - pub prev_user_channel_id: ::prost::alloc::string::String, - /// The node id of the previous node. - #[prost(string, tag = "9")] - pub prev_node_id: ::prost::alloc::string::String, - /// The node id of the next node. - #[prost(string, tag = "10")] - pub next_node_id: ::prost::alloc::string::String, - /// The `user_channel_id` of the outgoing channel between the next node and us. - /// This will be `None` if the payment was settled via an on-chain transaction. - /// See the caveat described for the `total_fee_earned_msat` field. - #[prost(string, optional, tag = "4")] - pub next_user_channel_id: ::core::option::Option<::prost::alloc::string::String>, - /// The total fee, in milli-satoshis, which was earned as a result of the payment. - /// - /// Note that if we force-closed the channel over which we forwarded an HTLC while the HTLC was pending, the amount the - /// next hop claimed will have been rounded down to the nearest whole satoshi. Thus, the fee calculated here may be - /// higher than expected as we still claimed the full value in millisatoshis from the source. - /// In this case, `claim_from_onchain_tx` will be set. - /// - /// If the channel which sent us the payment has been force-closed, we will claim the funds via an on-chain transaction. - /// In that case we do not yet know the on-chain transaction fees which we will spend and will instead set this to `None`. - #[prost(uint64, optional, tag = "5")] - pub total_fee_earned_msat: ::core::option::Option, - /// The share of the total fee, in milli-satoshis, which was withheld in addition to the forwarding fee. - /// This will only be set if we forwarded an intercepted HTLC with less than the expected amount. This means our - /// counterparty accepted to receive less than the invoice amount. - /// - /// The caveat described above the `total_fee_earned_msat` field applies here as well. - #[prost(uint64, optional, tag = "6")] - pub skimmed_fee_msat: ::core::option::Option, - /// If this is true, the forwarded HTLC was claimed by our counterparty via an on-chain transaction. - #[prost(bool, tag = "7")] - pub claim_from_onchain_tx: bool, - /// The final amount forwarded, in milli-satoshis, after the fee is deducted. - /// - /// The caveat described above the `total_fee_earned_msat` field applies here as well. - #[prost(uint64, optional, tag = "8")] - pub outbound_amount_forwarded_msat: ::core::option::Option, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Channel { - /// The channel ID (prior to funding transaction generation, this is a random 32-byte - /// identifier, afterwards this is the transaction ID of the funding transaction XOR the - /// funding transaction output). - /// - /// Note that this means this value is *not* persistent - it can change once during the - /// lifetime of the channel. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The node ID of our the channel's remote counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The channel's funding transaction output, if we've negotiated the funding transaction with - /// our counterparty already. - #[prost(message, optional, tag = "3")] - pub funding_txo: ::core::option::Option, - /// The hex-encoded local `user_channel_id` of this channel. - #[prost(string, tag = "4")] - pub user_channel_id: ::prost::alloc::string::String, - /// The value, in satoshis, that must always be held as a reserve in the channel for us. This - /// value ensures that if we broadcast a revoked state, our counterparty can punish us by - /// claiming at least this value on chain. - /// - /// This value is not included in \[`outbound_capacity_msat`\] as it can never be spent. - /// - /// This value will be `None` for outbound channels until the counterparty accepts the channel. - #[prost(uint64, optional, tag = "5")] - pub unspendable_punishment_reserve: ::core::option::Option, - /// The value, in satoshis, of this channel as it appears in the funding output. - #[prost(uint64, tag = "6")] - pub channel_value_sats: u64, - /// The currently negotiated fee rate denominated in satoshi per 1000 weight units, - /// which is applied to commitment and HTLC transactions. - #[prost(uint32, tag = "7")] - pub feerate_sat_per_1000_weight: u32, - /// The available outbound capacity for sending HTLCs to the remote peer. - /// - /// The amount does not include any pending HTLCs which are not yet resolved (and, thus, whose - /// balance is not available for inclusion in new outbound HTLCs). This further does not include - /// any pending outgoing HTLCs which are awaiting some other resolution to be sent. - #[prost(uint64, tag = "8")] - pub outbound_capacity_msat: u64, - /// The available outbound capacity for sending HTLCs to the remote peer. - /// - /// The amount does not include any pending HTLCs which are not yet resolved - /// (and, thus, whose balance is not available for inclusion in new inbound HTLCs). This further - /// does not include any pending outgoing HTLCs which are awaiting some other resolution to be - /// sent. - #[prost(uint64, tag = "9")] - pub inbound_capacity_msat: u64, - /// The number of required confirmations on the funding transactions before the funding is - /// considered "locked". The amount is selected by the channel fundee. - /// - /// The value will be `None` for outbound channels until the counterparty accepts the channel. - #[prost(uint32, optional, tag = "10")] - pub confirmations_required: ::core::option::Option, - /// The current number of confirmations on the funding transaction. - #[prost(uint32, optional, tag = "11")] - pub confirmations: ::core::option::Option, - /// Is `true` if the channel was initiated (and therefore funded) by us. - #[prost(bool, tag = "12")] - pub is_outbound: bool, - /// Is `true` if both parties have exchanged `channel_ready` messages, and the channel is - /// not currently being shut down. Both parties exchange `channel_ready` messages upon - /// independently verifying that the required confirmations count provided by - /// `confirmations_required` has been reached. - #[prost(bool, tag = "13")] - pub is_channel_ready: bool, - /// Is `true` if the channel (a) `channel_ready` messages have been exchanged, (b) the - /// peer is connected, and (c) the channel is not currently negotiating shutdown. - /// - /// This is a strict superset of `is_channel_ready`. - #[prost(bool, tag = "14")] - pub is_usable: bool, - /// Is `true` if this channel is (or will be) publicly-announced - #[prost(bool, tag = "15")] - pub is_announced: bool, - /// Set of configurable parameters set by self that affect channel operation. - #[prost(message, optional, tag = "16")] - pub channel_config: ::core::option::Option, - /// The available outbound capacity for sending a single HTLC to the remote peer. This is - /// similar to `outbound_capacity_msat` but it may be further restricted by - /// the current state and per-HTLC limit(s). This is intended for use when routing, allowing us - /// to use a limit as close as possible to the HTLC limit we can currently send. - #[prost(uint64, tag = "17")] - pub next_outbound_htlc_limit_msat: u64, - /// The minimum value for sending a single HTLC to the remote peer. This is the equivalent of - /// `next_outbound_htlc_limit_msat` but represents a lower-bound, rather than - /// an upper-bound. This is intended for use when routing, allowing us to ensure we pick a - /// route which is valid. - #[prost(uint64, tag = "18")] - pub next_outbound_htlc_minimum_msat: u64, - /// The number of blocks (after our commitment transaction confirms) that we will need to wait - /// until we can claim our funds after we force-close the channel. During this time our - /// counterparty is allowed to punish us if we broadcasted a stale state. If our counterparty - /// force-closes the channel and broadcasts a commitment transaction we do not have to wait any - /// time to claim our non-HTLC-encumbered funds. - /// - /// This value will be `None` for outbound channels until the counterparty accepts the channel. - #[prost(uint32, optional, tag = "19")] - pub force_close_spend_delay: ::core::option::Option, - /// The smallest value HTLC (in msat) the remote peer will accept, for this channel. - /// - /// This field is only `None` before we have received either the `OpenChannel` or - /// `AcceptChannel` message from the remote peer. - #[prost(uint64, optional, tag = "20")] - pub counterparty_outbound_htlc_minimum_msat: ::core::option::Option, - /// The largest value HTLC (in msat) the remote peer currently will accept, for this channel. - #[prost(uint64, optional, tag = "21")] - pub counterparty_outbound_htlc_maximum_msat: ::core::option::Option, - /// The value, in satoshis, that must always be held in the channel for our counterparty. This - /// value ensures that if our counterparty broadcasts a revoked state, we can punish them by - /// claiming at least this value on chain. - /// - /// This value is not included in `inbound_capacity_msat` as it can never be spent. - #[prost(uint64, tag = "22")] - pub counterparty_unspendable_punishment_reserve: u64, - /// Base routing fee in millisatoshis. - #[prost(uint32, optional, tag = "23")] - pub counterparty_forwarding_info_fee_base_msat: ::core::option::Option, - /// Proportional fee, in millionths of a satoshi the channel will charge per transferred satoshi. - #[prost(uint32, optional, tag = "24")] - pub counterparty_forwarding_info_fee_proportional_millionths: ::core::option::Option, - /// The minimum difference in CLTV expiry between an ingoing HTLC and its outgoing counterpart, - /// such that the outgoing HTLC is forwardable to this counterparty. - #[prost(uint32, optional, tag = "25")] - pub counterparty_forwarding_info_cltv_expiry_delta: ::core::option::Option, -} -/// ChannelConfig represents the configuration settings for a channel in a Lightning Network node. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ChannelConfig { - /// Amount (in millionths of a satoshi) charged per satoshi for payments forwarded outbound - /// over the channel. - /// See more: - #[prost(uint32, optional, tag = "1")] - pub forwarding_fee_proportional_millionths: ::core::option::Option, - /// Amount (in milli-satoshi) charged for payments forwarded outbound over the channel, - /// in excess of forwarding_fee_proportional_millionths. - /// See more: - #[prost(uint32, optional, tag = "2")] - pub forwarding_fee_base_msat: ::core::option::Option, - /// The difference in the CLTV value between incoming HTLCs and an outbound HTLC forwarded - /// over the channel this config applies to. - /// See more: - #[prost(uint32, optional, tag = "3")] - pub cltv_expiry_delta: ::core::option::Option, - /// The maximum additional fee we’re willing to pay to avoid waiting for the counterparty’s - /// to_self_delay to reclaim funds. - /// See more: - #[prost(uint64, optional, tag = "4")] - pub force_close_avoidance_max_fee_satoshis: ::core::option::Option, - /// If set, allows this channel’s counterparty to skim an additional fee off this node’s - /// inbound HTLCs. Useful for liquidity providers to offload on-chain channel costs to end users. - /// See more: - #[prost(bool, optional, tag = "5")] - pub accept_underpaying_htlcs: ::core::option::Option, - /// Limit our total exposure to potential loss to on-chain fees on close, including - /// in-flight HTLCs which are burned to fees as they are too small to claim on-chain - /// and fees on commitment transaction(s) broadcasted by our counterparty in excess of - /// our own fee estimate. - /// See more: - #[prost(oneof = "channel_config::MaxDustHtlcExposure", tags = "6, 7")] - pub max_dust_htlc_exposure: ::core::option::Option, -} -/// Nested message and enum types in `ChannelConfig`. -pub mod channel_config { - /// Limit our total exposure to potential loss to on-chain fees on close, including - /// in-flight HTLCs which are burned to fees as they are too small to claim on-chain - /// and fees on commitment transaction(s) broadcasted by our counterparty in excess of - /// our own fee estimate. - /// See more: - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum MaxDustHtlcExposure { - /// This sets a fixed limit on the total dust exposure in millisatoshis. - /// See more: - #[prost(uint64, tag = "6")] - FixedLimitMsat(u64), - /// This sets a multiplier on the ConfirmationTarget::OnChainSweep feerate (in sats/KW) to determine the maximum allowed dust exposure. - /// See more: - #[prost(uint64, tag = "7")] - FeeRateMultiplier(u64), - } -} -/// Represent a transaction outpoint. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct OutPoint { - /// The referenced transaction's txid. - #[prost(string, tag = "1")] - pub txid: ::prost::alloc::string::String, - /// The index of the referenced output in its transaction's vout. - #[prost(uint32, tag = "2")] - pub vout: u32, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BestBlock { - /// The block’s hash - #[prost(string, tag = "1")] - pub block_hash: ::prost::alloc::string::String, - /// The height at which the block was confirmed. - #[prost(uint32, tag = "2")] - pub height: u32, -} -/// Details about the status of a known Lightning balance. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct LightningBalance { - #[prost(oneof = "lightning_balance::BalanceType", tags = "1, 2, 3, 4, 5, 6")] - pub balance_type: ::core::option::Option, -} -/// Nested message and enum types in `LightningBalance`. -pub mod lightning_balance { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum BalanceType { - #[prost(message, tag = "1")] - ClaimableOnChannelClose(super::ClaimableOnChannelClose), - #[prost(message, tag = "2")] - ClaimableAwaitingConfirmations(super::ClaimableAwaitingConfirmations), - #[prost(message, tag = "3")] - ContentiousClaimable(super::ContentiousClaimable), - #[prost(message, tag = "4")] - MaybeTimeoutClaimableHtlc(super::MaybeTimeoutClaimableHtlc), - #[prost(message, tag = "5")] - MaybePreimageClaimableHtlc(super::MaybePreimageClaimableHtlc), - #[prost(message, tag = "6")] - CounterpartyRevokedOutputClaimable(super::CounterpartyRevokedOutputClaimable), - } -} -/// The channel is not yet closed (or the commitment or closing transaction has not yet appeared in a block). -/// The given balance is claimable (less on-chain fees) if the channel is force-closed now. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ClaimableOnChannelClose { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount available to claim, in satoshis, excluding the on-chain fees which will be required to do so. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, - /// The transaction fee we pay for the closing commitment transaction. - /// This amount is not included in the `amount_satoshis` value. - /// - /// Note that if this channel is inbound (and thus our counterparty pays the commitment transaction fee) this value - /// will be zero. - #[prost(uint64, tag = "4")] - pub transaction_fee_satoshis: u64, - /// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to - /// a payment which was sent by us. This is the sum of the millisatoshis part of all HTLCs which are otherwise - /// represented by `LightningBalance::MaybeTimeoutClaimableHTLC` with their - /// `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` flag set, as well as any dust HTLCs which would - /// otherwise be represented the same. - /// - /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - #[prost(uint64, tag = "5")] - pub outbound_payment_htlc_rounded_msat: u64, - /// The amount of millisatoshis which has been burned to fees from HTLCs which are outbound from us and are related to - /// a forwarded HTLC. This is the sum of the millisatoshis part of all HTLCs which are otherwise represented by - /// `LightningBalance::MaybeTimeoutClaimableHTLC` with their `LightningBalance::MaybeTimeoutClaimableHTLC::outbound_payment` - /// flag not set, as well as any dust HTLCs which would otherwise be represented the same. - /// - /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - #[prost(uint64, tag = "6")] - pub outbound_forwarded_htlc_rounded_msat: u64, - /// The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we know - /// the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by - /// `LightningBalance::ContentiousClaimable` on channel close, but whose current value is included in `amount_satoshis`, - /// as well as any dust HTLCs which would otherwise be represented the same. - /// - /// This amount (rounded up to a whole satoshi value) will not be included in `amount_satoshis`. - #[prost(uint64, tag = "7")] - pub inbound_claiming_htlc_rounded_msat: u64, - /// The amount of millisatoshis which has been burned to fees from HTLCs which are inbound to us and for which we do - /// not know the preimage. This is the sum of the millisatoshis part of all HTLCs which would be represented by - /// `LightningBalance::MaybePreimageClaimableHTLC` on channel close, as well as any dust HTLCs which would otherwise be - /// represented the same. - /// - /// This amount (rounded up to a whole satoshi value) will not be included in the counterparty’s `amount_satoshis`. - #[prost(uint64, tag = "8")] - pub inbound_htlc_rounded_msat: u64, -} -/// The channel has been closed, and the given balance is ours but awaiting confirmations until we consider it spendable. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ClaimableAwaitingConfirmations { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount available to claim, in satoshis, possibly excluding the on-chain fees which were spent in broadcasting - /// the transaction. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, - /// The height at which we start tracking it as `SpendableOutput`. - #[prost(uint32, tag = "4")] - pub confirmation_height: u32, - /// Whether this balance is a result of cooperative close, a force-close, or an HTLC. - #[prost(enumeration = "BalanceSource", tag = "5")] - #[cfg_attr( - feature = "serde", - serde(serialize_with = "crate::serde_utils::serialize_balance_source") - )] - pub source: i32, -} -/// The channel has been closed, and the given balance should be ours but awaiting spending transaction confirmation. -/// If the spending transaction does not confirm in time, it is possible our counterparty can take the funds by -/// broadcasting an HTLC timeout on-chain. -/// -/// Once the spending transaction confirms, before it has reached enough confirmations to be considered safe from chain -/// reorganizations, the balance will instead be provided via `LightningBalance::ClaimableAwaitingConfirmations`. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct ContentiousClaimable { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - /// the transaction. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, - /// The height at which the counterparty may be able to claim the balance if we have not done so. - #[prost(uint32, tag = "4")] - pub timeout_height: u32, - /// The payment hash that locks this HTLC. - #[prost(string, tag = "5")] - pub payment_hash: ::prost::alloc::string::String, - /// The preimage that can be used to claim this HTLC. - #[prost(string, tag = "6")] - pub payment_preimage: ::prost::alloc::string::String, -} -/// HTLCs which we sent to our counterparty which are claimable after a timeout (less on-chain fees) if the counterparty -/// does not know the preimage for the HTLCs. These are somewhat likely to be claimed by our counterparty before we do. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct MaybeTimeoutClaimableHtlc { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - /// the transaction. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, - /// The height at which we will be able to claim the balance if our counterparty has not done so. - #[prost(uint32, tag = "4")] - pub claimable_height: u32, - /// The payment hash whose preimage our counterparty needs to claim this HTLC. - #[prost(string, tag = "5")] - pub payment_hash: ::prost::alloc::string::String, - /// Indicates whether this HTLC represents a payment which was sent outbound from us. - #[prost(bool, tag = "6")] - pub outbound_payment: bool, -} -/// HTLCs which we received from our counterparty which are claimable with a preimage which we do not currently have. -/// This will only be claimable if we receive the preimage from the node to which we forwarded this HTLC before the -/// timeout. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct MaybePreimageClaimableHtlc { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount available to claim, in satoshis, excluding the on-chain fees which were spent in broadcasting - /// the transaction. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, - /// The height at which our counterparty will be able to claim the balance if we have not yet received the preimage and - /// claimed it ourselves. - #[prost(uint32, tag = "4")] - pub expiry_height: u32, - /// The payment hash whose preimage we need to claim this HTLC. - #[prost(string, tag = "5")] - pub payment_hash: ::prost::alloc::string::String, -} -/// The channel has been closed, and our counterparty broadcasted a revoked commitment transaction. -/// -/// Thus, we’re able to claim all outputs in the commitment transaction, one of which has the following amount. -/// -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CounterpartyRevokedOutputClaimable { - /// The identifier of the channel this balance belongs to. - #[prost(string, tag = "1")] - pub channel_id: ::prost::alloc::string::String, - /// The identifier of our channel counterparty. - #[prost(string, tag = "2")] - pub counterparty_node_id: ::prost::alloc::string::String, - /// The amount, in satoshis, of the output which we can claim. - #[prost(uint64, tag = "3")] - pub amount_satoshis: u64, -} -/// Details about the status of a known balance currently being swept to our on-chain wallet. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PendingSweepBalance { - #[prost(oneof = "pending_sweep_balance::BalanceType", tags = "1, 2, 3")] - pub balance_type: ::core::option::Option, -} -/// Nested message and enum types in `PendingSweepBalance`. -pub mod pending_sweep_balance { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum BalanceType { - #[prost(message, tag = "1")] - PendingBroadcast(super::PendingBroadcast), - #[prost(message, tag = "2")] - BroadcastAwaitingConfirmation(super::BroadcastAwaitingConfirmation), - #[prost(message, tag = "3")] - AwaitingThresholdConfirmations(super::AwaitingThresholdConfirmations), - } -} -/// The spendable output is about to be swept, but a spending transaction has yet to be generated and broadcast. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PendingBroadcast { - /// The identifier of the channel this balance belongs to. - #[prost(string, optional, tag = "1")] - pub channel_id: ::core::option::Option<::prost::alloc::string::String>, - /// The amount, in satoshis, of the output being swept. - #[prost(uint64, tag = "2")] - pub amount_satoshis: u64, -} -/// A spending transaction has been generated and broadcast and is awaiting confirmation on-chain. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct BroadcastAwaitingConfirmation { - /// The identifier of the channel this balance belongs to. - #[prost(string, optional, tag = "1")] - pub channel_id: ::core::option::Option<::prost::alloc::string::String>, - /// The best height when we last broadcast a transaction spending the output being swept. - #[prost(uint32, tag = "2")] - pub latest_broadcast_height: u32, - /// The identifier of the transaction spending the swept output we last broadcast. - #[prost(string, tag = "3")] - pub latest_spending_txid: ::prost::alloc::string::String, - /// The amount, in satoshis, of the output being swept. - #[prost(uint64, tag = "4")] - pub amount_satoshis: u64, -} -/// A spending transaction has been confirmed on-chain and is awaiting threshold confirmations. -/// -/// It will be considered irrevocably confirmed after reaching `ANTI_REORG_DELAY`. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct AwaitingThresholdConfirmations { - /// The identifier of the channel this balance belongs to. - #[prost(string, optional, tag = "1")] - pub channel_id: ::core::option::Option<::prost::alloc::string::String>, - /// The identifier of the confirmed transaction spending the swept output. - #[prost(string, tag = "2")] - pub latest_spending_txid: ::prost::alloc::string::String, - /// The hash of the block in which the spending transaction was confirmed. - #[prost(string, tag = "3")] - pub confirmation_hash: ::prost::alloc::string::String, - /// The height at which the spending transaction was confirmed. - #[prost(uint32, tag = "4")] - pub confirmation_height: u32, - /// The amount, in satoshis, of the output being swept. - #[prost(uint64, tag = "5")] - pub amount_satoshis: u64, -} -/// Token used to determine start of next page in paginated APIs. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PageToken { - #[prost(string, tag = "1")] - pub token: ::prost::alloc::string::String, - #[prost(int64, tag = "2")] - pub index: i64, -} -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Bolt11InvoiceDescription { - #[prost(oneof = "bolt11_invoice_description::Kind", tags = "1, 2")] - pub kind: ::core::option::Option, -} -/// Nested message and enum types in `Bolt11InvoiceDescription`. -pub mod bolt11_invoice_description { - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] - #[allow(clippy::derive_partial_eq_without_eq)] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Kind { - #[prost(string, tag = "1")] - Direct(::prost::alloc::string::String), - #[prost(string, tag = "2")] - Hash(::prost::alloc::string::String), - } -} -/// Configuration options for payment routing and pathfinding. -/// See for more details on each field. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct RouteParametersConfig { - /// The maximum total fees, in millisatoshi, that may accrue during route finding. - /// Defaults to 1% of the payment amount + 50 sats - #[prost(uint64, optional, tag = "1")] - pub max_total_routing_fee_msat: ::core::option::Option, - /// The maximum total CLTV delta we accept for the route. - /// Defaults to 1008. - #[prost(uint32, tag = "2")] - pub max_total_cltv_expiry_delta: u32, - /// The maximum number of paths that may be used by (MPP) payments. - /// Defaults to 10. - #[prost(uint32, tag = "3")] - pub max_path_count: u32, - /// Selects the maximum share of a channel's total capacity which will be - /// sent over a channel, as a power of 1/2. - /// Default value: 2 - #[prost(uint32, tag = "4")] - pub max_channel_saturation_power_of_half: u32, -} -/// Routing fees for a channel as part of the network graph. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GraphRoutingFees { - /// Flat routing fee in millisatoshis. - #[prost(uint32, tag = "1")] - pub base_msat: u32, - /// Liquidity-based routing fee in millionths of a routed amount. - #[prost(uint32, tag = "2")] - pub proportional_millionths: u32, -} -/// Details about one direction of a channel in the network graph, -/// as received within a `ChannelUpdate`. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GraphChannelUpdate { - /// When the last update to the channel direction was issued. - /// Value is opaque, as set in the announcement. - #[prost(uint32, tag = "1")] - pub last_update: u32, - /// Whether the channel can be currently used for payments (in this one direction). - #[prost(bool, tag = "2")] - pub enabled: bool, - /// The difference in CLTV values that you must have when routing through this channel. - #[prost(uint32, tag = "3")] - pub cltv_expiry_delta: u32, - /// The minimum value, which must be relayed to the next hop via the channel. - #[prost(uint64, tag = "4")] - pub htlc_minimum_msat: u64, - /// The maximum value which may be relayed to the next hop via the channel. - #[prost(uint64, tag = "5")] - pub htlc_maximum_msat: u64, - /// Fees charged when the channel is used for routing. - #[prost(message, optional, tag = "6")] - pub fees: ::core::option::Option, -} -/// Details about a channel in the network graph (both directions). -/// Received within a channel announcement. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GraphChannel { - /// Source node of the first direction of the channel (hex-encoded public key). - #[prost(string, tag = "1")] - pub node_one: ::prost::alloc::string::String, - /// Source node of the second direction of the channel (hex-encoded public key). - #[prost(string, tag = "2")] - pub node_two: ::prost::alloc::string::String, - /// The channel capacity as seen on-chain, if chain lookup is available. - #[prost(uint64, optional, tag = "3")] - pub capacity_sats: ::core::option::Option, - /// Details about the first direction of a channel. - #[prost(message, optional, tag = "4")] - pub one_to_two: ::core::option::Option, - /// Details about the second direction of a channel. - #[prost(message, optional, tag = "5")] - pub two_to_one: ::core::option::Option, -} -/// Information received in the latest node_announcement from this node. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GraphNodeAnnouncement { - /// When the last known update to the node state was issued. - /// Value is opaque, as set in the announcement. - #[prost(uint32, tag = "1")] - pub last_update: u32, - /// Moniker assigned to the node. - /// May be invalid or malicious (eg control chars), should not be exposed to the user. - #[prost(string, tag = "2")] - pub alias: ::prost::alloc::string::String, - /// Color assigned to the node as a hex-encoded RGB string, e.g. "ff0000". - #[prost(string, tag = "3")] - pub rgb: ::prost::alloc::string::String, - /// List of addresses on which this node is reachable. - #[prost(string, repeated, tag = "4")] - pub addresses: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, -} -/// Details of a known Lightning peer. -/// See more: -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Peer { - /// The hex-encoded node ID of the peer. - #[prost(string, tag = "1")] - pub node_id: ::prost::alloc::string::String, - /// The network address of the peer. - #[prost(string, tag = "2")] - pub address: ::prost::alloc::string::String, - /// Indicates whether we'll try to reconnect to this peer after restarts. - #[prost(bool, tag = "3")] - pub is_persisted: bool, - /// Indicates whether we currently have an active connection with the peer. - #[prost(bool, tag = "4")] - pub is_connected: bool, -} -/// Details about a node in the network graph, known from the network announcement. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[allow(clippy::derive_partial_eq_without_eq)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GraphNode { - /// All valid channels a node has announced. - #[prost(uint64, repeated, tag = "1")] - pub channels: ::prost::alloc::vec::Vec, - /// More information about a node from node_announcement. - /// Optional because we store a node entry after learning about it from - /// a channel announcement, but before receiving a node announcement. - #[prost(message, optional, tag = "2")] - pub announcement_info: ::core::option::Option, -} -/// Represents the direction of a payment. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum PaymentDirection { - /// The payment is inbound. - Inbound = 0, - /// The payment is outbound. - Outbound = 1, -} -impl PaymentDirection { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - PaymentDirection::Inbound => "INBOUND", - PaymentDirection::Outbound => "OUTBOUND", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "INBOUND" => Some(Self::Inbound), - "OUTBOUND" => Some(Self::Outbound), - _ => None, - } - } -} -/// Represents the current status of a payment. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum PaymentStatus { - /// The payment is still pending. - Pending = 0, - /// The payment succeeded. - Succeeded = 1, - /// The payment failed. - Failed = 2, -} -impl PaymentStatus { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - PaymentStatus::Pending => "PENDING", - PaymentStatus::Succeeded => "SUCCEEDED", - PaymentStatus::Failed => "FAILED", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "PENDING" => Some(Self::Pending), - "SUCCEEDED" => Some(Self::Succeeded), - "FAILED" => Some(Self::Failed), - _ => None, - } - } -} -/// Indicates whether the balance is derived from a cooperative close, a force-close (for holder or counterparty), -/// or whether it is for an HTLC. -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] -#[repr(i32)] -pub enum BalanceSource { - /// The channel was force closed by the holder. - HolderForceClosed = 0, - /// The channel was force closed by the counterparty. - CounterpartyForceClosed = 1, - /// The channel was cooperatively closed. - CoopClose = 2, - /// This balance is the result of an HTLC. - Htlc = 3, -} -impl BalanceSource { - /// String value of the enum field names used in the ProtoBuf definition. - /// - /// The values are not transformed in any way and thus are considered stable - /// (if the ProtoBuf definition does not change) and safe for programmatic use. - pub fn as_str_name(&self) -> &'static str { - match self { - BalanceSource::HolderForceClosed => "HOLDER_FORCE_CLOSED", - BalanceSource::CounterpartyForceClosed => "COUNTERPARTY_FORCE_CLOSED", - BalanceSource::CoopClose => "COOP_CLOSE", - BalanceSource::Htlc => "HTLC", - } - } - /// Creates an enum from field names used in the ProtoBuf definition. - pub fn from_str_name(value: &str) -> ::core::option::Option { - match value { - "HOLDER_FORCE_CLOSED" => Some(Self::HolderForceClosed), - "COUNTERPARTY_FORCE_CLOSED" => Some(Self::CounterpartyForceClosed), - "COOP_CLOSE" => Some(Self::CoopClose), - "HTLC" => Some(Self::Htlc), - _ => None, - } - } -} diff --git a/ldk-server/Cargo.toml b/ldk-server/Cargo.toml index 4156a649..7fddbc6e 100644 --- a/ldk-server/Cargo.toml +++ b/ldk-server/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "9e0a8124cbe2c00a06fc5c880113213d4b36d8aa" } serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde_json = "1.0" hyper = { version = "1", default-features = false, features = ["server", "http1"] } http-body-util = { version = "0.1", default-features = false } hyper-util = { version = "0.1", default-features = false, features = ["server-graceful"] } @@ -13,9 +14,7 @@ tokio = { version = "1.38.0", default-features = false, features = ["time", "sig tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] } ring = { version = "0.17", default-features = false } getrandom = { version = "0.2", default-features = false } -prost = { version = "0.11.6", default-features = false, features = ["std"] } -ldk-server-protos = { path = "../ldk-server-protos" } -bytes = { version = "1.4.0", default-features = false } +ldk-server-json-models = { path = "../ldk-server-json-models" } hex = { package = "hex-conservative", version = "0.2.1", default-features = false } rusqlite = { version = "0.31.0", features = ["bundled"] } async-trait = { version = "0.1.85", default-features = false } diff --git a/ldk-server/src/api/bolt11_claim_for_hash.rs b/ldk-server/src/api/bolt11_claim_for_hash.rs index e1e62288..a66247c9 100644 --- a/ldk-server/src/api/bolt11_claim_for_hash.rs +++ b/ldk-server/src/api/bolt11_claim_for_hash.rs @@ -7,33 +7,19 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::FromHex; use ldk_node::bitcoin::hashes::{sha256, Hash}; use ldk_node::lightning_types::payment::{PaymentHash, PaymentPreimage}; -use ldk_server_protos::api::{Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse}; +use ldk_server_json_models::api::{Bolt11ClaimForHashRequest, Bolt11ClaimForHashResponse}; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; pub(crate) fn handle_bolt11_claim_for_hash_request( context: Context, request: Bolt11ClaimForHashRequest, ) -> Result { - let preimage_bytes = <[u8; 32]>::from_hex(&request.preimage).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - "Invalid preimage, must be a 32-byte hex string.".to_string(), - ) - })?; - let preimage = PaymentPreimage(preimage_bytes); + let preimage = PaymentPreimage(request.preimage); - let payment_hash = if let Some(hash_hex) = &request.payment_hash { - let hash_bytes = <[u8; 32]>::from_hex(hash_hex).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - "Invalid payment_hash, must be a 32-byte hex string.".to_string(), - ) - })?; + let payment_hash = if let Some(hash_bytes) = request.payment_hash { PaymentHash(hash_bytes) } else { PaymentHash(sha256::Hash::hash(&preimage.0).to_byte_array()) diff --git a/ldk-server/src/api/bolt11_fail_for_hash.rs b/ldk-server/src/api/bolt11_fail_for_hash.rs index 9f349164..b269d919 100644 --- a/ldk-server/src/api/bolt11_fail_for_hash.rs +++ b/ldk-server/src/api/bolt11_fail_for_hash.rs @@ -7,24 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::FromHex; use ldk_node::lightning_types::payment::PaymentHash; -use ldk_server_protos::api::{Bolt11FailForHashRequest, Bolt11FailForHashResponse}; +use ldk_server_json_models::api::{Bolt11FailForHashRequest, Bolt11FailForHashResponse}; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; pub(crate) fn handle_bolt11_fail_for_hash_request( context: Context, request: Bolt11FailForHashRequest, ) -> Result { - let hash_bytes = <[u8; 32]>::from_hex(&request.payment_hash).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - "Invalid payment_hash, must be a 32-byte hex string.".to_string(), - ) - })?; - let payment_hash = PaymentHash(hash_bytes); + let payment_hash = PaymentHash(request.payment_hash); context.node.bolt11_payment().fail_for_hash(payment_hash)?; diff --git a/ldk-server/src/api/bolt11_receive.rs b/ldk-server/src/api/bolt11_receive.rs index 793a0928..d96cef6b 100644 --- a/ldk-server/src/api/bolt11_receive.rs +++ b/ldk-server/src/api/bolt11_receive.rs @@ -7,17 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::DisplayHex; -use ldk_server_protos::api::{Bolt11ReceiveRequest, Bolt11ReceiveResponse}; +use ldk_server_json_models::api::{Bolt11ReceiveRequest, Bolt11ReceiveResponse}; use crate::api::error::LdkServerError; use crate::service::Context; -use crate::util::proto_adapter::proto_to_bolt11_description; +use crate::util::adapter::bolt11_description_from_model; pub(crate) fn handle_bolt11_receive_request( context: Context, request: Bolt11ReceiveRequest, ) -> Result { - let description = proto_to_bolt11_description(request.description)?; + let description = bolt11_description_from_model(request.description)?; let invoice = match request.amount_msat { Some(amount_msat) => { context.node.bolt11_payment().receive(amount_msat, &description, request.expiry_secs)? @@ -28,8 +27,8 @@ pub(crate) fn handle_bolt11_receive_request( .receive_variable_amount(&description, request.expiry_secs)?, }; - let payment_hash = invoice.payment_hash().0.to_lower_hex_string(); - let payment_secret = invoice.payment_secret().0.to_lower_hex_string(); + let payment_hash = invoice.payment_hash().0; + let payment_secret = invoice.payment_secret().0; let response = Bolt11ReceiveResponse { invoice: invoice.to_string(), payment_hash, payment_secret }; Ok(response) diff --git a/ldk-server/src/api/bolt11_receive_for_hash.rs b/ldk-server/src/api/bolt11_receive_for_hash.rs index 8f687067..72122c17 100644 --- a/ldk-server/src/api/bolt11_receive_for_hash.rs +++ b/ldk-server/src/api/bolt11_receive_for_hash.rs @@ -7,26 +7,18 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::FromHex; use ldk_node::lightning_types::payment::PaymentHash; -use ldk_server_protos::api::{Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse}; +use ldk_server_json_models::api::{Bolt11ReceiveForHashRequest, Bolt11ReceiveForHashResponse}; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; -use crate::util::proto_adapter::proto_to_bolt11_description; +use crate::util::adapter::bolt11_description_from_model; pub(crate) fn handle_bolt11_receive_for_hash_request( context: Context, request: Bolt11ReceiveForHashRequest, ) -> Result { - let description = proto_to_bolt11_description(request.description)?; - let hash_bytes = <[u8; 32]>::from_hex(&request.payment_hash).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - "Invalid payment_hash, must be a 32-byte hex string.".to_string(), - ) - })?; - let payment_hash = PaymentHash(hash_bytes); + let description = bolt11_description_from_model(request.description)?; + let payment_hash = PaymentHash(request.payment_hash); let invoice = match request.amount_msat { Some(amount_msat) => context.node.bolt11_payment().receive_for_hash( @@ -42,5 +34,7 @@ pub(crate) fn handle_bolt11_receive_for_hash_request( )?, }; - Ok(Bolt11ReceiveForHashResponse { invoice: invoice.to_string() }) + let payment_hash = invoice.payment_hash().0; + let payment_secret = invoice.payment_secret().0; + Ok(Bolt11ReceiveForHashResponse { invoice: invoice.to_string(), payment_hash, payment_secret }) } diff --git a/ldk-server/src/api/bolt11_receive_via_jit_channel.rs b/ldk-server/src/api/bolt11_receive_via_jit_channel.rs index 552a1823..54b18087 100644 --- a/ldk-server/src/api/bolt11_receive_via_jit_channel.rs +++ b/ldk-server/src/api/bolt11_receive_via_jit_channel.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{ +use ldk_server_json_models::api::{ Bolt11ReceiveVariableAmountViaJitChannelRequest, Bolt11ReceiveVariableAmountViaJitChannelResponse, Bolt11ReceiveViaJitChannelRequest, Bolt11ReceiveViaJitChannelResponse, @@ -15,12 +15,12 @@ use ldk_server_protos::api::{ use crate::api::error::LdkServerError; use crate::service::Context; -use crate::util::proto_adapter::proto_to_bolt11_description; +use crate::util::adapter::bolt11_description_from_model; pub(crate) fn handle_bolt11_receive_via_jit_channel_request( context: Context, request: Bolt11ReceiveViaJitChannelRequest, ) -> Result { - let description = proto_to_bolt11_description(request.description)?; + let description = bolt11_description_from_model(request.description)?; let invoice = context.node.bolt11_payment().receive_via_jit_channel( request.amount_msat, &description, @@ -28,18 +28,30 @@ pub(crate) fn handle_bolt11_receive_via_jit_channel_request( request.max_total_lsp_fee_limit_msat, )?; - Ok(Bolt11ReceiveViaJitChannelResponse { invoice: invoice.to_string() }) + let payment_hash = invoice.payment_hash().0; + let payment_secret = invoice.payment_secret().0; + Ok(Bolt11ReceiveViaJitChannelResponse { + invoice: invoice.to_string(), + payment_hash, + payment_secret, + }) } pub(crate) fn handle_bolt11_receive_variable_amount_via_jit_channel_request( context: Context, request: Bolt11ReceiveVariableAmountViaJitChannelRequest, ) -> Result { - let description = proto_to_bolt11_description(request.description)?; + let description = bolt11_description_from_model(request.description)?; let invoice = context.node.bolt11_payment().receive_variable_amount_via_jit_channel( &description, request.expiry_secs, request.max_proportional_lsp_fee_limit_ppm_msat, )?; - Ok(Bolt11ReceiveVariableAmountViaJitChannelResponse { invoice: invoice.to_string() }) + let payment_hash = invoice.payment_hash().0; + let payment_secret = invoice.payment_secret().0; + Ok(Bolt11ReceiveVariableAmountViaJitChannelResponse { + invoice: invoice.to_string(), + payment_hash, + payment_secret, + }) } diff --git a/ldk-server/src/api/bolt11_send.rs b/ldk-server/src/api/bolt11_send.rs index 1298896e..ce32362a 100644 --- a/ldk-server/src/api/bolt11_send.rs +++ b/ldk-server/src/api/bolt11_send.rs @@ -10,9 +10,9 @@ use std::str::FromStr; use ldk_node::lightning_invoice::Bolt11Invoice; -use ldk_server_protos::api::{Bolt11SendRequest, Bolt11SendResponse}; +use ldk_server_json_models::api::{Bolt11SendRequest, Bolt11SendResponse}; -use crate::api::build_route_parameters_config_from_proto; +use crate::api::build_route_parameters_config_from_model; use crate::api::error::LdkServerError; use crate::service::Context; @@ -22,7 +22,7 @@ pub(crate) fn handle_bolt11_send_request( let invoice = Bolt11Invoice::from_str(request.invoice.as_str()) .map_err(|_| ldk_node::NodeError::InvalidInvoice)?; - let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + let route_parameters = build_route_parameters_config_from_model(request.route_parameters)?; let payment_id = match request.amount_msat { None => context.node.bolt11_payment().send(&invoice, route_parameters), @@ -31,6 +31,6 @@ pub(crate) fn handle_bolt11_send_request( }, }?; - let response = Bolt11SendResponse { payment_id: payment_id.to_string() }; + let response = Bolt11SendResponse { payment_id: payment_id.0 }; Ok(response) } diff --git a/ldk-server/src/api/bolt12_receive.rs b/ldk-server/src/api/bolt12_receive.rs index 42f4b489..badb0fae 100644 --- a/ldk-server/src/api/bolt12_receive.rs +++ b/ldk-server/src/api/bolt12_receive.rs @@ -7,8 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::DisplayHex; -use ldk_server_protos::api::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; +use ldk_server_json_models::api::{Bolt12ReceiveRequest, Bolt12ReceiveResponse}; use crate::api::error::LdkServerError; use crate::service::Context; @@ -29,7 +28,7 @@ pub(crate) fn handle_bolt12_receive_request( .receive_variable_amount(&request.description, request.expiry_secs)?, }; - let offer_id = offer.id().0.to_lower_hex_string(); + let offer_id = offer.id().0; let response = Bolt12ReceiveResponse { offer: offer.to_string(), offer_id }; Ok(response) } diff --git a/ldk-server/src/api/bolt12_send.rs b/ldk-server/src/api/bolt12_send.rs index 30df2dd8..4acdda06 100644 --- a/ldk-server/src/api/bolt12_send.rs +++ b/ldk-server/src/api/bolt12_send.rs @@ -10,9 +10,9 @@ use std::str::FromStr; use ldk_node::lightning::offers::offer::Offer; -use ldk_server_protos::api::{Bolt12SendRequest, Bolt12SendResponse}; +use ldk_server_json_models::api::{Bolt12SendRequest, Bolt12SendResponse}; -use crate::api::build_route_parameters_config_from_proto; +use crate::api::build_route_parameters_config_from_model; use crate::api::error::LdkServerError; use crate::service::Context; @@ -22,7 +22,7 @@ pub(crate) fn handle_bolt12_send_request( let offer = Offer::from_str(request.offer.as_str()).map_err(|_| ldk_node::NodeError::InvalidOffer)?; - let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + let route_parameters = build_route_parameters_config_from_model(request.route_parameters)?; let payment_id = match request.amount_msat { None => context.node.bolt12_payment().send( @@ -40,6 +40,6 @@ pub(crate) fn handle_bolt12_send_request( ), }?; - let response = Bolt12SendResponse { payment_id: payment_id.to_string() }; + let response = Bolt12SendResponse { payment_id: payment_id.0 }; Ok(response) } diff --git a/ldk-server/src/api/close_channel.rs b/ldk-server/src/api/close_channel.rs index 5ae4070b..51ce66ed 100644 --- a/ldk-server/src/api/close_channel.rs +++ b/ldk-server/src/api/close_channel.rs @@ -7,11 +7,9 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::str::FromStr; - use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::UserChannelId; -use ldk_server_protos::api::{ +use ldk_server_json_models::api::{ CloseChannelRequest, CloseChannelResponse, ForceCloseChannelRequest, ForceCloseChannelResponse, }; @@ -52,8 +50,8 @@ fn parse_user_channel_id(id: &str) -> Result { Ok(UserChannelId(parsed)) } -fn parse_counterparty_node_id(id: &str) -> Result { - PublicKey::from_str(id).map_err(|e| { +fn parse_counterparty_node_id(id: &[u8]) -> Result { + PublicKey::from_slice(id).map_err(|e| { LdkServerError::new( InvalidRequestError, format!("Invalid counterparty node ID, error: {}", e), diff --git a/ldk-server/src/api/connect_peer.rs b/ldk-server/src/api/connect_peer.rs index d3fac3a0..1039b67b 100644 --- a/ldk-server/src/api/connect_peer.rs +++ b/ldk-server/src/api/connect_peer.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::lightning::ln::msgs::SocketAddress; -use ldk_server_protos::api::{ConnectPeerRequest, ConnectPeerResponse}; +use ldk_server_json_models::api::{ConnectPeerRequest, ConnectPeerResponse}; use crate::api::error::LdkServerError; use crate::service::Context; @@ -19,7 +19,7 @@ use crate::service::Context; pub(crate) fn handle_connect_peer( context: Context, request: ConnectPeerRequest, ) -> Result { - let node_id = PublicKey::from_str(&request.node_pubkey) + let node_id = PublicKey::from_slice(&request.node_pubkey) .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; let address = SocketAddress::from_str(&request.address) .map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?; diff --git a/ldk-server/src/api/disconnect_peer.rs b/ldk-server/src/api/disconnect_peer.rs index 76f2ecce..8903e978 100644 --- a/ldk-server/src/api/disconnect_peer.rs +++ b/ldk-server/src/api/disconnect_peer.rs @@ -7,10 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::str::FromStr; - use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_server_protos::api::{DisconnectPeerRequest, DisconnectPeerResponse}; +use ldk_server_json_models::api::{DisconnectPeerRequest, DisconnectPeerResponse}; use crate::api::error::LdkServerError; use crate::service::Context; @@ -18,7 +16,7 @@ use crate::service::Context; pub(crate) fn handle_disconnect_peer( context: Context, request: DisconnectPeerRequest, ) -> Result { - let node_id = PublicKey::from_str(&request.node_pubkey) + let node_id = PublicKey::from_slice(&request.node_pubkey) .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; context.node.disconnect(node_id)?; diff --git a/ldk-server/src/api/error.rs b/ldk-server/src/api/error.rs index ad1c152f..b1b99999 100644 --- a/ldk-server/src/api/error.rs +++ b/ldk-server/src/api/error.rs @@ -41,16 +41,16 @@ impl fmt::Display for LdkServerError { #[derive(Clone, Debug, PartialEq, Eq)] #[allow(clippy::enum_variant_names)] pub(crate) enum LdkServerErrorCode { - /// Please refer to [`protos::error::ErrorCode::InvalidRequestError`]. + /// See [`ldk_server_json_models::error::ErrorCode::InvalidRequestError`]. InvalidRequestError, - /// Please refer to [`protos::error::ErrorCode::AuthError`]. + /// See [`ldk_server_json_models::error::ErrorCode::AuthError`]. AuthError, - /// Please refer to [`protos::error::ErrorCode::LightningError`]. + /// See [`ldk_server_json_models::error::ErrorCode::LightningError`]. LightningError, - /// Please refer to [`protos::error::ErrorCode::InternalServerError`]. + /// See [`ldk_server_json_models::error::ErrorCode::InternalServerError`]. InternalServerError, } diff --git a/ldk-server/src/api/export_pathfinding_scores.rs b/ldk-server/src/api/export_pathfinding_scores.rs index a6924a22..095ebf4d 100644 --- a/ldk-server/src/api/export_pathfinding_scores.rs +++ b/ldk-server/src/api/export_pathfinding_scores.rs @@ -7,7 +7,9 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{ExportPathfindingScoresRequest, ExportPathfindingScoresResponse}; +use ldk_server_json_models::api::{ + ExportPathfindingScoresRequest, ExportPathfindingScoresResponse, +}; use crate::api::error::LdkServerError; use crate::service::Context; @@ -17,6 +19,6 @@ pub(crate) fn handle_export_pathfinding_scores_request( ) -> Result { let scores = context.node.export_pathfinding_scores()?; - let response = ExportPathfindingScoresResponse { scores: scores.into() }; + let response = ExportPathfindingScoresResponse { scores }; Ok(response) } diff --git a/ldk-server/src/api/get_balances.rs b/ldk-server/src/api/get_balances.rs index 82ab69b7..90a7af27 100644 --- a/ldk-server/src/api/get_balances.rs +++ b/ldk-server/src/api/get_balances.rs @@ -7,11 +7,11 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{GetBalancesRequest, GetBalancesResponse}; +use ldk_server_json_models::api::{GetBalancesRequest, GetBalancesResponse}; use crate::api::error::LdkServerError; use crate::service::Context; -use crate::util::proto_adapter::{lightning_balance_to_proto, pending_sweep_balance_to_proto}; +use crate::util::adapter::{lightning_balance_to_model, pending_sweep_balance_to_model}; pub(crate) fn handle_get_balances_request( context: Context, _request: GetBalancesRequest, @@ -26,12 +26,12 @@ pub(crate) fn handle_get_balances_request( lightning_balances: balance_details .lightning_balances .into_iter() - .map(lightning_balance_to_proto) + .map(lightning_balance_to_model) .collect(), pending_balances_from_channel_closures: balance_details .pending_balances_from_channel_closures .into_iter() - .map(pending_sweep_balance_to_proto) + .map(pending_sweep_balance_to_model) .collect(), }; Ok(response) diff --git a/ldk-server/src/api/get_node_info.rs b/ldk-server/src/api/get_node_info.rs index c1b81279..c92fc93c 100644 --- a/ldk-server/src/api/get_node_info.rs +++ b/ldk-server/src/api/get_node_info.rs @@ -7,11 +7,13 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{GetNodeInfoRequest, GetNodeInfoResponse}; -use ldk_server_protos::types::BestBlock; +use ldk_node::bitcoin::hashes::Hash as _; +use ldk_server_json_models::api::{GetNodeInfoRequest, GetNodeInfoResponse}; +use ldk_server_json_models::types::BestBlock; use crate::api::error::LdkServerError; use crate::service::Context; +use crate::util::adapter::to_display_bytes; pub(crate) fn handle_get_node_info_request( context: Context, _request: GetNodeInfoRequest, @@ -19,7 +21,7 @@ pub(crate) fn handle_get_node_info_request( let node_status = context.node.status(); let best_block = BestBlock { - block_hash: node_status.current_best_block.block_hash.to_string(), + block_hash: to_display_bytes(node_status.current_best_block.block_hash.to_byte_array()), height: node_status.current_best_block.height, }; @@ -37,7 +39,7 @@ pub(crate) fn handle_get_node_info_request( let node_alias = context.node.node_alias().map(|alias| alias.to_string()); - let node_id = context.node.node_id().to_string(); + let node_id = context.node.node_id(); let node_uris = { let addrs = if announcement_addresses.is_empty() { @@ -48,8 +50,8 @@ pub(crate) fn handle_get_node_info_request( addrs.into_iter().map(|a| format!("{node_id}@{a}")).collect() }; let response = GetNodeInfoResponse { - node_id, - current_best_block: Some(best_block), + node_id: node_id.serialize(), + current_best_block: best_block, latest_lightning_wallet_sync_timestamp: node_status.latest_lightning_wallet_sync_timestamp, latest_onchain_wallet_sync_timestamp: node_status.latest_onchain_wallet_sync_timestamp, latest_fee_rate_cache_update_timestamp: node_status.latest_fee_rate_cache_update_timestamp, diff --git a/ldk-server/src/api/get_payment_details.rs b/ldk-server/src/api/get_payment_details.rs index 1d7b266a..140f4142 100644 --- a/ldk-server/src/api/get_payment_details.rs +++ b/ldk-server/src/api/get_payment_details.rs @@ -7,29 +7,19 @@ // You may not use this file except in accordance with one or both of these // licenses. -use hex::FromHex; use ldk_node::lightning::ln::channelmanager::PaymentId; -use ldk_server_protos::api::{GetPaymentDetailsRequest, GetPaymentDetailsResponse}; +use ldk_server_json_models::api::{GetPaymentDetailsRequest, GetPaymentDetailsResponse}; use crate::api::error::LdkServerError; -use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; -use crate::util::proto_adapter::payment_to_proto; +use crate::util::adapter::payment_to_model; pub(crate) fn handle_get_payment_details_request( context: Context, request: GetPaymentDetailsRequest, ) -> Result { - let payment_id_bytes = - <[u8; PaymentId::LENGTH]>::from_hex(&request.payment_id).map_err(|_| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid payment_id, must be a {}-byte hex-string.", PaymentId::LENGTH), - ) - })?; + let payment_details = context.node.payment(&PaymentId(request.payment_id)); - let payment_details = context.node.payment(&PaymentId(payment_id_bytes)); - - let response = GetPaymentDetailsResponse { payment: payment_details.map(payment_to_proto) }; + let response = GetPaymentDetailsResponse { payment: payment_details.map(payment_to_model) }; Ok(response) } diff --git a/ldk-server/src/api/graph_get_channel.rs b/ldk-server/src/api/graph_get_channel.rs index 3e20a2a0..4b896f14 100644 --- a/ldk-server/src/api/graph_get_channel.rs +++ b/ldk-server/src/api/graph_get_channel.rs @@ -7,12 +7,12 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{GraphGetChannelRequest, GraphGetChannelResponse}; +use ldk_server_json_models::api::{GraphGetChannelRequest, GraphGetChannelResponse}; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; -use crate::util::proto_adapter::graph_channel_to_proto; +use crate::util::adapter::graph_channel_to_model; pub(crate) fn handle_graph_get_channel_request( context: Context, request: GraphGetChannelRequest, @@ -28,6 +28,6 @@ pub(crate) fn handle_graph_get_channel_request( ) })?; - let response = GraphGetChannelResponse { channel: Some(graph_channel_to_proto(channel_info)) }; + let response = GraphGetChannelResponse { channel: Some(graph_channel_to_model(channel_info)) }; Ok(response) } diff --git a/ldk-server/src/api/graph_get_node.rs b/ldk-server/src/api/graph_get_node.rs index 9ddb0eac..3b210ef5 100644 --- a/ldk-server/src/api/graph_get_node.rs +++ b/ldk-server/src/api/graph_get_node.rs @@ -8,30 +8,27 @@ // licenses. use ldk_node::lightning::routing::gossip::NodeId; -use ldk_server_protos::api::{GraphGetNodeRequest, GraphGetNodeResponse}; +use ldk_server_json_models::api::{GraphGetNodeRequest, GraphGetNodeResponse}; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; -use crate::util::proto_adapter::graph_node_to_proto; +use crate::util::adapter::graph_node_to_model; pub(crate) fn handle_graph_get_node_request( context: Context, request: GraphGetNodeRequest, ) -> Result { - let node_id: NodeId = request.node_id.parse().map_err(|_| { + let node_id = NodeId::from_slice(&request.node_id).map_err(|_| { LdkServerError::new( InvalidRequestError, - format!("Invalid node_id: {}. Expected a hex-encoded public key.", request.node_id), + "Invalid node_id: expected a valid 33-byte public key.".to_string(), ) })?; let node_info = context.node.network_graph().node(&node_id).ok_or_else(|| { - LdkServerError::new( - InvalidRequestError, - format!("Node with ID {} not found in the network graph.", request.node_id), - ) + LdkServerError::new(InvalidRequestError, "Node not found in the network graph.".to_string()) })?; - let response = GraphGetNodeResponse { node: Some(graph_node_to_proto(node_info)) }; + let response = GraphGetNodeResponse { node: Some(graph_node_to_model(node_info)) }; Ok(response) } diff --git a/ldk-server/src/api/graph_list_channels.rs b/ldk-server/src/api/graph_list_channels.rs index 60543292..2cee05cb 100644 --- a/ldk-server/src/api/graph_list_channels.rs +++ b/ldk-server/src/api/graph_list_channels.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{GraphListChannelsRequest, GraphListChannelsResponse}; +use ldk_server_json_models::api::{GraphListChannelsRequest, GraphListChannelsResponse}; use crate::api::error::LdkServerError; use crate::service::Context; diff --git a/ldk-server/src/api/graph_list_nodes.rs b/ldk-server/src/api/graph_list_nodes.rs index 64ccd295..8a3c0283 100644 --- a/ldk-server/src/api/graph_list_nodes.rs +++ b/ldk-server/src/api/graph_list_nodes.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{GraphListNodesRequest, GraphListNodesResponse}; +use ldk_server_json_models::api::{GraphListNodesRequest, GraphListNodesResponse}; use crate::api::error::LdkServerError; use crate::service::Context; diff --git a/ldk-server/src/api/list_channels.rs b/ldk-server/src/api/list_channels.rs index 84d1584b..f133c9bd 100644 --- a/ldk-server/src/api/list_channels.rs +++ b/ldk-server/src/api/list_channels.rs @@ -7,16 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{ListChannelsRequest, ListChannelsResponse}; +use ldk_server_json_models::api::{ListChannelsRequest, ListChannelsResponse}; use crate::api::error::LdkServerError; use crate::service::Context; -use crate::util::proto_adapter::channel_to_proto; +use crate::util::adapter::channel_to_model; pub(crate) fn handle_list_channels_request( context: Context, _request: ListChannelsRequest, ) -> Result { - let channels = context.node.list_channels().into_iter().map(channel_to_proto).collect(); + let channels = context.node.list_channels().into_iter().map(channel_to_model).collect(); let response = ListChannelsResponse { channels }; Ok(response) diff --git a/ldk-server/src/api/list_forwarded_payments.rs b/ldk-server/src/api/list_forwarded_payments.rs index 78ce3dc4..8ccde337 100644 --- a/ldk-server/src/api/list_forwarded_payments.rs +++ b/ldk-server/src/api/list_forwarded_payments.rs @@ -7,13 +7,12 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bytes::Bytes; -use ldk_server_protos::api::{ListForwardedPaymentsRequest, ListForwardedPaymentsResponse}; -use ldk_server_protos::types::{ForwardedPayment, PageToken}; -use prost::Message; +use ldk_server_json_models::api::{ListForwardedPaymentsRequest, ListForwardedPaymentsResponse}; +use ldk_server_json_models::types::ForwardedPayment; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InternalServerError; +use crate::api::{decode_page_token, encode_page_token}; use crate::io::persist::{ FORWARDED_PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, @@ -23,7 +22,7 @@ use crate::service::Context; pub(crate) fn handle_list_forwarded_payments_request( context: Context, request: ListForwardedPaymentsRequest, ) -> Result { - let page_token = request.page_token.map(|p| (p.token, p.index)); + let page_token = decode_page_token(request.page_token)?; let list_response = context .paginated_kv_store .list( @@ -54,8 +53,8 @@ pub(crate) fn handle_list_forwarded_payments_request( format!("Failed to read forwarded payment data: {}", e), ) })?; - let forwarded_payment = ForwardedPayment::decode(Bytes::from(forwarded_payment_bytes)) - .map_err(|e| { + let forwarded_payment = + serde_json::from_slice::(&forwarded_payment_bytes).map_err(|e| { LdkServerError::new( InternalServerError, format!("Failed to decode forwarded payment: {}", e), @@ -65,9 +64,7 @@ pub(crate) fn handle_list_forwarded_payments_request( } let response = ListForwardedPaymentsResponse { forwarded_payments, - next_page_token: list_response - .next_page_token - .map(|(token, index)| PageToken { token, index }), + next_page_token: encode_page_token(list_response.next_page_token), }; Ok(response) } diff --git a/ldk-server/src/api/list_payments.rs b/ldk-server/src/api/list_payments.rs index fbaf7c55..a619e915 100644 --- a/ldk-server/src/api/list_payments.rs +++ b/ldk-server/src/api/list_payments.rs @@ -7,13 +7,12 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bytes::Bytes; -use ldk_server_protos::api::{ListPaymentsRequest, ListPaymentsResponse}; -use ldk_server_protos::types::{PageToken, Payment}; -use prost::Message; +use ldk_server_json_models::api::{ListPaymentsRequest, ListPaymentsResponse}; +use ldk_server_json_models::types::Payment; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InternalServerError; +use crate::api::{decode_page_token, encode_page_token}; use crate::io::persist::{ PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, }; @@ -22,7 +21,7 @@ use crate::service::Context; pub(crate) fn handle_list_payments_request( context: Context, request: ListPaymentsRequest, ) -> Result { - let page_token = request.page_token.map(|p| (p.token, p.index)); + let page_token = decode_page_token(request.page_token)?; let list_response = context .paginated_kv_store .list( @@ -49,16 +48,14 @@ pub(crate) fn handle_list_payments_request( format!("Failed to read payment data: {}", e), ) })?; - let payment = Payment::decode(Bytes::from(payment_bytes)).map_err(|e| { + let payment = serde_json::from_slice::(&payment_bytes).map_err(|e| { LdkServerError::new(InternalServerError, format!("Failed to decode payment: {}", e)) })?; payments.push(payment); } let response = ListPaymentsResponse { payments, - next_page_token: list_response - .next_page_token - .map(|(token, index)| PageToken { token, index }), + next_page_token: encode_page_token(list_response.next_page_token), }; Ok(response) } diff --git a/ldk-server/src/api/list_peers.rs b/ldk-server/src/api/list_peers.rs index c75e119e..54203c5f 100644 --- a/ldk-server/src/api/list_peers.rs +++ b/ldk-server/src/api/list_peers.rs @@ -7,16 +7,16 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{ListPeersRequest, ListPeersResponse}; +use ldk_server_json_models::api::{ListPeersRequest, ListPeersResponse}; use crate::api::error::LdkServerError; use crate::service::Context; -use crate::util::proto_adapter::peer_to_proto; +use crate::util::adapter::peer_to_model; pub(crate) fn handle_list_peers_request( context: Context, _request: ListPeersRequest, ) -> Result { - let peers = context.node.list_peers().into_iter().map(peer_to_proto).collect(); + let peers = context.node.list_peers().into_iter().map(peer_to_model).collect(); let response = ListPeersResponse { peers }; Ok(response) diff --git a/ldk-server/src/api/mod.rs b/ldk-server/src/api/mod.rs index ab8d7d53..8ca5d8fd 100644 --- a/ldk-server/src/api/mod.rs +++ b/ldk-server/src/api/mod.rs @@ -9,11 +9,34 @@ use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; use ldk_node::lightning::routing::router::RouteParametersConfig; -use ldk_server_protos::types::channel_config::MaxDustHtlcExposure; +use ldk_server_json_models::types::MaxDustHtlcExposure; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; +/// Decode an opaque page token string into the internal `(key, creation_time)` tuple. +pub(crate) fn decode_page_token( + token: Option, +) -> Result, LdkServerError> { + match token { + None => Ok(None), + Some(s) => { + let (key, idx) = s.rsplit_once(':').ok_or_else(|| { + LdkServerError::new(InvalidRequestError, "Invalid page token format") + })?; + let index = idx.parse::().map_err(|_| { + LdkServerError::new(InvalidRequestError, "Invalid page token index") + })?; + Ok(Some((key.to_string(), index))) + }, + } +} + +/// Encode the internal `(key, creation_time)` tuple into an opaque page token string. +pub(crate) fn encode_page_token(token: Option<(String, i64)>) -> Option { + token.map(|(key, index)| format!("{key}:{index}")) +} + pub(crate) mod bolt11_claim_for_hash; pub(crate) mod bolt11_fail_for_hash; pub(crate) mod bolt11_receive; @@ -48,10 +71,11 @@ pub(crate) mod unified_send; pub(crate) mod update_channel_config; pub(crate) mod verify_signature; -pub(crate) fn build_channel_config_from_proto( - default_config: ChannelConfig, proto_channel_config: ldk_server_protos::types::ChannelConfig, +pub(crate) fn build_channel_config_from_model( + default_config: ChannelConfig, + channel_config_model: ldk_server_json_models::types::ChannelConfig, ) -> Result { - let max_dust_htlc_exposure = proto_channel_config + let max_dust_htlc_exposure = channel_config_model .max_dust_htlc_exposure .map(|max_dust_htlc_exposure| match max_dust_htlc_exposure { MaxDustHtlcExposure::FixedLimitMsat(limit_msat) => { @@ -63,7 +87,7 @@ pub(crate) fn build_channel_config_from_proto( }) .unwrap_or(default_config.max_dust_htlc_exposure); - let cltv_expiry_delta = match proto_channel_config.cltv_expiry_delta { + let cltv_expiry_delta = match channel_config_model.cltv_expiry_delta { Some(c) => Some(u16::try_from(c).map_err(|_| { LdkServerError::new( InvalidRequestError, @@ -75,27 +99,27 @@ pub(crate) fn build_channel_config_from_proto( .unwrap_or(default_config.cltv_expiry_delta); Ok(ChannelConfig { - forwarding_fee_proportional_millionths: proto_channel_config + forwarding_fee_proportional_millionths: channel_config_model .forwarding_fee_proportional_millionths .unwrap_or(default_config.forwarding_fee_proportional_millionths), - forwarding_fee_base_msat: proto_channel_config + forwarding_fee_base_msat: channel_config_model .forwarding_fee_base_msat .unwrap_or(default_config.forwarding_fee_base_msat), cltv_expiry_delta, max_dust_htlc_exposure, - force_close_avoidance_max_fee_satoshis: proto_channel_config + force_close_avoidance_max_fee_satoshis: channel_config_model .force_close_avoidance_max_fee_satoshis .unwrap_or(default_config.force_close_avoidance_max_fee_satoshis), - accept_underpaying_htlcs: proto_channel_config + accept_underpaying_htlcs: channel_config_model .accept_underpaying_htlcs .unwrap_or(default_config.accept_underpaying_htlcs), }) } -pub(crate) fn build_route_parameters_config_from_proto( - proto_route_params: Option, +pub(crate) fn build_route_parameters_config_from_model( + route_params_model: Option, ) -> Result, LdkServerError> { - match proto_route_params { + match route_params_model { Some(params) => { let max_path_count = params.max_path_count.try_into().map_err(|_| { LdkServerError::new( diff --git a/ldk-server/src/api/onchain_receive.rs b/ldk-server/src/api/onchain_receive.rs index cad2837e..389cec84 100644 --- a/ldk-server/src/api/onchain_receive.rs +++ b/ldk-server/src/api/onchain_receive.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{OnchainReceiveRequest, OnchainReceiveResponse}; +use ldk_server_json_models::api::{OnchainReceiveRequest, OnchainReceiveResponse}; use crate::api::error::LdkServerError; use crate::service::Context; diff --git a/ldk-server/src/api/onchain_send.rs b/ldk-server/src/api/onchain_send.rs index 6eb1d63a..5ddc3574 100644 --- a/ldk-server/src/api/onchain_send.rs +++ b/ldk-server/src/api/onchain_send.rs @@ -9,12 +9,14 @@ use std::str::FromStr; +use ldk_node::bitcoin::hashes::Hash as _; use ldk_node::bitcoin::{Address, FeeRate}; -use ldk_server_protos::api::{OnchainSendRequest, OnchainSendResponse}; +use ldk_server_json_models::api::{OnchainSendRequest, OnchainSendResponse}; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; +use crate::util::adapter::to_display_bytes; pub(crate) fn handle_onchain_send_request( context: Context, request: OnchainSendRequest, @@ -45,6 +47,7 @@ pub(crate) fn handle_onchain_send_request( )) }, }; - let response = OnchainSendResponse { txid: txid.to_string() }; + let payment_id = txid.to_byte_array(); + let response = OnchainSendResponse { txid: to_display_bytes(txid.to_byte_array()), payment_id }; Ok(response) } diff --git a/ldk-server/src/api/open_channel.rs b/ldk-server/src/api/open_channel.rs index 6c470b71..a765ff6d 100644 --- a/ldk-server/src/api/open_channel.rs +++ b/ldk-server/src/api/open_channel.rs @@ -12,23 +12,23 @@ use std::str::FromStr; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::config::ChannelConfig; use ldk_node::lightning::ln::msgs::SocketAddress; -use ldk_server_protos::api::{OpenChannelRequest, OpenChannelResponse}; +use ldk_server_json_models::api::{OpenChannelRequest, OpenChannelResponse}; -use crate::api::build_channel_config_from_proto; +use crate::api::build_channel_config_from_model; use crate::api::error::LdkServerError; use crate::service::Context; pub(crate) fn handle_open_channel( context: Context, request: OpenChannelRequest, ) -> Result { - let node_id = PublicKey::from_str(&request.node_pubkey) + let node_id = PublicKey::from_slice(&request.node_pubkey) .map_err(|_| ldk_node::NodeError::InvalidPublicKey)?; let address = SocketAddress::from_str(&request.address) .map_err(|_| ldk_node::NodeError::InvalidSocketAddress)?; let channel_config = request .channel_config - .map(|proto_config| build_channel_config_from_proto(ChannelConfig::default(), proto_config)) + .map(|config_model| build_channel_config_from_model(ChannelConfig::default(), config_model)) .transpose()?; let user_channel_id = if request.announce_channel { diff --git a/ldk-server/src/api/sign_message.rs b/ldk-server/src/api/sign_message.rs index 8ef0e015..a4be8b18 100644 --- a/ldk-server/src/api/sign_message.rs +++ b/ldk-server/src/api/sign_message.rs @@ -7,7 +7,7 @@ // You may not use this file except in accordance with one or both of these // licenses. -use ldk_server_protos::api::{SignMessageRequest, SignMessageResponse}; +use ldk_server_json_models::api::{SignMessageRequest, SignMessageResponse}; use crate::api::error::LdkServerError; use crate::service::Context; diff --git a/ldk-server/src/api/splice_channel.rs b/ldk-server/src/api/splice_channel.rs index 5d6edfec..1f6f2518 100644 --- a/ldk-server/src/api/splice_channel.rs +++ b/ldk-server/src/api/splice_channel.rs @@ -12,7 +12,7 @@ use std::str::FromStr; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::bitcoin::Address; use ldk_node::UserChannelId; -use ldk_server_protos::api::{ +use ldk_server_json_models::api::{ SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse, }; @@ -69,8 +69,8 @@ fn parse_user_channel_id(id: &str) -> Result { Ok(UserChannelId(parsed)) } -fn parse_counterparty_node_id(id: &str) -> Result { - PublicKey::from_str(id).map_err(|e| { +fn parse_counterparty_node_id(id: &[u8]) -> Result { + PublicKey::from_slice(id).map_err(|e| { LdkServerError::new( InvalidRequestError, format!("Invalid counterparty node ID, error: {}", e), diff --git a/ldk-server/src/api/spontaneous_send.rs b/ldk-server/src/api/spontaneous_send.rs index 77fe3b8a..b1058b96 100644 --- a/ldk-server/src/api/spontaneous_send.rs +++ b/ldk-server/src/api/spontaneous_send.rs @@ -7,12 +7,10 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::str::FromStr; - use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_server_protos::api::{SpontaneousSendRequest, SpontaneousSendResponse}; +use ldk_server_json_models::api::{SpontaneousSendRequest, SpontaneousSendResponse}; -use crate::api::build_route_parameters_config_from_proto; +use crate::api::build_route_parameters_config_from_model; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; use crate::service::Context; @@ -20,15 +18,15 @@ use crate::service::Context; pub(crate) fn handle_spontaneous_send_request( context: Context, request: SpontaneousSendRequest, ) -> Result { - let node_id = PublicKey::from_str(&request.node_id).map_err(|_| { + let node_id = PublicKey::from_slice(&request.node_id).map_err(|_| { LdkServerError::new(InvalidRequestError, "Invalid node_id provided.".to_string()) })?; - let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + let route_parameters = build_route_parameters_config_from_model(request.route_parameters)?; let payment_id = context.node.spontaneous_payment().send(request.amount_msat, node_id, route_parameters)?; - let response = SpontaneousSendResponse { payment_id: payment_id.to_string() }; + let response = SpontaneousSendResponse { payment_id: payment_id.0 }; Ok(response) } diff --git a/ldk-server/src/api/unified_send.rs b/ldk-server/src/api/unified_send.rs index 3f7807b4..081cbf16 100644 --- a/ldk-server/src/api/unified_send.rs +++ b/ldk-server/src/api/unified_send.rs @@ -7,19 +7,20 @@ // You may not use this file except in accordance with one or both of these // licenses. +use ldk_node::bitcoin::hashes::Hash; use ldk_node::payment::UnifiedPaymentResult; -use ldk_server_protos::api::unified_send_response::PaymentResult; -use ldk_server_protos::api::{UnifiedSendRequest, UnifiedSendResponse}; +use ldk_server_json_models::api::{UnifiedSendRequest, UnifiedSendResponse}; use tokio::runtime::Handle; -use crate::api::build_route_parameters_config_from_proto; +use crate::api::build_route_parameters_config_from_model; use crate::api::error::LdkServerError; use crate::service::Context; +use crate::util::adapter::to_display_bytes; pub(crate) fn handle_unified_send_request( context: Context, request: UnifiedSendRequest, ) -> Result { - let route_parameters = build_route_parameters_config_from_proto(request.route_parameters)?; + let route_parameters = build_route_parameters_config_from_model(request.route_parameters)?; let result = tokio::task::block_in_place(|| { Handle::current().block_on(context.node.unified_payment().send( @@ -29,15 +30,19 @@ pub(crate) fn handle_unified_send_request( )) })?; - let payment_result = match result { - UnifiedPaymentResult::Onchain { txid } => PaymentResult::Txid(txid.to_string()), + Ok(match result { + UnifiedPaymentResult::Onchain { txid } => { + let payment_id = txid.to_byte_array(); + UnifiedSendResponse::Onchain { + txid: to_display_bytes(txid.to_byte_array()), + payment_id, + } + }, UnifiedPaymentResult::Bolt11 { payment_id } => { - PaymentResult::Bolt11PaymentId(payment_id.to_string()) + UnifiedSendResponse::Bolt11 { payment_id: payment_id.0 } }, UnifiedPaymentResult::Bolt12 { payment_id } => { - PaymentResult::Bolt12PaymentId(payment_id.to_string()) + UnifiedSendResponse::Bolt12 { payment_id: payment_id.0 } }, - }; - - Ok(UnifiedSendResponse { payment_result: Some(payment_result) }) + }) } diff --git a/ldk-server/src/api/update_channel_config.rs b/ldk-server/src/api/update_channel_config.rs index 780374c5..14f13a26 100644 --- a/ldk-server/src/api/update_channel_config.rs +++ b/ldk-server/src/api/update_channel_config.rs @@ -7,13 +7,11 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::str::FromStr; - use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::UserChannelId; -use ldk_server_protos::api::{UpdateChannelConfigRequest, UpdateChannelConfigResponse}; +use ldk_server_json_models::api::{UpdateChannelConfigRequest, UpdateChannelConfigResponse}; -use crate::api::build_channel_config_from_proto; +use crate::api::build_channel_config_from_model; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::{InvalidRequestError, LightningError}; use crate::service::Context; @@ -37,19 +35,20 @@ pub(crate) fn handle_update_channel_config_request( })? .config; - let updated_channel_config = build_channel_config_from_proto( + let updated_channel_config = build_channel_config_from_model( current_config, request.channel_config.ok_or_else(|| { LdkServerError::new(InvalidRequestError, "Channel config must be provided.") })?, )?; - let counterparty_node_id = PublicKey::from_str(&request.counterparty_node_id).map_err(|e| { - LdkServerError::new( - InvalidRequestError, - format!("Invalid counterparty node id, error {}", e), - ) - })?; + let counterparty_node_id = + PublicKey::from_slice(&request.counterparty_node_id).map_err(|e| { + LdkServerError::new( + InvalidRequestError, + format!("Invalid counterparty node id, error {}", e), + ) + })?; context .node diff --git a/ldk-server/src/api/verify_signature.rs b/ldk-server/src/api/verify_signature.rs index 9b7551ea..dcbbd4b6 100644 --- a/ldk-server/src/api/verify_signature.rs +++ b/ldk-server/src/api/verify_signature.rs @@ -7,10 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. -use std::str::FromStr; - use ldk_node::bitcoin::secp256k1::PublicKey; -use ldk_server_protos::api::{VerifySignatureRequest, VerifySignatureResponse}; +use ldk_server_json_models::api::{VerifySignatureRequest, VerifySignatureResponse}; use crate::api::error::LdkServerError; use crate::api::error::LdkServerErrorCode::InvalidRequestError; @@ -19,7 +17,7 @@ use crate::service::Context; pub(crate) fn handle_verify_signature_request( context: Context, request: VerifySignatureRequest, ) -> Result { - let public_key = PublicKey::from_str(&request.public_key).map_err(|_| { + let public_key = PublicKey::from_slice(&request.public_key).map_err(|_| { LdkServerError::new(InvalidRequestError, "Invalid public_key provided.".to_string()) })?; diff --git a/ldk-server/src/io/events/event_publisher.rs b/ldk-server/src/io/events/event_publisher.rs index 308bde25..f6091ac4 100644 --- a/ldk-server/src/io/events/event_publisher.rs +++ b/ldk-server/src/io/events/event_publisher.rs @@ -8,7 +8,7 @@ // licenses. use async_trait::async_trait; -use ldk_server_protos::events::EventEnvelope; +use ldk_server_json_models::events::Event; use crate::api::error::LdkServerError; @@ -20,10 +20,9 @@ use crate::api::error::LdkServerError; /// the `ldk-server.config` file. A no-op implementation is included by default, /// with specific implementations enabled via feature flags. /// -/// Events are represented as [`EventEnvelope`] messages, which are Protocol Buffers -/// ([protobuf](https://protobuf.dev/)) objects defined in [`ldk_server_protos::events`]. -/// These events are serialized to bytes by the publisher before transmission, and consumers can -/// deserialize them using the protobuf definitions. +/// Events are represented as [`Event`] messages defined in [`ldk_server_json_models::events`]. +/// These events are serialized to JSON by the publisher before transmission, and consumers can +/// deserialize them using the type definitions in that module. /// /// The underlying messaging system is expected to support durably buffered events, /// enabling easy decoupling between the LDK Server and event consumers. @@ -32,9 +31,9 @@ pub trait EventPublisher: Send + Sync { /// Publishes an event to the underlying messaging system. /// /// # Arguments - /// * `event` - The event message to publish, provided as an [`EventEnvelope`] - /// defined in [`ldk_server_protos::events`]. Implementors must serialize - /// the whole [`EventEnvelope`] to bytes before publishing. + /// * `event` - The event message to publish, provided as an [`Event`] + /// defined in [`ldk_server_json_models::events`]. Implementors must serialize + /// the [`Event`] to bytes before publishing. /// /// In order to ensure no events are lost, implementors of this trait must publish events /// durably to underlying messaging system. An event is considered published when @@ -48,7 +47,7 @@ pub trait EventPublisher: Send + Sync { /// may degrade performance until the underlying messaging system is operational again. /// /// [`LdkServerErrorCode::InternalServerError`]: crate::api::error::LdkServerErrorCode - async fn publish(&self, event: EventEnvelope) -> Result<(), LdkServerError>; + async fn publish(&self, event: Event) -> Result<(), LdkServerError>; } /// A no-op implementation of the [`EventPublisher`] trait. @@ -62,7 +61,7 @@ impl EventPublisher for NoopEventPublisher { /// /// This implementation does nothing and always returns `Ok(())`, serving as a /// default when no messaging system is configured. - async fn publish(&self, _event: EventEnvelope) -> Result<(), LdkServerError> { + async fn publish(&self, _event: Event) -> Result<(), LdkServerError> { Ok(()) } } diff --git a/ldk-server/src/io/events/mod.rs b/ldk-server/src/io/events/mod.rs index 4ec9a65a..2c584237 100644 --- a/ldk-server/src/io/events/mod.rs +++ b/ldk-server/src/io/events/mod.rs @@ -12,15 +12,15 @@ pub(crate) mod event_publisher; #[cfg(feature = "events-rabbitmq")] pub(crate) mod rabbitmq; -use ldk_server_protos::events::event_envelope; +use ldk_server_json_models::events::Event; /// Event variant to event name mapping. -pub(crate) fn get_event_name(event: &event_envelope::Event) -> &'static str { +pub(crate) fn get_event_name(event: &Event) -> &'static str { match event { - event_envelope::Event::PaymentReceived(_) => "PaymentReceived", - event_envelope::Event::PaymentSuccessful(_) => "PaymentSuccessful", - event_envelope::Event::PaymentFailed(_) => "PaymentFailed", - event_envelope::Event::PaymentForwarded(_) => "PaymentForwarded", - event_envelope::Event::PaymentClaimable(_) => "PaymentClaimable", + Event::PaymentReceived(_) => "PaymentReceived", + Event::PaymentSuccessful(_) => "PaymentSuccessful", + Event::PaymentFailed(_) => "PaymentFailed", + Event::PaymentForwarded(_) => "PaymentForwarded", + Event::PaymentClaimable(_) => "PaymentClaimable", } } diff --git a/ldk-server/src/io/events/rabbitmq/mod.rs b/ldk-server/src/io/events/rabbitmq/mod.rs index a20b5bd9..f92a2b7a 100644 --- a/ldk-server/src/io/events/rabbitmq/mod.rs +++ b/ldk-server/src/io/events/rabbitmq/mod.rs @@ -9,14 +9,13 @@ use std::sync::Arc; -use ::prost::Message; use async_trait::async_trait; use lapin::options::{BasicPublishOptions, ConfirmSelectOptions, ExchangeDeclareOptions}; use lapin::types::FieldTable; use lapin::{ BasicProperties, Channel, Connection, ConnectionProperties, ConnectionState, ExchangeKind, }; -use ldk_server_protos::events::EventEnvelope; +use ldk_server_json_models::events::Event; use tokio::sync::Mutex; use crate::api::error::LdkServerError; @@ -111,7 +110,7 @@ impl EventPublisher for RabbitMqEventPublisher { /// /// The event is published to a fanout exchange with persistent delivery mode, /// and the method waits for confirmation from RabbitMQ to ensure durability. - async fn publish(&self, event: EventEnvelope) -> Result<(), LdkServerError> { + async fn publish(&self, event: Event) -> Result<(), LdkServerError> { // Ensure connection is alive before proceeding self.ensure_connected().await?; @@ -126,7 +125,7 @@ impl EventPublisher for RabbitMqEventPublisher { &self.config.exchange_name, "", // Empty routing key should be used for fanout exchange, since it is ignored. BasicPublishOptions::default(), - &event.encode_to_vec(), + &serde_json::to_vec(&event).unwrap(), BasicProperties::default().with_delivery_mode(DELIVERY_MODE_PERSISTENT), ) .await @@ -166,8 +165,7 @@ mod integration_tests_events_rabbitmq { }; use lapin::types::FieldTable; use lapin::{Channel, Connection}; - use ldk_server_protos::events::event_envelope::Event; - use ldk_server_protos::events::PaymentForwarded; + use ldk_server_json_models::events::PaymentForwarded; use tokio; use super::*; @@ -188,8 +186,20 @@ mod integration_tests_events_rabbitmq { let queue_name = "test_queue"; setup_queue(&queue_name, &channel, &config).await; - let event = - EventEnvelope { event: Some(Event::PaymentForwarded(PaymentForwarded::default())) }; + let event = Event::PaymentForwarded(PaymentForwarded { + forwarded_payment: ldk_server_json_models::types::ForwardedPayment { + prev_channel_id: [0u8; 32], + next_channel_id: [0u8; 32], + prev_user_channel_id: String::new(), + prev_node_id: [0u8; 33], + next_node_id: [0u8; 33], + next_user_channel_id: None, + total_fee_earned_msat: None, + skimmed_fee_msat: None, + claim_from_onchain_tx: false, + outbound_amount_forwarded_msat: None, + }, + }); publisher.publish(event.clone()).await.expect("Failed to publish event"); consume_event(&queue_name, &channel, &event).await.expect("Failed to consume event"); @@ -223,7 +233,7 @@ mod integration_tests_events_rabbitmq { } async fn consume_event( - queue_name: &str, channel: &Channel, expected_event: &EventEnvelope, + queue_name: &str, channel: &Channel, expected_event: &Event, ) -> io::Result<()> { let mut consumer = channel .basic_consume( @@ -236,7 +246,8 @@ mod integration_tests_events_rabbitmq { .unwrap(); let delivery = tokio::time::timeout(Duration::from_secs(10), consumer.next()).await?.unwrap().unwrap(); - let received_event = EventEnvelope::decode(&*delivery.data)?; + let received_event: Event = serde_json::from_slice(&delivery.data) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; assert_eq!(received_event, *expected_event, "Event mismatch"); channel.basic_ack(delivery.delivery_tag, BasicAckOptions::default()).await.unwrap(); Ok(()) diff --git a/ldk-server/src/main.rs b/ldk-server/src/main.rs index 3de2a408..a83e311d 100644 --- a/ldk-server/src/main.rs +++ b/ldk-server/src/main.rs @@ -27,11 +27,9 @@ use ldk_node::config::Config; use ldk_node::entropy::NodeEntropy; use ldk_node::lightning::ln::channelmanager::PaymentId; use ldk_node::{Builder, Event, Node}; -use ldk_server_protos::events; -use ldk_server_protos::events::{event_envelope, EventEnvelope}; -use ldk_server_protos::types::Payment; +use ldk_server_json_models::events; +use ldk_server_json_models::types::Payment; use log::{debug, error, info}; -use prost::Message; use tokio::net::TcpListener; use tokio::select; use tokio::signal::unix::SignalKind; @@ -48,9 +46,9 @@ use crate::io::persist::{ PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, }; use crate::service::NodeService; +use crate::util::adapter::{forwarded_payment_to_model, payment_to_model}; use crate::util::config::{load_config, ArgsConfig, ChainSource}; use crate::util::logger::ServerLogger; -use crate::util::proto_adapter::{forwarded_payment_to_proto, payment_to_proto}; use crate::util::systemd; use crate::util::tls::get_or_generate_tls_config; @@ -322,8 +320,8 @@ fn main() { let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1"); publish_event_and_upsert_payment(&payment_id, - |payment_ref| event_envelope::Event::PaymentReceived(events::PaymentReceived { - payment: Some(payment_ref.clone()), + |payment_ref| events::Event::PaymentReceived(events::PaymentReceived { + payment: payment_ref.clone(), }), &event_node, Arc::clone(&event_publisher), @@ -333,8 +331,8 @@ fn main() { let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1"); publish_event_and_upsert_payment(&payment_id, - |payment_ref| event_envelope::Event::PaymentSuccessful(events::PaymentSuccessful { - payment: Some(payment_ref.clone()), + |payment_ref| events::Event::PaymentSuccessful(events::PaymentSuccessful { + payment: payment_ref.clone(), }), &event_node, Arc::clone(&event_publisher), @@ -344,8 +342,8 @@ fn main() { let payment_id = payment_id.expect("PaymentId expected for ldk-server >=0.1"); publish_event_and_upsert_payment(&payment_id, - |payment_ref| event_envelope::Event::PaymentFailed(events::PaymentFailed { - payment: Some(payment_ref.clone()), + |payment_ref| events::Event::PaymentFailed(events::PaymentFailed { + payment: payment_ref.clone(), }), &event_node, Arc::clone(&event_publisher), @@ -353,8 +351,8 @@ fn main() { }, Event::PaymentClaimable {payment_id, ..} => { publish_event_and_upsert_payment(&payment_id, - |payment_ref| event_envelope::Event::PaymentClaimable(events::PaymentClaimable { - payment: Some(payment_ref.clone()), + |payment_ref| events::Event::PaymentClaimable(events::PaymentClaimable { + payment: payment_ref.clone(), }), &event_node, Arc::clone(&event_publisher), @@ -377,7 +375,7 @@ fn main() { outbound_amount_forwarded_msat.unwrap_or(0), total_fee_earned_msat.unwrap_or(0), prev_channel_id, next_channel_id ); - let forwarded_payment = forwarded_payment_to_proto( + let forwarded_payment = forwarded_payment_to_model( prev_channel_id, next_channel_id, prev_user_channel_id, @@ -398,11 +396,11 @@ fn main() { let forwarded_payment_creation_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time must be > 1970").as_secs() as i64; - match event_publisher.publish(EventEnvelope { - event: Some(event_envelope::Event::PaymentForwarded(events::PaymentForwarded { - forwarded_payment: Some(forwarded_payment.clone()), - })), - }).await { + match event_publisher.publish( + events::Event::PaymentForwarded(events::PaymentForwarded { + forwarded_payment: forwarded_payment.clone(), + }), + ).await { Ok(_) => {}, Err(e) => { error!("Failed to publish 'PaymentForwarded' event: {}", e); @@ -413,7 +411,7 @@ fn main() { match paginated_store.write(FORWARDED_PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE,FORWARDED_PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, &forwarded_payment_id.to_lower_hex_string(), forwarded_payment_creation_time, - &forwarded_payment.encode_to_vec(), + &serde_json::to_vec(&forwarded_payment).unwrap(), ) { Ok(_) => { if let Err(e) = event_node.event_handled() { @@ -475,16 +473,15 @@ fn main() { } async fn publish_event_and_upsert_payment( - payment_id: &PaymentId, payment_to_event: fn(&Payment) -> event_envelope::Event, - event_node: &Node, event_publisher: Arc, - paginated_store: Arc, + payment_id: &PaymentId, payment_to_event: fn(&Payment) -> events::Event, event_node: &Node, + event_publisher: Arc, paginated_store: Arc, ) { if let Some(payment_details) = event_node.payment(payment_id) { - let payment = payment_to_proto(payment_details); + let payment = payment_to_model(payment_details); let event = payment_to_event(&payment); let event_name = get_event_name(&event); - match event_publisher.publish(EventEnvelope { event: Some(event) }).await { + match event_publisher.publish(event).await { Ok(_) => {}, Err(e) => { error!("Failed to publish '{event_name}' event, : {e}"); @@ -507,9 +504,9 @@ fn upsert_payment_details( match paginated_store.write( PAYMENTS_PERSISTENCE_PRIMARY_NAMESPACE, PAYMENTS_PERSISTENCE_SECONDARY_NAMESPACE, - &payment.id, + &payment.id.to_lower_hex_string(), time, - &payment.encode_to_vec(), + &serde_json::to_vec(&payment).unwrap(), ) { Ok(_) => { if let Err(e) = event_node.event_handled() { diff --git a/ldk-server/src/service.rs b/ldk-server/src/service.rs index 05004ae6..ac5e002f 100644 --- a/ldk-server/src/service.rs +++ b/ldk-server/src/service.rs @@ -18,7 +18,7 @@ use hyper::{Request, Response, StatusCode}; use ldk_node::bitcoin::hashes::hmac::{Hmac, HmacEngine}; use ldk_node::bitcoin::hashes::{sha256, Hash, HashEngine}; use ldk_node::Node; -use ldk_server_protos::endpoints::{ +use ldk_server_json_models::endpoints::{ BOLT11_CLAIM_FOR_HASH_PATH, BOLT11_FAIL_FOR_HASH_PATH, BOLT11_RECEIVE_FOR_HASH_PATH, BOLT11_RECEIVE_PATH, BOLT11_RECEIVE_VARIABLE_AMOUNT_VIA_JIT_CHANNEL_PATH, BOLT11_RECEIVE_VIA_JIT_CHANNEL_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH, @@ -30,7 +30,8 @@ use ldk_server_protos::endpoints::{ SPLICE_OUT_PATH, SPONTANEOUS_SEND_PATH, UNIFIED_SEND_PATH, UPDATE_CHANNEL_CONFIG_PATH, VERIFY_SIGNATURE_PATH, }; -use prost::Message; +use serde::de::DeserializeOwned; +use serde::Serialize; use crate::api::bolt11_claim_for_hash::handle_bolt11_claim_for_hash_request; use crate::api::bolt11_fail_for_hash::handle_bolt11_fail_for_hash_request; @@ -70,7 +71,7 @@ use crate::api::unified_send::handle_unified_send_request; use crate::api::update_channel_config::handle_update_channel_config_request; use crate::api::verify_signature::handle_verify_signature_request; use crate::io::persist::paginated_kv_store::PaginatedKVStore; -use crate::util::proto_adapter::to_error_response; +use crate::util::adapter::to_error_response; // Maximum request body size: 10 MB // This prevents memory exhaustion from large requests @@ -165,6 +166,23 @@ pub(crate) struct Context { pub(crate) paginated_kv_store: Arc, } +fn json_response(status: StatusCode, body: &impl Serialize) -> Response> { + match serde_json::to_vec(body) { + Ok(bytes) => Response::builder() + .status(status) + .header("Content-Type", "application/json") + .body(Full::new(Bytes::from(bytes))) + // unwrap safety: body only errors when previous chained calls failed. + .unwrap(), + Err(_) => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .header("Content-Type", "text/plain") + .body(Full::new(Bytes::from("Internal server error"))) + // unwrap safety: body only errors when previous chained calls failed. + .unwrap(), + } +} + impl Service> for NodeService { type Response = Response>; type Error = hyper::Error; @@ -176,13 +194,7 @@ impl Service> for NodeService { Ok(params) => params, Err(e) => { let (error_response, status_code) = to_error_response(e); - return Box::pin(async move { - Ok(Response::builder() - .status(status_code) - .body(Full::new(Bytes::from(error_response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()) - }); + return Box::pin(async move { Ok(json_response(status_code, &error_response)) }); }, }; @@ -442,8 +454,8 @@ impl Service> for NodeService { } async fn handle_request< - T: Message + Default, - R: Message, + T: DeserializeOwned, + R: Serialize, F: Fn(Context, T) -> Result, >( context: Context, request: Request, auth_params: AuthParams, api_key: String, @@ -458,11 +470,7 @@ async fn handle_request< InvalidRequestError, "Request body too large or failed to read.", )); - return Ok(Response::builder() - .status(status_code) - .body(Full::new(Bytes::from(error_response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()); + return Ok(json_response(status_code, &error_response)); }, }; @@ -471,36 +479,22 @@ async fn handle_request< validate_hmac_auth(auth_params.timestamp, &auth_params.hmac_hex, &bytes, &api_key) { let (error_response, status_code) = to_error_response(e); - return Ok(Response::builder() - .status(status_code) - .body(Full::new(Bytes::from(error_response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()); + return Ok(json_response(status_code, &error_response)); } - match T::decode(bytes) { + let decode_bytes = if bytes.is_empty() { &b"{}"[..] } else { &bytes[..] }; + match serde_json::from_slice::(decode_bytes) { Ok(request) => match handler(context, request) { - Ok(response) => Ok(Response::builder() - .body(Full::new(Bytes::from(response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()), + Ok(response) => Ok(json_response(StatusCode::OK, &response)), Err(e) => { let (error_response, status_code) = to_error_response(e); - Ok(Response::builder() - .status(status_code) - .body(Full::new(Bytes::from(error_response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()) + Ok(json_response(status_code, &error_response)) }, }, Err(_) => { let (error_response, status_code) = to_error_response(LdkServerError::new(InvalidRequestError, "Malformed request.")); - Ok(Response::builder() - .status(status_code) - .body(Full::new(Bytes::from(error_response.encode_to_vec()))) - // unwrap safety: body only errors when previous chained calls failed. - .unwrap()) + Ok(json_response(status_code, &error_response)) }, } } diff --git a/ldk-server/src/util/proto_adapter.rs b/ldk-server/src/util/adapter.rs similarity index 50% rename from ldk-server/src/util/proto_adapter.rs rename to ldk-server/src/util/adapter.rs index 77441dfd..11f49fc0 100644 --- a/ldk-server/src/util/proto_adapter.rs +++ b/ldk-server/src/util/adapter.rs @@ -7,10 +7,9 @@ // You may not use this file except in accordance with one or both of these // licenses. -use bytes::Bytes; use hex::prelude::*; use hyper::StatusCode; -use ldk_node::bitcoin::hashes::sha256; +use ldk_node::bitcoin::hashes::{sha256, Hash as _}; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::config::{ChannelConfig, MaxDustHTLCExposure}; use ldk_node::lightning::chain::channelmonitor::BalanceSource; @@ -23,20 +22,9 @@ use ldk_node::payment::{ ConfirmationStatus, PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus, }; use ldk_node::{ChannelDetails, LightningBalance, PeerDetails, PendingSweepBalance, UserChannelId}; -use ldk_server_protos::error::{ErrorCode, ErrorResponse}; -use ldk_server_protos::types::confirmation_status::Status::{Confirmed, Unconfirmed}; -use ldk_server_protos::types::lightning_balance::BalanceType::{ - ClaimableAwaitingConfirmations, ClaimableOnChannelClose, ContentiousClaimable, - CounterpartyRevokedOutputClaimable, MaybePreimageClaimableHtlc, MaybeTimeoutClaimableHtlc, -}; -use ldk_server_protos::types::payment_kind::Kind::{ - Bolt11, Bolt11Jit, Bolt12Offer, Bolt12Refund, Onchain, Spontaneous, -}; -use ldk_server_protos::types::pending_sweep_balance::BalanceType::{ - AwaitingThresholdConfirmations, BroadcastAwaitingConfirmation, PendingBroadcast, -}; -use ldk_server_protos::types::{ - bolt11_invoice_description, Channel, ForwardedPayment, LspFeeLimits, OutPoint, Payment, Peer, +use ldk_server_json_models::error::{ErrorCode, ErrorResponse}; +use ldk_server_json_models::types::{ + Channel, ForwardedPayment, LspFeeLimits, OutPoint, Payment, Peer, }; use crate::api::error::LdkServerError; @@ -44,22 +32,30 @@ use crate::api::error::LdkServerErrorCode::{ AuthError, InternalServerError, InvalidRequestError, LightningError, }; -pub(crate) fn peer_to_proto(peer: PeerDetails) -> Peer { +/// Bitcoin block hashes and txids are displayed in reversed byte order by convention. +/// `Hash::to_byte_array()` returns internal order; this helper reverses for display. +pub(crate) fn to_display_bytes(hash: [u8; 32]) -> [u8; 32] { + let mut bytes = hash; + bytes.reverse(); + bytes +} + +pub(crate) fn peer_to_model(peer: PeerDetails) -> Peer { Peer { - node_id: peer.node_id.to_string(), + node_id: peer.node_id.serialize(), address: peer.address.to_string(), is_persisted: peer.is_persisted, is_connected: peer.is_connected, } } -pub(crate) fn channel_to_proto(channel: ChannelDetails) -> Channel { +pub(crate) fn channel_to_model(channel: ChannelDetails) -> Channel { Channel { - channel_id: channel.channel_id.0.to_lower_hex_string(), - counterparty_node_id: channel.counterparty_node_id.to_string(), + channel_id: channel.channel_id.0, + counterparty_node_id: channel.counterparty_node_id.serialize(), funding_txo: channel .funding_txo - .map(|o| OutPoint { txid: o.txid.to_string(), vout: o.vout }), + .map(|o| OutPoint { txid: to_display_bytes(o.txid.to_byte_array()), vout: o.vout }), user_channel_id: channel.user_channel_id.0.to_string(), unspendable_punishment_reserve: channel.unspendable_punishment_reserve, channel_value_sats: channel.channel_value_sats, @@ -72,7 +68,7 @@ pub(crate) fn channel_to_proto(channel: ChannelDetails) -> Channel { is_channel_ready: channel.is_channel_ready, is_usable: channel.is_usable, is_announced: channel.is_announced, - channel_config: Some(channel_config_to_proto(channel.config)), + channel_config: Some(channel_config_to_model(channel.config)), next_outbound_htlc_limit_msat: channel.next_outbound_htlc_limit_msat, next_outbound_htlc_minimum_msat: channel.next_outbound_htlc_minimum_msat, force_close_spend_delay: channel.force_close_spend_delay.map(|x| x as u32), @@ -90,10 +86,10 @@ pub(crate) fn channel_to_proto(channel: ChannelDetails) -> Channel { } } -pub(crate) fn channel_config_to_proto( +pub(crate) fn channel_config_to_model( channel_config: ChannelConfig, -) -> ldk_server_protos::types::ChannelConfig { - ldk_server_protos::types::ChannelConfig { +) -> ldk_server_json_models::types::ChannelConfig { + ldk_server_json_models::types::ChannelConfig { forwarding_fee_proportional_millionths: Some( channel_config.forwarding_fee_proportional_millionths, ), @@ -105,20 +101,16 @@ pub(crate) fn channel_config_to_proto( accept_underpaying_htlcs: Some(channel_config.accept_underpaying_htlcs), max_dust_htlc_exposure: match channel_config.max_dust_htlc_exposure { MaxDustHTLCExposure::FixedLimit { limit_msat } => { - Some(ldk_server_protos::types::channel_config::MaxDustHtlcExposure::FixedLimitMsat( - limit_msat, - )) + Some(ldk_server_json_models::types::MaxDustHtlcExposure::FixedLimitMsat(limit_msat)) }, MaxDustHTLCExposure::FeeRateMultiplier { multiplier } => Some( - ldk_server_protos::types::channel_config::MaxDustHtlcExposure::FeeRateMultiplier( - multiplier, - ), + ldk_server_json_models::types::MaxDustHtlcExposure::FeeRateMultiplier(multiplier), ), }, } } -pub(crate) fn payment_to_proto(payment: PaymentDetails) -> Payment { +pub(crate) fn payment_to_model(payment: PaymentDetails) -> Payment { let PaymentDetails { id, kind, @@ -130,41 +122,43 @@ pub(crate) fn payment_to_proto(payment: PaymentDetails) -> Payment { } = payment; Payment { - id: id.to_string(), - kind: Some(payment_kind_to_proto(kind)), + id: id.0, + kind: payment_kind_to_model(kind), amount_msat, fee_paid_msat, direction: match direction { - PaymentDirection::Inbound => ldk_server_protos::types::PaymentDirection::Inbound.into(), - PaymentDirection::Outbound => { - ldk_server_protos::types::PaymentDirection::Outbound.into() - }, + PaymentDirection::Inbound => ldk_server_json_models::types::PaymentDirection::Inbound, + PaymentDirection::Outbound => ldk_server_json_models::types::PaymentDirection::Outbound, }, status: match status { - PaymentStatus::Pending => ldk_server_protos::types::PaymentStatus::Pending.into(), - PaymentStatus::Succeeded => ldk_server_protos::types::PaymentStatus::Succeeded.into(), - PaymentStatus::Failed => ldk_server_protos::types::PaymentStatus::Failed.into(), + PaymentStatus::Pending => ldk_server_json_models::types::PaymentStatus::Pending, + PaymentStatus::Succeeded => ldk_server_json_models::types::PaymentStatus::Succeeded, + PaymentStatus::Failed => ldk_server_json_models::types::PaymentStatus::Failed, }, latest_update_timestamp, } } -pub(crate) fn payment_kind_to_proto( +pub(crate) fn payment_kind_to_model( payment_kind: PaymentKind, -) -> ldk_server_protos::types::PaymentKind { +) -> ldk_server_json_models::types::PaymentKind { match payment_kind { - PaymentKind::Onchain { txid, status } => ldk_server_protos::types::PaymentKind { - kind: Some(Onchain(ldk_server_protos::types::Onchain { - txid: txid.to_string(), - status: Some(confirmation_status_to_proto(status)), - })), + PaymentKind::Onchain { txid, status } => { + ldk_server_json_models::types::PaymentKind::Onchain( + ldk_server_json_models::types::Onchain { + txid: to_display_bytes(txid.to_byte_array()), + status: confirmation_status_to_model(status), + }, + ) }, - PaymentKind::Bolt11 { hash, preimage, secret } => ldk_server_protos::types::PaymentKind { - kind: Some(Bolt11(ldk_server_protos::types::Bolt11 { - hash: hash.to_string(), - preimage: preimage.map(|p| p.to_string()), - secret: secret.map(|s| Bytes::copy_from_slice(&s.0)), - })), + PaymentKind::Bolt11 { hash, preimage, secret } => { + ldk_server_json_models::types::PaymentKind::Bolt11( + ldk_server_json_models::types::Bolt11 { + hash: hash.0, + preimage: preimage.map(|p| p.0), + secret: secret.map(|s| s.0), + }, + ) }, PaymentKind::Bolt11Jit { hash, @@ -172,73 +166,77 @@ pub(crate) fn payment_kind_to_proto( secret, lsp_fee_limits, counterparty_skimmed_fee_msat, - } => ldk_server_protos::types::PaymentKind { - kind: Some(Bolt11Jit(ldk_server_protos::types::Bolt11Jit { - hash: hash.to_string(), - preimage: preimage.map(|p| p.to_string()), - secret: secret.map(|s| Bytes::copy_from_slice(&s.0)), + } => ldk_server_json_models::types::PaymentKind::Bolt11Jit( + ldk_server_json_models::types::Bolt11Jit { + hash: hash.0, + preimage: preimage.map(|p| p.0), + secret: secret.map(|s| s.0), lsp_fee_limits: Some(LspFeeLimits { max_total_opening_fee_msat: lsp_fee_limits.max_total_opening_fee_msat, max_proportional_opening_fee_ppm_msat: lsp_fee_limits .max_proportional_opening_fee_ppm_msat, }), counterparty_skimmed_fee_msat, - })), - }, + }, + ), PaymentKind::Bolt12Offer { hash, preimage, secret, offer_id, payer_note, quantity } => { - ldk_server_protos::types::PaymentKind { - kind: Some(Bolt12Offer(ldk_server_protos::types::Bolt12Offer { - hash: hash.map(|h| h.to_string()), - preimage: preimage.map(|p| p.to_string()), - secret: secret.map(|s| Bytes::copy_from_slice(&s.0)), - offer_id: offer_id.0.to_lower_hex_string(), + ldk_server_json_models::types::PaymentKind::Bolt12Offer( + ldk_server_json_models::types::Bolt12Offer { + hash: hash.map(|h| h.0), + preimage: preimage.map(|p| p.0), + secret: secret.map(|s| s.0), + offer_id: offer_id.0, payer_note: payer_note.map(|s| s.to_string()), quantity, - })), - } + }, + ) }, PaymentKind::Bolt12Refund { hash, preimage, secret, payer_note, quantity } => { - ldk_server_protos::types::PaymentKind { - kind: Some(Bolt12Refund(ldk_server_protos::types::Bolt12Refund { - hash: hash.map(|h| h.to_string()), - preimage: preimage.map(|p| p.to_string()), - secret: secret.map(|s| Bytes::copy_from_slice(&s.0)), + ldk_server_json_models::types::PaymentKind::Bolt12Refund( + ldk_server_json_models::types::Bolt12Refund { + hash: hash.map(|h| h.0), + preimage: preimage.map(|p| p.0), + secret: secret.map(|s| s.0), payer_note: payer_note.map(|s| s.to_string()), quantity, - })), - } + }, + ) }, - PaymentKind::Spontaneous { hash, preimage } => ldk_server_protos::types::PaymentKind { - kind: Some(Spontaneous(ldk_server_protos::types::Spontaneous { - hash: hash.to_string(), - preimage: preimage.map(|p| p.to_string()), - })), + PaymentKind::Spontaneous { hash, preimage } => { + ldk_server_json_models::types::PaymentKind::Spontaneous( + ldk_server_json_models::types::Spontaneous { + hash: hash.0, + preimage: preimage.map(|p| p.0), + }, + ) }, } } -pub(crate) fn confirmation_status_to_proto( +pub(crate) fn confirmation_status_to_model( confirmation_status: ConfirmationStatus, -) -> ldk_server_protos::types::ConfirmationStatus { +) -> ldk_server_json_models::types::ConfirmationStatus { match confirmation_status { ConfirmationStatus::Confirmed { block_hash, height, timestamp } => { - ldk_server_protos::types::ConfirmationStatus { - status: Some(Confirmed(ldk_server_protos::types::Confirmed { - block_hash: block_hash.to_string(), + ldk_server_json_models::types::ConfirmationStatus::Confirmed( + ldk_server_json_models::types::Confirmed { + block_hash: to_display_bytes(block_hash.to_byte_array()), height, timestamp, - })), - } + }, + ) }, - ConfirmationStatus::Unconfirmed => ldk_server_protos::types::ConfirmationStatus { - status: Some(Unconfirmed(ldk_server_protos::types::Unconfirmed {})), + ConfirmationStatus::Unconfirmed => { + ldk_server_json_models::types::ConfirmationStatus::Unconfirmed( + ldk_server_json_models::types::Unconfirmed {}, + ) }, } } -pub(crate) fn lightning_balance_to_proto( +pub(crate) fn lightning_balance_to_model( lightning_balance: LightningBalance, -) -> ldk_server_protos::types::LightningBalance { +) -> ldk_server_json_models::types::LightningBalance { match lightning_balance { LightningBalance::ClaimableOnChannelClose { channel_id, @@ -249,48 +247,44 @@ pub(crate) fn lightning_balance_to_proto( outbound_forwarded_htlc_rounded_msat, inbound_claiming_htlc_rounded_msat, inbound_htlc_rounded_msat, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(ClaimableOnChannelClose( - ldk_server_protos::types::ClaimableOnChannelClose { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - transaction_fee_satoshis, - outbound_payment_htlc_rounded_msat, - outbound_forwarded_htlc_rounded_msat, - inbound_claiming_htlc_rounded_msat, - inbound_htlc_rounded_msat, - }, - )), - }, + } => ldk_server_json_models::types::LightningBalance::ClaimableOnChannelClose( + ldk_server_json_models::types::ClaimableOnChannelClose { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + transaction_fee_satoshis, + outbound_payment_htlc_rounded_msat, + outbound_forwarded_htlc_rounded_msat, + inbound_claiming_htlc_rounded_msat, + inbound_htlc_rounded_msat, + }, + ), LightningBalance::ClaimableAwaitingConfirmations { channel_id, counterparty_node_id, amount_satoshis, confirmation_height, source, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(ClaimableAwaitingConfirmations( - ldk_server_protos::types::ClaimableAwaitingConfirmations { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - confirmation_height, - source: match source { - BalanceSource::HolderForceClosed => { - ldk_server_protos::types::BalanceSource::HolderForceClosed.into() - }, - BalanceSource::CounterpartyForceClosed => { - ldk_server_protos::types::BalanceSource::CounterpartyForceClosed.into() - }, - BalanceSource::CoopClose => { - ldk_server_protos::types::BalanceSource::CoopClose.into() - }, - BalanceSource::Htlc => ldk_server_protos::types::BalanceSource::Htlc.into(), + } => ldk_server_json_models::types::LightningBalance::ClaimableAwaitingConfirmations( + ldk_server_json_models::types::ClaimableAwaitingConfirmations { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + confirmation_height, + source: match source { + BalanceSource::HolderForceClosed => { + ldk_server_json_models::types::BalanceSource::HolderForceClosed + }, + BalanceSource::CounterpartyForceClosed => { + ldk_server_json_models::types::BalanceSource::CounterpartyForceClosed }, + BalanceSource::CoopClose => { + ldk_server_json_models::types::BalanceSource::CoopClose + }, + BalanceSource::Htlc => ldk_server_json_models::types::BalanceSource::Htlc, }, - )), - }, + }, + ), LightningBalance::ContentiousClaimable { channel_id, counterparty_node_id, @@ -298,18 +292,16 @@ pub(crate) fn lightning_balance_to_proto( timeout_height, payment_hash, payment_preimage, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(ContentiousClaimable( - ldk_server_protos::types::ContentiousClaimable { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - timeout_height, - payment_hash: payment_hash.to_string(), - payment_preimage: payment_preimage.to_string(), - }, - )), - }, + } => ldk_server_json_models::types::LightningBalance::ContentiousClaimable( + ldk_server_json_models::types::ContentiousClaimable { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + timeout_height, + payment_hash: payment_hash.0, + payment_preimage: payment_preimage.0, + }, + ), LightningBalance::MaybeTimeoutClaimableHTLC { channel_id, counterparty_node_id, @@ -317,100 +309,90 @@ pub(crate) fn lightning_balance_to_proto( claimable_height, payment_hash, outbound_payment, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(MaybeTimeoutClaimableHtlc( - ldk_server_protos::types::MaybeTimeoutClaimableHtlc { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - claimable_height, - payment_hash: payment_hash.to_string(), - outbound_payment, - }, - )), - }, + } => ldk_server_json_models::types::LightningBalance::MaybeTimeoutClaimableHtlc( + ldk_server_json_models::types::MaybeTimeoutClaimableHtlc { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + claimable_height, + payment_hash: payment_hash.0, + outbound_payment, + }, + ), LightningBalance::MaybePreimageClaimableHTLC { channel_id, counterparty_node_id, amount_satoshis, expiry_height, payment_hash, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(MaybePreimageClaimableHtlc( - ldk_server_protos::types::MaybePreimageClaimableHtlc { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - expiry_height, - payment_hash: payment_hash.to_string(), - }, - )), - }, + } => ldk_server_json_models::types::LightningBalance::MaybePreimageClaimableHtlc( + ldk_server_json_models::types::MaybePreimageClaimableHtlc { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + expiry_height, + payment_hash: payment_hash.0, + }, + ), LightningBalance::CounterpartyRevokedOutputClaimable { channel_id, counterparty_node_id, amount_satoshis, - } => ldk_server_protos::types::LightningBalance { - balance_type: Some(CounterpartyRevokedOutputClaimable( - ldk_server_protos::types::CounterpartyRevokedOutputClaimable { - channel_id: channel_id.0.to_lower_hex_string(), - counterparty_node_id: counterparty_node_id.to_string(), - amount_satoshis, - }, - )), - }, + } => ldk_server_json_models::types::LightningBalance::CounterpartyRevokedOutputClaimable( + ldk_server_json_models::types::CounterpartyRevokedOutputClaimable { + channel_id: channel_id.0, + counterparty_node_id: counterparty_node_id.serialize(), + amount_satoshis, + }, + ), } } -pub(crate) fn pending_sweep_balance_to_proto( +pub(crate) fn pending_sweep_balance_to_model( pending_sweep_balance: PendingSweepBalance, -) -> ldk_server_protos::types::PendingSweepBalance { +) -> ldk_server_json_models::types::PendingSweepBalance { match pending_sweep_balance { PendingSweepBalance::PendingBroadcast { channel_id, amount_satoshis } => { - ldk_server_protos::types::PendingSweepBalance { - balance_type: Some(PendingBroadcast(ldk_server_protos::types::PendingBroadcast { - channel_id: channel_id.map(|c| c.0.to_lower_hex_string()), + ldk_server_json_models::types::PendingSweepBalance::PendingBroadcast( + ldk_server_json_models::types::PendingBroadcast { + channel_id: channel_id.map(|c| c.0), amount_satoshis, - })), - } + }, + ) }, PendingSweepBalance::BroadcastAwaitingConfirmation { channel_id, latest_broadcast_height, latest_spending_txid, amount_satoshis, - } => ldk_server_protos::types::PendingSweepBalance { - balance_type: Some(BroadcastAwaitingConfirmation( - ldk_server_protos::types::BroadcastAwaitingConfirmation { - channel_id: channel_id.map(|c| c.0.to_lower_hex_string()), - latest_broadcast_height, - latest_spending_txid: latest_spending_txid.to_string(), - amount_satoshis, - }, - )), - }, + } => ldk_server_json_models::types::PendingSweepBalance::BroadcastAwaitingConfirmation( + ldk_server_json_models::types::BroadcastAwaitingConfirmation { + channel_id: channel_id.map(|c| c.0), + latest_broadcast_height, + latest_spending_txid: to_display_bytes(latest_spending_txid.to_byte_array()), + amount_satoshis, + }, + ), PendingSweepBalance::AwaitingThresholdConfirmations { channel_id, latest_spending_txid, confirmation_hash, confirmation_height, amount_satoshis, - } => ldk_server_protos::types::PendingSweepBalance { - balance_type: Some(AwaitingThresholdConfirmations( - ldk_server_protos::types::AwaitingThresholdConfirmations { - channel_id: channel_id.map(|c| c.0.to_lower_hex_string()), - latest_spending_txid: latest_spending_txid.to_string(), - confirmation_hash: confirmation_hash.to_string(), - confirmation_height, - amount_satoshis, - }, - )), - }, + } => ldk_server_json_models::types::PendingSweepBalance::AwaitingThresholdConfirmations( + ldk_server_json_models::types::AwaitingThresholdConfirmations { + channel_id: channel_id.map(|c| c.0), + latest_spending_txid: to_display_bytes(latest_spending_txid.to_byte_array()), + confirmation_hash: to_display_bytes(confirmation_hash.to_byte_array()), + confirmation_height, + amount_satoshis, + }, + ), } } #[allow(clippy::too_many_arguments)] -pub(crate) fn forwarded_payment_to_proto( +pub(crate) fn forwarded_payment_to_model( prev_channel_id: ChannelId, next_channel_id: ChannelId, prev_user_channel_id: Option, next_user_channel_id: Option, prev_node_id: Option, next_node_id: Option, @@ -418,15 +400,15 @@ pub(crate) fn forwarded_payment_to_proto( outbound_amount_forwarded_msat: Option, ) -> ForwardedPayment { ForwardedPayment { - prev_channel_id: prev_channel_id.to_string(), - next_channel_id: next_channel_id.to_string(), + prev_channel_id: prev_channel_id.0, + next_channel_id: next_channel_id.0, prev_user_channel_id: prev_user_channel_id .expect("prev_user_channel_id expected for ldk-server >=0.1") .0 .to_string(), next_user_channel_id: next_user_channel_id.map(|u| u.0.to_string()), - prev_node_id: prev_node_id.expect("prev_node_id expected for ldk-server >=0.1").to_string(), - next_node_id: next_node_id.expect("next_node_id expected for ldk-node >=0.1").to_string(), + prev_node_id: prev_node_id.expect("prev_node_id expected for ldk-server >=0.1").serialize(), + next_node_id: next_node_id.expect("next_node_id expected for ldk-node >=0.1").serialize(), total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx, @@ -434,11 +416,11 @@ pub(crate) fn forwarded_payment_to_proto( } } -pub(crate) fn proto_to_bolt11_description( - description: Option, +pub(crate) fn bolt11_description_from_model( + description: Option, ) -> Result { - Ok(match description.and_then(|d| d.kind) { - Some(bolt11_invoice_description::Kind::Direct(s)) => { + Ok(match description { + Some(ldk_server_json_models::types::Bolt11InvoiceDescription::Direct(s)) => { Bolt11InvoiceDescription::Direct(Description::new(s).map_err(|e| { LdkServerError::new( InvalidRequestError, @@ -446,7 +428,7 @@ pub(crate) fn proto_to_bolt11_description( ) })?) }, - Some(bolt11_invoice_description::Kind::Hash(h)) => { + Some(ldk_server_json_models::types::Bolt11InvoiceDescription::Hash(h)) => { let hash_bytes = <[u8; 32]>::from_hex(&h).map_err(|_| { LdkServerError::new( InvalidRequestError, @@ -466,45 +448,45 @@ pub(crate) fn proto_to_bolt11_description( }) } -pub(crate) fn graph_routing_fees_to_proto( +pub(crate) fn graph_routing_fees_to_model( fees: RoutingFees, -) -> ldk_server_protos::types::GraphRoutingFees { - ldk_server_protos::types::GraphRoutingFees { +) -> ldk_server_json_models::types::GraphRoutingFees { + ldk_server_json_models::types::GraphRoutingFees { base_msat: fees.base_msat, proportional_millionths: fees.proportional_millionths, } } -pub(crate) fn graph_channel_update_to_proto( +pub(crate) fn graph_channel_update_to_model( update: ChannelUpdateInfo, -) -> ldk_server_protos::types::GraphChannelUpdate { - ldk_server_protos::types::GraphChannelUpdate { +) -> ldk_server_json_models::types::GraphChannelUpdate { + ldk_server_json_models::types::GraphChannelUpdate { last_update: update.last_update, enabled: update.enabled, cltv_expiry_delta: update.cltv_expiry_delta as u32, htlc_minimum_msat: update.htlc_minimum_msat, htlc_maximum_msat: update.htlc_maximum_msat, - fees: Some(graph_routing_fees_to_proto(update.fees)), + fees: graph_routing_fees_to_model(update.fees), } } -pub(crate) fn graph_channel_to_proto( +pub(crate) fn graph_channel_to_model( channel: ChannelInfo, -) -> ldk_server_protos::types::GraphChannel { - ldk_server_protos::types::GraphChannel { - node_one: channel.node_one.to_string(), - node_two: channel.node_two.to_string(), +) -> ldk_server_json_models::types::GraphChannel { + ldk_server_json_models::types::GraphChannel { + node_one: channel.node_one.as_slice().try_into().expect("NodeId should be 33 bytes"), + node_two: channel.node_two.as_slice().try_into().expect("NodeId should be 33 bytes"), capacity_sats: channel.capacity_sats, - one_to_two: channel.one_to_two.map(graph_channel_update_to_proto), - two_to_one: channel.two_to_one.map(graph_channel_update_to_proto), + one_to_two: channel.one_to_two.map(graph_channel_update_to_model), + two_to_one: channel.two_to_one.map(graph_channel_update_to_model), } } -pub(crate) fn graph_node_announcement_to_proto( +pub(crate) fn graph_node_announcement_to_model( announcement: NodeAnnouncementInfo, -) -> ldk_server_protos::types::GraphNodeAnnouncement { +) -> ldk_server_json_models::types::GraphNodeAnnouncement { let rgb = announcement.rgb(); - ldk_server_protos::types::GraphNodeAnnouncement { + ldk_server_json_models::types::GraphNodeAnnouncement { last_update: announcement.last_update(), alias: announcement.alias().to_string(), rgb: format!("{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]), @@ -512,10 +494,10 @@ pub(crate) fn graph_node_announcement_to_proto( } } -pub(crate) fn graph_node_to_proto(node: NodeInfo) -> ldk_server_protos::types::GraphNode { - ldk_server_protos::types::GraphNode { +pub(crate) fn graph_node_to_model(node: NodeInfo) -> ldk_server_json_models::types::GraphNode { + ldk_server_json_models::types::GraphNode { channels: node.channels, - announcement_info: node.announcement_info.map(graph_node_announcement_to_proto), + announcement_info: node.announcement_info.map(graph_node_announcement_to_model), } } @@ -525,7 +507,7 @@ pub(crate) fn to_error_response(ldk_error: LdkServerError) -> (ErrorResponse, St AuthError => ErrorCode::AuthError, LightningError => ErrorCode::LightningError, InternalServerError => ErrorCode::InternalServerError, - } as i32; + }; let status = match ldk_error.error_code { InvalidRequestError => StatusCode::BAD_REQUEST, diff --git a/ldk-server/src/util/mod.rs b/ldk-server/src/util/mod.rs index 5d74de43..52162b0a 100644 --- a/ldk-server/src/util/mod.rs +++ b/ldk-server/src/util/mod.rs @@ -7,8 +7,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +pub(crate) mod adapter; pub(crate) mod config; pub(crate) mod logger; -pub(crate) mod proto_adapter; pub(crate) mod systemd; pub(crate) mod tls;