Commit Graph

91 Commits

Author SHA1 Message Date
davide cfc48ff86f feat(ui): responsive layout for portrait mobile (Android)
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).
2026-06-14 19:54:11 +02:00
davide 5d061c6f21 docs(storage): document plaintext save caveat on WalletStore.Save
The password parameter accepts null, which saves the wallet in plaintext.
The XML doc comment now makes explicit that this is only acceptable when
the user has explicitly opted out of encryption with a UI warning shown.
2026-06-13 21:15:28 +02:00
davide 2ddc0f920c refactor(app): split MainWindowViewModel into partial files by area
1300-line monolith → 7 focused files (max 359 lines each):
  MainWindowViewModel.cs         core fields, constructor, lifecycle
  MainWindowViewModel.Wizard.cs  setup wizard and wallet open/close flows
  MainWindowViewModel.Settings.cs language, unit, overlay flags
  MainWindowViewModel.Sync.cs    server config, connection, sync loop
  MainWindowViewModel.Send.cs    send panel
  MainWindowViewModel.Contacts.cs contact management
  MainWindowViewModel.Receive.cs balance display, QR, addresses, tx details

No behaviour change, no XAML change, 127/127 tests pass.
2026-06-13 21:15:07 +02:00
davide 58645bd7a7 fix(storage): acquire wallet lock before load and before CloseWallet
Three improvements to WalletLock integration:

1. OpenExisting acquires the lock before decrypting the file — no point
   attempting a potentially expensive PBKDF2 derivation for a wallet
   already held by another instance.

2. OpenFromPath acquires the lock before CloseWallet — if the new wallet
   is unavailable, the current session stays intact instead of being
   silently destroyed.

3. OpenLoaded now receives an already-acquired WalletLock from the caller
   instead of acquiring it internally; TryAcquireWalletLock is removed.
   Callers that fail to open (wrong password, I/O error) dispose the lock
   in their catch blocks.

Also fix CoinAmount.Of() to throw ArgumentException for unknown units
instead of silently falling back to PLM.
2026-06-13 20:50:22 +02:00
davide 008e9c395a fix(wallet): reject amounts with sub-satoshi precision
Replace the silent (long) cast with an explicit fractional check:
if value * factor has a non-zero remainder, TryParseIn/TryParseCoins
return false. The caller already shows "Importo non valido." to the
user, so no UI change is needed.

Covers TryParseIn (all units) and TryParseCoins. Adds CoinAmountTests
with valid and invalid cases including the triggering example (1.9 sat).
2026-06-13 20:39:08 +02:00
davide 0f8a764a44 feat(storage): add WalletLock to prevent concurrent wallet access
Introduces an exclusive file lock (FileShare.None on wallet.json.lock)
held for the duration of a wallet session. A second instance trying to
open the same file receives null from TryAcquire and sees a localized
error message; UnauthorizedAccessException (OS permission issues)
propagates separately so the UI can show a distinct message.
The .lock file is deleted on Dispose (best-effort); the real guard is
the open FileStream, which the OS releases automatically on crash.
2026-06-13 20:34:06 +02:00
davide b00c5821f2 refactor(app): clear status bar during wizard steps
Remove the wizard-step status messages (welcome, password info,
seed instructions, etc.) — the XAML headings for each step already
communicate context. StatusMessage is now reserved for actionable
feedback (errors, sync progress, connection state).
2026-06-12 16:16:37 +02:00
davide e94eaf7700 refactor(arch): split App into shared library + Desktop + Android heads
The Avalonia UI code (App, Views, ViewModels, Localization, Assets) now
lives in src/App as a plain library (no OutputType). Two thin heads
reference it:

- src/App.Desktop/ — WinExe, Avalonia.Desktop, hosts MainView in a
  MainWindow; carries Program.cs and app.manifest (moved from src/App)
- src/App.Android/ — net10.0-android, Avalonia.Android, MainActivity/
  MainApplication; targets API 23+, EmbedAssembliesIntoApk=true so the
  apk is self-contained for sideloading

All event handlers and TopLevel-dependent calls (file picker, clipboard,
folder picker) moved from MainWindow.axaml.cs into the new shared
MainView.axaml.cs (UserControl), using TopLevel.GetTopLevel(this) so
they work on both platforms. Esc/Back key handling is also in MainView.
MainWindow becomes a thin shell that hosts MainView.

Framework bump: all projects move to net10.0; Cli and Tests follow.
2026-06-12 16:06:46 +02:00
davide 865daa137d feat(app): version in window title and Help overlay
- csproj: add <Version>0.9.0</Version> as single source of truth
- AppVersion reads Major.Minor.Build from the assembly; WindowTitle
  exposes "Palladium Wallet 0.9.0" bound to Window.Title
