Skip to main content

glsdk/
lib.rs

1uniffi::setup_scaffolding!();
2
3#[derive(uniffi::Error, thiserror::Error, Debug)]
4pub enum Error {
5    #[error("There is already a node for node_id={0}, maybe you want to recover?")]
6    DuplicateNode(String),
7
8    #[error("There is no node with node_id={0}, maybe you need to register first?")]
9    NoSuchNode(String),
10
11    #[error("The provided credentials could not be parsed, please recover.")]
12    UnparseableCreds(),
13
14    #[error("The passphrase you provided fails the checksum")]
15    PhraseCorrupted(),
16
17    #[error("Error calling the rpc: {0}")]
18    Rpc(String),
19
20    #[error("Invalid argument: {0}={1}")]
21    Argument(String, String),
22
23    #[error("Generic error: {0}")]
24    Other(String),
25}
26
27mod config;
28mod credentials;
29mod input;
30mod node;
31mod scheduler;
32mod signer;
33mod util;
34
35pub use crate::{
36    config::Config,
37    credentials::{Credentials, DeveloperCert},
38    node::{
39        ChannelState, FundChannel, FundOutput, GetInfoResponse, Invoice, InvoicePaidEvent,
40        InvoiceStatus, ListFundsResponse, ListIndex, ListInvoicesResponse,
41        ListPaymentsRequest, ListPeerChannelsResponse, ListPaysResponse, ListPeersResponse,
42        Node, NodeEvent, NodeEventStream, OnchainReceiveResponse, OnchainSendResponse,
43        OutputStatus, Pay, PayStatus, Payment, PaymentStatus, PaymentType, PaymentTypeFilter,
44        Peer, PeerChannel, ReceiveResponse, SendResponse,
45    },
46    input::{InputType, ParsedInvoice},
47    scheduler::Scheduler,
48    signer::{Handle, Signer},
49};
50
51/// Which scheduler operation to perform.
52enum SchedulerAction {
53    Register { invite_code: Option<String> },
54    Recover,
55}
56
57/// Shared implementation for register and recover flows.
58fn schedule_node(
59    seed: Vec<u8>,
60    config: &config::Config,
61    action: SchedulerAction,
62) -> Result<std::sync::Arc<node::Node>, Error> {
63    use std::sync::Arc;
64
65    let network = config.network;
66    let nobody = config.nobody();
67
68    let seed_for_async = seed.clone();
69    let credentials = util::exec(async move {
70        let signer =
71            gl_client::signer::Signer::new(seed_for_async, network, nobody.clone())
72                .map_err(|e| Error::Other(e.to_string()))?;
73
74        let scheduler = gl_client::scheduler::Scheduler::new(network, nobody)
75            .await
76            .map_err(|e| Error::Other(e.to_string()))?;
77
78        let node_id_hex = hex::encode(signer.node_id());
79
80        let creds_bytes = match action {
81            SchedulerAction::Register { invite_code } => {
82                scheduler
83                    .register(&signer, invite_code)
84                    .await
85                    .map_err(|e| map_scheduler_error(e, &node_id_hex))?
86                    .creds
87            }
88            SchedulerAction::Recover => {
89                scheduler
90                    .recover(&signer)
91                    .await
92                    .map_err(|e| map_scheduler_error(e, &node_id_hex))?
93                    .creds
94            }
95        };
96
97        credentials::Credentials::load(creds_bytes)
98    })?;
99
100    let authenticated_signer =
101        gl_client::signer::Signer::new(seed, network, credentials.inner.clone())
102            .map_err(|e| Error::Other(e.to_string()))?;
103
104    let handle = signer::Handle::spawn(authenticated_signer);
105    let node = node::Node::with_signer(credentials, handle)?;
106    Ok(Arc::new(node))
107}
108
109/// Map scheduler errors to specific Error variants.
110/// First tries tonic status codes, then falls back to error message matching.
111fn map_scheduler_error(e: anyhow::Error, node_id_hex: &str) -> Error {
112    // Walk the error chain looking for a tonic::Status with a specific code
113    for cause in e.chain() {
114        if let Some(status) = cause.downcast_ref::<tonic::Status>() {
115            match status.code() {
116                tonic::Code::AlreadyExists => {
117                    return Error::DuplicateNode(node_id_hex.to_string())
118                }
119                tonic::Code::NotFound => return Error::NoSuchNode(node_id_hex.to_string()),
120                // Don't return here — the tonic status might be a generic
121                // wrapper (e.g. Internal/Unknown) around a more specific
122                // error. Fall through to string matching.
123                _ => {}
124            }
125        }
126    }
127
128    // Fall back to checking the full error message for known patterns.
129    let msg = e.to_string();
130    if msg.contains("NOT_FOUND")
131        || msg.contains("no rows returned")
132        || msg.contains("Recovery failed")
133    {
134        Error::NoSuchNode(node_id_hex.to_string())
135    } else if msg.contains("ALREADY_EXISTS") {
136        Error::DuplicateNode(node_id_hex.to_string())
137    } else {
138        Error::Other(msg)
139    }
140}
141
142/// Parse a BIP39 mnemonic into a seed.
143fn parse_mnemonic(mnemonic: &str) -> Result<Vec<u8>, Error> {
144    use bip39::Mnemonic;
145    use std::str::FromStr;
146    let phrase = Mnemonic::from_str(mnemonic).map_err(|_e| Error::PhraseCorrupted())?;
147    Ok(phrase.to_seed_normalized("").to_vec())
148}
149
150/// Register a new Greenlight node and return a connected Node with signer running.
151///
152/// The app should call `node.credentials()` to get the credential bytes
153/// and persist them for future `connect()` calls.
154#[uniffi::export]
155pub fn register(
156    mnemonic: String,
157    invite_code: Option<String>,
158    config: &config::Config,
159) -> Result<std::sync::Arc<node::Node>, Error> {
160    let seed = parse_mnemonic(&mnemonic)?;
161    schedule_node(seed, config, SchedulerAction::Register { invite_code })
162}
163
164/// Recover credentials for an existing Greenlight node and return a connected Node.
165///
166/// The app should call `node.credentials()` to get the credential bytes
167/// and persist them for future `connect()` calls.
168#[uniffi::export]
169pub fn recover(
170    mnemonic: String,
171    config: &config::Config,
172) -> Result<std::sync::Arc<node::Node>, Error> {
173    let seed = parse_mnemonic(&mnemonic)?;
174    schedule_node(seed, config, SchedulerAction::Recover)
175}
176
177/// Connect to an existing Greenlight node using previously saved credentials.
178#[uniffi::export]
179pub fn connect(
180    mnemonic: String,
181    credentials: Vec<u8>,
182    config: &config::Config,
183) -> Result<std::sync::Arc<node::Node>, Error> {
184    use std::sync::Arc;
185
186    let seed = parse_mnemonic(&mnemonic)?;
187    let network = config.network;
188    let creds = credentials::Credentials::load(credentials)?;
189
190    let authenticated_signer =
191        gl_client::signer::Signer::new(seed, network, creds.inner.clone())
192            .map_err(|e| Error::Other(e.to_string()))?;
193
194    let handle = signer::Handle::spawn(authenticated_signer);
195    let node = node::Node::with_signer(creds, handle)?;
196    Ok(Arc::new(node))
197}
198
199/// Try to recover an existing node; if none exists, register a new one.
200#[uniffi::export]
201pub fn register_or_recover(
202    mnemonic: String,
203    invite_code: Option<String>,
204    config: &config::Config,
205) -> Result<std::sync::Arc<node::Node>, Error> {
206    match recover(mnemonic.clone(), config) {
207        Ok(node) => Ok(node),
208        Err(Error::NoSuchNode(_)) => register(mnemonic, invite_code, config),
209        Err(e) => Err(e),
210    }
211}
212
213/// Parse a string and identify whether it's a BOLT11 invoice or a node ID.
214///
215/// Strips `lightning:` / `LIGHTNING:` prefixes automatically.
216/// Works offline — no node connection needed.
217#[uniffi::export]
218pub fn parse_input(input: String) -> Result<input::InputType, Error> {
219    input::parse_input(input)
220}
221
222#[derive(uniffi::Enum, Debug)]
223pub enum Network {
224    BITCOIN,
225    REGTEST,
226}
227
228impl From<Network> for gl_client::bitcoin::Network {
229    fn from(n: Network) -> gl_client::bitcoin::Network {
230        match n {
231            Network::BITCOIN => gl_client::bitcoin::Network::Bitcoin,
232            Network::REGTEST => gl_client::bitcoin::Network::Regtest,
233        }
234    }
235}