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; }
}