- Menu Help → in-app overlay (same pattern as settings/server overlays)
  showing app name, version and a short description; backdrop tap and
  Esc close it
- i18n: menu.help, help.title, help.info in all 6 languages
2026-06-12 12:01:25 +02:00
davide 4735490759 feat(app): transaction detail overlay with full on-chain data
Double-clicking a history row opens an in-app overlay (same pattern as
address/server/settings overlays) that shows all available data for a
transaction. The overlay appears immediately with a spinner; data is
fetched from the server in the background and rendered once ready.
Backdrop tap and Esc close it; a pending fetch is cancelled.

New Core type — TransactionInspector (Core/Wallet/):
- FetchAsync() downloads the raw tx, all parent txs (parallel requests,
  one round-trip instead of N sequential), and the block header
- Reconstructs inputs (amounts, addresses, is-mine), outputs, fee,
  virtual size, RBF flag, block timestamp, confirmations

New App types:
- TransactionDetails record: full parsed data (fee, net, I/O lists,
  counterparty addresses, coinbase detection…)
- TransactionDetailsViewModel: pre-formats all strings in the current
  locale and unit (status, date, signed amounts, sat/vB rate…)
- TxIoRow record: one row in the input/output tables
- MineColorConverter: bool → brush (MediumSeaGreen for own addresses)

ViewModel changes:
- ShowTransactionDetailsAsync / CloseTransactionDetailsCommand
- BuildTransactionDetailsAsync (also public for future RBF/CPFP use)
- CancellationTokenSource so opening a new tx cancels the previous fetch
- All server work runs on Task.Run to keep the UI thread free

XAML: history rows get DoubleTapped handler, Hand cursor, hint label,
txid now uses TextTrimming instead of SelectableTextBlock.

i18n: 24 new tx.* / history.hint keys in all 6 languages.
2026-06-12 11:48:33 +02:00
davide 4c7e8696cb feat(app): copy-to-clipboard button for receive address
Adds a "Copy" button next to the receive address in the Receive tab.
Uses Avalonia's Clipboard API (async, code-behind) and calls
NotifyAddressCopied() to show the existing addr.copied status message.
New i18n key receive.copy in all 6 languages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:47:01 +02:00
davide 7727dbfddc feat(app): replace placeholder icon with Palladium Wallet logo
Add logo.png (source) and logo.ico (multi-size: 256/128/64/48/32/16)
generated with Pillow. Remove avalonia-logo.ico (Avalonia template
placeholder). Update MainWindow.axaml Icon and csproj ApplicationIcon
to point to the new files.
2026-06-12 10:40:29 +02:00
davide 58b86ad1af feat(app): QR code for receive address
Adds QRCoder 1.8.0 and generates a PNG QR code in-memory each time
the receive address changes (OnReceiveAddressChanged). The bitmap is
displayed in the Receive tab inside a white-background border with
pixel-perfect scaling (BitmapInterpolationMode=None). Previous bitmap
is disposed to avoid memory leaks.
2026-06-12 10:21:18 +02:00
davide 7f2759b2fc feat(app): multi-wallet chooser, confirm-password, encrypt toggle
Multi-wallet (§8):
- AppPaths.WalletFiles(net) enumerates all *.wallet.json in the wallets
  dir; WizardStartOpen shows a chooser step when more than one is found
- New StepChooseWallet + ChooseWalletCommand; Back from StepOpen returns
  to chooser (or to StepStart when only one wallet)
- WalletFileEntry record for the chooser ItemsControl

Password step (Electrum style):
- ConfirmPasswordInput: password must be typed twice before creating
- EncryptWallet checkbox (default true); when unchecked, password is
  null (plaintext file) with an explicit warning
- Validation: empty password with encryption on → msg.password.required;
  mismatch → msg.password.mismatch
- ConfirmPasswordInput cleared on wallet open and wizard back

AppPaths:
- DefaultDataRoot() is now platform-aware: %APPDATA%\PalladiumWallet on
  Windows, ~/.palladium-wallet on Linux/macOS (matches §8 convention)
- Legacy root fallback removed (no prior releases to migrate from)

i18n: wiz.choose.title, wiz.password.confirm, wiz.password.encrypt,
wiz.password.encrypt.hint, msg.choose.wallet, msg.password.required,
msg.password.mismatch — all 6 languages
2026-06-12 10:13:17 +02:00
davide a8a97f09b7 feat(app): connect to server before wallet open, status in bottom bar
- ConnectAndSync() no longer bails early when no wallet is open:
  connection is established immediately after the wizard completes
  (Electrum-style), sync starts only once an account is available
