Skip to main content

Wallet Architecture

Three-Layer MVVM

Nethereum.Wallet                         Core: vault, accounts, chain config,
transaction building, RPC, storage
+ Nethereum.Wallet.UI.Components Shared: ViewModels, validation,
localisation (CommunityToolkit.Mvvm)
+ .Blazor or .Maui Renderer: MudBlazor or .NET MAUI

Each layer only depends on the one below it. Platform renderers never contain business logic — they bind to ViewModels and forward user actions.

ViewModel Pattern

Every wallet screen has a ViewModel extending ObservableObject:

public partial class MnemonicAccountCreationViewModel : ObservableObject
{
[ObservableProperty] private string _mnemonic = string.Empty;
[ObservableProperty] private string? _mnemonicError;
[ObservableProperty] private string _derivedAddress = string.Empty;
[ObservableProperty] private bool _isRevealed;
[ObservableProperty] private bool _isBackedUp;

partial void OnMnemonicChanged(string value) => ValidateAndUpdateAddress();

[RelayCommand]
public Task GenerateMnemonicAsync()
{
Mnemonic = Bip39.GenerateMnemonic(12);
return Task.CompletedTask;
}

public bool IsFormValid => string.IsNullOrEmpty(MnemonicError)
&& !string.IsNullOrWhiteSpace(Mnemonic);
}

Key patterns:

  • [ObservableProperty] generates public property + PropertyChanged + On*Changed partial hook
  • [RelayCommand] generates ICommand
  • Field-level validation via *Error properties
  • IsFormValid aggregates all error fields

Localisation

All user-facing strings go through IComponentLocalizer<TViewModel>. Each ViewModel has a matching Localizer:

public class MnemonicAccountEditorLocalizer
: ComponentLocalizerBase<MnemonicAccountCreationViewModel>
{
public static class Keys
{
public const string DisplayName = "DisplayName";
public const string MnemonicRequired = "MnemonicRequired";
}

protected override void RegisterTranslations()
{
_globalService.RegisterTranslations(_componentName, "en-US", new Dictionary<string, string>
{
[Keys.DisplayName] = "Mnemonic Wallet",
[Keys.MnemonicRequired] = "Seed phrase is required",
});
_globalService.RegisterTranslations(_componentName, "es-ES", new Dictionary<string, string>
{
[Keys.DisplayName] = "Billetera Mnemónica",
[Keys.MnemonicRequired] = "La frase semilla es requerida",
});
}
}

Service Registration

One call registers everything:

builder.Services.AddNethereumWalletUI();

After building the host, initialise registries:

app.Services.InitializeAccountTypes();

This wires up account creation, details, dashboard plugins, and component registries.

Complete Screen Inventory

Account Creation Screens

All account creation ViewModels implement IAccountCreationViewModel and are registered with IAccountCreationRegistry.

ViewModelIconSortPurpose
MnemonicAccountCreationViewModelvpn_key1Generate or import 12/24-word BIP39 seed phrases
PrivateKeyAccountCreationViewModelkey2Import raw private keys (hex, with/without 0x)
ViewOnlyAccountCreationViewModelvisibility3Watch addresses without signing capability
SmartContractAccountCreationViewModelsmart_toy4Add smart contract wallet addresses (multisig, AA)

MnemonicAccountCreationViewModel — The most feature-rich creation screen:

  • Generate 12 or 24-word mnemonics (GenerateMnemonicAsync, GenerateMnemonic24Async)
  • Import existing mnemonics with validation
  • Optional passphrase support (MnemonicPassphrase)
  • Real-time address derivation preview (DerivedAddress)
  • Reveal/hide toggle for seed phrase (IsRevealed, ToggleRevealAsync)
  • Backup confirmation step (IsBackedUp, ConfirmBackupAsync)
  • Switch between generate and import modes (IsGenerateMode)
  • Copy to clipboard (CopyMnemonicToClipboardAsync, CopyAddressToClipboardAsync)
  • Label assignment (MnemonicLabel)

