use std::collections::HashMap;
uniffi::setup_scaffolding!();
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum Error {
#[error("{msg}")]
DuplicateNode {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
NoSuchNode {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
UnparseableCreds {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
PhraseCorrupted {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
Rpc {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
Argument {
code: i32,
msg: String,
values: HashMap<String, String>,
},
#[error("{msg}")]
Other {
code: i32,
msg: String,
values: HashMap<String, String>,
},
}
impl Error {
pub fn duplicate_node(node_id: impl Into<String>) -> Self {
let node_id = node_id.into();
Error::DuplicateNode {
code: 1000,
msg: format!(
"There is already a node for node_id={node_id}, maybe you want to recover?"
),
values: HashMap::from([("node_id".into(), node_id)]),
}
}
pub fn no_such_node(node_id: impl Into<String>) -> Self {
let node_id = node_id.into();
Error::NoSuchNode {
code: 1001,
msg: format!(
"There is no node with node_id={node_id}, maybe you need to register first?"
),
values: HashMap::from([("node_id".into(), node_id)]),
}
}
pub fn unparseable_creds() -> Self {
Error::UnparseableCreds {
code: 1100,
msg: "The provided credentials could not be parsed, please recover.".into(),
values: HashMap::new(),
}
}
pub fn phrase_corrupted() -> Self {
Error::PhraseCorrupted {
code: 1101,
msg: "The passphrase you provided fails the checksum".into(),
values: HashMap::new(),
}
}
pub fn rpc(detail: impl Into<String>) -> Self {
let detail = detail.into();
Error::Rpc {
code: 2000,
msg: format!("Error calling the rpc: {detail}"),
values: HashMap::from([("detail".into(), detail)]),
}
}
pub fn argument(arg_name: impl Into<String>, arg_value: impl Into<String>) -> Self {
let arg_name = arg_name.into();
let arg_value = arg_value.into();
Error::Argument {
code: 3000,
msg: format!("Invalid argument: {arg_name}={arg_value}"),
values: HashMap::from([
("arg_name".into(), arg_name),
("arg_value".into(), arg_value),
]),
}
}
pub fn other(detail: impl Into<String>) -> Self {
let detail = detail.into();
Error::Other {
code: 9000,
msg: format!("Generic error: {detail}"),
values: HashMap::from([("detail".into(), detail)]),
}
}
}
mod config;
mod credentials;
mod input;
mod lnurl;
mod logging;
mod node;
mod node_builder;
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, NodeEventListener, NodeEventStream, NodeState, OnchainBalanceState,
OnchainFeeRates, OnchainReceiveResponse, OnchainSendResponse, Outpoint, OutputStatus,
Pay, PayStatus, Payment, PaymentStatus, PaymentType, PaymentTypeFilter, Peer,
PeerChannel, PreparedOnchainSend, ReceiveResponse, SendResponse,
},
input::{ParsedInput, ParsedInvoice, ResolvedInput},
logging::{LogEntry, LogLevel, LogListener},
lnurl::{
LnUrlErrorData, LnUrlPayRequest, LnUrlPayRequestData, LnUrlPayResult,
LnUrlPaySuccessData, LnUrlWithdrawRequest, LnUrlWithdrawRequestData,
LnUrlWithdrawResult, LnUrlWithdrawSuccessData, SuccessActionProcessed,
},
node_builder::NodeBuilder,
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, network)?;
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::duplicate_node(node_id_hex.to_string())
}
tonic::Code::NotFound => return Error::no_such_node(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::no_such_node(node_id_hex.to_string())
} else if msg.contains("ALREADY_EXISTS") {
Error::duplicate_node(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::phrase_corrupted())?;
Ok(phrase.to_seed_normalized("").to_vec())
}
pub(crate) fn connect_internal(
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, network)?;
Ok(Arc::new(node))
}
pub(crate) fn register_internal(
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 })
}
pub(crate) fn recover_internal(
mnemonic: String,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
let seed = parse_mnemonic(&mnemonic)?;
schedule_node(seed, config, SchedulerAction::Recover)
}
pub(crate) fn register_or_recover_internal(
mnemonic: String,
invite_code: Option<String>,
config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
match recover_internal(mnemonic.clone(), config) {
Ok(node) => Ok(node),
Err(Error::NoSuchNode { .. }) => register_internal(mnemonic, invite_code, config),
Err(e) => Err(e),
}
}
pub(crate) fn connect_signerless_internal(
credentials: Vec<u8>,
_config: &config::Config,
) -> Result<std::sync::Arc<node::Node>, Error> {
use std::sync::Arc;
let creds = credentials::Credentials::load(credentials)?;
let node = node::Node::signerless(creds)?;
Ok(Arc::new(node))
}
#[uniffi::export]
pub fn parse_input(input: String) -> Result<input::ParsedInput, Error> {
input::parse_input(input)
}
#[uniffi::export]
pub fn resolve_input(input: String) -> Result<input::ResolvedInput, Error> {
util::exec(async { input::resolve_input(input).await })
}
#[uniffi::export]
pub fn set_logger(
level: logging::LogLevel,
listener: Box<dyn logging::LogListener>,
) -> Result<(), Error> {
logging::set_logger(level, listener)
}
#[uniffi::export]
pub fn set_log_level(level: logging::LogLevel) {
logging::set_log_level(level)
}
#[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,
}
}
}