Skip to main content

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​

  1. Extracts message and address from params (handles both param orders)
  2. Creates SignaturePromptContext with decoded message, dApp info
  3. Prompts user via ISignaturePromptService.PromptSignatureAsync()
  4. Signs with vault account's private key
  5. 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​

  1. Extracts address and EIP-712 typed data JSON
  2. Parses domain metadata (name, version, verifyingContract, chainId)
  3. Validates chainId matches current network
  4. Prompts user via ISignaturePromptService.PromptTypedDataSignAsync()
  5. 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​

  1. Parses TransactionInput from request params
  2. Sets from to selected account if not specified
  3. Prompts user via ITransactionPromptService.PromptTransactionAsync()
  4. Signs and broadcasts if approved
  5. 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​

  1. Parses SwitchEthereumChainParameter to extract chainId
  2. Creates ChainSwitchPromptRequest with chain metadata
  3. Prompts user via IChainSwitchPromptService
  4. Switches network if approved
  5. Returns null on success

wallet_addEthereumChain​

  1. Parses AddEthereumChainParameter (chainId, chainName, rpcUrls, nativeCurrency)
  2. If chain already exists, switches to it instead
  3. If new, prompts user via IChainAdditionPromptService
  4. Adds chain and optionally switches
  5. Returns null on success

Handler Quick Reference​

MethodHandlerPrompts?Service
eth_accountsEthAccountsHandlerNo—
eth_requestAccountsEthRequestAccountsHandlerYes (if unapproved)IDappPermissionPromptService
eth_chainIdEthChainIdHandlerNo—
eth_sendTransactionEthSendTransactionHandlerYesITransactionPromptService
personal_signPersonalSignHandlerYesISignaturePromptService
eth_signTypedData_v4EthSignTypedDataV4HandlerYesISignaturePromptService
wallet_switchEthereumChainWalletSwitchEthereumChainHandlerYesIChainSwitchPromptService
wallet_addEthereumChainWalletAddEthereumChainHandlerYesIChainAdditionPromptService
wallet_requestPermissionsWalletRequestPermissionsHandlerYes (if unapproved)IDappPermissionPromptService
wallet_getPermissionsWalletGetPermissionsHandlerNo—
wallet_revokePermissionsWalletRevokePermissionsHandlerNo—

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 -32601
  • InvalidParams(id, message) -- error code -32602
  • UserRejected(id) -- error code 4001
  • InternalError(id, message) -- error code -32603

Prompt Services​

Each prompt type has a dedicated service interface:

InterfacePurpose
ITransactionPromptServiceShow transaction confirmation dialog
ISignaturePromptServiceShow message/typed-data signing dialog
IDappPermissionPromptServiceShow dApp connection approval
IChainSwitchPromptServiceShow chain switch confirmation
IChainAdditionPromptServiceShow new chain addition dialog
ILoginPromptServiceShow vault unlock/login dialog

The Blazor renderer provides UI implementations of these services. Default implementations (NoOpDappPermissionPromptService, PermissiveDappPermissionService) are registered as fallbacks.

Next Steps​