![]() |
VOOZH | about |
dotnet add package Nethereum.Wallet.UI.Components.Maui --version 6.1.0
NuGet\Install-Package Nethereum.Wallet.UI.Components.Maui -Version 6.1.0
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" Version="6.1.0" />
<PackageVersion Include="Nethereum.Wallet.UI.Components.Maui" Version="6.1.0" />Directory.Packages.props
<PackageReference Include="Nethereum.Wallet.UI.Components.Maui" />Project file
paket add Nethereum.Wallet.UI.Components.Maui --version 6.1.0
#r "nuget: Nethereum.Wallet.UI.Components.Maui, 6.1.0"
#:package Nethereum.Wallet.UI.Components.Maui@6.1.0
#addin nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=6.1.0Install as a Cake Addin
#tool nuget:?package=Nethereum.Wallet.UI.Components.Maui&version=6.1.0Install as a Cake Tool
Cross-platform MAUI integration package providing platform-specific storage implementations and Blazor WebView hosting for Nethereum wallet components on mobile and desktop applications.
NuGet Packages:
Microsoft.AspNetCore.Components.WebView.Maui - Blazor WebView hostingMicrosoft.Extensions.Logging.Debug 9.0.0 - Debug loggingMicrosoft.Maui.Controls - MAUI frameworkProject References:
Nethereum.Wallet.RpcRequests - RPC request handlersNethereum.Wallet.UI.Components - Platform-agnostic UI componentsNethereum.Wallet - Core wallet servicesNote: This package does NOT reference Nethereum.Wallet.UI.Components.Blazor directly. Your application must add both packages to use Blazor wallet components in MAUI.
# 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
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();
}
}
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>
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!");
}
}
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>
The package provides two core service implementations using MAUI APIs:
Implements IWalletVaultService using MAUI SecureStorage for encrypted vault persistence.
Location: Services/MauiSecureStorageWalletVaultService.cs:7-41
Platform-Specific Security:
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 completelyImplements 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 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;
};
});
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 RPCsRPC 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.
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:
These RPCs are automatically added to networks during initialization if no RPCs are configured.
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
});
SecureStorage:
Preferences:
SharedPreferencesPermissions:
SecureStorage:
kSecAttrAccessibleWhenUnlocked access controlPreferences:
NSUserDefaultsPrivacy:
SecureStorage:
Preferences:
Encryption Algorithm: AES-256 (BouncyCastle implementation) Key Derivation: PBKDF2 from user password Storage: Platform secure storage (KeyStore/Keychain/Credential Locker)
Security Flow:
DApp permissions are stored in unencrypted Preferences (not sensitive data):
Recommendation: Regularly review and revoke unused DApp permissions.
Network and RPC configurations are stored in unencrypted Preferences (not sensitive data).
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
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();
}
}
<?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>
@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!");
}
}
@using Microsoft.AspNetCore.Components.Web
@using MudBlazor
@using Nethereum.Wallet.UI.Components.Blazor
<!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>
* {
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%;
}
# Build and deploy to Android emulator
dotnet build -f net9.0-android
dotnet run -f net9.0-android
# Build and deploy to iOS simulator (Mac only)
dotnet build -f net9.0-ios
dotnet run -f net9.0-ios
# Build and run on Windows
dotnet build -f net9.0-windows10.0.19041.0
dotnet run -f net9.0-windows10.0.19041.0
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>
Problem: Blank screen or "Loading..." never completes
Solutions:
index.html is in wwwroot/ directory<base href="/" /> is present in HTMLblazor.webview.js script is includedProblem: 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>
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.
Task.Run)Optimization: The package keeps storage lean:
Services:
Services/MauiSecureStorageWalletVaultService.cs:7-41 - Secure vault storageServices/MauiPreferencesWalletStorageService.cs:18-441 - Settings and data persistenceConfiguration:
Options/MauiWalletComponentOptions.cs:5-10 - Configuration optionsExtensions:
Extensions/MauiAppBuilderExtensions.cs:50-151 - Service registrationExtensions/MauiAppBuilderExtensions.cs:156-308 - WalletRegistryInitializer (automatic initialization)Extensions/MauiAppBuilderExtensions.cs:28-46 - Default RPC seedsMIT License - Part of the Nethereum project.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-android36.0 net10.0-android36.0 is compatible. net10.0-ios26.0 net10.0-ios26.0 is compatible. net10.0-maccatalyst26.0 net10.0-maccatalyst26.0 is compatible. net10.0-windows10.0.19041 net10.0-windows10.0.19041 is compatible. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.