PageSourceSearch

https://docs.nethereum.com/assets/js/b3220530.3abd51cc.js

js nethereum.com collected 2026-10-03 20:35:55 UTC 37,428 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkwebsite=globalThis.webpackChunkwebsite||[]).push([[818],{25303(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>h,frontMatter:()=>d,metadata:()=>r,toc:()=>o});const r=JSON.parse('{"id":"wallet-connectivity/guide-host-providers","title":"IEthereumHostProvider Architecture","description":"The universal wallet abstraction that powers MetaMask, EIP-6963, WalletConnect, Reown, Unity, and the Wallet SDK","source":"@site/docs/wallet-connectivity/guide-host-providers.md","sourceDirName":"wallet-connectivity","slug":"/wallet-connectivity/guide-host-providers","permalink":"/docs/wallet-connectivity/guide-host-providers","draft":false,"unlisted":false,"editUrl":"https://github.com/Nethereum/Nethereum.Documentation/tree/main/docs/wallet-connectivity/guide-host-providers.md","tags":[],"version":"current","sidebarPosition":5,"frontMatter":{"title":"IEthereumHostProvider Architecture","sidebar_label":"Host Provider Pattern","sidebar_position":5,"description":"The universal wallet abstraction that powers MetaMask, EIP-6963, WalletConnect, Reown, Unity, and the Wallet SDK"},"sidebar":"docsSidebar","previous":{"title":"Overview","permalink":"/docs/wallet-connectivity/overview"},"next":{"title":"Nethereum.UI","permalink":"/docs/wallet-connectivity/nethereum-ui"}}');var i=t(74848),s=t(28453);const d={title:"IEthereumHostProvider Architecture",sidebar_label:"Host Provider Pattern",sidebar_position:5,description:"The universal wallet abstraction that powers MetaMask, EIP-6963, WalletConnect, Reown, Unity, and the Wallet SDK"},l="IEthereumHostProvider Architecture",c={},o=[{value:"The Core Abstraction",id:"the-core-abstraction",level:2},{value:"Properties",id:"properties",level:3},{value:"Events",id:"events",level:3},{value:"Methods",id:"methods",level:3},{value:"Provider Implementations",id:"provider-implementations",level:2},{value:"MetamaskHostProvider",id:"metamaskhostprovider",level:3},{value:"EIP6963WalletHostProvider",id:"eip6963wallethostprovider",level:3},{value:"AppKitHostProvider (Reown)",id:"appkithostprovider-reown",level:3},{value:"NethereumWalletConnectHostProvider",id:"nethereumwalletconnecthostprovider",level:3},{value:"NethereumHostProvider (Development)",id:"nethereumhostprovider-development",level:3},{value:"Unity Providers",id:"unity-providers",level:3},{value:"SelectedEthereumHostProviderService",id:"selectedethereumhostproviderservice",level:2},{value:"Blazor Authentication Integration",id:"blazor-authentication-integration",level:2},{value:"How It Works",id:"how-it-works",level:3},{value:"Setup",id:"setup",level:3},{value:"Usage in Razor",id:"usage-in-razor",level:3},{value:"Event-Driven Architecture",id:"event-driven-architecture",level:2},{value:"Disposal Pattern",id:"disposal-pattern",level:3},{value:"From Provider to Wallet SDK",id:"from-provider-to-wallet-sdk",level:2},{value:"Writing Provider-Agnostic Code",id:"writing-provider-agnostic-code",level:2},{value:"Cross-Platform Architecture",id:"cross-platform-architecture",level:2},{value:"Next Steps",id:"next-steps",level:2}];function a(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",ol:"ol",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,s.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.header,{children:(0,i.jsx)(n.h1,{id:"iethereumhostprovider-architecture",children:"IEthereumHostProvider Architecture"})}),"\n",(0,i.jsx)(n.h2,{id:"the-core-abstraction",children:"The Core Abstraction"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"IEthereumHostProvider"})," is the universal interface that every wallet connection in Nethereum implements. Whether your app connects to MetaMask in a browser, discovers wallets via EIP-6963, connects mobile wallets through WalletConnect/Reown, or runs inside Unity WebGL -- they all implement this single interface."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public interface IEthereumHostProvider\n{\n    string Name { get; }\n    bool Available { get; }\n    string SelectedAccount { get; }\n    long SelectedNetworkChainId { get; }\n    bool Enabled { get; }\n    bool MultipleWalletsProvider { get; }\n    bool MultipleWalletSelected { get; }\n\n    event Func<string, Task> SelectedAccountChanged;\n    event Func<long, Task> NetworkChanged;\n    event Func<bool, Task> AvailabilityChanged;\n    event Func<bool, Task> EnabledChanged;\n\n    Task<bool> CheckProviderAvailabilityAsync();\n    Task<Web3.IWeb3> GetWeb3Async();\n    Task<string> EnableProviderAsync();\n    Task<string> GetProviderSelectedAccountAsync();\n    Task<string> SignMessageAsync(string message);\n}\n"})}),"\n",(0,i.jsx)(n.h3,{id:"properties",children:"Properties"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Member"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Name"})}),(0,i.jsxs)(n.td,{children:["Human-readable provider name (e.g. ",(0,i.jsx)(n.code,{children:'"Metamask"'}),", ",(0,i.jsx)(n.code,{children:'"EIP6963 Standard"'}),", ",(0,i.jsx)(n.code,{children:'"Nethereum.AppKit"'}),")"]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Available"})}),(0,i.jsx)(n.td,{children:"Whether the provider is detected and ready. For browser wallets, this means the extension is installed."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"SelectedAccount"})}),(0,i.jsxs)(n.td,{children:["The currently active Ethereum address, or ",(0,i.jsx)(n.code,{children:"null"})," if no account is connected."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"SelectedNetworkChainId"})}),(0,i.jsxs)(n.td,{children:["The chain ID of the network the wallet is connected to (e.g. ",(0,i.jsx)(n.code,{children:"1"})," for Ethereum mainnet)."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Enabled"})}),(0,i.jsxs)(n.td,{children:["Whether the provider has been activated. A provider can be ",(0,i.jsx)(n.code,{children:"Available"})," but not yet ",(0,i.jsx)(n.code,{children:"Enabled"})," (user hasn't approved the connection)."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"MultipleWalletsProvider"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"true"})," when the provider can discover multiple wallets (EIP-6963, Wallet SDK). ",(0,i.jsx)(n.code,{children:"false"})," for single-wallet providers (MetaMask, WalletConnect)."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"MultipleWalletSelected"})}),(0,i.jsxs)(n.td,{children:[(0,i.jsx)(n.code,{children:"true"})," when a specific wallet has been chosen from a multi-wallet provider."]})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"events",children:"Events"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Event"}),(0,i.jsx)(n.th,{children:"Fires When"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"SelectedAccountChanged"})}),(0,i.jsx)(n.td,{children:"The user switches accounts in their wallet."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"NetworkChanged"})}),(0,i.jsx)(n.td,{children:"The user switches networks (chain ID changes)."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"AvailabilityChanged"})}
1),(0,i.jsx)(n.td,{children:"The provider becomes available or unavailable."})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"EnabledChanged"})}),(0,i.jsx)(n.td,{children:"The provider is enabled or disabled (wallet connected/disconnected)."})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"methods",children:"Methods"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Method"}),(0,i.jsx)(n.th,{children:"Description"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"CheckProviderAvailabilityAsync()"})}),(0,i.jsxs)(n.td,{children:["Probes whether the provider is available. Updates ",(0,i.jsx)(n.code,{children:"Available"})," and fires ",(0,i.jsx)(n.code,{children:"AvailabilityChanged"}),"."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"GetWeb3Async()"})}),(0,i.jsxs)(n.td,{children:["Returns an ",(0,i.jsx)(n.code,{children:"IWeb3"})," instance configured with the provider's RPC interceptor. All transactions and signatures route through the wallet."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"EnableProviderAsync()"})}),(0,i.jsxs)(n.td,{children:["Requests wallet connection (triggers the wallet's approval popup). Returns the selected account address or ",(0,i.jsx)(n.code,{children:"null"}),"."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"GetProviderSelectedAccountAsync()"})}),(0,i.jsxs)(n.td,{children:["Fetches the current account from the wallet. Unlike the ",(0,i.jsx)(n.code,{children:"SelectedAccount"})," property, this queries the wallet directly."]})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"SignMessageAsync(string message)"})}),(0,i.jsxs)(n.td,{children:["Signs a UTF-8 message using ",(0,i.jsx)(n.code,{children:"personal_sign"}),". The wallet shows the message and asks for confirmation."]})]})]})]}),"\n",(0,i.jsx)(n.h2,{id:"provider-implementations",children:"Provider Implementations"}),"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n",(0,i.jsxs)(n.table,{children:[(0,i.jsx)(n.thead,{children:(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.th,{children:"Provider"}),(0,i.jsx)(n.th,{children:"Package"}),(0,i.jsx)(n.th,{children:"Name"}),(0,i.jsx)(n.th,{children:"MultipleWalletsProvider"}),(0,i.jsx)(n.th,{children:"Platform"}),(0,i.jsx)(n.th,{children:"Use Case"})]})}),(0,i.jsxs)(n.tbody,{children:[(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"MetamaskHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.Metamask"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"Metamask"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"false"})}),(0,i.jsx)(n.td,{children:"Blazor"}),(0,i.jsx)(n.td,{children:"MetaMask browser extension"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"EIP6963WalletHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.EIP6963WalletInterop"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"EIP6963 Standard"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"true"})}),(0,i.jsx)(n.td,{children:"Blazor"}),(0,i.jsx)(n.td,{children:"Any browser wallet via EIP-6963 discovery"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"AppKitHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.Reown.AppKit.Blazor"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"Nethereum.AppKit"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"false"})}),(0,i.jsx)(n.td,{children:"Blazor"}),(0,i.jsx)(n.td,{children:"Reown modal UI, mobile wallets, QR codes"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"NethereumWalletConnectHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.WalletConnect"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"WalletConnect"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"false"})}),(0,i.jsx)(n.td,{children:"Any .NET"}),(0,i.jsx)(n.td,{children:"WalletConnect v2 protocol (no UI)"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"MetamaskWebglHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.Unity.Metamask"})}),(0,i.jsxs)(n.td,{children:["(inherits ",(0,i.jsx)(n.code,{children:'"Metamask"'}),")"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"false"})}),(0,i.jsx)(n.td,{children:"Unity WebGL"}),(0,i.jsx)(n.td,{children:"MetaMask in Unity WebGL builds"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"EIP6963WebglHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.Unity.EIP6963"})}),(0,i.jsxs)(n.td,{children:["(inherits ",(0,i.jsx)(n.code,{children:'"EIP6963 Standard"'}),")"]}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"true"})}),(0,i.jsx)(n.td,{children:"Unity WebGL"}),(0,i.jsx)(n.td,{children:"Multi-wallet in Unity WebGL"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"NethereumHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.UI"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"Nethereum Host Provider"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"false"})}),(0,i.jsx)(n.td,{children:"Any .NET"}),(0,i.jsx)(n.td,{children:"Development/testing with private keys"})]}),(0,i.jsxs)(n.tr,{children:[(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"NethereumWalletHostProvider"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"Nethereum.Wallet"})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:'"Nethereum Wallet"'})}),(0,i.jsx)(n.td,{children:(0,i.jsx)(n.code,{children:"true"})}),(0,i.jsx)(n.td,{children:"Wallet SDK"}),(0,i.jsx)(n.td,{children:"Built-in vault-backed provider"})]})]})]}),"\n",(0,i.jsx)(n.h3,{id:"metamaskhostprovider",children:"MetamaskHostProvider"}),"\n",(0,i.jsx)(n.p,{children:"The original browser wallet provider targeting MetaMask specifically."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"dotnet add package Nethereum.Metamask\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Include the JS interop in ",(0,i.jsx)(n.code,{children:"index.html"})," or ",(0,i.jsx)(n.code,{children:"App.razor"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-html",children:'<script src="_content/Nethereum.Blazor/NethereumMetamask.js"><\/script>\n'})}),"\n",(0,i.jsx)(n.p,{children:"Register and use:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"builder.Services.AddSingleton<IMetamaskInterop, MetamaskBlazorInterop>();\nbuilder.Services.AddSingleton<MetamaskHostProvider>();\nbuilder.Services.AddSingleton<IEthereumHostProvider>(sp =>\n    sp.GetRequiredService<MetamaskHostProvider>());\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"MetamaskHostProvider"})," exposes a static ",(0,i.jsx)(n.code,{children:"Current"})," property set during construction, so downstream code can access the singleton without DI. Internally it uses a ",(0,i.jsx)(n.code,{children:"MetamaskInterceptor"})," that routes ",(0,i.jsx)(n.code,{children:"eth_sendTransaction"}),", ",(0,i.jsx)(n.code,{children:"personal_sign"}),", and ",(0,i.jsx)(n.code,{children:"eth_signTypedData_v4"})," calls through the MetaMask extension via JS interop."]}),"\n",(0,i.jsx)(n.h3,{id:"eip6963wallethostprovider",children:"EIP6963WalletHostProvider"}),"\n",(0,i.jsxs)(n.p,{children:["The recommended approach for modern dApps. EIP-6963 replaces the legacy ",(0,i.jsx)(n.code,{children:"window.ethereum"})," injection model -- all installed wallet extensions announce themselves, and your app presents them for the user to choose."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"dotnet add package Nethereum.Blazor\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-html",children:'<script src="_content/Nethereum.Blazor/NethereumEIP6963.js"><\/script>\n'})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"builder.Services.AddSingleton<IEIP6963WalletInterop, EIP6963WalletBlazorInterop>();\nbuilder.Services.AddSingleton<EIP6963WalletHostProvider>();\nbuilder.Services.AddSingleton<IEthereumHostProvider>(sp =>\n    sp.GetRequiredService<EIP6963WalletHostProvider>());\n"})}),"\n",(0,i.jsx)(n.p,{children:"Discover and select wallets:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'@inject EIP6963WalletHostProvider WalletProvider\n\n// Discover all installed browser wallets\nEIP6963WalletInfo[] wallets = await WalletProvider.GetAvailableWalletsAsync();\n\n// Each wallet has Name, Icon (data URI), Uuid, Rdns\nforeach (var wallet in wallets)\n{\n    Console.WriteLine($"{wallet.Name} - {wallet.Uuid}");\n}\n\n// User picks one\nawait WalletProvider.SelectWalletAsync(wallets[0].Uuid);\n\n// Now enable (triggers wallet popup)\nstring account = await WalletProvider.EnableProviderAsync();\n'})}),"\n",(0,i.jsxs)(n.p,{children:["After ",(0,i.jsx)(n.code,{children:"SelectWalletAsync"}),", ",(0,i.jsx)(n.code,{children:"MultipleWalletSelected"})," becomes ",(0,i.jsx)(n.code,{children:"true"}),". The provider uses an ",(0,i.jsx)(n.code,{children:"EIP6963WalletInterceptor"})," to route signing requests to the selected wallet."]}
1),"\n",(0,i.jsx)(n.h3,{id:"appkithostprovider-reown",children:"AppKitHostProvider (Reown)"}),"\n",(0,i.jsx)(n.p,{children:"Reown AppKit (formerly WalletConnect Web3Modal) provides a complete modal UI with wallet selection, QR codes for mobile wallets, social logins, and built-in network switching."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"dotnet add package Nethereum.Reown.AppKit.Blazor\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Register with ",(0,i.jsx)(n.code,{children:"AddAppKit()"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'builder.Services.AddAppKit(new AppKitConfiguration\n{\n    ProjectId = "your-reown-project-id",\n    Name = "My dApp",\n    Description = "A Nethereum dApp",\n    Url = "https://mydapp.com",\n    Icons = ["https://mydapp.com/icon.png"],\n    Networks = [\n        new Network { ChainId = 1, Name = "Ethereum", Currency = "ETH", RpcUrl = "https://..." },\n        new Network { ChainId = 137, Name = "Polygon", Currency = "MATIC", RpcUrl = "https://..." }\n    ]\n});\n'})}),"\n",(0,i.jsx)(n.p,{children:"Include the web components in your Blazor layout:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-html",children:"<appkit-button />\n<appkit-network-button />\n"})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"AppKitHostProvider"})," initializes asynchronously. It sets up ",(0,i.jsx)(n.code,{children:"WatchAccount"})," and ",(0,i.jsx)(n.code,{children:"WatchChainId"})," callbacks that automatically fire ",(0,i.jsx)(n.code,{children:"SelectedAccountChanged"})," and ",(0,i.jsx)(n.code,{children:"NetworkChanged"})," events when the user interacts with the Reown modal."]}),"\n",(0,i.jsx)(n.h3,{id:"nethereumwalletconnecthostprovider",children:"NethereumWalletConnectHostProvider"}),"\n",(0,i.jsx)(n.p,{children:"A lower-level WalletConnect v2 provider without built-in UI. Use this when you want full control over the connection flow or need WalletConnect in non-Blazor .NET environments."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-bash",children:"dotnet add package Nethereum.WalletConnect\n"})}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"var walletConnectService = new NethereumWalletConnectService(signClientOptions);\nvar provider = new NethereumWalletConnectHostProvider(walletConnectService);\n"})}),"\n",(0,i.jsx)(n.p,{children:"Or with a specific RPC endpoint:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'var provider = new NethereumWalletConnectHostProvider(\n    walletConnectService,\n    url: "https://mainnet.infura.io/v3/YOUR_KEY");\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The provider uses ",(0,i.jsx)(n.code,{children:"NethereumWalletConnectInterceptor"})," to route signing methods through the WalletConnect protocol while read-only RPC calls go directly to the configured endpoint."]}),"\n",(0,i.jsx)(n.h3,{id:"nethereumhostprovider-development",children:"NethereumHostProvider (Development)"}),"\n",(0,i.jsx)(n.p,{children:"A built-in provider for testing and development. No browser or wallet extension required -- it works with a private key and an RPC endpoint directly."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'var provider = new NethereumHostProvider();\n\n// Configure RPC endpoint (also fetches chainId automatically)\nbool connected = await provider.SetUrl("http://localhost:8545");\n\n// Set the signing account\nprovider.SetSelectedAccount("0xYOUR_PRIVATE_KEY");\n// or\nprovider.SetSelectedAccount(new Account("0xYOUR_PRIVATE_KEY", chainId: 1));\n'})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"SetUrl"})," calls ",(0,i.jsx)(n.code,{children:"eth_chainId"})," on the endpoint and updates ",(0,i.jsx)(n.code,{children:"SelectedNetworkChainId"})," automatically. ",(0,i.jsx)(n.code,{children:"SignMessageAsync"})," uses ",(0,i.jsx)(n.code,{children:"EthereumMessageSigner"})," locally -- no external wallet involved."]}),"\n",(0,i.jsx)(n.p,{children:"This provider is useful for integration tests, console apps, or backend services that need to interact with the blockchain without a user-facing wallet."}),"\n",(0,i.jsx)(n.h3,{id:"unity-providers",children:"Unity Providers"}),"\n",(0,i.jsx)(n.p,{children:"Both Unity providers are thin wrappers that substitute WebGL-specific JS interop implementations."}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"MetamaskWebglHostProvider"})," extends ",(0,i.jsx)(n.code,{children:"MetamaskHostProvider"})," and passes ",(0,i.jsx)(n.code,{children:"MetamaskWebglTaskRequestInterop"})," as the interop layer:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"// Typically used in Unity WebGL builds\nvar provider = MetamaskWebglHostProvider.CreateOrGetCurrentInstance();\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"EIP6963WebglHostProvider"})," extends ",(0,i.jsx)(n.code,{children:"EIP6963WalletHostProvider"})," and passes ",(0,i.jsx)(n.code,{children:"EIP6963WebglTaskRequestInterop"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"var provider = EIP6963WebglHostProvider.CreateOrGetCurrentInstance();\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Both use the ",(0,i.jsx)(n.code,{children:"CreateOrGetCurrentInstance()"})," factory pattern to maintain a single instance through the static ",(0,i.jsx)(n.code,{children:"Current"})," property inherited from their base classes."]}),"\n",(0,i.jsx)(n.h2,{id:"selectedethereumhostproviderservice",children:"SelectedEthereumHostProviderService"}),"\n",(0,i.jsx)(n.p,{children:'The global provider selection service holds the currently active provider. All consumers inject this singleton to get "the current wallet."'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public class SelectedEthereumHostProviderService\n{\n    public IEthereumHostProvider SelectedHost { get; }\n    public event Func<IEthereumHostProvider, Task> SelectedHostProviderChanged;\n\n    public Task SetSelectedEthereumHostProvider(IEthereumHostProvider provider);\n    public Task ClearSelectedEthereumHostProvider();\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"Register it as a singleton:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"builder.Services.AddSingleton<SelectedEthereumHostProviderService>();\n"})}),"\n",(0,i.jsxs)(n.p,{children:["When ",(0,i.jsx)(n.code,{children:"SetSelectedEthereumHostProvider"})," is called with a different provider than the current one, it fires ",(0,i.jsx)(n.code,{children:"SelectedHostProviderChanged"}),". Calling ",(0,i.jsx)(n.code,{children:"ClearSelectedEthereumHostProvider"})," sets the provider to ",(0,i.jsx)(n.code,{children:"null"})," and fires the event."]}),"\n",(0,i.jsxs)(n.p,{children:["This service is the single point of coordination. When a user picks a wallet in your UI, you call ",(0,i.jsx)(n.code,{children:"SetSelectedEthereumHostProvider"}),". Every component that depends on the active wallet -- authentication, balance displays, transaction forms -- reacts through the event."]}),"\n",(0,i.jsx)(n.h2,{id:"blazor-authentication-integration",children:"Blazor Authentication Integration"}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"EthereumAuthenticationStateProvider"})," bridges ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"})," to Blazor's built-in authentication system (",(0,i.jsx)(n.code,{children:"AuthenticationStateProvider"}),"). It listens to provider and account changes and translates them into ",(0,i.jsx)(n.code,{children:"ClaimsPrincipal"})," objects."]}),"\n",(0,i.jsx)(n.h3,{id:"how-it-works",children:"How It Works"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:["Subscribes to ",(0,i.jsx)(n.code,{children:"SelectedEthereumHostProviderService.SelectedHostProviderChanged"})]}),"\n",(0,i.jsxs)(n.li,{children:["When the provider changes, unsubscribes from the old provider's ",(0,i.jsx)(n.code,{children:"SelectedAccountChanged"})," and subscribes to the new one"]}),"\n",(0,i.jsxs)(n.li,{children:["When an account is connected, creates a ",(0,i.jsx)(n.code,{children:"ClaimsPrincipal"})," with:\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"ClaimTypes.NameIdentifier"})," = the Ethereum address"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.code,{children:"ClaimTypes.Role"}
1)," = ",(0,i.jsx)(n.code,{children:'"EthereumConnected"'})]}),"\n"]}),"\n"]}),"\n",(0,i.jsxs)(n.li,{children:["Calls ",(0,i.jsx)(n.code,{children:"NotifyAuthenticationStateChanged"})," to update all ",(0,i.jsx)(n.code,{children:"<AuthorizeView>"})," components"]}),"\n"]}),"\n",(0,i.jsx)(n.h3,{id:"setup",children:"Setup"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"builder.Services.AddSingleton<SelectedEthereumHostProviderService>();\nbuilder.Services.AddScoped<AuthenticationStateProvider, EthereumAuthenticationStateProvider>();\nbuilder.Services.AddAuthorizationCore();\n"})}),"\n",(0,i.jsx)(n.h3,{id:"usage-in-razor",children:"Usage in Razor"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-razor",children:'<AuthorizeView Roles="EthereumConnected">\n    <Authorized>\n        <p>Connected: @context.User.Identity?.Name</p>\n    </Authorized>\n    <NotAuthorized>\n        <button @onclick="ConnectAsync">Connect Wallet</button>\n    </NotAuthorized>\n</AuthorizeView>\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"EthereumAuthenticationStateProvider"})," implements ",(0,i.jsx)(n.code,{children:"IDisposable"})," and properly unsubscribes from all events on disposal."]}),"\n",(0,i.jsxs)(n.p,{children:["For SIWE authentication (which adds a ",(0,i.jsx)(n.code,{children:'"SiweAuthenticated"'})," role with cryptographic proof of address ownership), see ",(0,i.jsx)(n.a,{href:"../blazor-dapp-integration/guide-blazor-authentication",children:"SIWE Authentication"}),"."]}),"\n",(0,i.jsx)(n.h2,{id:"event-driven-architecture",children:"Event-Driven Architecture"}),"\n",(0,i.jsxs)(n.p,{children:["All events on ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"})," use ",(0,i.jsx)(n.code,{children:"Func<T, Task>"})," delegates rather than ",(0,i.jsx)(n.code,{children:"EventHandler"}),". This means subscribers can perform async work directly:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"ethereumHostProvider.SelectedAccountChanged += async (address) =>\n{\n    // Update UI, refresh balances, reload user data\n    await LoadBalanceAsync(address);\n    await InvokeAsync(StateHasChanged);\n};\n\nethereumHostProvider.NetworkChanged += async (chainId) =>\n{\n    // Reload chain-specific data (token lists, contract addresses)\n    await SwitchChainContextAsync(chainId);\n    await InvokeAsync(StateHasChanged);\n};\n"})}),"\n",(0,i.jsx)(n.h3,{id:"disposal-pattern",children:"Disposal Pattern"}),"\n",(0,i.jsx)(n.p,{children:"Always unsubscribe from events when your component is disposed to prevent memory leaks and stale callbacks:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-razor",children:"@implements IDisposable\n@inject IEthereumHostProvider Provider\n\n@code {\n    protected override void OnInitialized()\n    {\n        Provider.SelectedAccountChanged += OnAccountChanged;\n        Provider.NetworkChanged += OnNetworkChanged;\n    }\n\n    private async Task OnAccountChanged(string address)\n    {\n        await InvokeAsync(StateHasChanged);\n    }\n\n    private async Task OnNetworkChanged(long chainId)\n    {\n        await InvokeAsync(StateHasChanged);\n    }\n\n    public void Dispose()\n    {\n        Provider.SelectedAccountChanged -= OnAccountChanged;\n        Provider.NetworkChanged -= OnNetworkChanged;\n    }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"from-provider-to-wallet-sdk",children:"From Provider to Wallet SDK"}),"\n",(0,i.jsx)(n.p,{children:"The following diagram shows how provider implementations flow through the selection service, into Blazor authentication, and down into the Wallet SDK:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{children:"Browser Wallets (IEthereumHostProvider)\n\u251c\u2500\u2500 MetamaskHostProvider\n\u251c\u2500\u2500 
1EIP6963WalletHostProvider\n\u251c\u2500\u2500 AppKitHostProvider (Reown)\n\u2514\u2500\u2500 NethereumWalletConnectHostProvider\n         \u2502\n         \u25bc\nSelectedEthereumHostProviderService\n    (holds active provider reference)\n         \u2502\n         \u25bc\nEthereumAuthenticationStateProvider\n    (Blazor <AuthorizeView> integration)\n         \u2502\n         \u25bc\nNethereumWalletHostProvider (IWalletContext)\n    (vault-backed provider with RPC interception)\n         \u2502\n    \u250c\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2510\n    \u2502         \u2502\n    \u25bc         \u25bc\nWalletVault   NethereumWalletInterceptor\n(encrypted    (routes eth_sendTransaction,\n accounts)    personal_sign, etc. to\n              prompt services)\n"})}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"IWalletContext"})," extends ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"})," with wallet-specific capabilities:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public interface IWalletContext : IEthereumHostProvider\n{\n    IReadOnlyList<IWalletAccount> Accounts { get; }\n    IWalletAccount? SelectedWalletAccount { get; }\n    DappConnectionContext? SelectedDapp { get; set; }\n\n    Task<IWeb3> GetWalletWeb3Async();\n    Task<bool> SwitchChainAsync(string chainIdHex);\n    Task<string?> ShowTransactionDialogAsync(TransactionInput input);\n    Task<string?> RequestPersonalSignAsync(SignaturePromptContext context);\n    Task<bool> RequestDappPermissionAsync(DappConnectionContext dappContext, string accountAddress);\n    // ... additional wallet operations\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Because ",(0,i.jsx)(n.code,{children:"IWalletContext"})," extends ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"}),", the wallet provider IS an ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"}),". DApps running inside the wallet see it as their provider -- they call ",(0,i.jsx)(n.code,{children:"GetWeb3Async()"})," and ",(0,i.jsx)(n.code,{children:"SignMessageAsync()"})," the same way they would with MetaMask or any other provider. The wallet intercepts those calls and routes them through its prompt services (transaction confirmation dialogs, signature approvals, etc.)."]}),"\n",(0,i.jsx)(n.h2,{id:"writing-provider-agnostic-code",children:"Writing Provider-Agnostic Code"}),"\n",(0,i.jsxs)(n.p,{children:["Any code that depends on ",(0,i.jsx)(n.code,{children:"IEthereumHostProvider"})," works identically regardless of which provider is active:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"@inject IEthereumHostProvider Provider\n\n@code {\n    private async Task LoadDataAsync()\n    {\n        var web3 = await Provider.GetWeb3Async();\n        var balance = await web3.Eth.GetBalance.SendRequestAsync(Provider.SelectedAccount);\n        var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();\n    }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This code works whether the provider is MetaMask, EIP-6963, WalletConnect, Reown, the built-in wallet, or the development provider. The ",(0,i.jsx)(n.code,{children:"IWeb3"})," returned by ",(0,i.jsx)(n.code,{children:"GetWeb3Async()"})," has an ",(0,i.jsx)(n.code,{children:"OverridingRequestInterceptor"})," that routes signing methods to the wallet while passing read-only calls through to the RPC endpoint."]}),"\n",(0,i.jsxs)(n.p,{children:["For services and non-UI code, inject ",(0,i.jsx)(n.code,{children:"SelectedEthereumHostProviderService"})," to get the active provider:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"public class MyService\n{\n    private readonly SelectedEthereumHostProviderService _hostService;\n\n    public MyService(SelectedEthereumHostProviderService hostService)\n    {\n        _hostService = hostService;\n    }\n\n    public async Task DoSomethingAsync()\n    {\n        var provider = _hostService.SelectedHost;\n        if (provider == null || !provider.Enabled) return;\n\n        var web3 = await provider.GetWeb3Async();\n        // Use web3...\n    }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"cross-platform-architecture",children:"Cross-Platform Architecture"}),"\n",(0,i.jsx)(n.p,{children:"For Unity projects, use preprocessor directives to select the appropriate provider at compile time:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:'IEthereumHostProvider provider;\n\n#if UNITY_WEBGL && !UNITY_EDITOR\n    // In WebGL builds, use the browser wallet\n    provider = MetamaskWebglHostProvider.CreateOrGetCurrentInstance();\n#else\n    // In the editor or standalone builds, use a dev provider\n    var devProvider = new NethereumHostProvider();\n    await devProvider.SetUrl("http://localhost:8545");\n    devProvider.SetSelectedAccount(testPrivateKey);\n    provider = devProvider;\n#endif\n\n// From here on, identical code regardless of platform\nvar web3 = await provider.GetWeb3Async();\nvar balance = await web3.Eth.GetBalance.SendRequestAsync(provider.SelectedAccount);\n'})}),"\n",(0,i.jsx)(n.p,{children:"For multi-wallet discovery in Unity WebGL:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-csharp",children:"#if UNITY_WEBGL && !UNITY_EDITOR\n    var eip6963Provider = EIP6963WebglHostProvider.CreateOrGetCurrentInstance();\n    var wallets = await eip6963Provider.GetAvailableWalletsAsync();\n    // Present wallet picker UI\n    await eip6963Provider.SelectWalletAsync(selectedWallet.Uuid);\n    provider = eip6963Provider;\n#endif\n"})}),"\n",(0,i.jsx)(n.h2,{id:"next-steps",children:"Next Steps"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"../blazor-dapp-integration/guide-blazor-wallet-connect",children:"Connecting Browser Wallets"})," -- step-by-step setup for EIP-6963, MetaMask, and Reown in Blazor"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"../blazor-dapp-integration/guide-blazor-authentication",children:"SIWE Authentication"})," -- Sign-In with Ethereum with ",(0,i.jsx)(n.code,{children:"AuthorizeView"})," and JWT tokens"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"../wallet-sdk/guide-wallet-architecture",children:"Wallet SDK Architecture"})," -- Dashboard plugins, screen inventory, registry system"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"../wallet-sdk/guide-wallet-rpc-provider",children:"Wallet RPC Provider"})," -- How the wallet handles dApp RPC requests"]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.a,{href:"../unity/guide-unity-quickstart",children:"Unity Quickstart"})," -- Getting Nethereum into Unity with WebGL wallet integration"]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(a,{...e})}):a(e)}},28453(e,n,t){t.d(n,{R:()=>d,x:()=>l});var r=t(96540);const i={},s=r.createContext(i);function d(e){const n=r.useContext(s);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:d(e.components),r.createElement(s.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.