Skip to content

Commit 09435fc

Browse files
committed
Add Preimage type with CLI validation for spontaneous-send preimage arg
1 parent c41f7a9 commit 09435fc

2 files changed

Lines changed: 45 additions & 2 deletions

File tree

ldk-server-cli/src/main.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ use serde::Serialize;
5656
use serde_json::{json, Value};
5757
use types::{
5858
Amount, CliListForwardedPaymentsResponse, CliListPaymentsResponse, CliPaginatedResponse,
59+
Preimage,
5960
};
6061

6162
mod types;
@@ -324,7 +325,7 @@ enum Commands {
324325
long,
325326
help = "An optional hex-encoded 32-byte payment preimage. If provided, it will be used instead of generating a random one."
326327
)]
327-
preimage: Option<String>,
328+
preimage: Option<Preimage>,
328329
},
329330
#[command(
330331
about = "Pay a BIP 21 URI, BIP 353 Human-Readable Name, BOLT11 invoice, or BOLT12 offer"
@@ -847,7 +848,7 @@ async fn main() {
847848
node_id,
848849
route_parameters: Some(route_parameters),
849850
custom_tlvs: proto_custom_tlvs,
850-
preimage,
851+
preimage: preimage.map(|p| p.to_hex_string()),
851852
})
852853
.await,
853854
);

ldk-server-cli/src/types.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use std::fmt;
1717
use std::str::FromStr;
1818

19+
use hex_conservative::{DisplayHex, FromHex};
1920
use ldk_server_client::ldk_server_grpc::types::{ForwardedPayment, PageToken, Payment};
2021
use serde::Serialize;
2122

@@ -119,6 +120,26 @@ impl FromStr for Amount {
119120
}
120121
}
121122

123+
/// A validated 32-byte payment preimage, parsed from a 64-character hex string.
124+
#[derive(Debug, Clone)]
125+
pub struct Preimage(pub [u8; 32]);
126+
127+
impl Preimage {
128+
pub fn to_hex_string(&self) -> String {
129+
self.0.to_lower_hex_string()
130+
}
131+
}
132+
133+
impl FromStr for Preimage {
134+
type Err = String;
135+
136+
fn from_str(s: &str) -> Result<Self, Self::Err> {
137+
<[u8; 32]>::from_hex(s)
138+
.map(Preimage)
139+
.map_err(|_| "must be a 64-character hex string (32 bytes)".to_string())
140+
}
141+
}
142+
122143
#[cfg(test)]
123144
mod tests {
124145
use super::*;
@@ -185,4 +206,25 @@ mod tests {
185206
let big = format!("{}sat", u64::MAX);
186207
assert!(Amount::from_str(&big).is_err());
187208
}
209+
210+
#[test]
211+
fn preimage_parsing_and_roundtrip() {
212+
// valid 64-char hex string
213+
let hex = "2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b";
214+
let preimage = Preimage::from_str(hex).unwrap();
215+
assert_eq!(preimage.0, [0x2b; 32]);
216+
assert_eq!(preimage.to_hex_string(), hex);
217+
218+
// rejects empty string
219+
assert!(Preimage::from_str("").is_err());
220+
221+
// rejects too short (62 chars)
222+
assert!(Preimage::from_str(&"ab".repeat(31)).is_err());
223+
224+
// rejects too long (66 chars)
225+
assert!(Preimage::from_str(&"ab".repeat(33)).is_err());
226+
227+
// rejects non-hex characters
228+
assert!(Preimage::from_str(&"zz".repeat(32)).is_err());
229+
}
188230
}

0 commit comments

Comments
 (0)