Nethereum.Wallet.UI.Components.Blazor.Trezor
5.8.0
Prefix Reserved
dotnet add package Nethereum.Wallet.UI.Components.Blazor.Trezor --version 5.8.0
NuGet\Install-Package Nethereum.Wallet.UI.Components.Blazor.Trezor -Version 5.8.0
<PackageReference Include="Nethereum.Wallet.UI.Components.Blazor.Trezor" Version="5.8.0" />
<PackageVersion Include="Nethereum.Wallet.UI.Components.Blazor.Trezor" Version="5.8.0" />
<PackageReference Include="Nethereum.Wallet.UI.Components.Blazor.Trezor" />
paket add Nethereum.Wallet.UI.Components.Blazor.Trezor --version 5.8.0
#r "nuget: Nethereum.Wallet.UI.Components.Blazor.Trezor, 5.8.0"
#:package Nethereum.Wallet.UI.Components.Blazor.Trezor@5.8.0
#addin nuget:?package=Nethereum.Wallet.UI.Components.Blazor.Trezor&version=5.8.0
#tool nuget:?package=Nethereum.Wallet.UI.Components.Blazor.Trezor&version=5.8.0
Nethereum.Wallet.UI.Components.Blazor.Trezor
Blazor Razor components and prompt dialogs for Trezor hardware wallet integration. Provides browser-based UI for PIN entry, passphrase prompts, and account management for Trezor devices.
Installation
dotnet add package Nethereum.Wallet.UI.Components.Blazor.Trezor
Target Framework
- net9.0
Supported Platform
- browser (WebAssembly/Server)
Dependencies
NuGet Packages
- Microsoft.AspNetCore.Components.Web 9.0.6
Nethereum Packages
- Nethereum.Wallet.UI.Components.Blazor - Base Blazor components and services
- Nethereum.Wallet.UI.Components.Trezor - Trezor ViewModels and business logic
Source: Nethereum.Wallet.UI.Components.Blazor.Trezor.csproj:15-26
Overview
This package implements browser-based Trezor hardware wallet support for Blazor applications. It provides:
- Hardware Prompts - PIN and passphrase entry dialogs for Trezor device interaction
- Account Creation UI - Razor components for creating accounts from Trezor devices
- Account Management UI - Components for viewing and managing Trezor account details
- Device Management UI - Group details views for managing multiple accounts from a single Trezor
- Service Registration - DI extensions for complete Trezor Blazor integration
Service Registration
AddTrezorWalletBlazorComponents - Complete DI registration
public static IServiceCollection AddTrezorWalletBlazorComponents(this IServiceCollection services)
{
// Account metadata
services.AddSingleton<IAccountMetadataViewModel, TrezorAccountMetadataProvider>();
// ViewModels
services.AddTransient<TrezorAccountCreationViewModel>();
services.AddTransient<IAccountCreationViewModel>(sp => sp.GetRequiredService<TrezorAccountCreationViewModel>());
services.AddTransient<TrezorVaultAccountCreationViewModel>();
services.AddTransient<IAccountCreationViewModel>(sp => sp.GetRequiredService<TrezorVaultAccountCreationViewModel>());
services.AddTransient<TrezorAccountDetailsViewModel>();
services.AddTransient<IAccountDetailsViewModel, TrezorAccountDetailsViewModel>();
services.AddTransient<TrezorGroupDetailsViewModel>();
services.AddTransient<IGroupDetailsViewModel, TrezorGroupDetailsViewModel>();
// Localizers
services.TryAddSingleton<IComponentLocalizer<TrezorAccountCreationViewModel>, TrezorAccountCreationLocalizer>();
services.TryAddSingleton<IComponentLocalizer<TrezorVaultAccountCreationViewModel>, TrezorVaultAccountCreationLocalizer>();
services.TryAddSingleton<IComponentLocalizer<TrezorPinPrompt>, TrezorPinPromptLocalizer>();
services.TryAddSingleton<IComponentLocalizer<TrezorPassphrasePrompt>, TrezorPassphrasePromptLocalizer>();
services.TryAddSingleton<IComponentLocalizer<TrezorAccountDetailsViewModel>, TrezorAccountDetailsLocalizer>();
services.TryAddSingleton<IComponentLocalizer<TrezorGroupDetailsViewModel>, TrezorGroupDetailsLocalizer>();
// Prompt handler
services.AddTransient<BlazorTrezorPromptHandler>();
services.AddTransient<ITrezorPromptHandler, BlazorTrezorPromptHandler>();
// Registry contributor
services.AddSingleton<IWalletUIRegistryContributor, TrezorWalletUIRegistryContributor>();
return services;
}
Source: Extensions/ServiceCollectionExtensions.cs:18-43
Registry Contributor
TrezorWalletUIRegistryContributor - Registers Trezor components with wallet UI system
public sealed class TrezorWalletUIRegistryContributor : IWalletUIRegistryContributor
{
public void Configure(IServiceProvider serviceProvider)
{
var creationRegistry = serviceProvider.GetService<IAccountCreationRegistry>();
creationRegistry?.Register<TrezorAccountCreationViewModel, TrezorAccountCreation>();
creationRegistry?.Register<TrezorVaultAccountCreationViewModel, TrezorVaultAccountCreation>();
var accountDetailsRegistry = serviceProvider.GetService<IAccountDetailsRegistry>();
accountDetailsRegistry?.Register<TrezorAccountDetailsViewModel, TrezorAccountDetails>();
var groupDetailsRegistry = serviceProvider.GetService<IGroupDetailsRegistry>();
groupDetailsRegistry?.Register<TrezorGroupDetailsViewModel, TrezorGroupDetails>();
}
}
Source: Extensions/TrezorWalletUIRegistryContributor.cs:11-25
Maps ViewModels to Razor components:
- TrezorAccountCreationViewModel → TrezorAccountCreation.razor
- TrezorVaultAccountCreationViewModel → TrezorVaultAccountCreation.razor
- TrezorAccountDetailsViewModel → TrezorAccountDetails.razor
- TrezorGroupDetailsViewModel → TrezorGroupDetails.razor
Source: Extensions/TrezorWalletUIRegistryContributor.cs:16-23
Trezor Prompt Handler
BlazorTrezorPromptHandler - Implements ITrezorPromptHandler for browser-based prompts
public class BlazorTrezorPromptHandler : ITrezorPromptHandler
{
private readonly IWalletDialogAccessor _dialogAccessor;
private string? _cachedPassphrase;
public async Task<string> GetPinAsync()
{
var pin = await ShowDialogAsync<TrezorPinPrompt>();
if (pin == null)
{
throw new OperationCanceledException("PIN prompt canceled by user");
}
return pin;
}
public async Task<string> GetPassphraseAsync()
{
if (_cachedPassphrase != null)
{
return _cachedPassphrase;
}
var passphrase = await ShowDialogAsync<TrezorPassphrasePrompt>();
if (passphrase == null)
{
throw new OperationCanceledException("Passphrase prompt canceled by user");
}
_cachedPassphrase = passphrase;
return passphrase;
}
public Task ButtonAckAsync(string context) => Task.CompletedTask;
}
Source: Prompts/BlazorTrezorPromptHandler.cs:14-67
Dialog Service Handling:
Waits for MudBlazor DialogService with 5-second timeout:
private async Task<IDialogService> WaitForDialogServiceAsync()
{
var deadline = DateTime.UtcNow.AddSeconds(5);
while (true)
{
var dialogService = _dialogAccessor.DialogService;
if (dialogService != null)
{
return dialogService;
}
if (DateTime.UtcNow >= deadline)
{
throw new InvalidOperationException("Dialog service is not available.");
}
await Task.Delay(50).ConfigureAwait(false);
}
}
Source: Prompts/BlazorTrezorPromptHandler.cs:122-141
Dialog Display:
Uses WalletBlazorDispatcher for thread-safe UI updates:
private async Task<string?> ShowDialogAsync<TComponent>()
where TComponent : IComponent
{
var dialogService = await WaitForDialogServiceAsync().ConfigureAwait(false);
IDialogReference dialog;
try
{
dialog = await WalletBlazorDispatcher.RunAsync(() =>
{
return dialogService.ShowAsync<TComponent>(string.Empty);
});
}
catch (Exception ex)
{
Log($"Error creating dialog {typeof(TComponent).Name}: {ex.Message}");
throw;
}
DialogResult result;
try
{
result = await WalletBlazorDispatcher.RunAsync(() =>
{
return dialog.Result;
});
}
catch (Exception ex)
{
Log($"Error awaiting dialog result {typeof(TComponent).Name}: {ex.Message}");
throw;
}
if (result == null || result.Canceled)
{
return null;
}
var data = result.Data switch
{
string text => text,
_ => string.Empty
};
return data;
}
Source: Prompts/BlazorTrezorPromptHandler.cs:69-120
Passphrase Caching:
Caches passphrase for session to avoid repeated prompts. Source: Prompts/BlazorTrezorPromptHandler.cs:44-48,58
Prompt Components
TrezorPinPrompt
Modal dialog for entering Trezor PIN using position-based entry.
Dialog Configuration:
private static readonly DialogOptions DialogOptions = new()
{
CloseOnEscapeKey = true,
FullWidth = true,
MaxWidth = MaxWidth.ExtraSmall,
Position = DialogPosition.Center
};
Source: Prompts/TrezorPinPrompt.razor:71-77
PIN Grid Layout:
private readonly string[] keypadLayout = new[] { "7", "8", "9", "4", "5", "6", "1", "2", "3" };
Source: Prompts/TrezorPinPrompt.razor:79
Displays 3x3 grid of masked buttons (●) representing Trezor device layout. Source: Prompts/TrezorPinPrompt.razor:40-51
PIN Entry:
private string pinValue = string.Empty;
private string MaskedPin => new string('•', pinValue.Length);
private bool CanSubmit => pinValue.Length >= 1;
private void AppendDigit(string digit)
{
if (!CanAppendDigit(digit))
{
return;
}
pinValue += digit;
}
private bool CanAppendDigit(string digit) => pinValue.Length < 9 && digit.Length == 1 && char.IsDigit(digit[0]);
Source: Prompts/TrezorPinPrompt.razor:80-96
Maximum Length: 9 digits Source: Prompts/TrezorPinPrompt.razor:96
Clear Function:
private void ClearPin() => pinValue = string.Empty;
Source: Prompts/TrezorPinPrompt.razor:98
Submit:
private void Submit()
{
if (CanSubmit)
{
MudDialog.Close(DialogResult.Ok(pinValue));
}
}
Source: Prompts/TrezorPinPrompt.razor:100-106
Cancel:
private void Cancel() => MudDialog.Close(DialogResult.Cancel());
Source: Prompts/TrezorPinPrompt.razor:108
Localization Keys:
- Title - "Enter Trezor PIN"
- Description - "Use the PIN layout shown on your Trezor to enter the position digits."
- Helper - "Each digit corresponds to the position displayed on your device. Never share the visual layout."
- PinHelper - "Digits 1-9 only. The order must match what you enter on your Trezor."
- ClearButtonText - "Clear"
- CancelButtonText - "Cancel"
- ConfirmButtonText - "Confirm"
Source: Prompts/TrezorPinPromptLocalizer.cs:29-41
TrezorPassphrasePrompt
Modal dialog for entering optional BIP-39 passphrase (25th word).
Dialog Configuration:
private static readonly DialogOptions DialogOptions = new()
{
CloseOnEscapeKey = true,
FullWidth = true,
MaxWidth = MaxWidth.ExtraSmall,
Position = DialogPosition.Center
};
Source: Prompts/TrezorPassphrasePrompt.razor:54-60
Passphrase Entry:
private string Passphrase { get; set; } = string.Empty;
private bool showPassphrase;
private void ToggleVisibility() => showPassphrase = !showPassphrase;
Source: Prompts/TrezorPassphrasePrompt.razor:62-65
Input Field:
<MudTextField @bind-Value="Passphrase"
InputType="@(showPassphrase ? InputType.Text : InputType.Password)"
Immediate="true"
Adornment="Adornment.End"
AdornmentIcon="@(showPassphrase ? Icons.Material.Filled.Visibility : Icons.Material.Filled.VisibilityOff)"
OnAdornmentClick="ToggleVisibility" />
Source: Prompts/TrezorPassphrasePrompt.razor:21-30
Submit:
private void Submit() => MudDialog.Close(DialogResult.Ok(Passphrase ?? string.Empty));
Source: Prompts/TrezorPassphrasePrompt.razor:67
Empty passphrase is valid (standard wallet). Source: Prompts/TrezorPassphrasePrompt.razor:67
Localization Keys:
- Title - "Enter Passphrase"
- Description - "If your Trezor wallet uses a passphrase, enter it exactly as configured."
- PassphraseLabel - "Passphrase"
- PassphrasePlaceholder - "Optional passphrase"
- PassphraseHelper - "Leave blank if you use the standard wallet."
- PassphraseInfo - "Passphrases are case sensitive. Wrong values create a different hidden wallet."
- CancelButtonText - "Cancel"
- ConfirmButtonText - "Continue"
Source: Prompts/TrezorPassphrasePromptLocalizer.cs:28-39
Account Creation Components
TrezorAccountCreation
Multi-step wizard for creating account from new Trezor device.
Form Steps:
private enum FormStep
{
Connect = 0,
Select = 1,
Confirm = 2
}
Source: WalletAccounts/TrezorAccountCreation.razor:229-234
Step 1: Connect
Wallet name entry and address discovery:
<WalletTextField @bind-Value="ViewModel.WalletName"
LabelKey="@Keys.WalletNameLabel"
PlaceholderKey="@Keys.WalletNamePlaceholder"
HelpKey="@Keys.WalletNameHelper" />
<MudNumericField T="int"
@bind-Value="ViewModel.DiscoveryStartIndex"
Label="@Localizer.GetString(Keys.StartIndexLabel)"
Min="0" />
<MudButton OnClick="HandleScanAddresses">
@Localizer.GetString(Keys.ScanButtonText)
</MudButton>
<MudNumericField T="int"
@bind-Value="ViewModel.SingleIndexInput"
Label="@Localizer.GetString(Keys.SingleIndexLabel)"
Min="0" />
<MudButton OnClick="HandleLoadSingleIndex">
@Localizer.GetString(Keys.LoadIndexButtonText)
</MudButton>
Source: WalletAccounts/TrezorAccountCreation.razor:33-98
Scan Addresses:
private async Task HandleScanAddresses()
{
try
{
isScanning = true;
scanError = null;
StateHasChanged();
await ViewModel.DiscoverAsync(System.Threading.CancellationToken.None);
if (ViewModel.Previews.Any())
{
CurrentStep = FormStep.Select;
}
}
catch (Exception ex)
{
scanError = ex.Message;
}
finally
{
isScanning = false;
StateHasChanged();
}
}
Source: WalletAccounts/TrezorAccountCreation.razor:282-310
Scans 5 addresses starting from DiscoveryStartIndex.
Source: ViewModels/TrezorAccountCreationViewModel.cs:99
Load Single Index:
private async Task HandleLoadSingleIndex()
{
try
{
isScanning = true;
scanError = null;
StateHasChanged();
await ViewModel.LoadSingleIndexAsync(System.Threading.CancellationToken.None);
if (ViewModel.Previews.Any())
{
CurrentStep = FormStep.Select;
}
}
catch (Exception ex)
{
scanError = ex.Message;
}
finally
{
isScanning = false;
StateHasChanged();
}
}
Source: WalletAccounts/TrezorAccountCreation.razor:312-340
Initialization:
protected override void OnInitialized()
{
ViewModel.Reset();
ViewModel.PrepareForNewDevice();
SetupFormSteps();
}
Source: WalletAccounts/TrezorAccountCreation.razor:241-246
Generates unique device ID and default wallet name. Source: ViewModels/TrezorAccountCreationViewModel.cs:82-93
Step 2: Select
Address selection from previews:
@foreach (var preview in ViewModel.Previews)
{
<MudCard Class="@GetPreviewCardClasses(preview)"
@onclick="@(() => SelectPreview(preview))">
<MudCardContent>
<MudChip Color="@(IsSelected(preview) ? Color.Primary : Color.Default)">
@($"#{preview.Index}")
</MudChip>
<MudText Typo="Typo.caption">
@GetDerivationPath(preview.Index)
</MudText>
<WalletAddressDisplay Address="@preview.Address" />
</MudCardContent>
</MudCard>
}
Source: WalletAccounts/TrezorAccountCreation.razor:125-160
Derivation Path Display:
private static string GetDerivationPath(uint index) => $"m/44'/60'/{index}'/0/0";
Source: WalletAccounts/TrezorAccountCreation.razor:362
BIP-44 Ethereum path format. Source: WalletAccounts/TrezorAccountCreation.razor:362
Selection:
private void SelectPreview(TrezorDerivationPreview preview)
{
ViewModel.SelectedIndex = preview.Index;
ViewModel.SelectedAddress = preview.Address;
}
private bool IsSelected(TrezorDerivationPreview preview) =>
ViewModel.SelectedAddress == preview.Address;
Source: WalletAccounts/TrezorAccountCreation.razor:364-371
Step 3: Confirm
Account label and summary:
<WalletTextField @bind-Value="ViewModel.AccountLabel"
LabelKey="@Keys.AccountLabelField"
HelpKey="@Keys.AccountLabelHelper" />
<MudPaper>
<MudStack Spacing="2">
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.SelectedAddressLabel)</MudText>
<MudText Typo="Typo.body1">@ViewModel.SelectedAddress</MudText>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.DeviceSummaryLabel)</MudText>
<MudText Typo="Typo.body1">@ViewModel.DeviceId</MudText>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.IndexSummaryLabel)</MudText>
<MudText Typo="Typo.body1">@ViewModel.SelectedIndex</MudText>
</MudStack>
</MudPaper>
Source: WalletAccounts/TrezorAccountCreation.razor:188-212
Create Account:
private async Task CreateAccount()
{
if (!ViewModel.CanCreateAccount)
{
return;
}
if (OnAccountCreated.HasDelegate)
{
await OnAccountCreated.InvokeAsync();
}
}
Source: WalletAccounts/TrezorAccountCreation.razor:411-422
Component Parameters:
[Parameter] public required TrezorAccountCreationViewModel ViewModel { get; set; }
[Parameter] public EventCallback OnAccountCreated { get; set; }
[Parameter] public EventCallback OnBackToAccountSelection { get; set; }
[Parameter] public EventCallback OnBackToLogin { get; set; }
[Parameter] public bool ShowBackToAccountSelection { get; set; } = true;
[Parameter] public bool ShowBackToLogin { get; set; }
[Parameter] public bool IsCompactMode { get; set; }
[Parameter] public int ComponentWidth { get; set; } = 400;
Source: WalletAccounts/TrezorAccountCreation.razor:220-227
Responsive Layout:
private bool IsCompactLayout => IsCompactMode || ComponentWidth < 600;
private int GetPreviewSpacing() => IsCompactLayout ? 1 : 2;
Source: WalletAccounts/TrezorAccountCreation.razor:342-344
TrezorVaultAccountCreation
Multi-step wizard for deriving account from existing Trezor device in vault.
Form Steps:
private enum FormStep
{
SelectDevice = 0,
Discover = 1,
Confirm = 2
}
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:246-251
Step 1: Select Device
Choose from available Trezor devices:
<WalletSelect T="string"
@bind-Value="ViewModel.SelectedDeviceId"
Items="@ViewModel.Devices.Select(d => d.DeviceId)"
LabelKey="@Keys.SelectDeviceLabel"
DisplaySelector="@GetDeviceDisplayName" />
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:53-60
Device Display:
private string GetDeviceDisplayName(string? deviceId) => ViewModel.GetDeviceDisplayName(deviceId);
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:436
Returns: "Device Name (3 accounts)" format.
Source: ViewModels/TrezorVaultAccountCreationViewModel.cs:162-164
Initialization:
protected override async Task OnInitializedAsync()
{
ViewModel.Reset();
SetupFormSteps();
await ViewModel.LoadDevicesAsync();
}
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:258-263
Loads device summary with account counts and next suggested indices. Source: ViewModels/TrezorVaultAccountCreationViewModel.cs:67-109
Empty State:
@if (!ViewModel.HasDevices)
{
<WalletInfoCard Severity="WalletInfoCard.WalletInfoSeverity.Info"
Title="@Localizer.GetString(Keys.NoDevicesTitle)"
Description="@Localizer.GetString(Keys.NoDevicesDescription)" />
}
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:15-22
Only visible if vault contains at least one Trezor account. Source: ViewModels/TrezorVaultAccountCreationViewModel.cs:56-63
Step 2: Discover
Similar to TrezorAccountCreation but with pre-selected device:
<MudNumericField T="int"
@bind-Value="ViewModel.DiscoveryStartIndex" />
<MudButton OnClick="HandleScanAddresses">
@Localizer.GetString(Keys.ScanButtonText)
</MudButton>
<MudNumericField T="int"
@bind-Value="ViewModel.SingleIndexInput" />
<MudButton OnClick="HandleLoadSingleIndex">
@Localizer.GetString(Keys.LoadIndexButtonText)
</MudButton>
Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:76-127
Auto-suggests next available index for selected device. Source: ViewModels/TrezorVaultAccountCreationViewModel.cs:196-208
Step 3: Confirm
Same as TrezorAccountCreation - account label and summary. Source: WalletAccounts/Trezor/TrezorVaultAccountCreation.razor:195-221
Account Management Components
TrezorAccountDetails
View and manage individual Trezor account details.
View Sections:
private enum ViewSection
{
Overview,
EditName
}
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:159-163
Overview Section:
<WalletAddressDisplay Address="@trezorAccount.Address"
ShowFullAddress="true" />
<MudPaper Class="wallet-detail-card">
<MudStack Spacing="2">
<MudText Typo="Typo.subtitle1">@trezorAccount.Name</MudText>
<MudChip>@($"#{trezorAccount.Index}")</MudChip>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.DeviceIdLabel)</MudText>
<MudText Typo="Typo.body2">@trezorAccount.DeviceId</MudText>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.IndexLabel)</MudText>
<MudText Typo="Typo.body2">@trezorAccount.Index (@FormatDerivationPath(trezorAccount.Index))</MudText>
</MudStack>
</MudPaper>
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:65-106
Derivation Path:
private static string FormatDerivationPath(uint index) => $"m/44'/60'/{index}'/0/0";
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:281
Action Buttons:
<WalletBarActionButton Icon="@Icons.Material.Filled.Edit"
Text="@Localizer.GetString(Keys.AccountNameLabel)"
OnClick="@(() => NavigateToSection(ViewSection.EditName))" />
<WalletBarActionButton Icon="@Icons.Material.Filled.Delete"
Text="@Localizer.GetString(Keys.RemoveAccountButton)"
Class="wallet-button-danger"
OnClick="ViewModel.RemoveAccountAsync" />
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:32-38
Edit Name Section:
<WalletTextField @bind-Value="ViewModel.EditingAccountName"
LabelKey="@TrezorAccountDetailsLocalizer.Keys.AccountNameLabel"
PlaceholderKey="@TrezorAccountDetailsLocalizer.Keys.AccountNamePlaceholder"
Required="true" />
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:112-118
Save Account Name:
private async Task HandlePrimaryAction()
{
await ViewModel.SaveAccountNameAsync();
if (string.IsNullOrEmpty(ViewModel.ErrorMessage))
{
currentSection = ViewSection.Overview;
}
}
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:214-221
Initialization:
protected override async Task OnInitializedAsync()
{
if (ViewModel is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged += OnViewModelPropertyChanged;
}
if (Account != null)
{
await ViewModel.InitializeAsync(Account);
}
}
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:167-178
Subscribes to property changes for reactive UI updates. Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:169-172
Component Parameters:
[Parameter] public IWalletAccount? Account { get; set; }
[Parameter] public bool IsCompactMode { get; set; }
[Parameter] public int ComponentWidth { get; set; } = 400;
[Parameter] public EventCallback OnExit { get; set; }
Source: WalletAccounts/Trezor/TrezorAccountDetails.razor:154-157
TrezorGroupDetails
Manage all accounts from a single Trezor device.
Overview Card:
<MudCard Class="wallet-detail-card">
<MudCardContent>
<MudChip Color="Color.Primary">
@Localizer.GetString(Keys.HardwareTypeLabel)
</MudChip>
<MudChip Color="Color.Secondary">
@($"{ViewModel.Accounts.Count} {Localizer.GetString(Keys.AccountCountLabel)}")
</MudChip>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.NextIndexLabel)</MudText>
<MudText Typo="Typo.h6">@ViewModel.NextIndex</MudText>
<MudText Typo="Typo.caption">@Localizer.GetString(Keys.AccountListTitle)</MudText>
<MudText Typo="Typo.h6">@ViewModel.Accounts.Count</MudText>
</MudCardContent>
</MudCard>
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:58-95
Action Buttons:
<WalletBarActionButton Icon="@Icons.Material.Filled.Edit"
Text="@Localizer.GetString(Keys.RenameButton)"
Disabled="@(ViewModel.IsLoading || ViewModel.IsAdding)"
OnClick="@ViewModel.BeginEditDeviceLabel" />
<WalletBarActionButton Icon="@Icons.Material.Filled.Refresh"
Text="@Localizer.GetString(Keys.RefreshButton)"
OnClick="ViewModel.RefreshAsync" />
<WalletBarActionButton Icon="@Icons.Material.Filled.Add"
Text="@Localizer.GetString(Keys.AddAccountButton)"
OnClick="ViewModel.AddNextAccountAsync" />
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:29-43
Device Label Editing:
@if (ViewModel.IsEditingLabel)
{
<WalletTextField @bind-Value="ViewModel.EditingDeviceLabel"
LabelKey="@TrezorGroupDetailsLocalizer.Keys.DeviceLabelField"
HelpKey="@TrezorGroupDetailsLocalizer.Keys.DeviceLabelHelper" />
<MudButton OnClick="ViewModel.SaveDeviceLabelAsync">
@Localizer.GetString(Keys.SaveDeviceLabelButton)
</MudButton>
<MudButton OnClick="@ViewModel.CancelEditDeviceLabel">
@Localizer.GetString(Keys.CancelEditButton)
</MudButton>
}
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:98-119
Account List:
@foreach (var account in ViewModel.Accounts)
{
<AccountCard Account="account"
AccountDisplayName="@GetAccountDisplayName(account)"
FormattedAddress="@account.Address"
IsCompactMode="@IsCompactLayout" />
<MudText Typo="Typo.caption">@FormatDerivationPath(account.Index)</MudText>
}
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:126-140
Displays derivation path below each account card. Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:137
Account Display Name:
private static string GetAccountDisplayName(TrezorWalletAccount account) =>
string.IsNullOrWhiteSpace(account.Name) ? $"Account {account.Index}" : account.Name;
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:229-230
Derivation Path:
private static string FormatDerivationPath(uint index) => $"m/44'/60'/0'/0/{index}";
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:232
Initialization:
protected override async Task OnInitializedAsync()
{
if (ViewModel is INotifyPropertyChanged notifyPropertyChanged)
{
notifyPropertyChanged.PropertyChanged += OnViewModelPropertyChanged;
}
if (!string.IsNullOrEmpty(GroupId))
{
await ViewModel.InitializeAsync(GroupId, Accounts);
}
}
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:184-195
Component Parameters:
[Parameter] public string? GroupId { get; set; }
[Parameter] public IReadOnlyList<IWalletAccount> Accounts { get; set; } = Array.Empty<IWalletAccount>();
[Parameter] public bool IsCompactMode { get; set; }
[Parameter] public int ComponentWidth { get; set; } = 400;
[Parameter] public EventCallback OnExit { get; set; }
Source: WalletAccounts/Trezor/TrezorGroupDetails.razor:178-182
Localization
All components support English (en-US) and Spanish (es-ES) localization.
TrezorPinPromptLocalizer - 10 localization keys TrezorPassphrasePromptLocalizer - 9 localization keys
Plus all ViewModels have their own localizers from Nethereum.Wallet.UI.Components.Trezor:
- TrezorAccountCreationLocalizer - 40+ keys
- TrezorVaultAccountCreationLocalizer - 38+ keys
- TrezorAccountDetailsLocalizer - 30+ keys
- TrezorGroupDetailsLocalizer - 34+ keys
Source: Extensions/ServiceCollectionExtensions.cs:30-35
Usage Example
Basic Setup
// Program.cs or Startup.cs
builder.Services.AddNethereumWalletBlazorComponents(); // Base Blazor components
builder.Services.AddTrezorWalletBlazorComponents(); // Trezor Blazor support
// Add Trezor device service
builder.Services.AddScoped<ITrezorDeviceDiscoveryService, TrezorDeviceDiscoveryService>();
builder.Services.AddScoped<TrezorWalletAccountService>();
The registry contributor automatically registers all Trezor components when the service provider is built.
Component Usage
New Device Creation:
<TrezorAccountCreation ViewModel="@trezorAccountCreationViewModel"
OnAccountCreated="HandleAccountCreated"
OnBackToAccountSelection="BackToSelection"
ShowBackToAccountSelection="true" />
Existing Device Account:
<TrezorVaultAccountCreation ViewModel="@trezorVaultViewModel"
OnAccountCreated="HandleAccountCreated"
OnBackToAccountSelection="BackToSelection" />
Account Details:
<TrezorAccountDetails Account="@selectedAccount"
OnExit="HandleExit" />
Device Group:
<TrezorGroupDetails GroupId="@deviceId"
Accounts="@trezorAccounts"
OnExit="HandleExit" />
Related Packages
Dependencies
- Nethereum.Wallet.UI.Components.Trezor - Trezor ViewModels and business logic
- Nethereum.Wallet.UI.Components.Blazor - Base Blazor components
- Nethereum.Wallet.Trezor - Trezor device communication
- Nethereum.Signer.Trezor.Abstractions - ITrezorPromptHandler interface
See Also
- Nethereum.Wallet.UI.Components - Platform-agnostic MVVM components
- Nethereum.Wallet - Core wallet functionality
Additional Resources
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.AspNetCore.Components.Web (>= 9.0.6)
- Nethereum.Wallet.UI.Components.Blazor (>= 5.8.0)
- Nethereum.Wallet.UI.Components.Trezor (>= 5.8.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 5.8.0 | 40 | 1/6/2026 |