use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use super::Wallet;
use crate::error::Result;
#[derive(Serialize, Deserialize, Debug)]
pub struct WalletSnapshot {
pub master_secret: String,
pub unspent_outputs: Vec<UnspentOutputSnapshot>,
pub spent_hashes: Vec<SpentHashSnapshot>,
pub depths: HashMap<String, i64>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct UnspentOutputSnapshot {
pub secret: String,
pub amount: i64,
pub created_at: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct SpentHashSnapshot {
pub hash: String,
pub spent_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct WalletExport {
pub version: String,
pub metadata: HashMap<String, String>,
pub outputs: Vec<(String, i64, String, i32)>,
pub spent_hashes: Vec<(Vec<u8>, String)>,
pub exported_at: String,
}
impl Wallet {
pub fn export_snapshot(&self) -> Result<WalletSnapshot> {
let master_secret = self.store.get_meta("master_secret")?.unwrap_or_default();
let unspent = self.store.get_unspent_full()?
.into_iter()
.map(|(secret, amount, created_at)| UnspentOutputSnapshot { secret, amount, created_at })
.collect();
let spent = self.store.get_spent_hashes_with_time()?
.into_iter()
.map(|(hash_blob, spent_at)| SpentHashSnapshot {
hash: hex::encode(hash_blob),
spent_at,
})
.collect();
let depths = self.store.get_all_depths()?
.into_iter()
.map(|(k, v)| (k, v as i64))
.collect();
Ok(WalletSnapshot { master_secret, unspent_outputs: unspent, spent_hashes: spent, depths })
}
pub fn import_snapshot(&self, snapshot: &WalletSnapshot) -> Result<()> {
self.store.clear_all()?;
self.store.set_meta("master_secret", &snapshot.master_secret)?;
for (code, depth) in &snapshot.depths {
self.store.set_depth(code, *depth as u64)?;
}
for item in &snapshot.unspent_outputs {
let secret_hash = crate::crypto::sha256(item.secret.as_bytes());
self.store.insert_output(&secret_hash, &item.secret, item.amount)?;
}
for item in &snapshot.spent_hashes {
let hash_bytes = hex::decode(&item.hash)
.map_err(|_| crate::error::Error::wallet("Invalid hex in snapshot"))?;
self.store.insert_spent_hash(&hash_bytes)?;
}
Ok(())
}
pub(crate) async fn export_wallet_data(&self) -> Result<Vec<u8>> {
let metadata = self.store.get_all_meta()?;
let outputs = self.store.get_all_outputs()?;
let spent_hashes = self.store.get_spent_hashes_with_time()?;
let wallet_export = WalletExport {
version: "1.0".to_string(),
metadata,
outputs,
spent_hashes,
exported_at: chrono::Utc::now().to_rfc3339(),
};
serde_json::to_vec(&wallet_export)
.map_err(|e| crate::error::Error::wallet(format!("Failed to serialize wallet data: {}", e)))
}
pub(crate) async fn import_wallet_data(&self, data: &[u8]) -> Result<()> {
let wallet_export: WalletExport = serde_json::from_slice(data)
.map_err(|e| crate::error::Error::wallet(format!("Failed to deserialize wallet data: {}", e)))?;
self.store.clear_all()?;
for (key, value) in wallet_export.metadata {
self.store.set_meta(&key, &value)?;
}
for (secret, amount, _created_at, _spent) in wallet_export.outputs {
let secret_hash = crate::crypto::sha256(secret.as_bytes());
self.store.insert_output(&secret_hash, &secret, amount)?;
}
for (hash, _spent_at) in wallet_export.spent_hashes {
self.store.insert_spent_hash(&hash)?;
}
log::info!("Wallet data imported from encrypted backup");
Ok(())
}
}