export: implement export labels using BIP-0329

This commit is contained in:
pythcoiner 2025-03-10 16:08:09 +01:00
parent 38eed1b881
commit 86313f5282
No known key found for this signature in database
GPG Key ID: C1048AEEDF303B88
5 changed files with 88 additions and 6 deletions

View File

@ -40,9 +40,9 @@ impl ExportModal {
}
pub fn default_filename(&self) -> String {
let date = chrono::Local::now().format("%Y-%m-%dT%H-%M-%S");
match &self.import_export_type {
ImportExportType::Transactions => {
let date = chrono::Local::now().format("%Y-%m-%dT%H-%M-%S");
format!("liana-txs-{date}.csv")
}
ImportExportType::ExportPsbt(_) => "psbt.psbt".into(),
@ -57,6 +57,7 @@ impl ExportModal {
}
ImportExportType::ImportPsbt => "psbt.psbt".into(),
ImportExportType::ImportDescriptor => "descriptor.descriptor".into(),
ImportExportType::ExportLabels => format!("liana-labels-{date}.csv"),
}
}

View File

@ -5,6 +5,7 @@ use std::path::Path;
use async_trait::async_trait;
use lianad::bip329::Labels;
use lianad::commands::GetLabelsBip329Result;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use serde_json::json;
@ -207,8 +208,10 @@ impl<C: Client + Send + Sync + Debug> Daemon for Lianad<C> {
Ok(())
}
async fn get_labels_bip329(&self, _offset: u32, _limit: u32) -> Result<Labels, DaemonError> {
todo!()
async fn get_labels_bip329(&self, offset: u32, limit: u32) -> Result<Labels, DaemonError> {
let res: GetLabelsBip329Result =
self.call("getlabelsbip329", Some(vec![json!(offset), json!(limit)]))?;
Ok(res.labels)
}
}

View File

@ -16,6 +16,7 @@ use liana::{
descriptors::LianaDescriptor,
miniscript::bitcoin::{Amount, Psbt, Txid},
};
use lianad::bip329::{error::ExportError, Labels};
use tokio::{
task::{JoinError, JoinHandle},
time::sleep,
@ -32,6 +33,8 @@ use crate::{
lianalite::client::backend::api::DEFAULT_LIMIT,
};
const DUMP_LABELS_LIMIT: u32 = 100;
macro_rules! send_error {
($sender:ident, $error:ident) => {
if let Err(e) = $sender.send(Progress::Error(Error::$error)) {
@ -125,6 +128,7 @@ pub enum Error {
DaemonMissing,
ParsePsbt,
ParseDescriptor,
Bip329Export(String),
}
#[derive(Debug, PartialEq, Clone)]
@ -132,6 +136,7 @@ pub enum ImportExportType {
Transactions,
ExportPsbt(String),
Descriptor(LianaDescriptor),
ExportLabels,
ImportPsbt,
ImportDescriptor,
}
@ -154,6 +159,12 @@ impl From<DaemonError> for Error {
}
}
impl From<ExportError> for Error {
fn from(value: ExportError) -> Self {
Error::Bip329Export(format!("{:?}", value))
}
}
#[derive(Debug)]
pub enum Status {
Init,
@ -209,6 +220,7 @@ impl Export {
ImportExportType::Transactions => export_transactions(sender, daemon, path).await,
ImportExportType::ExportPsbt(psbt) => export_psbt(sender, path, psbt),
ImportExportType::Descriptor(descriptor) => export_descriptor(sender, path, descriptor),
ImportExportType::ExportLabels => export_labels(sender, daemon, path).await,
ImportExportType::ImportPsbt => import_psbt(sender, path),
ImportExportType::ImportDescriptor => import_descriptor(sender, path),
};
@ -538,6 +550,54 @@ pub fn import_descriptor(sender: Sender<Progress>, path: PathBuf) {
send_progress!(sender, Descriptor(descriptor));
}
pub async fn export_labels(
sender: Sender<Progress>,
daemon: Option<Arc<dyn Daemon + Sync + Send>>,
path: PathBuf,
) {
let daemon = match daemon {
Some(d) => d,
None => {
send_error!(sender, Error::DaemonMissing);
return;
}
};
let mut labels = Labels::new(Vec::new());
let mut offset = 0u32;
loop {
let mut fetched = match daemon.get_labels_bip329(offset, DUMP_LABELS_LIMIT).await {
Ok(l) => l,
Err(e) => {
send_error!(sender, e.into());
return;
}
}
.into_vec();
let fetch_len = fetched.len() as u32;
labels.append(&mut fetched);
if fetch_len < DUMP_LABELS_LIMIT {
break;
} else {
offset += DUMP_LABELS_LIMIT;
}
}
let json = match labels.export() {
Ok(j) => j,
Err(e) => {
send_error!(sender, e.into());
return;
}
};
let mut file = open_file!(path, sender);
if let Err(e) = file.write_all(json.as_bytes()) {
send_error!(sender, e.into());
return;
}
send_progress!(sender, Progress(100.0));
send_progress!(sender, Ended);
}
pub async fn get_path(filename: String) -> Option<PathBuf> {
rfd::AsyncFileDialog::new()
.set_title("Choose a location to export...")

View File

@ -333,6 +333,11 @@ pub struct ListPsbts {
pub psbts: Vec<Psbt>,
}
#[derive(Deserialize)]
pub struct Labels {
pub labels: lianad::bip329::Labels,
}
#[derive(Deserialize)]
pub struct Address {
#[serde(deserialize_with = "deser_addr_assume_checked")]

View File

@ -1117,9 +1117,22 @@ impl Daemon for BackendWalletClient {
Ok(())
}
async fn get_labels_bip329(&self, _offset: u32, _limit: u32) -> Result<Labels, DaemonError> {
// TODO: add an endpoint on backend
todo!()
async fn get_labels_bip329(&self, offset: u32, limit: u32) -> Result<Labels, DaemonError> {
let response: Response = self
.inner
.request(
Method::GET,
&format!(
"{}/v1/wallets/{}/labels/bip329?offset={}&limit={}",
self.inner.url, self.wallet_uuid, offset, limit
),
)
.await
.send()
.await?;
let res: api::Labels = response.json().await?;
Ok(res.labels)
}
}