use rusqlite::params;
use serde::{Deserialize, Serialize};
use super::keychain::MAX_LABELED_WALLETS;
use crate::error::{Error, Result};
use super::schema::canonical_label;
use super::WalletCore;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LabeledWallet {
pub family: String,
pub label: String,
pub slot_index: u32,
pub db_filename: String,
pub descriptor: String,
}
impl WalletCore {
pub fn derive_webcash_secret_for_label(&self, label: &str) -> Result<(String, u32)> {
let index = self.resolve_or_create_wallet_slot("webcash", label)?;
let secret = self.derive_slot_hex("webcash", index)?;
Ok((secret, index))
}
pub fn derive_bitcoin_secret_for_label(&self, label: &str) -> Result<(String, u32)> {
let index = self.resolve_or_create_wallet_slot("bitcoin", label)?;
let secret = self.derive_slot_hex("bitcoin", index)?;
Ok((secret, index))
}
pub fn derive_voucher_secret_for_label(&self, label: &str) -> Result<(String, u32)> {
let index = self.resolve_or_create_wallet_slot("voucher", label)?;
let secret = self.derive_slot_hex("voucher", index)?;
Ok((secret, index))
}
pub fn derive_rgb_secret_for_label(&self, label: &str) -> Result<(String, u32)> {
let index = self.resolve_or_create_wallet_slot("rgb", label)?;
let secret = self.derive_slot_hex("rgb", index)?;
Ok((secret, index))
}
pub fn list_labeled_wallets(&self, family: &str) -> Result<Vec<LabeledWallet>> {
let mut stmt = self.master_conn.prepare(
"SELECT family, slot_index, descriptor, db_rel_path, label
FROM wallet_slots
WHERE family = ?1
ORDER BY slot_index ASC",
)?;
let rows = stmt.query_map(params![family], |row| {
let family: String = row.get(0)?;
let slot_index: u32 = row.get(1)?;
let descriptor: String = row.get(2)?;
let db_rel_path: Option<String> = row.get(3)?;
let label: Option<String> = row.get(4)?;
let label = label.unwrap_or_else(|| {
if slot_index == 0 {
"main".to_string()
} else {
format!("{family}-{slot_index}")
}
});
let db_filename = db_rel_path.unwrap_or_else(|| format!("{}_{}.db", label, family));
Ok(LabeledWallet {
family,
label,
slot_index,
db_filename,
descriptor,
})
})?;
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Error::Storage)
}
pub fn wallet_db_filename(family: &str, label: &str) -> String {
format!("{}_{}.db", label, family)
}
fn resolve_or_create_wallet_slot(&self, family: &str, label: &str) -> Result<u32> {
let canonical = canonical_label(label)?;
let mut stmt = self.master_conn.prepare(
"SELECT slot_index FROM wallet_slots WHERE family = ?1 AND label = ?2 LIMIT 1",
)?;
let mut rows = stmt.query(params![family, canonical])?;
if let Some(row) = rows.next()? {
let index: u32 = row.get(0)?;
return Ok(index);
}
drop(rows);
drop(stmt);
if canonical == "main" {
self.register_wallet_slot(family, 0, "main")?;
return Ok(0);
}
let mut next_stmt = self
.master_conn
.prepare("SELECT COALESCE(MAX(slot_index), -1) FROM wallet_slots WHERE family = ?1")?;
let max_idx: i64 = next_stmt.query_row(params![family], |row| row.get(0))?;
let next = (max_idx + 1).max(1) as u32; if next >= MAX_LABELED_WALLETS {
return Err(Error::Other(anyhow::anyhow!(
"too many {family} wallets (max {})",
MAX_LABELED_WALLETS - 1
)));
}
self.register_wallet_slot(family, next, &canonical)?;
Ok(next)
}
fn register_wallet_slot(&self, family: &str, index: u32, label: &str) -> Result<()> {
let descriptor = self.derive_slot_hex(family, index)?;
let db_filename = Self::wallet_db_filename(family, label);
let now = chrono::Utc::now().to_rfc3339();
self.master_conn.execute(
"INSERT OR REPLACE INTO wallet_slots (family, slot_index, descriptor, db_rel_path, label, created_at, updated_at)
VALUES (?1, ?2, ?3, ?4, ?5, COALESCE((SELECT created_at FROM wallet_slots WHERE family=?1 AND slot_index=?2), ?6), ?6)",
params![family, i64::from(index), descriptor, db_filename, label, now],
)?;
Ok(())
}
}