Before reading this, it is very helpful to understand:
SCALE:
Substrate.Net.Api:
Make sure you have correctly registered your substrate chain to PlutoFramework according to this tutorial:
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
);
Please, use the generated classes and functions. Why?
It makes sure that your application code is always up-to-date, even if your blockchain get upgraded. You can simple sync the mobile application code by regenerating the C# blockchain representation by running ./generator.sh <Your chain name> command.
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
);
In Substrate.Net.Api Method is synonyme to Call.
Do not get confused by the namings.
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.
Always use Transaction Analyzer before submitting an extrinsic