PrivateKeyAccountCreationViewModel:

  • Private key input with format detection (PrivateKeyFormat)
  • Reveal/hide toggle for sensitive data
  • Automatic address derivation from key
  • Label assignment

ViewOnlyAccountCreationViewModel:

  • Ethereum address input with checksum validation
  • Label assignment
  • Cannot sign — GetAccountAsync() throws

SmartContractAccountCreationViewModel:

  • Contract address input
  • Label assignment
  • For multisig wallets, AA contracts, proxy wallets

Account Detail Screens

All detail ViewModels implement IAccountDetailsViewModel and are registered with IAccountDetailsRegistry.

ViewModelPurpose
MnemonicAccountDetailsViewModelManage mnemonic accounts: rename, reveal private key, show derivation path, remove
PrivateKeyAccountDetailsViewModelManage imported key accounts
ViewOnlyAccountDetailsViewModelManage watch-only accounts
SmartContractAccountMetadataViewModelView smart contract account info

MnemonicAccountDetailsViewModel features:

  • Edit account name inline (StartEditAccountNameAsync, SaveAccountNameAsync, CancelEditAccountNameAsync)
  • Reveal derived private key with confirmation (RevealPrivateKeyAsync, ShowRevealedPrivateKey)
  • Display derivation path, account index, parent mnemonic name
  • Remove account from vault (RemoveAccountAsync)

Account List & Group Screens

ViewModelPurpose
AccountListPluginViewModelDashboard plugin showing all accounts (PluginId: "account-list", SortOrder: 1)
AccountListViewModelFull account list with filtering and selection
AccountGroupViewModelGroup accounts by mnemonic seed
MnemonicListViewModelList all mnemonics in vault
VaultMnemonicAccountViewModelIndividual mnemonic display with derived accounts
MnemonicDetailsViewModelShow mnemonic info and all derived accounts
MnemonicAccountEditorViewModelEditor for mnemonic vault entries

Dashboard & Plugin System

The wallet dashboard is composed of plugins. Each plugin implements IDashboardPluginViewModel:

ViewModelPluginIdSortIconPurpose
WalletOverviewPluginViewModelwallet-overview0dashboardMain wallet summary
PromptsPluginViewModelPrompts-1NotificationsPending dApp approval requests
AccountListPluginViewModelaccount-list1account_circleAccount list
HoldingsPluginViewModelholdings15account_balance_walletToken portfolio
NetworkManagementPluginViewModelnetwork_management20languageNetwork management
ContactListPluginViewModelcontacts50contactsSaved addresses
TokenTransferPluginViewModel(transfer)Quick token transfer
CreateAccountPluginViewModel(create)Account creation wizard

Plugins are registered with IDashboardPluginRegistry:

componentRegistry.Register<AccountListPluginViewModel, AccountList>();
componentRegistry.Register<HoldingsPluginViewModel, Holdings>();
componentRegistry.Register<NetworkManagementPluginViewModel, NetworkManagement>();
componentRegistry.Register<PromptsPluginViewModel, Prompts>();

The dashboard resolves all IDashboardPluginViewModel instances and renders their associated components. New features can be added without modifying the shell.

WalletDashboardViewModel — The main dashboard orchestrator:

  • Properties: SelectedAccount, SelectedAccountIndex, SelectedNetworkName, SelectedNetworkLogoPath, SelectedChainId
  • Manages current account display, formatted address, network indicator

PromptsPluginViewModel — dApp interaction queue:

  • CurrentPrompt, CurrentIndex, TotalCount — tracks pending requests
  • ApproveCurrentPromptAsync, RejectCurrentPromptAsync, RejectAllPromptsAsync — user actions
  • QueueStatusText, IsEmpty, IsProcessing — queue state
  • SortOrder: -1 (always appears first when there are pending prompts)

Transaction Screens

