niko_trust_gui/src/book.rs
Niko Marmeladkov 6278321873
niko_trust_gui: TCE protocol client with iced GUI
- Byte-exact TCE codec (encoder/decoder, canonical numbers, vectors-tested)
- Ed25519 identities with bech32m trust1… addresses
- Relay client: challenge/auth handshake, request feed, responses,
  transparent re-auth on 401
- iced GUI (Material-dark, Solana-style palette): tab shell, approval
  prompts with native toasts on Linux, address book, history, settings,
  password change
- Portable single-file vault (identity + book + history + settings)
- Live 2FA roundtrip examples (send_2fa, await_2fa)
2026-08-22 23:20:41 +03:00

83 lines
2.5 KiB
Rust

//! Local address book: memorable labels for trust1… addresses.
//!
//! Entries map an address string to a free-form label ("Niko"); persistence
//! lives in the vault file. The display helper renders known addresses as
//! `Label (trust1qz8jr…7tx6w)` and unknown ones as just the shortened address.
use std::collections::BTreeMap;
#[derive(Debug, Default)]
pub struct AddressBook {
entries: BTreeMap<String, String>,
}
impl AddressBook {
pub fn from_entries(entries: BTreeMap<String, String>) -> AddressBook {
AddressBook { entries }
}
pub fn entries(&self) -> &BTreeMap<String, String> {
&self.entries
}
pub fn label_of(&self, addr: &str) -> Option<&str> {
self.entries.get(addr).map(|s| s.as_str())
}
/// Sets (or clears, when `label` is blank) the label for an address.
/// Returns true when the map changed.
pub fn set_label(&mut self, addr: &str, label: &str) -> bool {
let label = label.trim();
if label.is_empty() {
self.entries.remove(addr).is_some()
} else {
match self.entries.get(addr) {
Some(cur) if cur == label => false,
_ => {
self.entries.insert(addr.to_string(), label.to_string());
true
}
}
}
}
/// Compact display form: `Niko (trust1qz8jr…7tx6w)` when known, otherwise
/// just the shortened address.
pub fn display(&self, addr: &str) -> String {
match self.label_of(addr) {
Some(l) => format!("{l} ({})", short_addr(addr)),
None => short_addr(addr),
}
}
}
/// Bech32 addresses are long; keep a compact form for the UI.
pub fn short_addr(a: &str) -> String {
if a.len() <= 20 {
a.to_string()
} else {
format!("{}{}", &a[..10], &a[a.len() - 6..])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_known_and_unknown() {
let mut b = AddressBook::default();
let addr = "trust1qz8jrkwdnqscwrvptu69z6ntd2dp092s0fk25ayq94qlzsx649dszg7tx6w";
assert_eq!(b.display(addr), short_addr(addr));
assert!(b.set_label(addr, "Niko"));
assert_eq!(b.display(addr), format!("Niko ({})", short_addr(addr)));
assert_eq!(b.label_of(addr), Some("Niko"));
// Blank label clears the entry.
b.set_label(addr, " ");
assert_eq!(b.display(addr), short_addr(addr));
// Clearing a missing entry reports no change.
assert!(!b.set_label(addr, ""));
}
}