Nethereum.Wallet.UI.Components.Maui
5.8.0
Prefix Reserved
dotnet add package Nethereum.Wallet.UI.Components.Maui --version 5.8.0
NuGet\Install-Package Nethereum.Wallet.UI.Components.Maui -Version 5.8.0
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" Version="5.8.0" />
<PackageVersion Include="Nethereum.Wallet.UI.Components.Maui" Version="5.8.0" />
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" />
paket add Nethereum.Wallet.UI.Components.Maui --version 5.8.0
#r "nuget: Nethereum.Wallet.UI.Components.Maui, 5.8.0"
#:package Nethereum.Wallet.UI.Components.Maui@5.8.0
#addin nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=5.8.0
#tool nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=5.8.0
Nethereum.Wallet.UI.Components.Maui
Cross-platform MAUI integration package providing platform-specific storage implementations and Blazor WebView hosting for Nethereum wallet components on mobile and desktop applications.
Package Information
- Target Frameworks: net9.0-android, net9.0-ios, net9.0-maccatalyst, net9.0-windows10.0.19041.0
- Platform: .NET MAUI (Multi-platform App UI)
- UI Technology: Blazor WebView (hybrid web/native)
- Package ID: Nethereum.Wallet.UI.Components.Maui
Supported Platforms
- Android - Android 5.0 (API 21) and higher
- iOS - iOS 11.0 and higher
- macOS - macOS 10.15 and higher (via Mac Catalyst)
- Windows - Windows 10 version 1903 (build 19041) and higher
Dependencies
NuGet Packages:
Microsoft.AspNetCore.Components.WebView.Maui- Blazor WebView hostingMicrosoft.Extensions.Logging.Debug9.0.0 - Debug loggingMicrosoft.Maui.Controls- MAUI framework
Project References:
Nethereum.Wallet.RpcRequests- RPC request handlersNethereum.Wallet.UI.Components- Platform-agnostic UI componentsNethereum.Wallet- Core wallet services
Note: This package does NOT reference Nethereum.Wallet.UI.Components.Blazor directly. Your application must add both packages to use Blazor wallet components in MAUI.
Installation
# Install MAUI storage services
dotnet add package Nethereum.Wallet.UI.Components.Maui
# Install Blazor UI components separately
dotnet add package Nethereum.Wallet.UI.Components.Blazor
Quick Start
1. Configure MauiProgram.cs
using Nethereum.Wallet.UI.Components.Maui.Extensions;
using Nethereum.Wallet.UI.Components.Blazor.Extensions;
public static class MauiProgram
{
public static MauiApp CreateMauiProgram()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Register MAUI wallet services (storage, vault, initialization)
builder.AddNethereumWalletMauiComponents(options =>
{
options.DefaultChainId = 1; // Ethereum Mainnet
options.ConfigureUi = config =>
{
config.ApplicationName = "My Crypto Wallet";
config.LogoPath = "/logo.png";
};
});
// Register Blazor wallet UI components
builder.Services.AddNethereumWalletUI();
return builder.Build();
}
}
2. Create Blazor WebView Page
MainPage.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui"
x:Class="MyApp.MainPage">
<blazor:BlazorWebView HostPage="wwwroot/index.html">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Main}" />
</blazor:BlazorWebView.RootComponents>
</blazor:BlazorWebView>
</ContentPage>
3. Create Blazor Component
Components/Main.razor:
@using Nethereum.Wallet.UI.Components.Blazor.NethereumWallet
<NethereumWallet OnConnected="@HandleConnected"
Width="100%"
Height="100%" />
@code {
private void HandleConnected()
{
Console.WriteLine("Wallet connected!");
}
}
4. Create index.html
wwwroot/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Crypto Wallet</title>
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
</head>
<body>
<div id="app">Loading...</div>
<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>
Architecture
Platform Services
The package provides two core service implementations using MAUI APIs:
MauiSecureStorageWalletVaultService
Implements IWalletVaultService using MAUI SecureStorage for encrypted vault persistence.
Location: Services/MauiSecureStorageWalletVaultService.cs:7-41
Platform-Specific Security:
- Android: KeyStore-backed encryption (hardware-backed when available)
- iOS/macOS: Keychain services with access control
- Windows: Windows Credential Locker
Storage Key: wallet.vault
Methods:
// Check if encrypted vault exists in secure storage
public override async Task<bool> VaultExistsAsync()
Lines 11-15
// Retrieve encrypted vault data from secure storage
protected override Task<string?> GetEncryptedAsync()
Lines 17-20
// Save encrypted vault data to secure storage
protected override Task SaveEncryptedAsync(string encrypted)
Lines 22-25
// Remove vault from secure storage (with error handling)
protected override Task ResetStorageAsync()
Lines 27-39
Exception handling in ResetStorageAsync prevents platform-specific errors from crashing logout flows.
Inherited Methods (from WalletVaultServiceBase):
UnlockAsync(password)- Decrypt and load vault into memoryCreateNewAsync(password)- Create new encrypted vaultGetAccountsAsync()- Get all accounts from vaultSaveAsync(password)- Encrypt and save vaultLockAsync()- Clear in-memory vault dataResetAsync()- Delete vault completely
MauiPreferencesWalletStorageService
Implements IWalletStorageService using MAUI Preferences for persistent storage of wallet configuration, networks, transactions, and DApp permissions.
Location: Services/MauiPreferencesWalletStorageService.cs:18-441
Storage Keys:
private const string UserNetworksKey = "wallet.userNetworks";
private const string SelectedNetworkKey = "wallet.selectedNetwork";
private const string SelectedAccountKey = "wallet.selectedAccount";
private const string TransactionsPrefix = "wallet.transactions.";
private const string RpcSelectionPrefix = "wallet.rpcSelection.";
private const string ActiveRpcsPrefix = "wallet.activeRpcs.";
private const string CustomRpcsPrefix = "wallet.customRpcs.";
private const string RpcHealthPrefix = "wallet.rpcHealth.";
private const string DappPermissionsIndexKey = "wallet.dappPermissions.index";
private const string DappPermissionsPrefix = "wallet.dappPermissions.";
private const string NetworkPreferencePrefix = "wallet.networkPref.";
Lines 20-30
JSON Serialization Configuration:
public MauiPreferencesWalletStorageService()
{
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
};
_jsonOptions.Converters.Add(new BigIntegerConverter());
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
}
Lines 34-43
Key Methods:
Network Management (Lines 45-84):
Task<List<ChainFeature>> GetUserNetworksAsync()
Task SaveUserNetworksAsync(List<ChainFeature> networks)
Task SaveUserNetworkAsync(ChainFeature network) // Upsert single network
Task DeleteUserNetworkAsync(BigInteger chainId)
Task<bool> UserNetworksExistAsync()
Task ClearUserNetworksAsync()
RPC Management (Lines 86-138):
Task SetRpcHealthCacheAsync(string rpcUrl, RpcEndpointHealthCache healthInfo)
Task<RpcEndpointHealthCache?> GetRpcHealthCacheAsync(string rpcUrl)
Task<List<string>> GetActiveRpcsAsync(BigInteger chainId)
Task SetActiveRpcsAsync(BigInteger chainId, List<string> rpcUrls)
Task RemoveRpcAsync(BigInteger chainId, string rpcUrl)
Task<List<string>> GetCustomRpcsAsync(BigInteger chainId)
Task SaveCustomRpcAsync(BigInteger chainId, string rpcUrl)
Task RemoveCustomRpcAsync(BigInteger chainId, string rpcUrl)
RPC health cache key encoding (Lines 401-405):
private static string GetRpcHealthKey(string rpcUrl)
{
var encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(rpcUrl));
return RpcHealthPrefix + encoded;
}
Account Selection (Lines 140-172):
Task SetSelectedNetworkAsync(long chainId)
Task<long?> GetSelectedNetworkAsync()
Task SetSelectedAccountAsync(string accountAddress)
Task<string?> GetSelectedAccountAsync()
RPC Configuration (Lines 174-182):
Task<RpcSelectionConfiguration?> GetRpcSelectionConfigAsync(BigInteger chainId)
Task SaveRpcSelectionConfigAsync(RpcSelectionConfiguration config)
Transaction Management (Lines 184-244):
Task<List<TransactionInfo>> GetPendingTransactionsAsync(BigInteger chainId)
Task<List<TransactionInfo>> GetRecentTransactionsAsync(BigInteger chainId)
Task SaveTransactionAsync(BigInteger chainId, TransactionInfo transaction)
Task UpdateTransactionStatusAsync(BigInteger chainId, string hash, TransactionStatus status)
Task<TransactionInfo?> GetTransactionByHashAsync(BigInteger chainId, string hash)
Task DeleteTransactionAsync(BigInteger chainId, string hash)
Task ClearTransactionsAsync(BigInteger chainId)
Transaction status updates automatically set ConfirmedAt timestamp (Lines 217-220):
if (status == TransactionStatus.Confirmed && !existing.ConfirmedAt.HasValue)
{
existing.ConfirmedAt = DateTime.UtcNow;
}
DApp Permissions (Lines 246-295):
Task<List<DappPermission>> GetDappPermissionsAsync(string? accountAddress = null)
Task AddDappPermissionAsync(string accountAddress, string origin)
Task RemoveDappPermissionAsync(string accountAddress, string origin)
Permissions are normalized for consistent matching (Lines 395-399):
private static string NormalizeAccount(string account)
=> account.Trim().ToLowerInvariant();
private static string NormalizeOrigin(string origin)
=> origin.Trim().ToLowerInvariant();
Permission indexing enables efficient "all permissions" queries (Lines 327-336):
private void EnsureAccountIndexed(string accountAddress)
{
var index = GetStringList(DappPermissionsIndexKey);
var normalized = NormalizeAccount(accountAddress);
if (!index.Contains(normalized, StringComparer.OrdinalIgnoreCase))
{
index.Add(normalized);
SetStringList(DappPermissionsIndexKey, index);
}
}
Network Preferences (Lines 297-312):
Task SaveNetworkPreferenceAsync(string key, bool value)
Task<bool?> GetNetworkPreferenceAsync(string key)
BigInteger JSON Converter (Lines 407-439):
private sealed class BigIntegerConverter : JsonConverter<BigInteger>
{
public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
var value = reader.GetString();
if (BigInteger.TryParse(value, out var result))
{
return result;
}
}
else if (reader.TokenType == JsonTokenType.Number)
{
if (reader.TryGetInt64(out var longValue))
{
return new BigInteger(longValue);
}
}
throw new JsonException("Unable to convert value to BigInteger");
}
public override void Write(Utf8JsonWriter writer, BigInteger value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
Handles BigInteger serialization as strings to prevent precision loss.
Configuration
MauiWalletComponentOptions
Configuration options for MAUI wallet initialization.
Location: Options/MauiWalletComponentOptions.cs:5-10
public class MauiWalletComponentOptions
{
public long DefaultChainId { get; set; } = 1; // Ethereum Mainnet
public Action<NethereumWalletUIConfiguration>? ConfigureUi { get; set; }
}
Usage:
builder.AddNethereumWalletMauiComponents(options =>
{
options.DefaultChainId = 137; // Polygon
options.ConfigureUi = config =>
{
config.ApplicationName = "My Wallet";
config.LogoPath = "/logo.png";
config.WalletConfig.Security.MinPasswordLength = 12;
};
});
Service Registration
AddNethereumWalletMauiComponents Extension Method
Main registration method for MAUI wallet services.
Location: Extensions/MauiAppBuilderExtensions.cs:50-151
Signature:
public static MauiAppBuilder AddNethereumWalletMauiComponents(
this MauiAppBuilder builder,
Action<MauiWalletComponentOptions>? configure = null)
Registered Services:
Storage Services (Lines 57-61):
IEncryptionStrategy -> BouncyCastleAes256EncryptionStrategy (Singleton)
IWalletVaultService -> MauiSecureStorageWalletVaultService (Singleton)
IWalletStorageService -> MauiPreferencesWalletStorageService (Singleton)
IWalletConfigurationService -> InMemoryWalletConfigurationService (Singleton)
IDappPermissionService -> DefaultDappPermissionService (Singleton)
Chain Management (Lines 63-112):
IChainFeaturesService -> ChainFeaturesService.Current (Singleton)
Configuration (Lines 65-112):
EnableExternalChainList: true- Fetch chains from external sourcesStrategy: PreconfiguredEnrich- Merge external with preconfigured chainsPostProcessPreconfigured- Adds localhost (1337) and enriches with default RPCs
RPC Services (Lines 114-117):
IRpcEndpointService -> RpcEndpointService (Singleton)
IRpcClientFactory -> RpcClientFactory (Scoped)
Wallet Services (Lines 117-127):
services.AddWalletPromptServices(ServiceLifetime.Singleton)
services.AddNethereumWalletServicesSingleton(options.DefaultChainId)
ICoreWalletAccountService -> Factory-created from vault (Singleton)
UI Services (Lines 128-148):
INethereumWalletUIConfiguration -> Configured singleton
IDashboardNavigationService -> DashboardNavigationService (Scoped)
MAUI Integration (Lines 147-148):
services.AddMauiBlazorWebView()
IMauiInitializeService -> WalletRegistryInitializer (Singleton)
Default UI Configuration (Lines 128-143):
ApplicationName = "Nethereum Wallet"
ShowApplicationName = true
ShowLogo = true
LogoPath = "/nethereum-logo.png"
WelcomeLogoPath = "/nethereum-logo-large.png"
DrawerBehavior = DrawerBehavior.Responsive
ResponsiveBreakpoint = 1024
SidebarWidth = 200
WalletConfig.Security.MinPasswordLength = 8
WalletConfig.Behavior.EnableWalletReset = true
WalletConfig.AllowPasswordVisibilityToggle = true
User-provided configuration via options.ConfigureUi overrides defaults.
Default RPC Seeds
The package includes fallback RPC endpoints for 16 networks.
Location: Extensions/MauiAppBuilderExtensions.cs:28-46
private static readonly Dictionary<long, string[]> DefaultRpcSeed = new()
{
{ 1, new[]{ "https://rpc.mevblocker.io", "https://eth.llamarpc.com" } },
{ 10, new[]{ "https://mainnet.optimism.io" } },
{ 56, new[]{ "https://bsc-dataseed.binance.org" } },
{ 100, new[]{ "https://rpc.gnosischain.com" } },
{ 137, new[]{ "https://polygon-rpc.com" } },
{ 324, new[]{ "https://mainnet.era.zksync.io" } },
{ 42161, new[]{ "https://arb1.arbitrum.io/rpc" } },
{ 42220, new[]{ "https://forno.celo.org" } },
{ 43114, new[]{ "https://api.avax.network/ext/bc/C/rpc" } },
{ 59144, new[]{ "https://linea-mainnet.infura.io/v3/" } },
{ 8453, new[]{ "https://mainnet.base.org" } },
{ 84532, new[]{ "https://sepolia.base.org" } },
{ 11155111, new[]{ "https://rpc.sepolia.org" } },
{ 11155420, new[]{ "https://optimism-sepolia.blockpi.network/v1/rpc/public" } },
{ 1337, new[]{ "http://localhost:8545" } },
{ 421614, new[]{ "https://sepolia-rollup.arbitrum.io/rpc" } }
};
Supported Networks:
- Ethereum Mainnet (1)
- Optimism (10)
- BSC (56)
- Gnosis (100)
- Polygon (137)
- zkSync Era (324)
- Arbitrum One (42161)
- Celo (42220)
- Avalanche C-Chain (43114)
- Linea (59144)
- Base (8453)
- Base Sepolia (84532)
- Sepolia (11155111)
- Optimism Sepolia (11155420)
- Localhost (1337)
- Arbitrum Sepolia (421614)
These RPCs are automatically added to networks during initialization if no RPCs are configured.
Automatic Initialization
WalletRegistryInitializer
Internal class implementing IMauiInitializeService to initialize wallet on app startup.
Location: Extensions/MauiAppBuilderExtensions.cs:156-308
Initialization Steps:
1. RPC Handler Registration (Lines 160-164):
var registry = services.GetService<RpcHandlerRegistry>();
if (registry != null)
{
WalletRpcHandlerRegistration.RegisterAll(registry);
}
Registers all wallet RPC request handlers for EIP-1193 compatibility.
2. Default Network Preloading (Lines 179-195):
var defaultChainIds = new long[] { 1, 10, 100, 137, 56, 42161, 42220, 43114, 59144, 8453, 1337 };
foreach (var chainId in defaultChainIds)
{
try
{
var feature = await chainManagement.GetChainAsync(new BigInteger(chainId));
if (feature != null)
{
await configuration.AddOrUpdateChainAsync(feature);
}
}
catch
{
// Ignore failures when preloading optional networks
}
}
Preloads 11 popular networks into IWalletConfigurationService for immediate availability.
3. User Network Storage Initialization (Lines 197-258):
If no user networks exist, seeds storage with default networks (Lines 206-236):
if (networks.Count == 0)
{
foreach (var chainId in defaultChainIds)
{
try
{
var seedFeature = await chainManagement.GetChainAsync(new BigInteger(chainId));
if (seedFeature == null) continue;
// Enrich with default RPCs if missing
if (seedFeature.HttpRpcs == null || seedFeature.HttpRpcs.Count == 0)
{
if (MauiAppBuilderExtensions.GetDefaultRpcSeed().TryGetValue(chainId, out var seedUrls))
{
seedFeature.HttpRpcs = seedUrls.ToList();
}
}
await storage.SaveUserNetworkAsync(seedFeature);
updated = true;
}
catch { /* Ignore seeding failures */ }
}
}
Enriches existing networks with default RPCs if missing (Lines 237-247):
foreach (var network in networks)
{
var chainId = (long)network.ChainId;
if ((network.HttpRpcs == null || network.HttpRpcs.Count == 0) &&
MauiAppBuilderExtensions.GetDefaultRpcSeed().TryGetValue(chainId, out var urls))
{
network.HttpRpcs = urls.ToList();
await storage.SaveUserNetworkAsync(network);
updated = true;
}
}
4. RPC Configuration Seeding (Lines 260-295):
Creates default RpcSelectionConfiguration for networks without RPC configuration:
foreach (var network in networks)
{
try
{
var chainId = network.ChainId;
var config = await rpcEndpointService.GetConfigurationAsync(chainId);
if (config == null || config.SelectedRpcUrls == null || config.SelectedRpcUrls.Count == 0)
{
// Get candidate URLs from network or seed
var candidateUrls = (network.HttpRpcs ?? new List<string>())
.Where(u => !string.IsNullOrWhiteSpace(u))
.ToList();
if (candidateUrls.Count == 0 &&
MauiAppBuilderExtensions.GetDefaultRpcSeed().TryGetValue((long)chainId, out var seedUrls))
{
candidateUrls = seedUrls.ToList();
}
if (candidateUrls.Count > 0)
{
var rpcConfig = new RpcSelectionConfiguration
{
ChainId = chainId,
Mode = RpcSelectionMode.Single,
SelectedRpcUrls = new List<string> { candidateUrls[0] },
LastModified = DateTime.UtcNow
};
await rpcEndpointService.SaveConfigurationAsync(rpcConfig);
}
}
}
catch { /* Ignore configuration seeding failures */ }
}
5. Active Chain Selection (Lines 297-305):
var activeChainId = options?.DefaultChainId ?? 1;
try
{
await configuration.SetActiveChainAsync(new BigInteger(activeChainId));
}
catch { /* Ignore failures */ }
Error Handling:
All initialization steps are wrapped in try-catch blocks to prevent app crashes. Failures are logged but don't stop initialization. The wallet UI can prompt users to configure missing settings.
Background Execution:
Initialization runs in Task.Run to avoid blocking app startup (Line 177):
_ = Task.Run(async () =>
{
// Initialization code
});
Platform-Specific Considerations
Android
SecureStorage:
- Uses Android KeyStore for encryption key storage
- Hardware-backed encryption on supported devices
- Falls back to software KeyStore on older devices
Preferences:
- Stored in
SharedPreferences - Automatically backed up if app backup is enabled
Permissions:
- No special permissions required for storage
iOS/macOS
SecureStorage:
- Uses iOS/macOS Keychain
kSecAttrAccessibleWhenUnlockedaccess control- Syncs across devices if iCloud Keychain is enabled
Preferences:
- Stored in
NSUserDefaults - Automatically backed up via iCloud
Privacy:
- No App Tracking Transparency (ATT) permission required
Windows
SecureStorage:
- Uses Windows Credential Locker (PasswordVault)
- User-specific credential storage
- Encrypted at rest
Preferences:
- Stored in application local storage
- Roaming settings not used (privacy consideration)
Security
Vault Encryption
Encryption Algorithm: AES-256 (BouncyCastle implementation) Key Derivation: PBKDF2 from user password Storage: Platform secure storage (KeyStore/Keychain/Credential Locker)
Security Flow:
- User provides password
- PBKDF2 derives encryption key
- Vault JSON serialized and encrypted with AES-256
- Encrypted blob stored in platform secure storage
- Secure storage manages key protection
DApp Permissions
DApp permissions are stored in unencrypted Preferences (not sensitive data):
- Origin normalization prevents case-sensitivity issues
- Timestamp tracking for permission grant time
- Per-account permission isolation
Recommendation: Regularly review and revoke unused DApp permissions.
Network Preferences
Network and RPC configurations are stored in unencrypted Preferences (not sensitive data).
Complete Example: MAUI Crypto Wallet
Project Structure
MyMauiWallet/
├── MauiProgram.cs
├── App.xaml
├── App.xaml.cs
├── MainPage.xaml
├── MainPage.xaml.cs
├── Components/
│ ├── Main.razor
│ └── _Imports.razor
└── wwwroot/
├── index.html
├── logo.png
└── css/
└── app.css
MauiProgram.cs
using Microsoft.Extensions.Logging;
using Nethereum.Wallet.UI.Components.Maui.Extensions;
using Nethereum.Wallet.UI.Components.Blazor.Extensions;
using MudBlazor.Services;
namespace MyMauiWallet;
public static class MauiProgram
{
public static MauiApp CreateMauiProgram()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
// Configure MAUI wallet services
builder.AddNethereumWalletMauiComponents(options =>
{
options.DefaultChainId = 1; // Ethereum Mainnet
options.ConfigureUi = config =>
{
config.ApplicationName = "My Crypto Wallet";
config.LogoPath = "/logo.png";
config.WelcomeLogoPath = "/logo.png";
config.ShowLogo = true;
config.ShowApplicationName = true;
config.DrawerBehavior = DrawerBehavior.Responsive;
config.ResponsiveBreakpoint = 768;
config.WalletConfig.Security.MinPasswordLength = 10;
config.WalletConfig.Behavior.EnableWalletReset = true;
};
});
// Register Blazor wallet UI components
builder.Services.AddNethereumWalletUI();
#if DEBUG
builder.Services.AddBlazorWebViewDeveloperTools();
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:blazor="clr-namespace:Microsoft.AspNetCore.Components.WebView.Maui;assembly=Microsoft.AspNetCore.Components.WebView.Maui"
xmlns:local="clr-namespace:MyMauiWallet"
x:Class="MyMauiWallet.MainPage"
Shell.NavBarIsVisible="False">
<blazor:BlazorWebView HostPage="wwwroot/index.html">
<blazor:BlazorWebView.RootComponents>
<blazor:RootComponent Selector="#app" ComponentType="{x:Type local:Components.Main}" />
</blazor:BlazorWebView.RootComponents>
</blazor:BlazorWebView>
</ContentPage>
Components/Main.razor
@using Nethereum.Wallet.UI.Components.Blazor.NethereumWallet
<div style="height: 100vh; width: 100vw;">
<NethereumWallet OnConnected="@HandleWalletConnected"
Width="100%"
Height="100%" />
</div>
@code {
private void HandleWalletConnected()
{
// Wallet is unlocked and ready
Console.WriteLine("Wallet connected successfully!");
}
}
Components/_Imports.razor
@using Microsoft.AspNetCore.Components.Web
@using MudBlazor
@using Nethereum.Wallet.UI.Components.Blazor
wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>My Crypto Wallet</title>
<base href="/" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="https://use.fontawesome.com/releases/v6.3.0/css/all.css" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<div style="display: flex; align-items: center; justify-content: center; height: 100vh;">
<div style="text-align: center;">
<h2>Loading Wallet...</h2>
<p>Please wait</p>
</div>
</div>
</div>
<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>
wwwroot/css/app.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
height: 100%;
width: 100%;
overflow: hidden;
font-family: 'Roboto', sans-serif;
}
#app {
height: 100%;
width: 100%;
}
Testing on Platforms
Android
# Build and deploy to Android emulator
dotnet build -f net9.0-android
dotnet run -f net9.0-android
iOS
# Build and deploy to iOS simulator (Mac only)
dotnet build -f net9.0-ios
dotnet run -f net9.0-ios
Windows
# Build and run on Windows
dotnet build -f net9.0-windows10.0.19041.0
dotnet run -f net9.0-windows10.0.19041.0
Troubleshooting
SecureStorage Errors
Problem: PlatformNotSupportedException on Windows when using SecureStorage
Solution: Ensure targeting Windows 10 SDK 10.0.19041.0 or higher:
<TargetFrameworks>net9.0-windows10.0.19041.0</TargetFrameworks>
Blazor WebView Not Loading
Problem: Blank screen or "Loading..." never completes
Solutions:
- Verify
index.htmlis inwwwroot/directory - Check
<base href="/" />is present in HTML - Ensure
blazor.webview.jsscript is included - Check browser console for errors (Developer Tools)
MudBlazor Styles Not Loading
Problem: Wallet UI appears unstyled
Solution: Ensure MudBlazor CSS and JS are referenced in index.html:
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
Storage Permissions
Problem: UnauthorizedAccessException when accessing storage
Solution: Add permissions to platform-specific manifests:
Android (Platforms/Android/AndroidManifest.xml):
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
iOS (Platforms/iOS/Info.plist):
No additional permissions required for SecureStorage/Preferences.
Performance Considerations
Startup Time
- Initialization runs in background (
Task.Run) - Network preloading happens asynchronously
- First wallet unlock may take 1-2 seconds (decryption)
Storage Limits
- Android SharedPreferences: Recommended max 1MB per key
- iOS NSUserDefaults: Recommended max 100KB total
- Windows LocalStorage: No hard limit, but keep under 10MB
Optimization: The package keeps storage lean:
- Recent transactions limited to last 50 per chain
- Pending transactions moved to recent when confirmed
- RPC health cache expires automatically
Memory Usage
- Vault stored in memory while unlocked
- Transaction lists loaded on-demand per chain
- Network list cached in memory after first load
Related Packages
- Nethereum.Wallet.UI.Components - Platform-agnostic ViewModels and abstractions
- Nethereum.Wallet.UI.Components.Blazor - Blazor UI components (required for UI)
- Nethereum.Wallet - Core wallet services
- Nethereum.Wallet.RpcRequests - RPC request handlers
- Nethereum.Web3 - Ethereum RPC client
Source Files
Services:
Services/MauiSecureStorageWalletVaultService.cs:7-41- Secure vault storageServices/MauiPreferencesWalletStorageService.cs:18-441- Settings and data persistence
Configuration:
Options/MauiWalletComponentOptions.cs:5-10- Configuration options
Extensions:
Extensions/MauiAppBuilderExtensions.cs:50-151- Service registrationExtensions/MauiAppBuilderExtensions.cs:156-308- WalletRegistryInitializer (automatic initialization)Extensions/MauiAppBuilderExtensions.cs:28-46- Default RPC seeds
License
MIT License - Part of the Nethereum project.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0-android35.0 is compatible. net9.0-ios18.0 is compatible. net9.0-maccatalyst18.0 is compatible. net9.0-windows10.0.19041 is compatible. net10.0-android was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-windows was computed. |
-
net9.0-android35.0
- Microsoft.AspNetCore.Components.WebView.Maui (>= 9.0.111)
- Microsoft.Extensions.Logging.Debug (>= 9.0.0)
- Microsoft.Maui.Controls (>= 9.0.111)
- Nethereum.Wallet (>= 5.8.0)
- Nethereum.Wallet.RpcRequests (>= 5.8.0)
- Nethereum.Wallet.UI.Components (>= 5.8.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net9.0-ios18.0
- Microsoft.AspNetCore.Components.WebView.Maui (>= 9.0.111)
- Microsoft.Extensions.Logging.Debug (>= 9.0.0)
- Microsoft.Maui.Controls (>= 9.0.111)
- Nethereum.Wallet (>= 5.8.0)
- Nethereum.Wallet.RpcRequests (>= 5.8.0)
- Nethereum.Wallet.UI.Components (>= 5.8.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net9.0-maccatalyst18.0
- Microsoft.AspNetCore.Components.WebView.Maui (>= 9.0.111)
- Microsoft.Extensions.Logging.Debug (>= 9.0.0)
- Microsoft.Maui.Controls (>= 9.0.111)
- Nethereum.Wallet (>= 5.8.0)
- Nethereum.Wallet.RpcRequests (>= 5.8.0)
- Nethereum.Wallet.UI.Components (>= 5.8.0)
- Newtonsoft.Json (>= 11.0.2 && < 14.0.0)
-
net9.0-windows10.0.19041
- Microsoft.AspNetCore.Components.WebView.Maui (>= 9.0.111)
- Microsoft.Extensions.Logging.Debug (>= 9.0.0)
- Microsoft.Maui.Controls (>= 9.0.111)
- Nethereum.Wallet (>= 5.8.0)
- Nethereum.Wallet.RpcRequests (>= 5.8.0)
- Nethereum.Wallet.UI.Components (>= 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 | 39 | 1/6/2026 |