using System.Text.Json; using System.Text.Json.Serialization; namespace PalladiumWallet.Core.Storage; /// /// Schema del file wallet (blueprint §8), versionato per consentire migrazioni /// automatiche all'apertura. La cifratura (quando c'è una password) avvolge /// l'intero documento via . /// public sealed class WalletDocument { public const int CurrentVersion = 1; public int Version { get; set; } = CurrentVersion; /// Nome rete (mainnet/testnet/regtest), come ChainProfile.NetName. public required string Network { get; set; } /// Tipo di script (nome dell'enum ScriptKind). public required string ScriptKind { get; set; } /// Mnemonica BIP39 in chiaro nel documento (il documento va cifrato!); null se watch-only. public string? Mnemonic { get; set; } /// Extension word BIP39 (§4.1); null se assente. public string? Passphrase { get; set; } /// Path di account (es. "84'/746'/0'"). public required string AccountPath { get; set; } /// Xpub di account in SLIP-132: basta da sola per il watch-only. public required string AccountXpub { get; set; } public string? MasterFingerprint { get; set; } /// Gap limit per la scansione indirizzi (§5), configurabile. public int GapLimit { get; set; } = 20; /// Etichette per indirizzo/txid (§12). public Dictionary Labels { get; set; } = []; /// Cache dell'ultimo stato sincronizzato (saldo/storico mostrabili offline). public SyncCache? Cache { get; set; } public bool IsWatchOnly => Mnemonic is null; private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, }; public string ToJson() => JsonSerializer.Serialize(this, JsonOptions); public static WalletDocument FromJson(string json) { var doc = JsonSerializer.Deserialize(json, JsonOptions) ?? throw new InvalidDataException("File wallet non valido."); return Migrate(doc); } /// Migrazioni di schema all'apertura (§8). Per ora esiste solo la v1. private static WalletDocument Migrate(WalletDocument doc) => doc.Version switch { CurrentVersion => doc, _ => throw new InvalidDataException( $"Versione file wallet {doc.Version} non supportata (max {CurrentVersion})."), }; } /// Stato sincronizzato persistito: permette di mostrare saldo/storico offline. public sealed class SyncCache { public int TipHeight { get; set; } public long ConfirmedSats { get; set; } public long UnconfirmedSats { get; set; } public int NextReceiveIndex { get; set; } public int NextChangeIndex { get; set; } public List History { get; set; } = []; public List Utxos { get; set; } = []; public List Addresses { get; set; } = []; } /// Indirizzo scansionato con saldo proprio e numero di transazioni (vista indirizzi). public sealed class CachedAddress { public required string Address { get; set; } public bool IsChange { get; set; } public int Index { get; set; } public long BalanceSats { get; set; } public int TxCount { get; set; } } public sealed class CachedTx { public required string Txid { get; set; } public int Height { get; set; } public long DeltaSats { get; set; } public bool Verified { get; set; } } public sealed class CachedUtxo { public required string Txid { get; set; } public int Vout { get; set; } public long ValueSats { get; set; } public required string Address { get; set; } public bool IsChange { get; set; } public int AddressIndex { get; set; } public int Height { get; set; } public bool Frozen { get; set; } }