docs: translate all code comments to English (language policy)
Translate every Italian /// XML doc comment, <!-- --> XAML comment, and // inline comment to English across all source files (Core, App, tests). Add the language policy to CLAUDE.md (conversation Italian; all code and docs English). Update one test assertion that checked an Italian exception message that was also translated.
This commit is contained in:
@@ -3,17 +3,17 @@ using System.Globalization;
|
||||
namespace PalladiumWallet.Core.Wallet;
|
||||
|
||||
/// <summary>
|
||||
/// Conversione satoshi ↔ unità coin (8 decimali) per visualizzazione e input.
|
||||
/// Si lavora sempre in satoshi internamente; la stringa è solo presentazione.
|
||||
/// Satoshi ↔ coin unit conversion (8 decimal places) for display and input.
|
||||
/// All internal computation uses satoshis; the formatted string is presentation only.
|
||||
/// </summary>
|
||||
public static class CoinAmount
|
||||
{
|
||||
public const long SatsPerCoin = 100_000_000;
|
||||
|
||||
/// <summary>Unità di visualizzazione selezionabili (config §8).</summary>
|
||||
/// <summary>Selectable display units (config §8).</summary>
|
||||
public static readonly string[] Units = ["PLM", "mPLM", "µPLM", "sat"];
|
||||
|
||||
/// <summary>(satoshi per unità, decimali mostrati) di ciascuna unità.</summary>
|
||||
/// <summary>(satoshis per unit, displayed decimals) for each unit.</summary>
|
||||
private static (long Factor, int Decimals) Of(string unit) => unit switch
|
||||
{
|
||||
"PLM" => (SatsPerCoin, 8),
|
||||
@@ -27,7 +27,7 @@ public static class CoinAmount
|
||||
(sats / (decimal)SatsPerCoin).ToString("0.00000000", CultureInfo.InvariantCulture)
|
||||
+ (unit.Length > 0 ? " " + unit : "");
|
||||
|
||||
/// <summary>Formatta nell'unità scelta (es. 150000 sat → "1.50000 mPLM").</summary>
|
||||
/// <summary>Formats in the chosen unit (e.g. 150000 sat → "1.50000 mPLM").</summary>
|
||||
public static string FormatIn(long sats, string unit, bool withLabel = true)
|
||||
{
|
||||
var (factor, decimals) = Of(unit);
|
||||
@@ -36,7 +36,7 @@ public static class CoinAmount
|
||||
return withLabel ? $"{value} {unit}" : value;
|
||||
}
|
||||
|
||||
/// <summary>Parsa un importo espresso nell'unità scelta in satoshi.</summary>
|
||||
/// <summary>Parses an amount expressed in the chosen unit into satoshis.</summary>
|
||||
public static bool TryParseIn(string text, string unit, out long sats)
|
||||
{
|
||||
sats = 0;
|
||||
@@ -59,7 +59,7 @@ public static class CoinAmount
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Parsa un importo in coin (punto o virgola decimale) in satoshi.</summary>
|
||||
/// <summary>Parses a coin amount (decimal point or comma) into satoshis.</summary>
|
||||
public static bool TryParseCoins(string text, out long sats)
|
||||
{
|
||||
sats = 0;
|
||||
|
||||
@@ -6,7 +6,7 @@ using PalladiumWallet.Core.Storage;
|
||||
|
||||
namespace PalladiumWallet.Core.Wallet;
|
||||
|
||||
/// <summary>Esito della costruzione di una transazione.</summary>
|
||||
/// <summary>Result of a transaction build operation.</summary>
|
||||
public sealed class BuiltTransaction
|
||||
{
|
||||
public required Transaction Transaction { get; init; }
|
||||
@@ -14,7 +14,7 @@ public sealed class BuiltTransaction
|
||||
public required FeeRate FeeRate { get; init; }
|
||||
public required bool Signed { get; init; }
|
||||
|
||||
/// <summary>PSBT per i flussi watch-only/air-gapped/multisig (§6.5).</summary>
|
||||
/// <summary>PSBT for watch-only/air-gapped/multisig flows (§6.5).</summary>
|
||||
public required PSBT Psbt { get; init; }
|
||||
|
||||
public string ToHex() => Transaction.ToHex();
|
||||
@@ -22,25 +22,25 @@ public sealed class BuiltTransaction
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Costruzione e firma delle transazioni (blueprint §6) sopra le primitive
|
||||
/// NBitcoin: selezione monete (manuale o automatica), fee a rate fisso,
|
||||
/// invia-tutto con fee sottratta, change sulla catena interna, RBF di default.
|
||||
/// Con un account watch-only produce la PSBT non firmata (§6.5).
|
||||
/// Transaction construction and signing (blueprint §6) on top of NBitcoin primitives:
|
||||
/// coin selection (manual or automatic), fixed fee rate, send-all with fee subtracted,
|
||||
/// change on the internal chain, RBF on by default.
|
||||
/// With a watch-only account produces an unsigned PSBT (§6.5).
|
||||
/// </summary>
|
||||
public sealed class TransactionFactory(IWalletAccount account)
|
||||
{
|
||||
private Network Network => PalladiumNetworks.For(account.Profile.Kind);
|
||||
|
||||
/// <summary>
|
||||
/// Costruisce (e se possibile firma) una transazione.
|
||||
/// Builds (and signs if possible) a transaction.
|
||||
/// </summary>
|
||||
/// <param name="utxos">UTXO selezionati (coin control §6.2) o tutti quelli spendibili.</param>
|
||||
/// <param name="transactions">Tx di provenienza degli UTXO (txid → tx), dalla sincronizzazione.</param>
|
||||
/// <param name="destination">Indirizzo destinatario.</param>
|
||||
/// <param name="amountSats">Importo; ignorato se <paramref name="sendAll"/>.</param>
|
||||
/// <param name="feeRateSatPerVByte">Fee rate fisso in sat/vByte (§6.4).</param>
|
||||
/// <param name="changeIndex">Indice del prossimo indirizzo di change (catena interna).</param>
|
||||
/// <param name="sendAll">Invia tutto: fee sottratta dall'importo (§6.1).</param>
|
||||
/// <param name="utxos">Selected UTXOs (coin control §6.2) or all spendable ones.</param>
|
||||
/// <param name="transactions">Source transactions for the UTXOs (txid → tx), from sync.</param>
|
||||
/// <param name="destination">Recipient address.</param>
|
||||
/// <param name="amountSats">Amount; ignored when <paramref name="sendAll"/> is true.</param>
|
||||
/// <param name="feeRateSatPerVByte">Fixed fee rate in sat/vByte (§6.4).</param>
|
||||
/// <param name="changeIndex">Index of the next change address (internal chain).</param>
|
||||
/// <param name="sendAll">Send all: fee subtracted from the amount (§6.1).</param>
|
||||
public BuiltTransaction Build(
|
||||
IReadOnlyList<CachedUtxo> utxos,
|
||||
IReadOnlyDictionary<string, Transaction> transactions,
|
||||
@@ -50,15 +50,15 @@ public sealed class TransactionFactory(IWalletAccount account)
|
||||
int changeIndex,
|
||||
bool sendAll = false)
|
||||
{
|
||||
// Si spendono solo UTXO confermati: i fondi in mempool si vedono nel
|
||||
// saldo "in attesa" ma non sono spendibili finché non confermano.
|
||||
// Only confirmed UTXOs are spent: mempool funds appear in the "pending"
|
||||
// balance but are not spendable until confirmed.
|
||||
var spendable = utxos.Where(u => !u.Frozen && u.Height > 0).ToList();
|
||||
if (spendable.Count == 0)
|
||||
{
|
||||
var pending = utxos.Where(u => !u.Frozen && u.Height <= 0).Sum(u => u.ValueSats);
|
||||
throw new WalletSpendException(pending > 0
|
||||
? $"Nessun fondo confermato: {CoinAmount.Format(pending)} in attesa di conferma (non spendibile)."
|
||||
: "Nessun UTXO spendibile selezionato.");
|
||||
? $"No confirmed funds: {CoinAmount.Format(pending)} pending confirmation (not spendable)."
|
||||
: "No spendable UTXOs selected.");
|
||||
}
|
||||
|
||||
var coins = spendable.Select(u => new Coin(
|
||||
@@ -68,7 +68,7 @@ public sealed class TransactionFactory(IWalletAccount account)
|
||||
var feeRate = new FeeRate(Money.Satoshis(feeRateSatPerVByte * 1000m), 1000);
|
||||
var builder = Network.CreateTransactionBuilder();
|
||||
builder.SetVersion(2);
|
||||
// Sequence RBF per consentire il bump della fee (§6.6).
|
||||
// RBF sequence to allow fee bumping (§6.6).
|
||||
builder.OptInRBF = true;
|
||||
builder.AddCoins(coins);
|
||||
builder.SetChange(account.GetChangeAddress(changeIndex));
|
||||
@@ -94,14 +94,14 @@ public sealed class TransactionFactory(IWalletAccount account)
|
||||
}
|
||||
catch (NotEnoughFundsException ex)
|
||||
{
|
||||
throw new WalletSpendException($"Fondi insufficienti: {ex.Message}");
|
||||
throw new WalletSpendException($"Insufficient funds: {ex.Message}");
|
||||
}
|
||||
|
||||
if (!account.IsWatchOnly)
|
||||
{
|
||||
if (!builder.Verify(tx, out TransactionPolicyError[] errors))
|
||||
throw new WalletSpendException(
|
||||
"Transazione non valida: " + string.Join("; ", errors.Select(e => e.ToString())));
|
||||
"Invalid transaction: " + string.Join("; ", errors.Select(e => e.ToString())));
|
||||
}
|
||||
|
||||
return new BuiltTransaction
|
||||
@@ -123,5 +123,5 @@ public sealed class TransactionFactory(IWalletAccount account)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Errore di costruzione/firma della spesa (fondi, policy, parametri).</summary>
|
||||
/// <summary>Error during transaction construction/signing (funds, policy, parameters).</summary>
|
||||
public sealed class WalletSpendException(string message) : Exception(message);
|
||||
|
||||
@@ -4,36 +4,36 @@ using PalladiumWallet.Core.Spv;
|
||||
|
||||
namespace PalladiumWallet.Core.Wallet;
|
||||
|
||||
/// <summary>Un input di una transazione, con l'output speso risolto dal server.</summary>
|
||||
/// <summary>An input of a transaction, with the spent output resolved from the server.</summary>
|
||||
public sealed record TxInputInfo(
|
||||
string PrevTxid, uint PrevIndex, long? AmountSats, string? Address, bool IsMine, bool IsCoinbase);
|
||||
|
||||
/// <summary>Un output di una transazione.</summary>
|
||||
/// <summary>An output of a transaction.</summary>
|
||||
public sealed record TxOutputInfo(
|
||||
uint Index, long AmountSats, string? Address, string ScriptType, bool IsMine);
|
||||
|
||||
/// <summary>
|
||||
/// Dati completi di una transazione, assemblati interrogando il server: la tx
|
||||
/// grezza più gli output spesi dagli input (per ricavare importi, indirizzi e
|
||||
/// fee) e l'header del blocco (per la data). Tutto ciò che il protocollo
|
||||
/// ElectrumX-like (§10) permette di sapere su una transazione.
|
||||
/// Complete transaction data assembled by querying the server: the raw transaction
|
||||
/// plus the outputs spent by each input (to derive amounts, addresses, and fees)
|
||||
/// and the block header (for the timestamp). Everything the ElectrumX-like protocol
|
||||
/// (§10) can tell us about a transaction.
|
||||
/// </summary>
|
||||
public sealed class TransactionDetails
|
||||
{
|
||||
public required string Txid { get; init; }
|
||||
/// <summary>Altezza del blocco; ≤0 = ancora in mempool.</summary>
|
||||
/// <summary>Block height; ≤0 = still in mempool.</summary>
|
||||
public required int Height { get; init; }
|
||||
public required int Confirmations { get; init; }
|
||||
/// <summary>Effetto netto sul saldo del wallet (delta calcolato in sincronizzazione).</summary>
|
||||
/// <summary>Net effect on the wallet balance (delta computed during sync).</summary>
|
||||
public required long NetSats { get; init; }
|
||||
/// <summary>Fee della transazione; null se un input ha importo non risolvibile (es. coinbase).</summary>
|
||||
/// <summary>Transaction fee; null if any input amount cannot be resolved (e.g. coinbase).</summary>
|
||||
public required long? FeeSats { get; init; }
|
||||
public required int TotalSize { get; init; }
|
||||
public required int VirtualSize { get; init; }
|
||||
public required uint Version { get; init; }
|
||||
public required uint LockTime { get; init; }
|
||||
public required bool RbfSignaled { get; init; }
|
||||
/// <summary>Merkle proof verificata in sincronizzazione (§7.4).</summary>
|
||||
/// <summary>Merkle proof verified during sync (§7.4).</summary>
|
||||
public required bool Verified { get; init; }
|
||||
public required DateTimeOffset? BlockTime { get; init; }
|
||||
public required long TotalOutSats { get; init; }
|
||||
@@ -43,13 +43,13 @@ public sealed class TransactionDetails
|
||||
|
||||
public bool IsCoinbase => Inputs.Count > 0 && Inputs[0].IsCoinbase;
|
||||
public bool IsIncoming => NetSats >= 0;
|
||||
/// <summary>Importo verso destinatari esterni (output non nostri): l'importo "inviato".</summary>
|
||||
/// <summary>Amount sent to external recipients (outputs not ours): the "sent" amount.</summary>
|
||||
public long SentToOthersSats => Outputs.Where(o => !o.IsMine).Sum(o => o.AmountSats);
|
||||
public long ReceivedSats => Outputs.Where(o => o.IsMine).Sum(o => o.AmountSats);
|
||||
public double? FeeRateSatPerVb => FeeSats is { } f && VirtualSize > 0 ? (double)f / VirtualSize : null;
|
||||
/// <summary>
|
||||
/// Indirizzi della controparte: i destinatari esterni per un invio (output non
|
||||
/// nostri), i mittenti esterni per una ricezione (input non nostri).
|
||||
/// Counterparty addresses: external recipients for a send (outputs not ours),
|
||||
/// external senders for a receive (inputs not ours).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> CounterpartyAddresses => IsIncoming
|
||||
? [.. Inputs.Where(i => !i.IsMine && i.Address is not null).Select(i => i.Address!).Distinct()]
|
||||
@@ -57,9 +57,9 @@ public sealed class TransactionDetails
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recupera dal server tutti i dati di una singola transazione (blueprint §10):
|
||||
/// la transazione grezza e gli output spesi dai suoi input, per ricostruire
|
||||
/// importi, fee e indirizzi che il server non riassume.
|
||||
/// Fetches all data for a single transaction from the server (blueprint §10):
|
||||
/// the raw transaction and the outputs spent by its inputs, to reconstruct
|
||||
/// amounts, fees, and addresses that the server does not summarise.
|
||||
/// </summary>
|
||||
public static class TransactionInspector
|
||||
{
|
||||
@@ -112,10 +112,10 @@ public static class TransactionInspector
|
||||
|
||||
var rbf = tx.Inputs.Any(i => i.Sequence.IsRBF);
|
||||
|
||||
// Le transazioni degli input servono per importi/indirizzi/fee. Si
|
||||
// scaricano in parallelo (id univoci, richieste concorrenti supportate
|
||||
// da ElectrumClient): in sequenza la finestra impiegava un round-trip
|
||||
// per input. Anche l'header del blocco è recuperato in parallelo.
|
||||
// Input transactions are needed for amounts/addresses/fees. Fetched in
|
||||
// parallel (unique ids, concurrent requests supported by ElectrumClient):
|
||||
// sequential fetching costs one round-trip per input. The block header
|
||||
// is also fetched in parallel.
|
||||
var prevTxids = tx.IsCoinBase
|
||||
? []
|
||||
: tx.Inputs.Select(i => i.PrevOut.Hash.ToString()).Distinct().ToList();
|
||||
|
||||
@@ -6,8 +6,8 @@ using PalladiumWallet.Core.Storage;
|
||||
namespace PalladiumWallet.Core.Wallet;
|
||||
|
||||
/// <summary>
|
||||
/// Factory dei tipi di wallet (blueprint §4.5): dal documento ricostruisce il tipo
|
||||
/// di account corretto (HD da seed, HD da xprv importata, importato WIF, watch-only).
|
||||
/// Wallet type factory (blueprint §4.5): reconstructs the correct account type
|
||||
/// from a document (HD from seed, HD from imported xprv, imported WIF, watch-only).
|
||||
/// </summary>
|
||||
public static class WalletLoader
|
||||
{
|
||||
@@ -20,25 +20,25 @@ public static class WalletLoader
|
||||
var kind = Enum.Parse<ScriptKind>(doc.ScriptKind);
|
||||
var network = PalladiumNetworks.For(profile.Kind);
|
||||
|
||||
// 1. HD da seed (caso più comune)
|
||||
// 1. HD from seed (most common case)
|
||||
if (doc.Mnemonic is { } words)
|
||||
{
|
||||
if (!Bip39.TryParse(words, out var mnemonic))
|
||||
throw new InvalidDataException("Mnemonica del file wallet non valida.");
|
||||
throw new InvalidDataException("Invalid mnemonic in wallet file.");
|
||||
var path = KeyPath.Parse(doc.AccountPath ?? DerivationPaths.AccountPath(kind, profile).ToString());
|
||||
return HdAccount.FromSeed(Bip39.ToSeed(mnemonic!, doc.Passphrase), kind, profile, path);
|
||||
}
|
||||
|
||||
// 2. HD da xprv importata (spendibile, senza seed)
|
||||
// 2. HD from imported xprv (spendable, no seed)
|
||||
if (doc.AccountXprv is { } xprvStr)
|
||||
{
|
||||
if (!Slip132.TryDecodePrivate(xprvStr, profile, out var xprv, out _))
|
||||
throw new InvalidDataException("Xprv del file wallet non valida per questa rete.");
|
||||
throw new InvalidDataException("Xprv in wallet file is invalid for this network.");
|
||||
var path = doc.AccountPath is { Length: > 0 } p ? KeyPath.Parse(p) : null;
|
||||
return HdAccount.FromAccountXprv(xprv!, kind, profile, path);
|
||||
}
|
||||
|
||||
// 3. Chiavi WIF importate
|
||||
// 3. Imported WIF keys
|
||||
if (doc.WifKeys is { Count: > 0 } wifKeys)
|
||||
{
|
||||
var entries = wifKeys.Select(wif =>
|
||||
@@ -51,21 +51,21 @@ public static class WalletLoader
|
||||
return new ImportedKeyAccount(entries, kind, profile);
|
||||
}
|
||||
|
||||
// 4. Watch-only da xpub
|
||||
// 4. Watch-only from xpub
|
||||
if (doc.AccountXpub is null)
|
||||
throw new InvalidDataException("File wallet senza xpub e senza seed.");
|
||||
throw new InvalidDataException("Wallet file has no xpub and no seed.");
|
||||
if (!Slip132.TryDecodePublic(doc.AccountXpub, profile, out var xpub, out _))
|
||||
throw new InvalidDataException("Xpub del file wallet non valida per questa rete.");
|
||||
throw new InvalidDataException("Xpub in wallet file is invalid for this network.");
|
||||
var accountPath = doc.AccountPath is { Length: > 0 } ap ? KeyPath.Parse(ap) : null;
|
||||
return HdAccount.FromAccountXpub(xpub!, kind, profile, accountPath);
|
||||
}
|
||||
|
||||
/// <summary>Crea il documento per un nuovo wallet da seed.</summary>
|
||||
/// <summary>Creates the document for a new seed wallet.</summary>
|
||||
public static (WalletDocument Doc, HdAccount Account) NewFromMnemonic(
|
||||
string words, string? passphrase, ScriptKind kind, ChainProfile profile, KeyPath? customPath = null)
|
||||
{
|
||||
if (!Bip39.TryParse(words, out var mnemonic))
|
||||
throw new InvalidDataException("Mnemonica non valida (parole o checksum errati).");
|
||||
throw new InvalidDataException("Invalid mnemonic (wrong words or checksum).");
|
||||
|
||||
var account = customPath is null
|
||||
? HdAccount.FromMnemonic(mnemonic!, passphrase, kind, profile)
|
||||
@@ -85,15 +85,15 @@ public static class WalletLoader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea il documento da una xpub SLIP-132 importata (watch-only).
|
||||
/// Rileva il ScriptKind dagli header SLIP-132; <paramref name="kindOverride"/> lo sovrascrive
|
||||
/// per i prefissi ambigui (xpub può essere Legacy o Taproot).
|
||||
/// Creates the document from an imported SLIP-132 xpub (watch-only).
|
||||
/// Detects the ScriptKind from the SLIP-132 header; <paramref name="kindOverride"/> overrides
|
||||
/// it for ambiguous prefixes (xpub can be Legacy or Taproot).
|
||||
/// </summary>
|
||||
public static (WalletDocument Doc, HdAccount Account) NewFromXpub(
|
||||
string slip132, ChainProfile profile, ScriptKind? kindOverride = null)
|
||||
{
|
||||
if (!Slip132.TryDecodePublic(slip132.Trim(), profile, out var xpub, out var detectedKind))
|
||||
throw new InvalidDataException("Chiave pubblica estesa non valida o non riconosciuta per questa rete.");
|
||||
throw new InvalidDataException("Extended public key is invalid or unrecognised for this network.");
|
||||
var kind = kindOverride ?? detectedKind;
|
||||
var account = HdAccount.FromAccountXpub(xpub!, kind, profile);
|
||||
|
||||
@@ -108,14 +108,14 @@ public static class WalletLoader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea il documento da una xprv SLIP-132 importata (spendibile senza seed).
|
||||
/// Il documento contiene la xprv in chiaro: deve essere obbligatoriamente cifrato.
|
||||
/// Creates the document from an imported SLIP-132 xprv (spendable without seed).
|
||||
/// The document contains the xprv in plaintext — it must be encrypted.
|
||||
/// </summary>
|
||||
public static (WalletDocument Doc, HdAccount Account) NewFromXprv(
|
||||
string slip132, ChainProfile profile, ScriptKind? kindOverride = null)
|
||||
{
|
||||
if (!Slip132.TryDecodePrivate(slip132.Trim(), profile, out var xprv, out var detectedKind))
|
||||
throw new InvalidDataException("Chiave privata estesa non valida o non riconosciuta per questa rete.");
|
||||
throw new InvalidDataException("Extended private key is invalid or unrecognised for this network.");
|
||||
var kind = kindOverride ?? detectedKind;
|
||||
var account = HdAccount.FromAccountXprv(xprv!, kind, profile);
|
||||
|
||||
@@ -131,14 +131,14 @@ public static class WalletLoader
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crea il documento da una o più chiavi WIF importate.
|
||||
/// Il documento contiene le chiavi WIF in chiaro: deve essere obbligatoriamente cifrato.
|
||||
/// Creates the document from one or more imported WIF keys.
|
||||
/// The document contains the WIF keys in plaintext — it must be encrypted.
|
||||
/// </summary>
|
||||
public static (WalletDocument Doc, ImportedKeyAccount Account) NewFromWif(
|
||||
IReadOnlyList<string> wifKeys, ScriptKind kind, ChainProfile profile)
|
||||
{
|
||||
if (wifKeys.Count == 0)
|
||||
throw new InvalidDataException("Almeno una chiave WIF richiesta.");
|
||||
throw new InvalidDataException("At least one WIF key is required.");
|
||||
|
||||
var network = PalladiumNetworks.For(profile.Kind);
|
||||
var entries = new List<(BitcoinAddress, Key?)>();
|
||||
@@ -153,7 +153,7 @@ public static class WalletLoader
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidDataException($"Chiave WIF non valida: {ex.Message}");
|
||||
throw new InvalidDataException($"Invalid WIF key: {ex.Message}");
|
||||
}
|
||||
var addr = secret.PrivateKey.PubKey
|
||||
.GetAddress(DerivationPaths.ScriptPubKeyTypeFor(kind), network);
|
||||
|
||||
Reference in New Issue
Block a user