glsdk/node_builder.rs
1// Builder-style Node creation.
2//
3// `NodeBuilder` is the sole public entry point for Node construction
4// across all foreign bindings. The shape is "request the action,
5// optionally with modifiers":
6//
7// // Signerless connect — caller does not have the mnemonic in
8// // this process. The SDK runs no signer; signing happens
9// // elsewhere (paired device, CLN node's local signer, hardware
10// // signer). This is the supported model for keyless clients.
11// let node = NodeBuilder::new(&config).connect(credentials, None)?;
12//
13// // Signed connect — caller hands the mnemonic per build call,
14// // SDK spawns a signer.
15// let node = NodeBuilder::new(&config).connect(credentials, Some(mnemonic))?;
16//
17// // Register / recover require a mnemonic by definition (the
18// // signer must sign the registration/recovery challenge).
19// let node = NodeBuilder::new(&config)
20// .with_event_listener(listener)
21// .register(mnemonic, invite_code)?;
22//
23// The mnemonic is the security-sensitive input: hand it in only when
24// you actually want the SDK to act as a signer for that call.
25//
26// Adding a new modifier later is a new `with_*` setter — additive,
27// never breaks existing callers.
28
29use std::sync::Arc;
30
31use crate::{
32 config::Config,
33 node::{Node, NodeEventListener},
34 Error,
35};
36
37/// Configurable Node construction. See module docs.
38///
39/// All fields are immutable after construction. Each `with_*` setter
40/// returns a fresh `Arc<NodeBuilder>` that shares ownership of any
41/// previously-installed modifiers via `Arc<dyn …>`. No interior
42/// mutability, no locks — the builder is a value, not a state
43/// machine.
44#[derive(uniffi::Object)]
45pub struct NodeBuilder {
46 config: Arc<Config>,
47 event_listener: Option<Arc<dyn NodeEventListener>>,
48}
49
50#[uniffi::export]
51impl NodeBuilder {
52 /// Create a builder for a Node with `config`. No I/O happens
53 /// until you call `connect` / `register` / `recover` /
54 /// `register_or_recover`.
55 #[uniffi::constructor]
56 pub fn new(config: &Config) -> Arc<Self> {
57 Arc::new(Self {
58 config: Arc::new(config.clone()),
59 event_listener: None,
60 })
61 }
62
63 /// Install a node event listener. Events fire from the moment the
64 /// gRPC stream is established by the build call (`register` /
65 /// `recover` / `connect` / …), so attach the listener via the
66 /// builder rather than after the fact to capture events from the
67 /// very first moment.
68 ///
69 /// Returns a new builder that shares the rest of the
70 /// configuration. Build calls on the returned builder will
71 /// install the listener; the original builder is unchanged.
72 pub fn with_event_listener(
73 self: Arc<Self>,
74 listener: Box<dyn NodeEventListener>,
75 ) -> Arc<Self> {
76 // UniFFI's callback-interface lowering hands us a
77 // `Box<dyn Trait>`. We re-wrap it as `Arc<dyn Trait>` because
78 // the builder is reusable across multiple build calls — each
79 // build clones the Arc into the resulting Node, and `Box`
80 // can't be cloned. This is a one-time cost paid per setter
81 // call.
82 Arc::new(Self {
83 config: Arc::clone(&self.config),
84 event_listener: Some(Arc::from(listener)),
85 })
86 }
87
88 /// Register a new Greenlight node and return a connected Node
89 /// with the SDK signer running and any configured modifiers
90 /// applied.
91 ///
92 /// `mnemonic` is required — registration drives the signer to
93 /// sign the registration challenge, so the SDK must hold the
94 /// seed for this call.
95 pub fn register(
96 &self,
97 mnemonic: String,
98 invite_code: Option<String>,
99 ) -> Result<Arc<Node>, Error> {
100 let node = crate::register_internal(mnemonic, invite_code, &self.config)?;
101 self.attach_observers(&node)?;
102 Ok(node)
103 }
104
105 /// Recover credentials for an existing node and return a
106 /// connected Node with any configured modifiers applied.
107 ///
108 /// `mnemonic` is required — recovery drives the signer to
109 /// authenticate.
110 pub fn recover(&self, mnemonic: String) -> Result<Arc<Node>, Error> {
111 let node = crate::recover_internal(mnemonic, &self.config)?;
112 self.attach_observers(&node)?;
113 Ok(node)
114 }
115
116 /// Connect to an existing node using saved credentials and return
117 /// a connected Node with any configured modifiers applied.
118 ///
119 /// If `mnemonic` is `Some(...)`, the SDK spawns a signer for the
120 /// connected Node. If `None`, the Node is signerless and signing
121 /// happens elsewhere (paired device, CLN node's local signer,
122 /// hardware signer).
123 pub fn connect(
124 &self,
125 credentials: Vec<u8>,
126 mnemonic: Option<String>,
127 ) -> Result<Arc<Node>, Error> {
128 let node = match mnemonic {
129 Some(mnemonic) => crate::connect_internal(mnemonic, credentials, &self.config)?,
130 None => crate::connect_signerless_internal(credentials, &self.config)?,
131 };
132 self.attach_observers(&node)?;
133 Ok(node)
134 }
135
136 /// Try to recover; if the node doesn't exist, register a new one.
137 ///
138 /// `mnemonic` is required — both recover and register drive the
139 /// signer.
140 pub fn register_or_recover(
141 &self,
142 mnemonic: String,
143 invite_code: Option<String>,
144 ) -> Result<Arc<Node>, Error> {
145 let node =
146 crate::register_or_recover_internal(mnemonic, invite_code, &self.config)?;
147 self.attach_observers(&node)?;
148 Ok(node)
149 }
150}
151
152impl NodeBuilder {
153 /// Attach all configured modifiers to a freshly-built Node.
154 /// Modifiers are shared (not consumed) — the same builder can
155 /// drive multiple builds and they all get the same listener.
156 fn attach_observers(&self, node: &Arc<Node>) -> Result<(), Error> {
157 if let Some(listener) = self.event_listener.as_ref() {
158 node.set_event_listener(Arc::clone(listener))?;
159 }
160 Ok(())
161 }
162}