uniffi::setup_scaffolding!();
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum Error {
#[error("There is already a node for node_id={0}, maybe you want to recover?")]
DuplicateNode(String),
#[error("There is no node with node_id={0}, maybe you need to register first?")]
NoSuchNode(String),
#[error("The provided credentials could not be parsed, please recover.")]
UnparseableCreds(),
#[error("The passphrase you provided fails the checksum")]
PhraseCorrupted(),
#[error("Error calling the rpc: {0}")]
Rpc(String),
#[error("Invalid argument: {0}={1}")]
Argument(String, String),
#[error("Generic error: {0}")]
Other(String),
}
mod config;
mod credentials;
mod input;
mod node;
mod scheduler;
mod signer;
mod util;
pub use crate::{
config::Config,
credentials::{Credentials, DeveloperCert},
node::{
ChannelState, FundChannel, FundOutput, GetInfoResponse, Invoice, InvoicePaidEvent,
InvoiceStatus, ListFundsResponse, ListIndex, ListInvoicesResponse,
ListPaymentsRequest, ListPeerChannelsResponse, ListPaysResponse, ListPeersResponse,
Node, NodeEvent, NodeEventStream, OnchainReceiveResponse, OnchainSendResponse,
OutputStatus, Pay, PayStatus, Payment, PaymentStatus, PaymentType, PaymentTypeFilter,
Peer, PeerChannel, ReceiveResponse, SendResponse,
},
input::{InputType, ParsedInvoice},
scheduler::Scheduler,
signer::{Handle, Signer},
};
enum SchedulerAction {
Register { invite_code: Option<String> },
Recover,
}
fn schedule_node(
seed: Vec<u8>,
config: &config::Config,
action: SchedulerAction,
) -> Result<std::sync::Arc<node::Node>, Error> {
use std::sync::Arc;
let network = config.network;
let nobody = config.nobody();
let seed_for_async = seed.clone();
let credentials = util::exec(async move {
let signer =
gl_client::signer::Signer::new(seed_for_async, network, nobody.clone())
.map_err(|e| Error::Other(e.to_string()))?;
let scheduler = gl_client::scheduler::Scheduler::new(network, nobody)
.await
.map_err(|e| Error::Other(e.to_string()))?;
let node_id_hex = hex::encode(signer.node_id());
let creds_bytes = match action {
SchedulerAction::Register { invite_code } => {
scheduler
.register(&signer, invite_code)
.await
.map_err(|e| map_scheduler_error(e, &node_id_hex))?
.creds
}
SchedulerAction::Recover => {
scheduler
.recover(&signer)
.await
.map_err(|e| map_scheduler_error(e, &node_id_hex))?
.creds
}
};
credentials::Credentials::load(creds_bytes)
})?;
let authenticated_signer =
gl_client::signer::Signer::new(seed, network, credentials.inner.clone())
.map_err(|e| Error::Other(e.to_string()))?;
let handle = signer::Handle::spawn(authenticated_signer);
let node = node::Node::with_signer(credentials, handle)?;
Ok(Arc::new(node))
}
fn map_scheduler_error(e: anyhow::Error, node_id_hex: &str) -> Error {
for cause in e.chain() {
if let Some(status) = cause.downcast_ref::<tonic::Status>() {
match status.code() {
tonic::Code::AlreadyExists => {
return Error::DuplicateNode(node_id_hex.to_string())
}
tonic::Code::NotFound => return Error::NoSuchNode(node_id_hex.to_string()),
_ => {}
}
}
}
let msg = e.to_string();
if msg.contains("NOT_FOUND")
|| msg.contains("no rows returned")
|| msg.contains("Recovery failed")
{
Error::NoSuchNode(node_id_hex.to_string())
} else if msg.contains("ALREADY_EXISTS") {
Error::DuplicateNode(node_id_hex.to_string())
} else {
Error::Other(msg)
}
}
fn parse_mnemonic(mnemonic: &str) -> Result<Vec<u8>, Error> {
use bip39::Mnemonic;
use std::str::FromStr;
let phrase = Mnemonic::from_str(mnemonic).map_err(|_e| Error::PhraseCorrupted())?;
Ok(phrase.to_seed_normalized("").to_vec())
}
#[uniffi::export]
pub fn register(
mnemonic: String,
invite_code: Option<String>,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
let seed = parse_mnemonic(&mnemonic)?;
schedule_node(seed, config, SchedulerAction::Register { invite_code })
}
#[uniffi::export]
pub fn recover(
mnemonic: String,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
let seed = parse_mnemonic(&mnemonic)?;
schedule_node(seed, config, SchedulerAction::Recover)
}
#[uniffi::export]
pub fn connect(
mnemonic: String,
credentials: Vec<u8>,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
use std::sync::Arc;
let seed = parse_mnemonic(&mnemonic)?;
let network = config.network;
let creds = credentials::Credentials::load(credentials)?;
let authenticated_signer =
gl_client::signer::Signer::new(seed, network, creds.inner.clone())
.map_err(|e| Error::Other(e.to_string()))?;
let handle = signer::Handle::spawn(authenticated_signer);
let node = node::Node::with_signer(creds, handle)?;
Ok(Arc::new(node))
}
#[uniffi::export]
pub fn register_or_recover(
mnemonic: String,
invite_code: Option<String>,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
match recover(mnemonic.clone(), config) {
Ok(node) => Ok(node),
Err(Error::NoSuchNode(_)) => register(mnemonic, invite_code, config),
Err(e) => Err(e),
}
}
#[uniffi::export]
pub fn parse_input(input: String) -> Result<input::InputType, Error> {
input::parse_input(input)
}
#[derive(uniffi::Enum, Debug)]
pub enum Network {
BITCOIN,
REGTEST,
}
impl From<Network> for gl_client::bitcoin::Network {
fn from(n: Network) -> gl_client::bitcoin::Network {
match n {
Network::BITCOIN => gl_client::bitcoin::Network::Bitcoin,
Network::REGTEST => gl_client::bitcoin::Network::Regtest,
}
}
}