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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" Version="5.8.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Nethereum.Wallet.UI.Components.Maui" Version="5.8.0" />
                    
Directory.Packages.props
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Nethereum.Wallet.UI.Components.Maui --version 5.8.0
                    
#r "nuget: Nethereum.Wallet.UI.Components.Maui, 5.8.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Nethereum.Wallet.UI.Components.Maui@5.8.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=5.8.0
                    
Install as a Cake Addin
#tool nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=5.8.0
                    
Install as a Cake Tool

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 hosting
  • Microsoft.Extensions.Logging.Debug 9.0.0 - Debug logging
  • Microsoft.Maui.Controls - MAUI framework

Project References:

  • Nethereum.Wallet.RpcRequests - RPC request handlers
  • Nethereum.Wallet.UI.Components - Platform-agnostic UI components
  • Nethereum.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 memory
  • CreateNewAsync(password) - Create new encrypted vault
  • GetAccountsAsync() - Get all accounts from vault
  • SaveAsync(password) - Encrypt and save vault
  • LockAsync() - Clear in-memory vault data
  • ResetAsync() - 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 sources
  • Strategy: PreconfiguredEnrich - Merge external with preconfigured chains
  • PostProcessPreconfigured - 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
  • kSecAttrAccessibleWhenUnlocked access 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:

  1. User provides password
  2. PBKDF2 derives encryption key
  3. Vault JSON serialized and encrypted with AES-256
  4. Encrypted blob stored in platform secure storage
  5. 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:

  1. Verify index.html is in wwwroot/ directory
  2. Check <base href="/" /> is present in HTML
  3. Ensure blazor.webview.js script is included
  4. 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
  • 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 storage
  • Services/MauiPreferencesWalletStorageService.cs:18-441 - Settings and data persistence

Configuration:

  • Options/MauiWalletComponentOptions.cs:5-10 - Configuration options

Extensions:

  • Extensions/MauiAppBuilderExtensions.cs:50-151 - Service registration
  • Extensions/MauiAppBuilderExtensions.cs:156-308 - WalletRegistryInitializer (automatic initialization)
  • Extensions/MauiAppBuilderExtensions.cs:28-46 - Default RPC seeds

License

MIT License - Part of the Nethereum project.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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