Substrate

How to query Substrate chain state and how to sign and submit extrinsics.

Before reading this, it is very helpful to understand:

Make sure you have correctly registered your substrate chain to PlutoFramework according to this tutorial:

Register new Chain

About PlutoFrameworkSubstrateClient

SubstrateClient is the main communicator with Substrate chains. In fact, PlutoFramework creates a new independent instance of SubstrateClient for each Substrate chain it connects to and maintains this connection to be ready to query new state or submit extrinsics at any time.

We have created a SubstrateClient wrapper called PlutoFrameworkSubstrateClient that handles the UI synchronization, listens to extrinsic status callbacks, handles reconnections and more.

At the moment, there are 2 main ways to get access to the SubstrateClient.

1) Through ISubstrateClientLoadable interfaces implemented on a component.

// Example of such implemented interface method
public Task LoadAsync(PlutoFrameworkSubstrateClient client, CancellationToken token)
{
    // You can allow only your chain.
    if (client is null || client.Endpoint.Key != Constants.EndpointEnum.YourChain || !client.SubstrateClient.IsConnected)
    {
        return Task.FromResult(0);
    }

    return ((IdentityViewModel)BindingContext).GetIdentityAsync((PolkadotPeople.NetApi.Generated.SubstrateClientExt)client.SubstrateClient, token);
}

2) GetOrAddSubstrateClientAsync method:

var client = await SubstrateClientModel.GetOrAddSubstrateClientAsync(EndpointEnum.YourChain, CancellationToken.None)

Both of these ways give you PlutoFrameworkSubstrateClient. To get access to the SubstrateClient. just write:

var substrateClient = (YourChain.NetApi.Generated.SubstrateClientExt)client.SubstrateClient;

Query chain state (Pallet storage)

I recommend creating your own model class in PlutoFramework.Model folder to make the code reusable and testable.

Then, find the pallet storages you want to query from in your Chain generated folder.

Example: If I would want to query System.Account storage on PolkadotAssetHub parachain, I need to use the following method found in Generated/PolkadotAssetHub.NetApi/Generated/Storage/MainSystem.cs:

/// <summary>
/// >> Account
///  The full account information for a particular account ID.
/// </summary>
public async Task<PolkadotAssetHub.NetApi.Generated.Model.frame_system.AccountInfo> Account(PolkadotAssetHub.NetApi.Generated.Model.sp_core.crypto.AccountId32 key, string blockhash, CancellationToken token)
{
    string parameters = SystemStorage.AccountParams(key);
    var result = await _client.GetStorageAsync<PolkadotAssetHub.NetApi.Generated.Model.frame_system.AccountInfo>(parameters, blockhash, token);
    return result;
}

This is the code example of querying such storage:

var token = CancellationToken.None;

// I make sure that the client is PolkadotAssetHub
if (client.Endpoint.Key != EndpointEnum.PolkadotAssetHub)
{
    return;
}
    
var substrateClient = (PolkadotAssetHub.NetApi.Generated.SubstrateClientExt)client.SubstrateClient;

// Example Substrate address
var substrateAddress = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";

// Parameter of the query
var account = new PolkadotAssetHub.NetApi.Generated.Model.sp_core.crypto.AccountId32();
account.Create(Utils.GetPublicKeyFrom(substrateAddress));

// Query chain state
var result = await substrateClient.SystemStorage.Account(
     account,
     null,
     token
);

Submitting extrinsics

Submitting extrinsics works very similarly to querying on-chain data.

You are also strongly recommended to utilise the generated classes and functions, also found in the Generated/Storage/MainSomething.cs files, but found in the lower portion of the file.

Example: If I want to call Balances.transfer_keep_alive extrinsic, I need to use the following method found in Generated/PolkadotAssetHub.NetApi/Generated/Storage/MainBalances.cs.

/// <summary>
/// >> BalancesCalls
/// </summary>
public sealed class BalancesCalls
{
    // ...
    
    /// <summary>
    /// >> transfer_keep_alive
    /// Contains a variant per dispatchable extrinsic that this pallet has.
    /// </summary>
    public static Method TransferKeepAlive(PolkadotAssetHub.NetApi.Generated.Model.sp_runtime.multiaddress.EnumMultiAddress dest, Substrate.NetApi.Model.Types.Base.BaseCom<Substrate.NetApi.Model.Types.Primitive.U128> value)
    {
        System.Collections.Generic.List<byte> byteArray = new List<byte>();
        byteArray.AddRange(dest.Encode());
        byteArray.AddRange(value.Encode());
        return new Method(10, "Balances", 3, "transfer_keep_alive", byteArray.ToArray());
    }
    
    // ...
}

Example of such call:

var token = CancellationToken.None;

// Example Substrate address
var substrateAddress = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
var amount = 10_000_000_000 // 1 DOT

var accountId = new AccountId32();
accountId.Create(Utils.GetPublicKeyFrom(substrateAddress));

var multiAddress = new EnumMultiAddress();
multiAddress.Create(0, accountId);

var baseComAmount = new BaseCom<U128>();
baseComAmount.Create(amount);

// Extrinsic Call itself
var method = BalancesCalls.TransferKeepAlive(multiAddress, baseComAmount);

// Submitting the extrinsic
var transactionAnalyzerConfirmationViewModel = DependencyService.Get<TransactionAnalyzerConfirmationViewModel>();
await transactionAnalyzerConfirmationViewModel.LoadAsync(
    client, // PlutoFrameworkSubstrateClient
    method,
    showDAppView: false,
    token: token
);

The last 2 lines are used to summon a confirmation popup for the user before they actually submit the extrinsic. This shows the TransactionAnalyzer simulated result, extrinsic payload can be expanded, estimated fees are shown and more. Your users will be sure that they have not accidentally misclicked or that they have not been hacked/scammed by someone. Accidents happen and it is better to be safe than sorry.

Transaction Analyzer

Last updated