Technical changelog covering the full history from the initial commit,
grouped by subsystem (Core/Chain, Crypto, Net, Spv, Storage, Wallet, App UI,
Android, CLI, Build, Testing, Docs) rather than raw commit order, since this
is the first release.
build_android now requires the keystore generated by the previous commit,
prompts for its passwords at build time, and signs the APK with it instead
of an ephemeral debug key auto-generated per container run — that was the
actual cause of every release needing a manual uninstall to update. Also
derives versionCode from <Version> instead of leaving it fixed at 1, so
version ordering stays monotonic across releases.
Docs (docker/README.md, CLAUDE.md) updated to match the new signing flow.
An Android APK's signature identifies the app to the OS; a new build must
carry the same one as the last install or Android refuses it as an update.
generate-keystore.sh creates the keystore once, interactively, and refuses
to run again once one exists to avoid ever changing it by accident. The
resulting file is git-ignored — it's a secret that must live outside the
repo, backed up separately.
Compares the running assembly version against the latest GitHub release
tag on every app launch (desktop and Android) and shows an in-app
overlay with the new version tag when one is available. Best-effort:
network/parse failures are silent, matching the app's existing
non-blocking startup checks.
Add a feature request template and a config.yml (blank issues disabled,
security reports routed to SECURITY.md) alongside the existing bug report
template, plus a PR template with a checklist matching the project's
security rules. Point the app's "Report a bug" button at the new GitHub
template now that the repo is hosted there instead of the old self-hosted
tracker.
Previously the server settings overlay had only "Connect and sync" and
"Reset certs" — peer discovery had no button and only ran implicitly
when reopening the dialog while connected. Add a dedicated "Sincronizza"
button wired to DiscoverServersCommand, and make it work even without
an active connection by opening a short-lived connection to a candidate
server just to query its peer list, then closing it without touching
the wallet's connection state.
Extract confirmation-threshold logic into UtxoSpendability (shared by
TransactionFactory and WalletSynchronizer) and use it to compute
ImmatureSats, so coinbase/under-confirmed amounts are shown separately
from spendable balance in the GUI and CLI instead of being lumped into
"confirmed".
Coinbase outputs require COINBASE_MATURITY + 1 = 121 confirmations before
they can be spent. The consensus rule (palladiumcore tx_verify.cpp) is
nSpendHeight - nHeight >= 120; the +1 adds one block of safety margin,
matching the Qt wallet (wallet.cpp: COINBASE_MATURITY+1). Regular UTXOs
require MinConfirmations (6 on mainnet, 1 on testnet/regtest — wallet
policy, no consensus rule).
Changes:
- ChainProfile: add CoinbaseMaturity and MinConfirmations fields
- ChainProfiles: mainnet CoinbaseMaturity=120 / MinConfirmations=6;
testnet/regtest inherit 120, override MinConfirmations=1
- PalladiumNetworks: align NBitcoin Consensus.CoinbaseMaturity to 120
- CachedUtxo: add IsCoinbase flag (ElectrumX listunspent does not expose
this; derived from Transaction.IsCoinBase on the raw tx)
- WalletSynchronizer: set IsCoinbase during local UTXO reconstruction
- TransactionFactory.Build: add tipHeight param; filter by threshold;
error reports reason (immature coinbase with X/121 counter, under-
confirmed regulars with best-seen count, mempool amounts)
- All Build() call sites updated (App, Donate, CLI)
- Two new tests: coinbase maturity boundary (119→220 tip) and mainnet
min-conf boundary (5→6 confirmations)
When IsSyncing and the user requests a different server, cancel the
running CancellationTokenSource instead of silently ignoring the click.
OperationCanceledException is caught separately (not an error), and
ConnectAndSync restarts automatically with the new server after the
cancelled task unwinds.
The "Connect" button no longer has IsEnabled="{Binding !IsSyncing}":
it is always clickable, and clicking it with the same server sets
_resyncRequested as before. CancellationToken is now passed to both
ElectrumClient.ConnectAsync and WalletSynchronizer.SyncOnceAsync.
All six languages updated: the one-liner was replaced with a two-sentence
description that names the network (PLM), the lightweight SPV design,
the non-custodial model, and the local encryption guarantee.
README.md: add "Reproducible builds (Docker)" section at the top of
Building, explaining why it matters for a wallet (auditable toolchain,
no drift between releases, anyone can verify binaries from source).
Fix the manual Windows publish command to include
IncludeNativeLibrariesForSelfExtract=true — the previous command produced
an exe that silently failed to start because Avalonia's native DLLs were
left outside it.
CLAUDE.md: document docker/ in the project tree, promote ./docker/build.sh
as the primary release path, record the two non-obvious gotchas (native
libs flag for single-file desktop; XA5207 / API level coupling for Android).
Adds docker/ with two Dockerfiles and a build.sh interactive script that
builds all three release targets (Windows exe, Linux binary, Android APK)
inside pinned Docker containers — no SDK required on the host.
Key design decisions:
- Source mounted read-only; build runs on an in-container copy so bin/obj
never pollute the working tree.
- NuGet packages cached in a named Docker volume (plm-nuget-cache) across
runs to avoid re-downloading on each build.
- Single-file desktop publishes use IncludeNativeLibrariesForSelfExtract so
Avalonia's native libs (Skia, HarfBuzz, ANGLE) are embedded — without this
flag the exe/binary silently fails to start.
- Android Dockerfile pins platform API 36 (dictated by the .NET 10 android
workload, error XA5207 if mismatched) and bakes the full Android SDK +
workload into the image layer.
- Artifacts are chown'd back to the host user so dist/ files are never
owned by root.
All three targets verified end-to-end from a clean docker system prune -a:
Windows PE32+ exe (103 MB), Linux single-file binary launches on this host,
Android APK installs and renders UI on API-34 x86_64 emulator.
Adds a "Report a bug" / "Segnala un bug" button (6 languages) in
Help → Info that opens the issue tracker in the system browser via
TopLevel.Launcher (cross-platform: desktop + Android).
URL points to the current self-hosted repo; a TODO comment marks
the constant for easy update once the project moves to GitHub.
Adds .github/ISSUE_TEMPLATE/bug_report.yml — GitHub issue form with
structured fields for version, platform, network, repro steps,
expected behavior, and logs.
Input rows switch to a two-line layout (full prev-txid:index on the first
line, address + amount on the second) so nothing is cut by a fixed 160px
column. Output address column gets TextWrapping instead of TextTrimming.
Overlay grows to MaxWidth=660/MaxHeight=800 for better desktop use; the
same XAML wraps naturally on narrow mobile screens.
Removes the Shorten() helper that was masking input references.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SPV is the only verification method the wallet uses, so showing it as a
badge adds no information — if a transaction appears confirmed, it has
already passed Merkle verification.
- Remove the SPV chip from the transaction detail overlay
- Remove the Verificata column from the history list (desktop and mobile)
- Drop VerifiedText property from TransactionDetailsViewModel
- Drop the Verificata field from HistoryRow
- Remove the orphaned tx.verified loc key
The wizard already covers opening existing wallets; a separate file-picker
entry in the menu duplicates that flow without adding value. Remove the menu
item, its click handler, the OpenFromPath VM method, and the loc key.
- File → Esci (separator + Quit at the bottom) calls IClassicDesktopStyleApplicationLifetime.Shutdown()
- Wallet menu item and mobile button now use IsVisible instead of IsEnabled:
they disappear entirely on the wizard/setup screen and reappear once a
wallet is open, avoiding a confusing greyed-out entry
Replace the direct private-key display in the address detail overlay with a
two-step reveal flow:
- Unencrypted wallet: clicking Show reveals the WIF key immediately.
- Encrypted wallet: clicking Show opens a modal password-prompt overlay; the
key is displayed only if the entered password matches the wallet file
password; a wrong password shows an inline error without closing the prompt.
Hiding the key (Hide button) clears the revealed state. Closing the address
overlay or opening a different one always resets both the reveal state and
the password prompt, so no sensitive material lingers between sessions.
Add a Wallet menu item (between File and Settings) that opens an in-app
overlay showing wallet metadata: file name, network, type (HD seed / xprv /
WIF / watch-only), script kind, derivation path, master fingerprint, and
account xpub.
Includes a seed section: if the wallet has no seed, a note is shown; if it
has one and is unencrypted the seed is revealed directly; if encrypted the
user must enter the wallet file password before the mnemonic is displayed.
The revealed seed is shown in a danger-coloured bordered box with a warning.
Closing the overlay (backdrop, Close button, or Esc) always clears the
password input and hides the seed.
Also adds the private-key password-prompt overlay and its localization keys
(shared UI scaffolding used by the following commit).
- AppConfig stores LastServerHost/Port/UseSsl; restored on next launch
so the wallet reconnects to the last-used server instead of defaulting
to the first bootstrap entry
- Added ConnectionStatusShort for the status bar (short label only);
ConnectionStatus retains the full "Connesso a ip:porta" shown in the
server settings overlay
- Removed TLS checkbox (SSL still used internally, just not exposed in UI)
- Removed "Discover servers" button (discovery now runs automatically on panel open)
- ConnectionStatus now includes host:port when connected ("Connesso a ip:porta")
- Replaced status ellipse+text with a single TextBlock
Implementation is complete; the blueprint.md file no longer exists in the
repo. Drop the dead reference, all §X section markers, the "Implementation
state" progress block, and the TODO list. General rules (role, language
policy, architecture invariants, working rules) are preserved unchanged.
Help button now shows a two-tab overlay:
- Info: existing about/version content
- Donate: donate flow to the dev address (plm1qdq3…) with free amount
input, prepare preview, and confirm-and-broadcast button
New MainWindowViewModel.Donate.cs partial: DonateAmount / DonatePreview
/ HasPendingDonate observable properties, PrepareDonateCommand (builds
the tx with hardcoded dev address via the existing TransactionFactory),
ConfirmDonateCommand (broadcasts and syncs). Donate state is reset on
overlay close. Six-language Loc keys added (help.tab.info/donate,
donate.desc/dev.address/amount/prepare/confirm).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- UniformGrid Columns="5" on TabStrip so all 5 tabs always stay on one
row on Android (Rows="1" alone did not auto-calculate columns at
measure time on the Android Avalonia renderer)
- Contacts tab header was using hardcoded FontSize=13/Width=24/Spacing=4
instead of the TabFontSize/TabIconSize/TabSpacing ViewModel bindings,
making it visually larger than the other four tabs on mobile
- ContentPresenter Margin="0,0,0,4" in Controls.axaml pushes the tab
header content above the 2 px selection indicator, which is drawn at
the absolute bottom of the template Panel and was overlapping the label
- TabItem horizontal padding 4→8 px and TabSpacing 2→4 for both
desktop and mobile to give more breathing room between tab items
Send tab: Recipient and Amount cards side-by-side on desktop using a
two-column Grid, making full use of the available window width.
Summary card and action buttons remain full-width below both columns.
Mobile retains the original vertical stack.
Receive tab: QR card on the left (auto-width), address card expands
to fill the right column on desktop — standard fintech wallet pattern.
Mobile retains QR above, address below.
Window enlarged to 1020×680 (from 900×620) and fixed with CanResize=False;
Min/Max dimensions match so the resize handle never appears.
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.
Transaction details overlay redesigned from a flat 12-row grid into a
scannable layout: a header with the net amount in focus plus SPV/status
badges, then three labeled cards (Overview, Amounts & fees, Technical
details), with inputs/outputs as carded rows. Adds a .section heading
style and tx.sect.* localization keys (6 languages).
Coinbase transactions are now explicitly identified: an amber "Coinbase"
badge in the header, and the misleading "From: —" / "coinbase —" replaced
with "Newly generated (mining)" (localized). Exposes IsCoinbase on the
details view model; no consensus/logic change, presentation only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace raw Fluent defaults + inline hex with a semantic design system,
themed for both light/dark (follows system variant).
- Styles/Theme.axaml: semantic brushes (surface/text/border/status) per
variant via ThemeDictionaries; override Fluent SystemAccentColor to brand
blue (#2563EB) and ControlCornerRadius=8; blue->indigo balance gradient.
- Styles/Controls.axaml: reusable classes (card, balance-hero, chip, ghost),
type scale (amount with tabular figures, mono, h1, label), button/input
polish, tab selected-in-blue, list hover/selected tints, card hover-lift.
- MainView: balance shown as gradient hero card; dashboard tabs use Material
SVG Path icons (Fill tracks TabItem selection) instead of Unicode glyphs;
all hardcoded colors (LimeGreen/IndianRed/Orange/Gray/#99000000) and
generic Fluent surface brushes mapped to semantic tokens; app background,
stronger modal scrim, status bar top border.
Palette derived from the logo (blue=trust, green=money). Build clean, app
starts with no binding errors.
Speed up wallet synchronization, especially re-syncs of large wallets:
- Incremental address discovery: known indices from the previous sync
(NextReceiveIndex/NextChangeIndex) are fetched in a single parallel
burst instead of sequential gap-limit rounds, cutting round-trips from
O(used/gapLimit) to ~O(1). Receive and change chains scan in parallel.
- Merge tx download and Merkle verification into one parallel phase
(no barrier between them); the per-call semaphore is dropped in favor
of the transport-level in-flight gate.
- Persist verified block headers (SyncCache.BlockHeaders, height -> hex):
immutable once stored, so blockchain.block.header round-trips for
already-verified proofs are eliminated on later syncs.
- RetryOnBusyAsync with exponential backoff around server calls, treating
-101/-102 and "server busy"/"excessive resource usage" as transient.
Wire the new PreloadCaches/ExportCaches signatures through the desktop
view model and the CLI sync command.
Rework the ElectrumClient transport to cut latency and allocations
during sync:
- Channel-based single-reader write loop: drains the whole queue into a
single WriteAsync+FlushAsync, replacing the _writeLock that forced one
flush per message. N queued requests now travel in one TCP segment.
- PipeReader read loop parsing via Utf8JsonReader over pooled spans: no
StreamReader/ReadLineAsync and no intermediate string per response.
- TcpClient.NoDelay = true: no Nagle wait on small packets.
- Concurrency gate (SemaphoreSlim, MaxInFlight=32) in RequestAsync:
caps requests in flight to the server without serializing writes,
avoiding floods (-101/-102) and connection drops on wallets with large
history.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last wizard step (password) now shows an optional name field.
The name becomes the filename: <name>.wallet.json in the wallets
directory. Invalid filename characters are replaced with '_'.
If left empty, the existing auto-name logic applies (default.wallet.json,
wallet-2.wallet.json, …). If the chosen name already exists the wizard
shows an error instead of overwriting.
The name field is visually separated from the encryption section by a
distinct background panel and a horizontal divider.
Three interrelated fixes for sync reliability on large wallets:
1. Prevent -101 "excessive resource usage":
ScanChainAsync uses GetHistoryAsync for discovery instead of
SubscribeScripthashAsync. Subscriptions are limited to the gap window
only (≤ 2×gapLimit addresses), so the session subscription count stays
constant regardless of wallet history size — eliminating -101 on
wallets with 1000+ historical addresses.
2. Persist and resume tx cache across sessions and disconnects:
WalletSynchronizer gains ExportCaches() and PreloadCaches(). On every
successful sync (and on partial failure) the downloaded raw tx hex and
verified Merkle heights are written to SyncCache.RawTxHex/VerifiedAt
in the wallet file. On reconnect or app restart the new synchronizer
is pre-populated from disk so only new transactions are downloaded.
_synchronizer is always nulled on reconnect (new client = new instance)
while caches survive via the wallet file.
3. Auto-connect with multi-server fallback and retry:
ConnectAndSync iterates BuildServerCandidates() (current server first,
then all KnownServers in order) and stops at the first that responds.
_autoReconnect and _syncFailed are set in OpenLoaded() so the keepalive
timer (20 s) retries automatically on any failure — connection error,
-101 mid-download, or timeout. PersistPartialTxCache() saves whatever
was downloaded so each retry resumes from its checkpoint.
Add two new import paths to the setup wizard:
- "Importa xpub / xprv": accepts any SLIP-132 key (xpub/ypub/zpub or
xprv/yprv/zprv), auto-detects script type from the version prefix,
and saves a watch-only or fully-spendable HD account accordingly.
- "Importa chiave WIF": accepts one or more WIF private keys (one per
line), goes through script-type selection, and saves an ImportedKeyAccount.
WizardFlowKind enum (New, Restore, ImportXkey, ImportWif) governs which
steps are shown; WizardBack() updated for all four flows. OpenLoaded()
updated to accept IWalletAccount and display wallet kind tags
(watch-only / imported) in the network info line.
Localization keys added for all six languages (it/en/es/fr/pt/de).
ViewModel._account changed from HdAccount? to IWalletAccount?.
Introduce IWalletAccount interface to abstract HD, imported-xprv, WIF,
and watch-only account types. HdAccount implements the interface; the
new ImportedKeyAccount handles lists of WIF keys with fixed addresses
(no gap scanning, change returns to first address).
WalletLoader gains three factory methods:
- NewFromXpub: watch-only from SLIP-132 xpub/ypub/zpub
- NewFromXprv: spendable from SLIP-132 xprv/yprv/zprv
- NewFromWif: spendable from one or more WIF private keys
WalletDocument gains optional AccountXprv and WifKeys fields; AccountPath
and AccountXpub become nullable (absent for WIF wallets). IsWatchOnly
updated to cover all non-signing wallet kinds.
TransactionFactory and CLI OpenWallet() updated to IWalletAccount.
12 new unit tests in ImportedKeyAccountTests cover factory round-trips,
address derivation, and watch-only detection.
On mobile the server settings panel now fills the entire screen
(no MaxWidth/MaxHeight/Margin constraints, no rounded corners) using
a desktop-only Border.desktop-overlay style so inline values never
fight the style system. Input font sizes increased (11→13/15px),
known-server list taller (200→300px) and server hostname larger (13→15px).
Desktop layout unchanged.
UnconfirmedText (pending/unconfirmed balance) now wraps instead of
clipping horizontally. Contact name in mobile list gets CharacterEllipsis
trimming for arbitrarily long user-defined names.
Drop the mainnet/testnet/regtest ComboBox from the wizard and fix
Net to always return NetKind.Mainnet. Core profiles and CLI --net
flag are untouched; the wallet file still records the network name.
Replace the mobile-only custom bottom Grid nav bar with a single styled
TabStrip that works the same on both platforms: icon + label headers,
gray background, UniformGrid for equal-width columns.
Also adds the 📷 scan button in the Send address field (visible on mobile,
triggers ScanQrCommand wired in the previous commit).
Camera2 + ZXing.Net 0.16.9 activity (ScannerActivity) that captures JPEG
frames at ~2.5 fps and decodes QR codes without Xamarin dependencies.
Result is returned to MainActivity via OnActivityResult/TaskCompletionSource.
PlatformServices.ScanQrAsync seam wires Android head to shared App layer;
ScanQrCommand in the Send ViewModel strips BIP21 URI scheme/query parameters.
CAMERA permission and uses-feature added to AndroidManifest.
Generate mipmap ic_launcher.png at all standard densities (mdpi→xxxhdpi)
from logo.png, cropped to bounding box and scaled to 88% of the tile on
a white background (legacy fallback for API < 26).
For API 26+ provide an adaptive icon (ic_launcher.xml / ic_launcher_round.xml)
with white background and a foreground PNG where the logo occupies 56% of
the 108dp canvas — within the safe zone, fully visible on every launcher
shape without clipping.
Avalonia's TabStrip ignores HorizontalAlignment overrides when placed at
Bottom, causing the items to cluster left. Replace it with a dedicated
Grid (ColumnDefinitions="*,*,*,*,*") that guarantees 5 equal columns
across the full width. The built-in TabStrip is hidden on mobile; buttons
in the custom bar call SelectTabCommand to drive SelectedTabIndex on the
TabControl.
Tab bar: replace plain Header strings with icon+label StackPanels
(≡ ↑ ↓ ⊙ ⊕); style TabStrip with UniformGrid so all five tabs share
the full width equally; center content and add touch padding. Icons
are visible only on mobile (IsMobile binding); desktop keeps text only.
Server settings overlay: dual layout for host/port/TLS input (vertical
on mobile: host full-width, port+TLS side by side) and for action
buttons (stacked full-width on mobile). Known-server list item shows
host on one line and tcp/ssl ports on the line below on mobile.
Connection status bar: simplify to "connesso" / "non connesso" —
remove the "a host:port" suffix from the connected state and
consolidate all disconnected/error/cert-changed states to conn.none.
Add IsMobile property and BoolToTabPlacementConverter; wire
TabStripPlacement to move tabs to the bottom on mobile (standard
Android pattern). Hide the desktop menu bar on mobile and expose
Settings/Help via a compact header row instead.
Replace fixed-width multi-column grids with dual desktop/mobile
templates in History, Addresses and Contacts lists; adapt Send,
Receive and the add-contact form for narrow screens. Remove hard-coded
Width from all five overlays (560–640 px) in favour of MaxWidth +
Margin="16" so they never overflow a 360 px screen; add ScrollViewer
to Address info, Server settings and Settings overlays.
Desktop layout is unchanged: all differences are gated on the
IsMobile/IsDesktop bool, which is a platform constant (false on desktop).
9 property tests covering:
- CoinAmount: TryParseIn/TryParseCoins never throw on arbitrary strings;
FormatIn→TryParseIn roundtrip holds for any sats in [0, MaxSupply];
parsed results always ≥ 0
- EncryptedFile: Encrypt→Decrypt roundtrip for any plaintext/password;
wrong password always raises WrongPasswordException (never other exceptions);
IsEncrypted never throws
- MerkleProof: every leaf in a randomly generated tree verifies against its root
(1–16 leaves, covers odd/even/single at every position); foreign txid never
verifies and never crashes