Add lianalite module
This commit is contained in:
parent
ccc88421b3
commit
ca662eea6a
@ -44,7 +44,7 @@ chrono = "0.4.38"
|
||||
# Used for managing internal bitcoind
|
||||
base64 = "0.21"
|
||||
bitcoin_hashes = "0.12"
|
||||
reqwest = { version = "0.11", default-features=false, features = ["rustls-tls"] }
|
||||
reqwest = { version = "0.11", default-features=false, features = ["json", "rustls-tls"] }
|
||||
rust-ini = "0.19.0"
|
||||
|
||||
|
||||
|
||||
176
gui/src/lianalite/client/auth.rs
Normal file
176
gui/src/lianalite/client/auth.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use reqwest::{Error, IntoUrl, Method, RequestBuilder, Response};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct SignInOtp<'a> {
|
||||
email: &'a str,
|
||||
create_user: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct VerifyOtp<'a, 'b> {
|
||||
email: &'a str,
|
||||
token: &'b str,
|
||||
#[serde(rename = "type")]
|
||||
kind: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ResendOtp<'a> {
|
||||
email: &'a str,
|
||||
#[serde(rename = "type")]
|
||||
kind: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RefreshToken<'a> {
|
||||
refresh_token: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AccessTokenResponse {
|
||||
pub access_token: String,
|
||||
pub expires_at: i64,
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthClient {
|
||||
http: reqwest::Client,
|
||||
url: String,
|
||||
api_public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AuthError {
|
||||
pub http_status: Option<u16>,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AuthError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
if let Some(status) = self.http_status {
|
||||
write!(f, "{}: {}", status, self.error)
|
||||
} else {
|
||||
write!(f, "{}", self.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for AuthError {
|
||||
fn from(value: Error) -> Self {
|
||||
AuthError {
|
||||
http_status: None,
|
||||
error: value.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthClient {
|
||||
pub fn new(url: String, api_public_key: String) -> Self {
|
||||
AuthClient {
|
||||
http: reqwest::Client::new(),
|
||||
url,
|
||||
api_public_key,
|
||||
}
|
||||
}
|
||||
|
||||
fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
|
||||
let req = self
|
||||
.http
|
||||
.request(method, url)
|
||||
.header("apikey", &self.api_public_key)
|
||||
.header("Content-Type", "application/json");
|
||||
tracing::debug!("Sending http request: {:?}", req);
|
||||
req
|
||||
}
|
||||
|
||||
pub async fn sign_in_otp(&self, email: &str) -> Result<(), AuthError> {
|
||||
let response: Response = self
|
||||
.request(Method::POST, &format!("{}/auth/v1/otp", self.url))
|
||||
.json(&SignInOtp {
|
||||
email,
|
||||
create_user: true,
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(AuthError {
|
||||
http_status: Some(response.status().into()),
|
||||
error: response.text().await?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn resend_otp(&self, email: &str) -> Result<Response, AuthError> {
|
||||
let response: Response = self
|
||||
.request(Method::POST, &format!("{}/auth/v1/resend", self.url))
|
||||
.json(&ResendOtp {
|
||||
email,
|
||||
kind: "email",
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
if !response.status().is_success() {
|
||||
return Err(AuthError {
|
||||
http_status: Some(response.status().into()),
|
||||
error: response.text().await?,
|
||||
});
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn verify_otp(
|
||||
&self,
|
||||
email: &str,
|
||||
token: &str,
|
||||
) -> Result<AccessTokenResponse, AuthError> {
|
||||
let response: Response = self
|
||||
.http
|
||||
.post(&format!("{}/auth/v1/verify", self.url))
|
||||
.header("apikey", &self.api_public_key)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&VerifyOtp {
|
||||
email,
|
||||
token,
|
||||
kind: "email",
|
||||
})
|
||||
.send()
|
||||
.await?;
|
||||
if !response.status().is_success() {
|
||||
return Err(AuthError {
|
||||
http_status: Some(response.status().into()),
|
||||
error: response.text().await?,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(response.json().await?)
|
||||
}
|
||||
|
||||
pub async fn refresh_token(
|
||||
&self,
|
||||
refresh_token: &str,
|
||||
) -> Result<AccessTokenResponse, AuthError> {
|
||||
let response: Response = self
|
||||
.http
|
||||
.post(&format!(
|
||||
"{}/auth/v1/token?grant_type=refresh_token",
|
||||
self.url
|
||||
))
|
||||
.header("apikey", &self.api_public_key)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&RefreshToken { refresh_token })
|
||||
.send()
|
||||
.await?;
|
||||
if !response.status().is_success() {
|
||||
return Err(AuthError {
|
||||
http_status: Some(response.status().into()),
|
||||
error: response.text().await?,
|
||||
});
|
||||
}
|
||||
Ok(response.json().await?)
|
||||
}
|
||||
}
|
||||
416
gui/src/lianalite/client/backend/api.rs
Normal file
416
gui/src/lianalite/client/backend/api.rs
Normal file
@ -0,0 +1,416 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use liana::{
|
||||
descriptors::LianaDescriptor,
|
||||
miniscript::bitcoin::{self, bip32, consensus, hashes::hex::FromHex, Amount, OutPoint, Txid},
|
||||
};
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
|
||||
pub fn deser_fromstr<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
T::from_str(&string).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// Deserialize an address from string, assuming the network was checked.
|
||||
pub fn deser_addr_assume_checked<'de, D>(deserializer: D) -> Result<bitcoin::Address, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
bitcoin::Address::from_str(&string)
|
||||
.map(|addr| addr.assume_checked())
|
||||
.map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// Deserialize an amount from sats
|
||||
pub fn deser_amount_from_sats<'de, D>(deserializer: D) -> Result<bitcoin::Amount, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let a = u64::deserialize(deserializer)?;
|
||||
Ok(bitcoin::Amount::from_sat(a))
|
||||
}
|
||||
|
||||
pub fn deser_hex<'de, D, T>(d: D) -> Result<T, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: consensus::Decodable,
|
||||
{
|
||||
let s = String::deserialize(d)?;
|
||||
let s = Vec::from_hex(&s).map_err(de::Error::custom)?;
|
||||
consensus::deserialize(&s).map_err(de::Error::custom)
|
||||
}
|
||||
|
||||
/// The maximum number of item to return.
|
||||
pub const DEFAULT_LIMIT: usize = 20;
|
||||
/// The maximum number of outpoints that can be provided as a filter.
|
||||
pub const DEFAULT_OUTPOINTS_LIMIT: usize = 50;
|
||||
/// The maximum number of items that can be provided as a filter.
|
||||
pub const DEFAULT_LABEL_ITEMS_LIMIT: usize = 50;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct NetworkInfo {
|
||||
pub feerate: Feerate,
|
||||
pub rates: HashMap<String, f32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Feerate {
|
||||
pub low: Option<i32>,
|
||||
pub high: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WalletBalance {
|
||||
/// Total of funds that present in a block.
|
||||
pub confirmed: u64,
|
||||
/// Total of funds that is not yet in a block.
|
||||
pub unconfirmed: u64,
|
||||
/// Total of funds that are mined but not yet available
|
||||
pub immature: u64,
|
||||
/// Total of funds that are unconfirmed but are coming from
|
||||
/// the wallet
|
||||
pub unconfirmed_change: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum WalletStatus {
|
||||
Normal,
|
||||
Recovering,
|
||||
Recovered,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct RecoveryPath {
|
||||
pub sequence: u16,
|
||||
pub available_balance: u64,
|
||||
pub total_coins: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct Wallet {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
pub descriptor: LianaDescriptor,
|
||||
pub recovery_paths: Vec<RecoveryPath>,
|
||||
pub biggest_remaining_sequence: Option<u32>,
|
||||
pub smallest_remaining_sequence: Option<u32>,
|
||||
pub metadata: WalletMetadata,
|
||||
pub created_at: i64,
|
||||
pub balance: WalletBalance,
|
||||
pub status: WalletStatus,
|
||||
pub tip_height: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListWallets {
|
||||
pub wallets: Vec<Wallet>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct WalletMetadata {
|
||||
pub ledger_hmacs: Vec<LedgerHmac>,
|
||||
pub fingerprint_aliases: Vec<FingerprintAlias>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct LedgerHmac {
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
pub fingerprint: bip32::Fingerprint,
|
||||
pub user_id: String,
|
||||
pub hmac: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub struct FingerprintAlias {
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
pub fingerprint: bip32::Fingerprint,
|
||||
pub user_id: String,
|
||||
pub alias: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WalletLabels {
|
||||
pub labels: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum PaymentKind {
|
||||
Outgoing,
|
||||
Incoming,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Payment {
|
||||
pub txuuid: String,
|
||||
pub txid: String,
|
||||
pub vout: u32,
|
||||
pub amount: u64,
|
||||
pub block_height: Option<i32>,
|
||||
pub confirmed_at: Option<i64>,
|
||||
pub label: Option<String>,
|
||||
pub address_label: Option<String>,
|
||||
pub transaction_label: Option<String>,
|
||||
pub kind: PaymentKind,
|
||||
pub is_single: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListPayments {
|
||||
pub payments: Vec<Payment>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Coin {
|
||||
#[serde(deserialize_with = "deser_addr_assume_checked")]
|
||||
pub address: bitcoin::Address,
|
||||
#[serde(deserialize_with = "deser_amount_from_sats")]
|
||||
pub amount: Amount,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
pub outpoint: OutPoint,
|
||||
pub block_height: Option<i32>,
|
||||
pub spend_info: Option<CoinSpendInfo>,
|
||||
pub is_immature: bool,
|
||||
pub is_change_address: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct CoinSpendInfo {
|
||||
pub txid: Txid,
|
||||
pub height: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListCoins {
|
||||
pub coins: Vec<Coin>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum UTXOKind {
|
||||
Deposit,
|
||||
Change,
|
||||
External,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Transaction {
|
||||
pub uuid: String,
|
||||
pub txid: String,
|
||||
pub fee: u64,
|
||||
pub fee_rate: u64,
|
||||
pub block_height: Option<i32>,
|
||||
pub confirmed_at: Option<i64>,
|
||||
pub label: Option<String>,
|
||||
#[serde(deserialize_with = "deser_hex")]
|
||||
pub raw: bitcoin::Transaction,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
/// If the transaction has multiple incoming or ougoing payment.
|
||||
pub is_batch: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListTransactions {
|
||||
pub transactions: Vec<Transaction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Output {
|
||||
pub address: Option<String>,
|
||||
pub label: Option<String>,
|
||||
pub address_label: Option<String>,
|
||||
pub amount: u64,
|
||||
pub kind: UTXOKind,
|
||||
pub coin: Option<Coin>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Input {
|
||||
pub txid: String,
|
||||
pub vout: usize,
|
||||
pub amount: Option<u64>,
|
||||
pub label: Option<String>,
|
||||
pub kind: UTXOKind,
|
||||
pub coin: Option<Coin>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct Psbt {
|
||||
pub uuid: String,
|
||||
pub txid: Txid,
|
||||
pub fee: Option<u64>,
|
||||
pub fee_rate: Option<u64>,
|
||||
pub label: Option<String>,
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
pub raw: bitcoin::Psbt,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub is_batch: bool,
|
||||
pub updated_at: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DraftPsbtResult {
|
||||
Success(DraftPsbt),
|
||||
InsufficientFunds(InsufficientFundsInfo),
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct InsufficientFundsInfo {
|
||||
pub missing: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct DraftPsbt {
|
||||
pub uuid: Option<String>,
|
||||
pub txid: Txid,
|
||||
pub fee: u64,
|
||||
pub fee_rate: u64,
|
||||
pub label: Option<String>,
|
||||
#[serde(deserialize_with = "deser_fromstr")]
|
||||
pub raw: bitcoin::Psbt,
|
||||
pub inputs: Vec<Input>,
|
||||
pub outputs: Vec<Output>,
|
||||
pub warnings: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListPsbts {
|
||||
pub psbts: Vec<Psbt>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Address {
|
||||
#[serde(deserialize_with = "deser_addr_assume_checked")]
|
||||
pub address: bitcoin::Address,
|
||||
pub derivation_index: bip32::ChildNumber,
|
||||
}
|
||||
|
||||
pub mod payload {
|
||||
use liana::miniscript::bitcoin;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
pub fn ser_to_string<T: std::fmt::Display, S: Serializer>(
|
||||
field: T,
|
||||
s: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
s.serialize_str(&field.to_string())
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ImportPsbt {
|
||||
pub psbt: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Recipient {
|
||||
/// Recipient cannot have an empty amount and is_max set to false
|
||||
/// Amount cannot be less that the DUST limit.
|
||||
pub amount: Option<u64>,
|
||||
pub address: bitcoin::Address<bitcoin::address::NetworkUnchecked>,
|
||||
/// If is_max is set to true, API will calculate the remaining funds and
|
||||
/// use it for psbt output amount.
|
||||
/// Only one recipient can have is_max set to true
|
||||
pub is_max: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GeneratePsbt<'a> {
|
||||
pub recipients: Vec<Recipient>,
|
||||
/// The outpoints of coins to use as transaction inputs. If empty,
|
||||
/// coins will be selected automatically from the set of confirmed coins
|
||||
/// and those unconfirmed coins at a change address, excluding immature
|
||||
/// coins.
|
||||
pub inputs: &'a [bitcoin::OutPoint],
|
||||
// The feerate to use for this transaction.
|
||||
pub feerate: u64,
|
||||
/// If save is set to true, API will save in database the generated psbt
|
||||
/// and store the generated change address.
|
||||
pub save: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GenerateRecoveryPsbt {
|
||||
/// The address to sweep funds to.
|
||||
pub address: bitcoin::Address<bitcoin::address::NetworkUnchecked>,
|
||||
// The feerate to use for this transaction.
|
||||
pub feerate: u64,
|
||||
/// Timelock of the recovery path to use.
|
||||
pub timelock: u16,
|
||||
/// If save is set to true, API will save in database the generated psbt
|
||||
/// and store the generated change address.
|
||||
pub save: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Labels {
|
||||
pub labels: Vec<Label>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Label {
|
||||
pub item: String,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GenerateRbfPsbt {
|
||||
/// ID of the transaction to be replaced.
|
||||
#[serde(serialize_with = "ser_to_string")]
|
||||
pub txid: bitcoin::Txid,
|
||||
/// The target feerate (sat/vb) to use for the replacement transaction
|
||||
/// in order to bump the fee of the transaction being replaced.
|
||||
///
|
||||
/// Must be provided if and only if `is_cancel` is `false`.
|
||||
pub feerate: Option<u64>,
|
||||
/// Whether to cancel the transaction.
|
||||
///
|
||||
/// If `true`, the feerate of the replacement transaction will be set
|
||||
/// automatically to the lowest possible feerate that satisfies all
|
||||
/// RBF policies.
|
||||
///
|
||||
/// If `false`, the transaction will be replaced by another at the target
|
||||
/// `feerate` in order to bump its fee.
|
||||
pub is_cancel: bool,
|
||||
/// If save is set to true, API will save in database the generated psbt
|
||||
/// and, if a new change address is generated for the replacement, store
|
||||
/// this also. Note that if the transaction being replaced has a change
|
||||
/// output, then its corresponding change address will be reused in the
|
||||
/// replacement.
|
||||
pub save: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateWallet {
|
||||
pub ledger_hmac: Option<UpdateLedgerHmac>,
|
||||
pub fingerprint_aliases: Option<Vec<UpdateFingerprintAlias>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateLedgerHmac {
|
||||
pub fingerprint: String,
|
||||
pub hmac: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UpdateFingerprintAlias {
|
||||
pub fingerprint: String,
|
||||
pub alias: String,
|
||||
}
|
||||
}
|
||||
1042
gui/src/lianalite/client/backend/mod.rs
Normal file
1042
gui/src/lianalite/client/backend/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
32
gui/src/lianalite/client/mod.rs
Normal file
32
gui/src/lianalite/client/mod.rs
Normal file
@ -0,0 +1,32 @@
|
||||
pub mod auth;
|
||||
pub mod backend;
|
||||
|
||||
use liana::miniscript::bitcoin;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
const LIANALITE_SIGNET_URL: &str = "https://signet.lianalite.com";
|
||||
const LIANALITE_MAINNET_URL: &str = "https://lianalite.com";
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ServiceConfig {
|
||||
pub auth_api_url: String,
|
||||
pub auth_api_public_key: String,
|
||||
pub backend_api_url: String,
|
||||
}
|
||||
|
||||
pub async fn get_service_config(
|
||||
network: bitcoin::Network,
|
||||
) -> Result<ServiceConfig, reqwest::Error> {
|
||||
reqwest::get(format!(
|
||||
"{}/api/env",
|
||||
if network == bitcoin::Network::Bitcoin {
|
||||
LIANALITE_MAINNET_URL
|
||||
} else {
|
||||
LIANALITE_SIGNET_URL
|
||||
}
|
||||
))
|
||||
.await?
|
||||
.json()
|
||||
.await
|
||||
}
|
||||
86
gui/src/lianalite/mod.rs
Normal file
86
gui/src/lianalite/mod.rs
Normal file
@ -0,0 +1,86 @@
|
||||
pub mod client;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use liana::miniscript::bitcoin::Network;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Config {
|
||||
auth: HashMap<String, NetworkAuthConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct NetworkAuthConfig {
|
||||
email: String,
|
||||
access_token: String,
|
||||
expires_at: i64,
|
||||
refresh_token: String,
|
||||
}
|
||||
|
||||
pub const DEFAULT_FILE_NAME: &str = "lite.json";
|
||||
|
||||
impl Config {
|
||||
pub fn file_path(datadir: PathBuf, network: Network) -> PathBuf {
|
||||
let mut path = datadir;
|
||||
path.push(network.to_string());
|
||||
path.push(DEFAULT_FILE_NAME);
|
||||
path
|
||||
}
|
||||
pub fn from_file(datadir: PathBuf, network: Network) -> Result<Self, ConfigError> {
|
||||
let path = Self::file_path(datadir, network);
|
||||
|
||||
let config = std::fs::read(path)
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => ConfigError::NotFound,
|
||||
_ => ConfigError::ReadingFile(format!("Reading settings file: {}", e)),
|
||||
})
|
||||
.and_then(|file_content| {
|
||||
serde_json::from_slice::<Config>(&file_content)
|
||||
.map_err(|e| ConfigError::ReadingFile(format!("Parsing settings file: {}", e)))
|
||||
})?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub fn to_file(&self, datadir: PathBuf, network: Network) -> Result<(), ConfigError> {
|
||||
let path = Self::file_path(datadir, network);
|
||||
|
||||
let content = serde_json::to_string_pretty(&self).map_err(|e| {
|
||||
ConfigError::WritingFile(format!("Failed to serialize settings: {}", e))
|
||||
})?;
|
||||
|
||||
let mut settings_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(path)
|
||||
.map_err(|e| ConfigError::WritingFile(e.to_string()))?;
|
||||
|
||||
settings_file.write_all(content.as_bytes()).map_err(|e| {
|
||||
tracing::warn!("failed to write to file: {:?}", e);
|
||||
ConfigError::WritingFile(e.to_string())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum ConfigError {
|
||||
NotFound,
|
||||
ReadingFile(String),
|
||||
WritingFile(String),
|
||||
Unexpected(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ConfigError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::NotFound => write!(f, "Settings file not found"),
|
||||
Self::ReadingFile(e) => write!(f, "Error while reading file: {}", e),
|
||||
Self::WritingFile(e) => write!(f, "Error while writing file: {}", e),
|
||||
Self::Unexpected(e) => write!(f, "Unexpected error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ pub mod download;
|
||||
pub mod hw;
|
||||
pub mod installer;
|
||||
pub mod launcher;
|
||||
pub mod lianalite;
|
||||
pub mod loader;
|
||||
pub mod logger;
|
||||
pub mod signer;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user