1use std::collections::HashMap;
2
3uniffi::setup_scaffolding!();
4
5#[derive(uniffi::Error, thiserror::Error, Debug)]
6pub enum Error {
7 #[error("{msg}")]
8 DuplicateNode {
9 code: i32,
10 msg: String,
11 values: HashMap<String, String>,
12 },
13
14 #[error("{msg}")]
15 NoSuchNode {
16 code: i32,
17 msg: String,
18 values: HashMap<String, String>,
19 },
20
21 #[error("{msg}")]
22 UnparseableCreds {
23 code: i32,
24 msg: String,
25 values: HashMap<String, String>,
26 },
27
28 #[error("{msg}")]
29 PhraseCorrupted {
30 code: i32,
31 msg: String,
32 values: HashMap<String, String>,
33 },
34
35 #[error("{msg}")]
36 Rpc {
37 code: i32,
38 msg: String,
39 values: HashMap<String, String>,
40 },
41
42 #[error("{msg}")]
43 Argument {
44 code: i32,
45 msg: String,
46 values: HashMap<String, String>,
47 },
48
49 #[error("{msg}")]
50 Other {
51 code: i32,
52 msg: String,
53 values: HashMap<String, String>,
54 },
55}
56
57impl Error {
58 pub fn duplicate_node(node_id: impl Into<String>) -> Self {
59 let node_id = node_id.into();
60 Error::DuplicateNode {
61 code: 1000,
62 msg: format!(
63 "There is already a node for node_id={node_id}, maybe you want to recover?"
64 ),
65 values: HashMap::from([("node_id".into(), node_id)]),
66 }
67 }
68
69 pub fn no_such_node(node_id: impl Into<String>) -> Self {
70 let node_id = node_id.into();
71 Error::NoSuchNode {
72 code: 1001,
73 msg: format!(
74 "There is no node with node_id={node_id}, maybe you need to register first?"
75 ),
76 values: HashMap::from([("node_id".into(), node_id)]),
77 }
78 }
79
80 pub fn unparseable_creds() -> Self {
81 Error::UnparseableCreds {
82 code: 1100,
83 msg: "The provided credentials could not be parsed, please recover.".into(),
84 values: HashMap::new(),
85 }
86 }
87
88 pub fn phrase_corrupted() -> Self {
89 Error::PhraseCorrupted {
90 code: 1101,
91 msg: "The passphrase you provided fails the checksum".into(),
92 values: HashMap::new(),
93 }
94 }
95
96 pub fn rpc(detail: impl Into<String>) -> Self {
97 let detail = detail.into();
98 Error::Rpc {
99 code: 2000,
100 msg: format!("Error calling the rpc: {detail}"),
101 values: HashMap::from([("detail".into(), detail)]),
102 }
103 }
104
105 pub fn argument(arg_name: impl Into<String>, arg_value: impl Into<String>) -> Self {
106 let arg_name = arg_name.into();
107 let arg_value = arg_value.into();
108 Error::Argument {
109 code: 3000,
110 msg: format!("Invalid argument: {arg_name}={arg_value}"),
111 values: HashMap::from([
112 ("arg_name".into(), arg_name),
113 ("arg_value".into(), arg_value),
114 ]),
115 }
116 }
117
118 pub fn other(detail: impl Into<String>) -> Self {
119 let detail = detail.into();
120 Error::Other {
121 code: 9000,
122 msg: format!("Generic error: {detail}"),
123 values: HashMap::from([("detail".into(), detail)]),
124 }
125 }
126}
127
128mod config;
129mod credentials;
130mod input;
131mod lnurl;
132mod logging;
133mod node;
134mod node_builder;
135mod scheduler;
136mod signer;
137mod util;
138
139pub use crate::{
140 config::Config,
141 credentials::{Credentials, DeveloperCert},
142 node::{
143 ChannelState, FundChannel, FundOutput, GetInfoResponse, Invoice,
144 InvoicePaidEvent, InvoiceStatus, ListFundsResponse, ListIndex, ListInvoicesResponse,
145 ListPaymentsRequest, ListPeerChannelsResponse, ListPaysResponse, ListPeersResponse,
146 Node, NodeEvent, NodeEventListener, NodeEventStream, NodeState, OnchainBalanceState,
147 OnchainFeeRates, OnchainReceiveResponse, OnchainSendResponse, Outpoint, OutputStatus,
148 Pay, PayStatus, Payment, PaymentStatus, PaymentType, PaymentTypeFilter, Peer,
149 PeerChannel, PreparedOnchainSend, ReceiveResponse, SendResponse,
150 },
151 input::{ParsedInput, ParsedInvoice, ResolvedInput},
152 logging::{LogEntry, LogLevel, LogListener},
153 lnurl::{
154 LnUrlErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlPayResult,
155 LnUrlPaySuccessData, LnUrlWithdrawRequest, LnUrlWithdrawRequestData,
156 LnUrlWithdrawResult, LnUrlWithdrawSuccessData, SuccessActionProcessed,
157 },
158 node_builder::NodeBuilder,
159 scheduler::Scheduler,
160 signer::{Handle, Signer},
161};
162
163enum SchedulerAction {
165 Register { invite_code: Option<String> },
166 Recover,
167}
168
169fn schedule_node(
171 seed: Vec<u8>,
172 config: &config::Config,
173 action: SchedulerAction,
174) -> Result<std::sync::Arc<node::Node>, Error> {
175 use std::sync::Arc;
176
177 let network = config.network;
178 let nobody = config.nobody();
179
180 let seed_for_async = seed.clone();
181 let credentials = util::exec(async move {
182 let signer =
183 gl_client::signer::Signer::new(seed_for_async, network, nobody.clone())
184 .map_err(|e| Error::other(e.to_string()))?;
185
186 let scheduler = gl_client::scheduler::Scheduler::new(network, nobody)
187 .await
188 .map_err(|e| Error::other(e.to_string()))?;
189
190 let node_id_hex = hex::encode(signer.node_id());
191
192 let creds_bytes = match action {
193 SchedulerAction::Register { invite_code } => {
194 scheduler
195 .register(&signer, invite_code)
196 .await
197 .map_err(|e| map_scheduler_error(e, &node_id_hex))?
198 .creds
199 }
200 SchedulerAction::Recover => {
201 scheduler
202 .recover(&signer)
203 .await
204 .map_err(|e| map_scheduler_error(e, &node_id_hex))?
205 .creds
206 }
207 };
208
209 credentials::Credentials::load(creds_bytes)
210 })?;
211
212 let authenticated_signer =
213 gl_client::signer::Signer::new(seed, network, credentials.inner.clone())
214 .map_err(|e| Error::other(e.to_string()))?;
215
216 let handle = signer::Handle::spawn(authenticated_signer);
217 let node = node::Node::with_signer(credentials, handle, network)?;
218 Ok(Arc::new(node))
219}
220
221fn map_scheduler_error(e: anyhow::Error, node_id_hex: &str) -> Error {
224 for cause in e.chain() {
226 if let Some(status) = cause.downcast_ref::<tonic::Status>() {
227 match status.code() {
228 tonic::Code::AlreadyExists => {
229 return Error::duplicate_node(node_id_hex.to_string())
230 }
231 tonic::Code::NotFound => return Error::no_such_node(node_id_hex.to_string()),
232 _ => {}
236 }
237 }
238 }
239
240 let msg = e.to_string();
242 if msg.contains("NOT_FOUND")
243 || msg.contains("no rows returned")
244 || msg.contains("Recovery failed")
245 {
246 Error::no_such_node(node_id_hex.to_string())
247 } else if msg.contains("ALREADY_EXISTS") {
248 Error::duplicate_node(node_id_hex.to_string())
249 } else {
250 Error::other(msg)
251 }
252}
253
254fn parse_mnemonic(mnemonic: &str) -> Result<Vec<u8>, Error> {
256 use bip39::Mnemonic;
257 use std::str::FromStr;
258 let phrase = Mnemonic::from_str(mnemonic).map_err(|_e| Error::phrase_corrupted())?;
259 Ok(phrase.to_seed_normalized("").to_vec())
260}
261
262pub(crate) fn connect_internal(
265 mnemonic: String,
266 credentials: Vec<u8>,
267 config: &config::Config,
268) -> Result<std::sync::Arc<node::Node>, Error> {
269 use std::sync::Arc;
270
271 let seed = parse_mnemonic(&mnemonic)?;
272 let network = config.network;
273 let creds = credentials::Credentials::load(credentials)?;
274
275 let authenticated_signer =
276 gl_client::signer::Signer::new(seed, network, creds.inner.clone())
277 .map_err(|e| Error::other(e.to_string()))?;
278
279 let handle = signer::Handle::spawn(authenticated_signer);
280 let node = node::Node::with_signer(creds, handle, network)?;
281 Ok(Arc::new(node))
282}
283
284pub(crate) fn register_internal(
287 mnemonic: String,
288 invite_code: Option<String>,
289 config: &config::Config,
290) -> Result<std::sync::Arc<node::Node>, Error> {
291 let seed = parse_mnemonic(&mnemonic)?;
292 schedule_node(seed, config, SchedulerAction::Register { invite_code })
293}
294
295pub(crate) fn recover_internal(
298 mnemonic: String,
299 config: &config::Config,
300) -> Result<std::sync::Arc<node::Node>, Error> {
301 let seed = parse_mnemonic(&mnemonic)?;
302 schedule_node(seed, config, SchedulerAction::Recover)
303}
304
305pub(crate) fn register_or_recover_internal(
308 mnemonic: String,
309 invite_code: Option<String>,
310 config: &config::Config,
311) -> Result<std::sync::Arc<node::Node>, Error> {
312 match recover_internal(mnemonic.clone(), config) {
313 Ok(node) => Ok(node),
314 Err(Error::NoSuchNode { .. }) => register_internal(mnemonic, invite_code, config),
315 Err(e) => Err(e),
316 }
317}
318
319pub(crate) fn connect_signerless_internal(
329 credentials: Vec<u8>,
330 _config: &config::Config,
331) -> Result<std::sync::Arc<node::Node>, Error> {
332 use std::sync::Arc;
333 let creds = credentials::Credentials::load(credentials)?;
334 let node = node::Node::signerless(creds)?;
335 Ok(Arc::new(node))
336}
337
338#[uniffi::export]
351pub fn parse_input(input: String) -> Result<input::ParsedInput, Error> {
352 input::parse_input(input)
353}
354
355#[uniffi::export]
374pub fn resolve_input(input: String) -> Result<input::ResolvedInput, Error> {
375 util::exec(async { input::resolve_input(input).await })
376}
377
378#[uniffi::export]
386pub fn set_logger(
387 level: logging::LogLevel,
388 listener: Box<dyn logging::LogListener>,
389) -> Result<(), Error> {
390 logging::set_logger(level, listener)
391}
392
393#[uniffi::export]
395pub fn set_log_level(level: logging::LogLevel) {
396 logging::set_log_level(level)
397}
398
399#[derive(uniffi::Enum, Debug)]
400pub enum Network {
401 BITCOIN,
402 REGTEST,
403}
404
405impl From<Network> for gl_client::bitcoin::Network {
406 fn from(n: Network) -> gl_client::bitcoin::Network {
407 match n {
408 Network::BITCOIN => gl_client::bitcoin::Network::Bitcoin,
409 Network::REGTEST => gl_client::bitcoin::Network::Regtest,
410 }
411 }
412}