Nethereum / Nethereum.Templates.Metamask.Blazor

Metamask + Nethereum + Blazor interop
Apache License 2.0
87 stars 31 forks source link

Nethereum.Templates.Metamask.Blazor

This projects provides the quick start example/template on how to integrate Metamask + Nethereum using Blazor interop. The project allows you to work both in Wasm and Server side, so you can create a fully decentralised application using Wasm or semi-decentralised using server hosted components (Web3+Web2).

Both project types include and example on how to:

Note If you want to create your own custom Metamask component or add remove functionality check https://github.com/Nethereum/Nethereum/blob/master/src/Nethereum.Metamask.Blazor/Metamask.razor

Demo

Metamask

Example code

Configuration

builder.Services.AddSingleton<IMetamaskInterop, MetamaskBlazorInterop>();
builder.Services.AddSingleton<MetamaskInterceptor>();
builder.Services.AddSingleton<MetamaskHostProvider>();

//Add metamask as the selected ethereum host provider
builder.Services.AddSingleton(services =>
{
    var metamaskHostProvider = services.GetService<MetamaskHostProvider>();
    var selectedHostProvider = new SelectedEthereumHostProviderService();
    selectedHostProvider.SetSelectedEthereumHostProvider(metamaskHostProvider);
    return selectedHostProvider;
});

builder.Services.AddSingleton<AuthenticationStateProvider, EthereumAuthenticationStateProvider>();
builder.Services.AddValidatorsFromAssemblyContaining<Nethereum.Erc20.Blazor.Erc20Transfer>();

Page

@page "/"
@using Nethereum.Erc20.Blazor;
@implements IDisposable;
@inject IJSRuntime jsRuntime;
@inject SelectedEthereumHostProviderService selectedHostProviderService
@inject NavigationManager _navigationManager
@inject AuthenticationStateProvider _siweAuthenticationStateProvider;
@using Nethereum.Hex.HexTypes;
@using Microsoft.AspNetCore.Components.Authorization
@using System.Security.Claims

<AuthorizeView Roles="EthereumConnected">
    <Authorized>
        <div class="card m-1">
            <div class="card-body">
                <div class="row">
                    <label class="col-sm-3 col-form-label-lg">Selected Account:</label>
                    <div class="col-sm-6">
                        @SelectedAccount
                        <small id="selectedAccountHelp" class="form-text text-muted">The selected account is bound to the host (ie Metamask) on change</small>
                    </div>
                </div>
                 <div class="row">
                    <label class="col-sm-3 col-form-label-lg">Selected Account from Claims Principal</label>
                    <div class="col-sm-6">
                        @context?.User?.FindFirst(c => c.Type.Contains(ClaimTypes.NameIdentifier))?.Value
                        <small id="selectedAccountHelp" class="form-text text-muted">The selected account is bound to the claims principal</small>
                    </div>
                </div>
            </div>

            <div class="card-body">
                <div class="row">
                    <label class="col-sm-3 col-form-label-lg">Selected Network ChainId:</label>
                    <div class="col-sm-6">
                        @SelectedChainId
                        <small id="selectedAccountHelp" class="form-text text-muted">The selected chain Id</small>
                    </div>
                </div>
            </div>
        </div>

        <div class="card m-1">
            <div class="card-body">
                <div class="row">
                    <label class="col-sm-3 col-form-label-lg">Block hash of block number 0:</label>
                    <div class="col-sm-6">
                        <button @onclick="@GetBlockHashAsync">Get BlockHash</button>
                        <div>@BlockHash</div>
                        <small id="selectedAccountHelp" class="form-text text-muted">With Metamask calls are redirected to its configured node (i.e http://localhost:8545)</small>
                    </div>
                </div>
            </div>
        </div>
        <Erc20Transfer></Erc20Transfer>
    </Authorized>
    <NotAuthorized>

        <div>
            Please connect to Ethereum !
        </div>

    </NotAuthorized>
</AuthorizeView>

@code {

    [CascadingParameter]
    public Task<AuthenticationState> AuthenticationState { get; set; }

    bool EthereumAvailable { get; set; }
    string SelectedAccount { get; set; }
    int SelectedChainId { get; set; }
    string BlockHash { get; set; }
    string TransactionHash { get; set; }
    string ErrorTransferMessage { get; set; }
    string ErrorAuthenticateMessage { get; set; }
    string UserName { get; set; }
    protected string AuthenticatedAccount { get; set; }
    IEthereumHostProvider _ethereumHostProvider;

    protected override void OnInitialized()
    {
        //metamask is selected
        _ethereumHostProvider = selectedHostProviderService.SelectedHost;
        _ethereumHostProvider.SelectedAccountChanged += HostProvider_SelectedAccountChanged;
        _ethereumHostProvider.NetworkChanged += HostProvider_NetworkChanged;
        _ethereumHostProvider.EnabledChanged += HostProviderOnEnabledChanged;
    }

    public void Dispose()
    {
        _ethereumHostProvider.SelectedAccountChanged -= HostProvider_SelectedAccountChanged;
        _ethereumHostProvider.NetworkChanged -= HostProvider_NetworkChanged;
        _ethereumHostProvider.EnabledChanged -= HostProviderOnEnabledChanged;
    }

    protected override async Task OnInitializedAsync()
    {
        EthereumAvailable = await _ethereumHostProvider.CheckProviderAvailabilityAsync();
        if (EthereumAvailable)
        {
            SelectedAccount = await _ethereumHostProvider.GetProviderSelectedAccountAsync();
            await GetChainId();
        }

        var authState = await AuthenticationState; ;
        if (authState != null)
        {
            UserName = authState.User.FindFirst(c => c.Type.Contains(ClaimTypes.NameIdentifier))?.Value;
        }
    }

    private async Task HostProviderOnEnabledChanged(bool enabled)
    {
        if (enabled)
        {
            await GetChainId();
            this.StateHasChanged();
        }
    }

    private async Task GetChainId()
    {
        var web3 = await _ethereumHostProvider.GetWeb3Async();
        var chainId = await web3.Eth.ChainId.SendRequestAsync();
        SelectedChainId = (int)chainId.Value;
    }

    private async Task HostProvider_SelectedAccountChanged(string account)
    {
        SelectedAccount = account;
        await GetChainId();
        this.StateHasChanged();
    }

    private async Task HostProvider_NetworkChanged(int chainId)
    {
        SelectedChainId = chainId;
        this.StateHasChanged();
    }

    protected async Task GetBlockHashAsync()
    {
        var web3 = await _ethereumHostProvider.GetWeb3Async();
        var block = await web3.Eth.Blocks.GetBlockWithTransactionsByNumber.SendRequestAsync(new HexBigInteger(1));
        BlockHash = block.BlockHash;
    }

    protected async Task TransferEtherAsync()
    {
        try
        {
            var web3 = await _ethereumHostProvider.GetWeb3Async();

            TransactionHash = await web3.Eth.GetEtherTransferService().TransferEtherAsync("0x13f022d72158410433cbd66f5dd8bf6d2d129924", 0.001m);
        }
        catch (Exception ex)
        {
            ErrorTransferMessage = ex.Message;
        }
    }
}