ViewModelPurpose
SendNativeTokenViewModelMulti-step send wizard (token/chain/account selection, amount, gas, confirm)
TransactionViewModelCore transaction building, gas estimation, EVM simulation
DAppTransactionPromptViewModelApprove/reject dApp transaction requests
TransactionStatusViewModelMonitor confirmation, display explorer link
TransactionHistoryViewModelPending and confirmed transactions with filtering

SendNativeTokenViewModel — Multi-step wizard:

  • Step 1: Select chain, account, token, recipient, amount
  • Step 2: Gas configuration and nonce
  • Step 3: Confirmation and send
  • Properties: CurrentStep, TotalSteps, AvailableChains, SelectedChain, AvailableAccounts, SelectedAccount, AvailableTokens, SelectedToken, TokenPrice, CurrencySymbol
  • Commands: NextStepAsync, PreviousStep, SetMaxAmount, SendTransactionAsync, SimulateTransactionAsync, AddCustomTokenAsync, SearchTokensAsync

TransactionViewModel — Gas and execution:

  • Gas strategies: GasStrategies, SelectMultiplierAsync, EnableCustomModeAsync, ToggleGasModeAsync
  • EVM simulation: SimulateTransactionAsync, PreviewStateChangesAsync, SimulationResult, StateChangesPreview
  • Data decoding: DecodeTransactionDataAsync, DecodedData, ShowRawData
  • Cost calculation: GetTotalGasCost(), GetTotalTransactionCost(), FormatCurrency()
  • Gas buffer: GasBufferPercentage for safety margin

DAppTransactionPromptViewModel — dApp approval:

  • Multi-step review: CurrentStep, NextStep, PreviousStep
  • Approve flow: ApproveAndSendTransactionAsync
  • Reject: RejectTransaction, CancelWithError
  • Retry on failure: RetryTransactionAsync, ShowRetry
  • Data: PromptInfo, Transaction, TransactionStatus, TransactionHash

TransactionStatusViewModel — Post-send monitoring:

  • 3-second polling timer for receipt
  • Properties: TransactionHash, IsSuccess, ConfirmationCount, GasUsed, ActualCost, ExplorerUrl
  • Commands: StartMonitoring, ViewTransactionAsync, NavigateToHistoryAsync, NewTransactionAsync

TransactionHistoryViewModel — History list:

  • PendingTransactions, RecentTransactions with filtering
  • RetryTransactionAsync, RequestCopyHash, RequestViewOnExplorerAsync
  • Filter: FilterText, FilteredPendingTransactions, FilteredRecentTransactions

Network Management Screens

ViewModelPurpose
NetworkListViewModelBrowse, search, filter, and add networks
NetworkDetailsViewModelView/edit network RPC, explorer, chain info
AddCustomNetworkViewModelAdd custom network with RPC validation

NetworkListViewModel:

  • Browse configured networks with ShowTestnets toggle
  • Search Chainlist: ChainlistResults, IsChainlistLoading, AddChainlistNetworkAsync
  • Internal suggestions: InternalSuggestions, AddInternalNetworkAsync
  • Sub-ViewModels: NetworkItemViewModel (wraps ChainFeature), ChainlistNetworkResult

Holdings & Portfolio Screens

ViewModelPurpose
HoldingsViewModelMulti-tab portfolio tracker (Accounts/Networks/Tokens)
EditHoldingsViewModelSelect accounts/networks to track
HoldingsAccountItemViewModelIndividual account in holdings
HoldingsNetworkItemViewModelIndividual network in holdings with scan progress
HoldingsTokenItemViewModelToken aggregated across chains/accounts
TokenSettingsViewModelCurrency, refresh interval, auto-refresh
AddCustomTokenViewModelAdd ERC-20 by contract address with metadata fetch
TokenListViewModelToken list for selected account/network

