Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion ldk-server-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use ldk_server_client::ldk_server_protos::api::{
Bolt11ReceiveRequest, Bolt11SendRequest, Bolt12ReceiveRequest, Bolt12SendRequest,
CloseChannelRequest, ForceCloseChannelRequest, GetBalancesRequest, GetNodeInfoRequest,
ListChannelsRequest, ListPaymentsRequest, OnchainReceiveRequest, OnchainSendRequest,
OpenChannelRequest,
OpenChannelRequest, SpliceInRequest, SpliceOutRequest,
};
use ldk_server_client::ldk_server_protos::types::{
bolt11_invoice_description, Bolt11InvoiceDescription, PageToken, Payment,
Expand Down Expand Up @@ -102,6 +102,24 @@ enum Commands {
#[arg(long)]
announce_channel: bool,
},
SpliceIn {
#[arg(short, long)]
user_channel_id: String,
#[arg(short, long)]
counterparty_node_id: String,
#[arg(long)]
splice_amount_sats: u64,
},
SpliceOut {
#[arg(short, long)]
user_channel_id: String,
#[arg(short, long)]
counterparty_node_id: String,
#[arg(short, long)]
address: Option<String>,
#[arg(long)]
splice_amount_sats: u64,
},
ListChannels,
ListPayments {
#[arg(short, long)]
Expand Down Expand Up @@ -227,6 +245,34 @@ async fn main() {
.await,
);
},
Commands::SpliceIn { user_channel_id, counterparty_node_id, splice_amount_sats } => {
handle_response_result(
client
.splice_in(SpliceInRequest {
user_channel_id,
counterparty_node_id,
splice_amount_sats,
})
.await,
);
},
Commands::SpliceOut {
user_channel_id,
counterparty_node_id,
address,
splice_amount_sats,
} => {
handle_response_result(
client
.splice_out(SpliceOutRequest {
user_channel_id,
counterparty_node_id,
address,
splice_amount_sats,
})
.await,
);
},
Commands::ListChannels => {
handle_response_result(client.list_channels(ListChannelsRequest {}).await);
},
Expand Down
23 changes: 21 additions & 2 deletions ldk-server-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use ldk_server_protos::api::{
GetBalancesRequest, GetBalancesResponse, GetNodeInfoRequest, GetNodeInfoResponse,
ListChannelsRequest, ListChannelsResponse, ListPaymentsRequest, ListPaymentsResponse,
OnchainReceiveRequest, OnchainReceiveResponse, OnchainSendRequest, OnchainSendResponse,
OpenChannelRequest, OpenChannelResponse,
OpenChannelRequest, OpenChannelResponse, SpliceInRequest, SpliceInResponse, SpliceOutRequest,
SpliceOutResponse,
};
use ldk_server_protos::endpoints::{
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
CLOSE_CHANNEL_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH,
LIST_CHANNELS_PATH, LIST_PAYMENTS_PATH, ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH,
OPEN_CHANNEL_PATH,
OPEN_CHANNEL_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
};
use ldk_server_protos::error::{ErrorCode, ErrorResponse};
use reqwest::header::CONTENT_TYPE;
Expand Down Expand Up @@ -119,6 +120,24 @@ impl LdkServerClient {
self.post_request(&request, &url).await
}

/// Splices funds into the channel specified by given request.
/// For API contract/usage, refer to docs for [`SpliceInRequest`] and [`SpliceInResponse`].
pub async fn splice_in(
&self, request: SpliceInRequest,
) -> Result<SpliceInResponse, LdkServerError> {
let url = format!("http://{}/{SPLICE_IN_PATH}", self.base_url);
self.post_request(&request, &url).await
}

/// Splices funds out of the channel specified by given request.
/// For API contract/usage, refer to docs for [`SpliceOutRequest`] and [`SpliceOutResponse`].
pub async fn splice_out(
&self, request: SpliceOutRequest,
) -> Result<SpliceOutResponse, LdkServerError> {
let url = format!("http://{}/{SPLICE_OUT_PATH}", self.base_url);
self.post_request(&request, &url).await
}

/// Closes the channel specified by given request.
/// For API contract/usage, refer to docs for [`CloseChannelRequest`] and [`CloseChannelResponse`].
pub async fn close_channel(
Expand Down
49 changes: 49 additions & 0 deletions ldk-server-protos/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,55 @@ pub struct OpenChannelResponse {
#[prost(string, tag = "1")]
pub user_channel_id: ::prost::alloc::string::String,
}
/// Increases the channel balance by the given amount.
/// See more: <https://docs.rs/ldk-node/latest/ldk_node/struct.Node.html#method.splice_in_channel>
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SpliceInRequest {
/// The local `user_channel_id` of the channel.
#[prost(string, tag = "1")]
pub user_channel_id: ::prost::alloc::string::String,
/// The hex-encoded public key of the channel's counterparty node.
#[prost(string, tag = "2")]
pub counterparty_node_id: ::prost::alloc::string::String,
/// 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`.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct 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_channel>
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SpliceOutRequest {
/// The local `user_channel_id` of this channel.
#[prost(string, tag = "1")]
pub user_channel_id: ::prost::alloc::string::String,
/// The hex-encoded public key of the channel's counterparty node.
#[prost(string, tag = "2")]
pub counterparty_node_id: ::prost::alloc::string::String,
/// 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>,
/// 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`.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct SpliceOutResponse {
/// The Bitcoin on-chain address where the funds will be sent.
#[prost(string, tag = "1")]
pub address: ::prost::alloc::string::String,
}
/// 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>
#[allow(clippy::derive_partial_eq_without_eq)]
Expand Down
2 changes: 2 additions & 0 deletions ldk-server-protos/src/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub const BOLT11_SEND_PATH: &str = "Bolt11Send";
pub const BOLT12_RECEIVE_PATH: &str = "Bolt12Receive";
pub const BOLT12_SEND_PATH: &str = "Bolt12Send";
pub const OPEN_CHANNEL_PATH: &str = "OpenChannel";
pub const SPLICE_IN_PATH: &str = "SpliceIn";
pub const SPLICE_OUT_PATH: &str = "SpliceOut";
pub const CLOSE_CHANNEL_PATH: &str = "CloseChannel";
pub const FORCE_CLOSE_CHANNEL_PATH: &str = "ForceCloseChannel";
pub const LIST_CHANNELS_PATH: &str = "ListChannels";
Expand Down
45 changes: 45 additions & 0 deletions ldk-server-protos/src/proto/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,51 @@ message OpenChannelResponse {
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 {
Expand Down
1 change: 1 addition & 0 deletions ldk-server/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ pub(crate) mod list_payments;
pub(crate) mod onchain_receive;
pub(crate) mod onchain_send;
pub(crate) mod open_channel;
pub(crate) mod splice_channel;
pub(crate) mod update_channel_config;
2 changes: 1 addition & 1 deletion ldk-server/src/api/onchain_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub(crate) fn handle_onchain_send_request(
.map_err(|_| {
LdkServerError::new(
InvalidRequestError,
"Address is not valid for LdkServer's configured network.".to_string(),
"Address is not valid for the configured network.".to_string(),
)
})?;

Expand Down
68 changes: 68 additions & 0 deletions ldk-server/src/api/splice_channel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::api::error::LdkServerError;
use crate::api::error::LdkServerErrorCode::InvalidRequestError;
use crate::service::Context;
use ldk_node::bitcoin::secp256k1::PublicKey;
use ldk_node::bitcoin::Address;
use ldk_node::UserChannelId;
use ldk_server_protos::api::{
SpliceInRequest, SpliceInResponse, SpliceOutRequest, SpliceOutResponse,
};
use std::str::FromStr;

pub(crate) fn handle_splice_in_request(
context: Context, request: SpliceInRequest,
) -> Result<SpliceInResponse, LdkServerError> {
let user_channel_id = parse_user_channel_id(&request.user_channel_id)?;
let counterparty_node_id = parse_counterparty_node_id(&request.counterparty_node_id)?;

context.node.splice_in(&user_channel_id, counterparty_node_id, request.splice_amount_sats)?;

Ok(SpliceInResponse {})
}

pub(crate) fn handle_splice_out_request(
context: Context, request: SpliceOutRequest,
) -> Result<SpliceOutResponse, LdkServerError> {
let user_channel_id = parse_user_channel_id(&request.user_channel_id)?;
let counterparty_node_id = parse_counterparty_node_id(&request.counterparty_node_id)?;

let address = request
.address
.map(|address| {
Address::from_str(&address)
.and_then(|address| address.require_network(context.node.config().network))
.map_err(|_| ldk_node::NodeError::InvalidAddress)
})
.unwrap_or_else(|| context.node.onchain_payment().new_address())
.map_err(|_| {
LdkServerError::new(
InvalidRequestError,
"Address is not valid for the configured network.".to_string(),
)
})?;

context.node.splice_out(
&user_channel_id,
counterparty_node_id,
&address,
request.splice_amount_sats,
)?;

Ok(SpliceOutResponse { address: address.to_string() })
}

fn parse_user_channel_id(id: &str) -> Result<UserChannelId, LdkServerError> {
let parsed = id.parse::<u128>().map_err(|_| {
LdkServerError::new(InvalidRequestError, "Invalid UserChannelId.".to_string())
})?;
Ok(UserChannelId(parsed))
}

fn parse_counterparty_node_id(id: &str) -> Result<PublicKey, LdkServerError> {
PublicKey::from_str(id).map_err(|e| {
LdkServerError::new(
InvalidRequestError,
format!("Invalid counterparty node ID, error: {}", e),
)
})
}
6 changes: 5 additions & 1 deletion ldk-server/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use ldk_server_protos::endpoints::{
BOLT11_RECEIVE_PATH, BOLT11_SEND_PATH, BOLT12_RECEIVE_PATH, BOLT12_SEND_PATH,
CLOSE_CHANNEL_PATH, FORCE_CLOSE_CHANNEL_PATH, GET_BALANCES_PATH, GET_NODE_INFO_PATH,
GET_PAYMENT_DETAILS_PATH, LIST_CHANNELS_PATH, LIST_FORWARDED_PAYMENTS_PATH, LIST_PAYMENTS_PATH,
ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, UPDATE_CHANNEL_CONFIG_PATH,
ONCHAIN_RECEIVE_PATH, ONCHAIN_SEND_PATH, OPEN_CHANNEL_PATH, SPLICE_IN_PATH, SPLICE_OUT_PATH,
UPDATE_CHANNEL_CONFIG_PATH,
};

use prost::Message;
Expand All @@ -30,6 +31,7 @@ use crate::api::list_payments::handle_list_payments_request;
use crate::api::onchain_receive::handle_onchain_receive_request;
use crate::api::onchain_send::handle_onchain_send_request;
use crate::api::open_channel::handle_open_channel;
use crate::api::splice_channel::{handle_splice_in_request, handle_splice_out_request};
use crate::api::update_channel_config::handle_update_channel_config_request;
use crate::io::persist::paginated_kv_store::PaginatedKVStore;
use crate::util::proto_adapter::to_error_response;
Expand Down Expand Up @@ -91,6 +93,8 @@ impl Service<Request<Incoming>> for NodeService {
},
BOLT12_SEND_PATH => Box::pin(handle_request(context, req, handle_bolt12_send_request)),
OPEN_CHANNEL_PATH => Box::pin(handle_request(context, req, handle_open_channel)),
SPLICE_IN_PATH => Box::pin(handle_request(context, req, handle_splice_in_request)),
SPLICE_OUT_PATH => Box::pin(handle_request(context, req, handle_splice_out_request)),
CLOSE_CHANNEL_PATH => {
Box::pin(handle_request(context, req, handle_close_channel_request))
},
Expand Down