Wallet RPC Provider
The Nethereum Wallet acts as an EIP-1193 provider for dApps. When a dApp calls eth_sendTransaction, personal_sign, or wallet_switchEthereumChain, the wallet intercepts these requests, shows approval dialogs, signs with vault keys, and returns responses — just like MetaMask or any browser wallet.
Architecture
dApp (via Web3)
│
▼
Web3.Client.SendRequestAsync()
│
▼
NethereumWalletInterceptor (RequestInterceptor)
│ checks InterceptedMethods list
▼
RpcHandlerRegistry.TryGetHandler(method)
│ dispatches to handler
▼
IRpcMethodHandler.HandleAsync(request, context)
│ uses IWalletContext for wallet state + prompts
▼
Prompt Service (UI dialog) → User approves/rejects
│
▼
RpcResponseMessage returned to dApp
IWalletContext
IWalletContext extends IEthereumHostProvider with wallet-specific capabilities. RPC handlers receive it to access accounts, sign transactions, and show prompts:
public interface IWalletContext : IEthereumHostProvider
{
IReadOnlyList<IWalletAccount> Accounts { get; }
IWalletAccount? SelectedWalletAccount { get; }
DappConnectionContext? SelectedDapp { get; set; }
IDappPermissionService DappPermissions { get; }
HexBigInteger? ChainId { get; }
IWalletConfigurationService Configuration { get; }
Task<IAccount?> GetSelectedAccountAsync();
Task<IClient?> GetRpcClientAsync();
Task<IWeb3> GetWalletWeb3Async();
Task<bool> SwitchChainAsync(string chainIdHex);
// Prompt methods (show UI to user)
Task<string?> ShowTransactionDialogAsync(TransactionInput input);
Task<string?> RequestPersonalSignAsync(SignaturePromptContext context);
Task<string?> RequestTypedDataSignAsync(TypedDataSignPromptContext context);
Task<bool> RequestDappPermissionAsync(DappConnectionContext dapp, string address);
Task<ChainSwitchPromptResult> RequestChainSwitchAsync(ChainSwitchPromptRequest request);
Task<ChainAdditionPromptResult> RequestChainAdditionAsync(ChainAdditionPromptRequest request);
}
Because IWalletContext extends IEthereumHostProvider, the wallet provider can be used anywhere a host provider is expected.
Intercepted Methods
The NethereumWalletInterceptor intercepts these JSON-RPC methods:
public static List<string> InterceptedMethods { get; } = new()
{
"eth_sendTransaction",
"eth_signTransaction",
"eth_sign",
"personal_sign",
"eth_signTypedData",
"eth_signTypedData_v3",
"eth_signTypedData_v4",
"eth_requestAccounts",
"wallet_requestPermissions",
"wallet_switchEthereumChain",
"wallet_addEthereumChain",
"eth_accounts"
};
Non-intercepted methods (like eth_call, eth_getBalance, eth_blockNumber) pass through to the underlying RPC client.
RPC Handler Reference
Account Access
eth_accounts
Returns the selected account if the dApp has permission, empty array otherwise. Does not prompt.
eth_requestAccounts
Returns the selected account. If the dApp is not yet approved, prompts the user via IDappPermissionPromptService. Returns error code 4001 if rejected.
wallet_requestPermissions
EIP-2255 permission request. Prompts if needed, returns permission objects:
[{ "parentCapability": "eth_accounts", "caveats": [] }]
wallet_getPermissions
Returns current permissions for the dApp. Does not prompt.
wallet_revokePermissions
Revokes dApp permissions. Does not prompt.
Signing
personal_sign
- Extracts message and address from params (handles both param orders)
- Creates
SignaturePromptContextwith decoded message, dApp info - Prompts user via
ISignaturePromptService.PromptSignatureAsync() - Signs with vault account's private key
- Returns signature or error code 4001
The prompt context includes:
public sealed class SignaturePromptContext
{
public string Method { get; init; } // "personal_sign"
public string Message { get; init; } // Raw message
public string? DecodedMessage { get; init; } // UTF-8 decoded if hex
public bool IsMessageHex { get; init; }
public string Address { get; init; }
public string? Origin { get; init; } // dApp origin URL
public string? DappName { get; init; }
public string? DappIcon { get; init; }
}
eth_signTypedData_v4
- Extracts address and EIP-712 typed data JSON
- Parses domain metadata (name, version, verifyingContract, chainId)
- Validates chainId matches current network
- Prompts user via
ISignaturePromptService.PromptTypedDataSignAsync() - Returns signature or error code 4001
The prompt context includes full EIP-712 metadata:
public sealed class TypedDataSignPromptContext
{
public string Address { get; init; }
public string TypedDataJson { get; init; }
public string? DomainName { get; init; }
public string? DomainVersion { get; init; }
public string? VerifyingContract { get; init; }
public string? PrimaryType { get; init; }
public string? ChainId { get; init; }
public string? Origin { get; init; }
public string? DappName { get; init; }
public string? DappIcon { get; init; }
}
Transactions
eth_sendTransaction
- Parses
TransactionInputfrom request params - Sets
fromto selected account if not specified - Prompts user via
ITransactionPromptService.PromptTransactionAsync() - Signs and broadcasts if approved
- Returns transaction hash or error code 4001
This is the only handler that both signs AND sends.
Network Management
eth_chainId
Returns current chain ID as hex. Does not prompt.
wallet_switchEthereumChain
- Parses
SwitchEthereumChainParameterto extract chainId - Creates
ChainSwitchPromptRequestwith chain metadata - Prompts user via
IChainSwitchPromptService - Switches network if approved
- Returns null on success
wallet_addEthereumChain
- Parses
AddEthereumChainParameter(chainId, chainName, rpcUrls, nativeCurrency) - If chain already exists, switches to it instead
- If new, prompts user via
IChainAdditionPromptService - Adds chain and optionally switches
- Returns null on success
Handler Quick Reference
| Method | Handler | Prompts? | Service |
|---|---|---|---|
eth_accounts | EthAccountsHandler | No | — |
eth_requestAccounts | EthRequestAccountsHandler | Yes (if unapproved) | IDappPermissionPromptService |
eth_chainId | EthChainIdHandler | No | — |
eth_sendTransaction | EthSendTransactionHandler | Yes | ITransactionPromptService |
personal_sign | PersonalSignHandler | Yes | ISignaturePromptService |
eth_signTypedData_v4 | EthSignTypedDataV4Handler | Yes | ISignaturePromptService |
wallet_switchEthereumChain | WalletSwitchEthereumChainHandler | Yes | IChainSwitchPromptService |
wallet_addEthereumChain | WalletAddEthereumChainHandler | Yes | IChainAdditionPromptService |
wallet_requestPermissions | WalletRequestPermissionsHandler | Yes (if unapproved) | IDappPermissionPromptService |
wallet_getPermissions | WalletGetPermissionsHandler | No | — |
wallet_revokePermissions | WalletRevokePermissionsHandler | No | — |
Permission System
Handlers check permissions before prompting:
// Normalize for case-insensitive comparison
var origin = dapp.Origin.Trim().ToLowerInvariant();
var account = address.Trim().ToLowerInvariant();
if (!await context.DappPermissions.IsApprovedAsync(origin, account))
{
var approved = await context.RequestDappPermissionAsync(dapp, address);
if (!approved)
return UserRejected(request.Id);
}
The IDappPermissionService caches approvals so repeat requests from the same dApp don't prompt again.
Registration
All handlers are registered at startup:
public static class WalletRpcHandlerRegistration
{
public static void RegisterAll(RpcHandlerRegistry registry)
{
registry.Register(new WalletAddEthereumChainHandler());
registry.Register(new WalletSwitchEthereumChainHandler());
registry.Register(new WalletGetPermissionsHandler());
registry.Register(new WalletRequestPermissionsHandler());
registry.Register(new WalletRevokePermissionsHandler());
registry.Register(new PersonalSignHandler());
registry.Register(new EthSignTypedDataV4Handler());
registry.Register(new EthRequestAccountsHandler());
registry.Register(new EthAccountsHandler());
registry.Register(new EthSendTransactionHandler());
// ... additional handlers
}
}
The RpcHandlerRegistry is a simple Dictionary<string, IRpcMethodHandler>. Custom handlers can be registered for additional methods.
Writing Custom Handlers
Extend RpcMethodHandlerBase to handle additional RPC methods:
public class MyCustomHandler : RpcMethodHandlerBase
{
public override string MethodName => "my_customMethod";
public override async Task<RpcResponseMessage> HandleAsync(
RpcRequestMessage request, IWalletContext context)
{
var param = request.GetFirstParamAs<string>();
if (string.IsNullOrEmpty(param))
return InvalidParams(request.Id, "Parameter required");
// Do work using context...
var result = await DoWorkAsync(param, context);
return new RpcResponseMessage(request.Id, result);
}
}
// Register it
registry.Register(new MyCustomHandler());
Helper methods from RpcMethodHandlerBase:
MethodNotImplemented(id)-- error code -32601InvalidParams(id, message)-- error code -32602UserRejected(id)-- error code 4001InternalError(id, message)-- error code -32603
Prompt Services
Each prompt type has a dedicated service interface:
| Interface | Purpose |
|---|---|
ITransactionPromptService | Show transaction confirmation dialog |
ISignaturePromptService | Show message/typed-data signing dialog |
IDappPermissionPromptService | Show dApp connection approval |
IChainSwitchPromptService | Show chain switch confirmation |
IChainAdditionPromptService | Show new chain addition dialog |
ILoginPromptService | Show vault unlock/login dialog |
The Blazor renderer provides UI implementations of these services. Default implementations (NoOpDappPermissionPromptService, PermissiveDappPermissionService) are registered as fallbacks.
Next Steps
- Host Provider Pattern -- The IEthereumHostProvider abstraction
- Wallet Architecture -- Dashboard plugins, registry system, screen inventory
- Transactions -- Send flow, gas strategies, EVM simulation