HoldingsViewModel — Multi-tab portfolio:

  • Tabs: Accounts, Networks, Tokens
  • Scanning: ScanAsync (blockchain token discovery), CancelScan
  • Refresh: RefreshAsync (last 100 blocks), RefreshPricesAsync
  • Totals: TotalValue, Currency, CurrencySymbol, LastUpdated
  • Edit mode: ShowEditPage, ApplySettingsAsync

HoldingsTokenItemViewModel — Per-token aggregation:

  • Contains ChainBalanceItemViewModel (per-chain breakdown)
  • Contains AccountBalanceItemViewModel (per-account breakdown)
  • Properties: Symbol, Name, LogoUri, TotalBalance, TotalValue, Price

Prompt/dApp Interaction Screens

ViewModelPurpose
DAppSignaturePromptViewModelApprove personal_sign and eth_signTypedData_v4 requests
DAppPermissionPromptViewModelApprove dApp connection (wallet_requestPermissions)
DAppTransactionPromptViewModelApprove eth_sendTransaction requests

DAppSignaturePromptViewModel:

  • Displays message to sign with decoded preview
  • EIP-712 support: TypedDataDomainHash, TypedDataMessageHash, TypedDataHash, HasTypedDataHashes
  • Commands: SignAsync
  • State: IsSigning, ErrorMessage, SignaturePreview

Settings & Contacts

ViewModelPurpose
TokenSettingsViewModelCurrency selection, auto-refresh settings
ContactListViewModelManage saved addresses
ContactListPluginViewModelDashboard plugin for contacts

Top-Level Wallet

ViewModelPurpose
NethereumWalletViewModelApplication-level state and navigation
WalletDashboardViewModelDashboard orchestrator
NotificationBadgeViewModelNotification count badges

Registry System

The wallet uses four registries to map ViewModels to UI components:

IAccountCreationRegistry

Maps account types to creation ViewModels and Blazor components:

registry.Register<MnemonicAccountCreationViewModel, MnemonicAccountCreation>();
registry.Register<PrivateKeyAccountCreationViewModel, PrivateKeyAccountCreation>();
registry.Register<ViewOnlyAccountCreationViewModel, ViewOnlyAccountCreation>();
registry.Register<SmartContractAccountCreationViewModel, SmartContractAccountCreation>();

IAccountDetailsRegistry

Maps account types to detail ViewModels:

registry.Register<MnemonicAccountDetailsViewModel, MnemonicAccountDetails>();

IDashboardPluginRegistry

Maps plugin IDs to ViewModels and components. Plugins are sorted by SortOrder and rendered in the dashboard.

IAccountTypeMetadataRegistry

Maps account type strings to metadata ViewModels for display.

IComponentRegistry

Core registry that underlies all others — a generic Register<TViewModel, TComponent>() mapping.

Adding a New Wallet Screen

  1. Create a ViewModel in Nethereum.Wallet.UI.Components/YourFeature/:
public partial class MyFeatureViewModel : ObservableObject
{
[ObservableProperty] private string _inputField = "";
[ObservableProperty] private string? _inputFieldError;

public bool IsFormValid => string.IsNullOrEmpty(InputFieldError);
}
  1. Create a Localizer in the same folder.

  2. Create a Blazor component in Nethereum.Wallet.UI.Components.Blazor/YourFeature/:

@inject MyFeatureViewModel ViewModel
@inject IComponentLocalizer<MyFeatureViewModel> Localizer

<MudTextField @bind-Value="ViewModel.InputField"
Label="@Localizer.GetString(Keys.InputLabel)"
Error="@(!string.IsNullOrEmpty(ViewModel.InputFieldError))"
ErrorText="@ViewModel.InputFieldError" />
  1. Register in ServiceCollectionExtensions:
services.AddScoped<MyFeatureViewModel>();
services.TryAddSingleton<IComponentLocalizer<MyFeatureViewModel>, MyFeatureLocalizer>();
  1. To add as a dashboard plugin, implement IDashboardPluginViewModel and register with the plugin registry.

Next Steps