- KeepAlive tick no longer requires IsWalletOpen so the connection is
  maintained and auto-reconnected at all times
- WalletSynchronizer is lazily created on first sync (not on connect)
  to avoid a null-ref guard on _synchronizer
- Connection status indicator (dot + text) moved from wallet header to
  the status bar (always visible); tapping it still opens the server
  settings overlay
- Menu "Rete" removed: Discover and ResetCerts are inside the server
  settings overlay; redundant top-level menu entry eliminated
- "Server" button in settings overlay is no longer gated on IsWalletOpen
2026-06-12 10:13:04 +02:00
davide f3bf4cf94a feat(app): first-run data location wizard step
On first launch (no data yet, no portable dir, no pointer file),
the wizard now shows a new step-0 screen asking where to store
wallets, config and certificates.

AppPaths changes:
- DefaultDataRoot() → ~/.PalladiumWallet (home, always writable)
- IsDataLocationConfigured() → true when portable / override / pointer
  already written / legacy or default already has data
- ConfigureDataLocation(root) writes a bootstrap pointer file and
  creates the directory
- DataRoot() resolution order: override → portable → pointer → legacy
  (has data) → default

ViewModel: new StepDataLocation step, UseDefaultDataLocationCommand,
ApplyDataLocation(root) (also called from View's folder picker).

View: new wizard panel with description, default-path display, two
buttons (use default / choose folder); folder picker via
StorageProvider.OpenFolderPickerAsync.

Loc: add wiz.data.* keys (6 languages); fix fallback language "it"→"en";
update test assertion accordingly.
2026-06-12 09:25:04 +02:00
davide 87e1c82610 feat(app): connection status indicator opens server settings on tap
The green/red dot + status text in the wallet header is now tappable:
opens the server settings overlay directly. Added Hand cursor and
tooltip (server title) to signal interactivity.
Status text uses SystemAccentColor instead of Gray.
2026-06-12 09:24:57 +02:00
davide 51c87a7dc9 refactor(app): replace nested settings submenu with in-app overlay
The Impostazioni menu now opens a single overlay (same pattern as
address/server overlays) instead of nested OS popup menus, which are
slow to respond under WSLg.

The overlay groups language (RadioButton) and unit (RadioButton) in one
panel, with a button to open the server overlay. Esc and backdrop tap
close it. OpenServerSettings() closes the settings overlay first so the
two overlays never stack.
2026-06-12 09:09:31 +02:00
davide 28cb4ce6ae feat(app): server settings as in-app overlay, split host/port fields
- Moves server configuration from the wallet panel into an overlay
  (Impostazioni → Server), same pattern as the address detail overlay
- Splits the single "host:port" text box into separate Host and Port
  fields; port changes auto-toggle the TLS checkbox when the typed
  port matches a known SSL/TCP port, and vice-versa
- Adds DisconnectAsync() so changing server/TLS reconnects to the new
  endpoint instead of reusing the old socket
- Adds i18n keys (IT/EN/ES/FR/PT/DE) for the new overlay
- Removes the server row from the main wallet panel; connection status
  indicator (green/red dot) is now shown in the wallet header
2026-06-12 09:02:19 +02:00
davide cf6e2d7654 refactor(app): replace AddressInfoWindow with in-app overlay
The address detail panel is now rendered as a modal overlay inside
MainWindow instead of a separate top-level Window, avoiding the
create/destroy cost of a dialog and working better under WSLg.

Backdrop tap and Esc both close the overlay.
2026-06-12 08:22:30 +02:00
davide fe320584eb feat(app): navbar restructure, contacts tab and address info window
- Reorder tabs: Storico / Invia / Ricevi / Indirizzi / Contatti
- Invia: ComboBox to quick-fill recipient from saved contacts
- Contatti: new tab to add/remove contacts (name + address), persisted
  to the wallet file via WalletDocument.Contacts
- Indirizzi: clicking any address (left or right click) opens a modal
  window with derivation path, public key and private key as selectable
  text (no copy button needed — user selects and copies manually);
  AddressRow now carries PubKey, PrivKey, DerivPath pre-computed
- AddressInfoWindow: new Window with SelectableTextBlock per field,
  private key section hidden for watch-only wallets
- Loc: adds tab.contacts, addr.*, contacts.*, send.from.contact keys
  in all 6 languages
2026-06-11 21:39:36 +02:00
davide 6fe31964e1 feat(storage): add contacts list to WalletDocument
Adds StoredContact class and a Contacts list to WalletDocument so the
address book survives app restarts inside the encrypted wallet file.
2026-06-11 21:39:18 +02:00
davide 8ab8bbd8b3 feat(storage): XDG-compliant paths and default language English
AppPaths: use Environment.SpecialFolder.ApplicationData for all platforms
  - Windows  → %APPDATA%\PalladiumWallet  (unchanged)
  - Linux    → ~/.config/PalladiumWallet  (XDG standard, was ~/.palladium-wallet)
  - macOS    → ~/Library/Application Support/PalladiumWallet

AppConfig: change default language from "it" to "en" for new installs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 18:41:47 +02:00
davide 71a604c8a5 feat(i18n): add ES, FR, PT, DE languages and fix live switching
Add Spanish, French, Portuguese, German translations to all UI strings.

Fix language switching in Avalonia compiled bindings: instead of firing
PropertyChanged("Item[]") on a singleton (which Avalonia ignores when the
object reference is unchanged), Loc.SwitchTo() now creates a new Loc
instance for the selected language and replaces both Loc.Instance and the
ViewModel's _loc field. OnPropertyChanged("Loc") then forces Avalonia to
re-evaluate all {Binding Loc[key]} bindings with the new instance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 18:41:31 +02:00
davide 75583f7a69 feat(app): settings menu with language and display unit
Add global AppConfig (config.json, blueprint §8) persisting language
and amount unit. CoinAmount gains unit-aware formatting and parsing
(PLM, mPLM, µPLM, sat), applied everywhere amounts are shown or
entered, including the send form. New Loc i18n layer (it/en) with
live-updating XAML bindings covering menu, wizard, wallet panel and
status messages.

The Impostazioni menu opens a dropdown with separate Lingua and Unità
entries (radio-checked submenus, applied and saved on click), ready to
host future settings.
2026-06-11 16:23:06 +02:00
davide e8777fb458 feat(app): step-by-step setup wizard instead of single-form panel
Startup now follows the blueprint §15 flow, one field per screen with
Avanti/Indietro: initial choice (open / create / restore), seed display
with write-it-down warning, seed confirmation by re-typing, mnemonic
validation on restore, optional BIP39 passphrase, and wallet file
password as the final step. Opening an encrypted file from the menu
routes to the same password step.
2026-06-11 16:04:29 +02:00
davide 73a2d5356d feat(spv): Electrum-style continuous updates with incremental parallel sync
WalletSynchronizer now pipelines JSON-RPC requests (address subscribes,
histories, tx downloads and Merkle proofs run in parallel batches) and
keeps per-connection caches of downloaded transactions and verified
proofs, so notification-triggered resyncs only cost the delta.

The app connects automatically when a wallet opens, keeps one
synchronizer per connection, and queues notifications that arrive
during a sync instead of dropping them: incoming transactions now show
up within seconds.
2026-06-11 13:37:34 +02:00
davide ece634f42e feat(wallet): show pending mempool balance and exclude unconfirmed UTXOs from spending
TransactionFactory now selects only confirmed UTXOs (height > 0), so
mempool funds are visible as pending balance but never spendable, with
a clear error when only unconfirmed funds are available. GUI and CLI
labels updated to "in attesa di conferma (non spendibile)".
2026-06-11 11:34:21 +02:00
davide af10482e54 feat(cli): servers command, info --addresses, registry fallback for --server 2026-06-11 11:28:00 +02:00
davide 46d8c19784 feat(app): server selector, peer discovery, address view, menu bar, keep-alive 2026-06-11 11:27:43 +02:00
davide 08217e5306 feat(spv): per-address balance and tx count in sync result 2026-06-11 11:27:31 +02:00
davide 1c2dacc27c feat(net): server registry, bootstrap servers, peer discovery 2026-06-11 11:27:16 +02:00
davide 0e32b9bc9b feat(cli): command-line interface 2026-06-11 10:48:12 +02:00
davide bb8563580f feat(app): Avalonia UI shell 2026-06-11 10:47:56 +02:00
davide 3d6597a3b2 feat(wallet): coin selection, tx factory, wallet loader 2026-06-11 10:47:43 +02:00
davide 4d2ae1fd13 feat(storage): encrypted wallet file and persistence 2026-06-11 10:47:31 +02:00
davide bd5e740599 feat(spv): header sync, Merkle proofs, scripthash 2026-06-11 10:47:16 +02:00
davide df60763ddd feat(net): ElectrumX client with TLS pinning 2026-06-11 10:46:57 +02:00
davide 8a0dc920f6 feat(crypto): BIP-32/39/84, HD accounts, SLIP-132 2026-06-11 10:46:38 +02:00
davide cdd19024ad feat(chain): network profiles and consensus constants 2026-06-11 10:46:26 +02:00
davide ca70570242 chore: solution and project files 2026-06-11 10:46:10 +02:00