use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::net::SocketAddr;
use std::str::FromStr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use lwk_common::Signer;
use lwk_jade::{Jade, Network};
use lwk_rpc_model::request;
use lwk_signer::AnySigner;
use lwk_signer::SwSigner;
use lwk_tiny_jrpc::Request;
use lwk_wollet::asset_ids;
use lwk_wollet::bitcoin::bip32::{Fingerprint, Xpub};
use lwk_wollet::bitcoin::XKeyIdentifier;
use lwk_wollet::elements::encode::serialize;
use lwk_wollet::elements::hex::ToHex;
use lwk_wollet::elements::pset::elip100::AssetMetadata;
use lwk_wollet::elements::{Address, AssetId, OutPoint, Transaction, Txid};
use lwk_wollet::Contract;
use lwk_wollet::Wollet;
use serde::Serialize;
use crate::config::Config;
use crate::method::Method;
use crate::Error;
#[derive(Debug)]
enum AppSignerInner {
JadeId(XKeyIdentifier, Network),
AvailableSigner(AnySigner),
ExternalSigner(Fingerprint),
}
#[derive(Debug)]
pub struct AppSigner {
inner: AppSignerInner,
persist: bool,
}
impl AppSigner {
pub fn new_sw(mnemonic: &str, is_mainnet: bool, persist: bool) -> Result<Self, Error> {
let sw = SwSigner::new(mnemonic, is_mainnet)?;
let inner = AppSignerInner::AvailableSigner(AnySigner::Software(sw));
Ok(AppSigner { inner, persist })
}
pub fn new_jade(
id: XKeyIdentifier,
emulator: Option<SocketAddr>,
network: Network,
) -> Result<Self, Error> {
let inner = if let Some(socket) = emulator {
let jade = Jade::from_socket(socket, network)?;
AppSignerInner::AvailableSigner(AnySigner::Jade(jade, id))
} else {
AppSignerInner::JadeId(id, network)
};
Ok(AppSigner {
inner,
persist: true,
})
}
pub fn new_external(fingerprint: Fingerprint) -> Self {
AppSigner {
inner: AppSignerInner::ExternalSigner(fingerprint),
persist: false,
}
}
pub fn fingerprint(&self) -> Result<Fingerprint, Error> {
Ok(match &self.inner {
AppSignerInner::AvailableSigner(s) => s.fingerprint()?,
AppSignerInner::ExternalSigner(f) => *f,
AppSignerInner::JadeId(id, _) => id_to_fingerprint(id),
})
}
pub fn xpub(&self) -> Result<Option<Xpub>, Error> {
Ok(match &self.inner {
AppSignerInner::AvailableSigner(s) => Some(s.xpub()?),
_ => None,
})
}
pub fn id(&self) -> Result<Option<XKeyIdentifier>, Error> {
Ok(match &self.inner {
AppSignerInner::AvailableSigner(s) => Some(s.identifier()?),
AppSignerInner::JadeId(id, _) => Some(*id),
_ => None,
})
}
pub fn mnemonic(&self) -> Option<String> {
match &self.inner {
AppSignerInner::AvailableSigner(AnySigner::Software(s)) => {
s.mnemonic().map(|m| m.to_string())
}
_ => None,
}
}
pub fn type_(&self) -> String {
match &self.inner {
AppSignerInner::ExternalSigner(_) => "external".into(),
AppSignerInner::JadeId(_, _) => "jade-id".into(),
AppSignerInner::AvailableSigner(AnySigner::Software(_)) => "software".into(),
AppSignerInner::AvailableSigner(AnySigner::Jade(_, _)) => "jade".into(),
#[allow(unreachable_patterns)]
_ => todo!(),
}
}
}
pub fn id_to_fingerprint(id: &XKeyIdentifier) -> Fingerprint {
id[0..4].try_into().expect("4 is the fingerprint length")
}
#[derive(Debug, Clone)]
pub struct RegistryAssetData {
asset_id: AssetId,
token_id: AssetId,
issuance_vin: u32,
issuance_tx: Transaction,
contract: Contract,
}
impl RegistryAssetData {
pub fn new(
asset_id: AssetId,
issuance_tx: Transaction,
contract: Contract,
) -> Result<Self, Error> {
for (vin, txin) in issuance_tx.input.iter().enumerate() {
let (asset_id_txin, token_id) = txin.issuance_ids();
if asset_id_txin == asset_id {
let (asset_id_contract, token_id_contract) = asset_ids(txin, &contract)?;
if asset_id_contract != asset_id || token_id_contract != token_id {
return Err(Error::InvalidContractForAsset(asset_id.to_string()));
}
return Ok(Self {
asset_id,
token_id,
issuance_vin: vin as u32,
issuance_tx,
contract,
});
}
}
Err(Error::InvalidIssuanceTxtForAsset(asset_id.to_string()))
}
pub fn contract_str(&self) -> String {
serde_json::to_string(&self.contract).expect("contract")
}
pub fn contract(&self) -> &Contract {
&self.contract
}
pub fn issuance_prevout(&self) -> OutPoint {
self.issuance_tx.input[self.issuance_vin as usize].previous_output
}
}
pub enum AppAsset {
PolicyAsset(AssetId),
RegistryAsset(RegistryAssetData),
ReissuanceToken(RegistryAssetData),
}
impl AppAsset {
pub fn name(&self) -> String {
match self {
AppAsset::PolicyAsset(_) => "liquid bitcoin".into(),
AppAsset::RegistryAsset(d) => d.contract.name.clone(),
AppAsset::ReissuanceToken(d) => {
format!("reissuance token for {}", d.contract.name)
}
}
}
pub fn ticker(&self) -> String {
match self {
AppAsset::PolicyAsset(_) => "L-BTC".into(),
AppAsset::RegistryAsset(d) => d.contract.ticker.clone(),
AppAsset::ReissuanceToken(d) => {
format!("reissuance token for {}", d.contract.ticker)
}
}
}
pub fn asset_metadata(&self) -> Option<AssetMetadata> {
match self {
AppAsset::PolicyAsset(_) => None,
AppAsset::RegistryAsset(d) => {
Some(AssetMetadata::new(d.contract_str(), d.issuance_prevout()))
}
AppAsset::ReissuanceToken(d) => {
Some(AssetMetadata::new(d.contract_str(), d.issuance_prevout()))
}
}
}
pub fn asset_id(&self) -> AssetId {
match self {
AppAsset::PolicyAsset(asset) => *asset,
AppAsset::RegistryAsset(d) => d.asset_id,
AppAsset::ReissuanceToken(d) => d.token_id,
}
}
pub fn issuance_tx(&self) -> Option<Transaction> {
match self {
AppAsset::RegistryAsset(d) => Some(d.issuance_tx.clone()),
_ => None,
}
}
pub fn request(&self) -> Option<Request> {
match self {
AppAsset::RegistryAsset(a) => {
let params = request::AssetInsert {
asset_id: a.asset_id.to_string(),
contract: a.contract_str(),
issuance_tx: serialize(&a.issuance_tx).to_hex(),
};
Some(Request {
jsonrpc: "2.0".into(),
id: None,
method: Method::AssetInsert.to_string(),
params: Some(serde_json::to_value(params).expect("derived")),
})
}
_ => None,
}
}
}
#[derive(Default)]
pub struct Wollets(HashMap<String, Wollet>);
#[derive(Default)]
pub struct Signers(HashMap<String, AppSigner>);
#[derive(Default)]
pub struct Assets(HashMap<AssetId, AppAsset>);
#[derive(Default)]
pub struct TxMemos(HashMap<String, HashMap<Txid, String>>);
#[derive(Default)]
pub struct AddrMemos(HashMap<String, HashMap<Address, String>>);
pub struct State {
pub config: Config,
pub wollets: Wollets,
pub signers: Signers,
pub assets: Assets,
pub tx_memos: TxMemos,
pub addr_memos: AddrMemos,
pub do_persist: bool,
pub scan_loops_started: u32,
pub scan_loops_completed: u32,
pub interrupt_wait: bool,
}
impl Wollets {
pub fn get(&self, name: &str) -> Result<&Wollet, Error> {
self.0
.get(name)
.ok_or_else(|| Error::WalletNotExist(name.to_string()))
}
pub fn get_mut(&mut self, name: &str) -> Result<&mut Wollet, Error> {
self.0
.get_mut(name)
.ok_or_else(|| Error::WalletNotExist(name.to_string()))
}
pub fn insert(&mut self, name: &str, wollet: Wollet) -> Result<(), Error> {
if self.0.contains_key(name) {
return Err(Error::WalletAlreadyLoaded(name.to_string()));
}
let first_addr = |w: &Wollet| w.address(Some(0)).map(|a| a.address().clone());
let other = first_addr(&wollet)?;
let ours: Vec<_> = self.0.values().map(first_addr).collect::<Result<_, _>>()?;
let vec: Vec<_> = self
.0
.keys()
.zip(ours.iter())
.filter(|(_, b)| &other == *b)
.map(|(n, _)| n)
.collect();
if let Some(existing) = vec.first() {
return Err(Error::WalletAlreadyLoaded(existing.to_string()));
}
self.0.insert(name.to_string(), wollet);
Ok(())
}
pub fn remove(&mut self, name: &str) -> Result<Wollet, Error> {
self.0
.remove(name)
.ok_or_else(|| Error::WalletNotExist(name.to_string()))
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Wollet)> {
self.0.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&String, &mut Wollet)> {
self.0.iter_mut()
}
}
impl Signers {
pub fn get(&self, name: &str) -> Result<&AppSigner, Error> {
self.0
.get(name)
.ok_or_else(|| Error::SignerNotExist(name.to_string()))
}
#[allow(dead_code)]
pub fn get_mut(&mut self, name: &str) -> Result<&mut AppSigner, Error> {
self.0
.get_mut(name)
.ok_or_else(|| Error::SignerNotExist(name.to_string()))
}
fn get_available(
&mut self,
name: &str,
timeout: Option<Duration>,
) -> Result<&AnySigner, Error> {
let app_signer = self.get(name)?;
tracing::debug!("get_available({}) return {:?}", name, app_signer);
let jade = match &app_signer.inner {
#[cfg(not(feature = "serial"))]
AppSignerInner::JadeId(_, _) => {
let _timeout = timeout;
return Err(Error::FeatSerialDisabled);
}
#[cfg(feature = "serial")]
AppSignerInner::JadeId(id, network) => {
lwk_jade::Jade::from_serial_matching_id(*network, id, timeout)
.map(|jade| AppSignerInner::AvailableSigner(AnySigner::Jade(jade, *id)))
}
AppSignerInner::AvailableSigner(AnySigner::Jade(j, id)) => {
if j.unlock().is_err() {
Some(AppSignerInner::JadeId(*id, j.network()))
} else {
None
}
}
_ => None,
};
if let Some(inner) = jade {
let signer = AppSigner {
inner,
persist: true,
};
self.0.insert(name.to_string(), signer);
}
match &self.get(name)?.inner {
AppSignerInner::AvailableSigner(signer) => Ok(signer),
AppSignerInner::ExternalSigner(_) => Err(Error::Generic(
"Invalid operation for external signer".to_string(),
)),
AppSignerInner::JadeId(_, _) => Err(Error::Generic(
"Invalid operation jade is not connected".to_string(),
)),
}
}
pub fn insert(&mut self, name: &str, signer: AppSigner) -> Result<(), Error> {
if self.0.contains_key(name) {
return Err(Error::SignerAlreadyLoaded(name.to_string()));
}
let inserting_fingerprint = signer.fingerprint()?;
let vec: Vec<_> = self.names_matching_fingerprint(&inserting_fingerprint)?;
if let Some(existing) = vec.first() {
return Err(Error::SignerAlreadyLoaded(existing.to_string()));
}
self.0.insert(name.to_string(), signer);
Ok(())
}
pub fn remove(&mut self, name: &str) -> Result<AppSigner, Error> {
self.0
.remove(name)
.ok_or_else(|| Error::SignerNotExist(name.to_string()))
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &AppSigner)> {
self.0.iter()
}
fn names_matching_fingerprint(&self, fingerprint: &Fingerprint) -> Result<Vec<String>, Error> {
let fingerprints = self
.iter()
.map(|s| s.1.fingerprint())
.collect::<Result<Vec<Fingerprint>, Error>>()?;
Ok(self
.iter()
.map(|(n, _)| n)
.zip(fingerprints.iter())
.filter(|(_, b)| *b == fingerprint)
.map(|(n, _)| n.clone())
.collect())
}
pub fn name_from_fingerprint(
&self,
fingerprint: &Fingerprint,
warnings: &mut Vec<String>,
) -> Option<String> {
let names = self.names_matching_fingerprint(fingerprint).ok()?;
match names.len() {
0 => None,
1 => Some(names[0].clone()),
_ => {
warnings.push(format!(
"{fingerprint} corresponds to multiple loaded signers"
));
None
}
}
}
}
impl Assets {
pub fn iter(&self) -> impl Iterator<Item = (&AssetId, &AppAsset)> {
self.0.iter()
}
}
impl TxMemos {
pub fn for_wollet(&self, wollet: &str) -> HashMap<Txid, String> {
self.0.get(wollet).cloned().unwrap_or_default()
}
pub fn set(&mut self, wollet: &str, txid: &Txid, memo: &str) -> Result<(), Error> {
if let Some(wollet_memos) = self.0.get_mut(wollet) {
wollet_memos.insert(*txid, memo.to_string());
} else {
let mut wollet_memos = HashMap::new();
wollet_memos.insert(*txid, memo.to_string());
self.0.insert(wollet.to_string(), wollet_memos);
}
Ok(())
}
pub fn remove(&mut self, wollet: &str) {
self.0.remove(wollet);
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &HashMap<Txid, String>)> {
self.0.iter()
}
}
impl AddrMemos {
pub fn for_wollet(&self, wollet: &str) -> HashMap<Address, String> {
self.0.get(wollet).cloned().unwrap_or_default()
}
pub fn set(&mut self, wollet: &str, addr: &Address, memo: &str) -> Result<(), Error> {
if let Some(wollet_memos) = self.0.get_mut(wollet) {
wollet_memos.insert(addr.clone(), memo.to_string());
} else {
let mut wollet_memos = HashMap::new();
wollet_memos.insert(addr.clone(), memo.to_string());
self.0.insert(wollet.to_string(), wollet_memos);
}
Ok(())
}
pub fn remove(&mut self, wollet: &str) {
self.0.remove(wollet);
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &HashMap<Address, String>)> {
self.0.iter()
}
}
impl State {
pub fn insert_policy_asset(&mut self) {
let asset_id = self.config.network.policy_asset();
self.assets
.0
.insert(asset_id, AppAsset::PolicyAsset(asset_id));
}
pub fn get_asset(&self, asset: &AssetId) -> Result<&AppAsset, Error> {
self.assets
.0
.get(asset)
.ok_or_else(|| Error::AssetNotExist(asset.to_string()))
}
pub fn insert_asset(
&mut self,
asset_id: AssetId,
issuance_tx: Transaction,
contract: Contract,
) -> Result<(), Error> {
let data = RegistryAssetData::new(asset_id, issuance_tx, contract)?;
self.assets
.0
.insert(asset_id, AppAsset::RegistryAsset(data.clone()));
self.assets
.0
.insert(data.token_id, AppAsset::ReissuanceToken(data));
Ok(())
}
pub fn remove_asset(&mut self, asset: &AssetId) -> Result<(), Error> {
self.assets
.0
.remove(asset)
.ok_or_else(|| Error::AssetNotExist(asset.to_string()))?;
Ok(())
}
fn get_asset_from_str(&self, asset: &str) -> Result<&AppAsset, Error> {
let asset = AssetId::from_str(asset).map_err(|e| Error::Generic(e.to_string()))?;
self.get_asset(&asset)
}
pub fn get_issuance_tx(&self, asset: &AssetId) -> Option<Transaction> {
self.get_asset(asset).ok().and_then(|a| a.issuance_tx())
}
pub fn replace_id_with_ticker(
&self,
balance: impl IntoIterator<Item = (String, i64)>,
) -> HashMap<String, i64> {
balance
.into_iter()
.map(|(k, v)| {
let t = self.get_asset_from_str(&k).map(|a| a.ticker()).unwrap_or(k);
(t, v)
})
.collect()
}
pub fn persist<T: Serialize>(&mut self, data: T) -> Result<(), Error> {
if self.do_persist {
let data = serde_json::to_string(&data)?;
let path = self.config.state_path()?;
let mut file = OpenOptions::new()
.create_new(!path.exists())
.append(true)
.open(path)?;
writeln!(file, "{}", data)?;
file.sync_all()?;
}
Ok(())
}
pub fn persist_all(&mut self) -> Result<(), Error> {
let path = self.config.state_path()?;
let mut temp = path.clone();
let millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Clock may have gone backwards")
.as_millis();
temp.set_file_name(millis.to_string());
let mut file = File::create(&temp)?;
for req in self.as_requests()? {
let data = serde_json::to_string(&req)?;
writeln!(file, "{}", data)?;
}
std::fs::rename(temp, path)?;
file.sync_all()?;
Ok(())
}
fn as_requests(&self) -> Result<Vec<Request>, Error> {
let mut requests = vec![];
for (n, w) in self.wollets.iter() {
let params = request::WalletLoad {
descriptor: w.descriptor().to_string(),
name: n.to_string(),
};
let r = Request {
jsonrpc: "2.0".into(),
id: None,
method: Method::WalletLoad.to_string(),
params: Some(serde_json::to_value(params)?),
};
requests.push(r);
}
for (name, wollet_memos) in self.tx_memos.iter() {
for (txid, memo) in wollet_memos.iter() {
let params = request::WalletSetTxMemo {
name: name.to_string(),
txid: txid.to_string(),
memo: memo.to_string(),
};
let r = Request {
jsonrpc: "2.0".into(),
id: None,
method: Method::WalletSetTxMemo.to_string(),
params: Some(serde_json::to_value(params)?),
};
requests.push(r);
}
}
for (name, wollet_memos) in self.addr_memos.iter() {
for (address, memo) in wollet_memos.iter() {
let params = request::WalletSetAddrMemo {
name: name.to_string(),
address: address.to_string(),
memo: memo.to_string(),
};
let r = Request {
jsonrpc: "2.0".into(),
id: None,
method: Method::WalletSetAddrMemo.to_string(),
params: Some(serde_json::to_value(params)?),
};
requests.push(r);
}
}
for (n, s) in self.signers.iter() {
let (params, method) = match &s.inner {
AppSignerInner::JadeId(id, _) => {
let params = request::SignerLoadJade {
name: n.to_string(),
id: id.to_string(),
emulator: None, };
(serde_json::to_value(params)?, Method::SignerLoadJade)
}
AppSignerInner::AvailableSigner(a) => match a {
AnySigner::Software(a) => {
let params = request::SignerLoadSoftware {
name: n.to_string(),
mnemonic: a
.mnemonic()
.expect("we only create signers from mnemonic")
.to_string(),
persist: s.persist,
};
(serde_json::to_value(params)?, Method::SignerLoadSoftware)
}
AnySigner::Jade(_, id) => {
let params = request::SignerLoadJade {
name: n.to_string(),
id: id.to_string(),
emulator: None, };
(serde_json::to_value(params)?, Method::SignerLoadJade)
}
#[allow(unreachable_patterns)]
_ => todo!(),
},
AppSignerInner::ExternalSigner(f) => {
let params = request::SignerLoadExternal {
name: n.to_string(),
fingerprint: f.to_string(),
};
(serde_json::to_value(params)?, Method::SignerLoadExternal)
}
};
let r = Request {
jsonrpc: "2.0".into(),
id: None,
method: method.to_string(),
params: Some(params),
};
requests.push(r);
}
for (_, a) in self.assets.iter() {
if let Some(r) = a.request() {
requests.push(r);
}
}
Ok(requests)
}
pub fn get_available_signer(&mut self, name: &str) -> Result<&AnySigner, Error> {
let timeout = Some(self.config.timeout);
self.signers.get_available(name, timeout)
}
}