development local blockchain - ajuna-network/Polkadot.Unity.SDK GitHub Wiki

Step-by-Step Guide

For who is it?

The full development environment targets devs and teams, that want to test the functionalities of a substrate-based chain in a local environment. It also allows us to have a very close runtime and unity frontend development workflow.

What skills are needed?

A basic understanding of substrate and pallets will help a lot in moving fast forward. It's not an essential requirement, but it will make your life easier. For this purpose, it is suggested to read the Substrate Tutorials.

1. Setup a local blockchain environment

If you want to start working with Unity on a blockchain the minimum requirement is that you can access a blockchain. The following possibilities exist.

  • Target a hosted Blockchain, through a (secure) web socket
  • Target a local Blockchain, through a HTTP-connection

If you target a hosted blockchain you can skip this part and jump to the generate the extension part.

To set up a local blockchain, you can either run a docker container, as an example you can follow the hexalem docker guide or you can set up your substrate blockchain environment, which is advised for runtime engineers, and devs that want to leverage the full potential of the SDK.

To set up a local substrate blockchain environment ready to hack, follow this tutorial build a local blockchain.

In the next step, you will create the individual SDK to access your local blockchain.

2. Generate the Extension

To create your Extension library, which builds upon the Substrate .NET API, you will need to have your local blockchain up and running, you can follow either the Toolchain readme, read the generate extension page, or follow the few steps here.

dotnet new install Substrate.DotNet.Template

Create a directory called Substrate.MyNode.NET (mkdir Substrate.MyNode.NET) then change into this directory (cd Substrate.MyNode.NET) and execute the commands. (Make sure to change to the version of the template you previously installed) For not local blockchain make sure to replace ws://127.0.0.1:9944 with the secure web socket node URL.

dotnet new sln
dotnet new substrate \
   --sdk_version 0.4.8 \
   --rest_service Substrate.MyNode.NET.RestService \
   --net_api Substrate.MyNode.NET.NetApiExt \
   --rest_client Substrate.MyNode.NET.RestClient \
   --metadata_websocket ws://127.0.0.1:9944 \
   --generate_openapi_documentation false \
   --force \
   --allow-scripts yes

This should look similar to this ...

Generate Extension

3. Integrate the Extension

As a result of the generation you will have now a c# solution including your extension, and additional services like a REST-Services and a REST-Client, the DLLs are builder as part of the toolchain execution and can be found under the following directory ..\Substrate.MyNode.NET\Substrate.MyNode.NET.NetApiExt\bin\Release you can choose between NET6.0, NETStandard2.0, and NETStandard2.1, we suggest you always choose the highest NETStandard that you Unity version is using. Please verify this on the Unity page .NET profile support.

Copy the Substrate.MyNode.NET.NetApiExt.dll into the Unity Asset/Plugins folder and you are ready to go!

4. Connect to the local blockchain

using Substrate.MyNode.NET.NetApiExt.Generated;
using Substrate.NetApi.Model.Extrinsics;
using System;
using System.Threading.Tasks;

namespace MyNode
{
    internal class Program
    {
        static async Task Main(string[] args)
        {

            var client = new SubstrateClientExt(new Uri("ws://127.0.0.1:9944"), ChargeTransactionPayment.Default());

            await client.ConnectAsync();

            Console.WriteLine($"Client is connected? {client.IsConnected}");

            await client.CloseAsync();
        }

    }
}

5. Access RPC Modules, Extension Storage & Execute Transactions

using Schnorrkel.Keys;
using Serilog;
using StreamJsonRpc;
using Substrate.MyNode.NET.NetApiExt.Generated;
using Substrate.MyNode.NET.NetApiExt.Generated.Model.sp_core.crypto;
using Substrate.MyNode.NET.NetApiExt.Generated.Model.sp_runtime.multiaddress;
using Substrate.MyNode.NET.NetApiExt.Generated.Storage;
using Substrate.NetApi;
using Substrate.NetApi.Model.Extrinsics;
using Substrate.NetApi.Model.Rpc;
using Substrate.NetApi.Model.Types;
using Substrate.NetApi.Model.Types.Base;
using Substrate.NetApi.Model.Types.Primitive;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyNode
{
    internal class Program
    {
        public static MiniSecret MiniSecretAlice => new MiniSecret(Utils.HexToByteArray("0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"), ExpandMode.Ed25519);
        public static Account Alice => Account.Build(KeyType.Sr25519, MiniSecretAlice.ExpandToSecret().ToBytes(), MiniSecretAlice.GetPair().Public.Key);

        static async Task Main(string[] args)
        {

            var yourAccount = Alice; // local blockchain use alice account

            var client = new SubstrateClientExt(new Uri("ws://127.0.0.1:9944"), ChargeTransactionPayment.Default());

            // connect to substrate node
            await client.ConnectAsync();

            // leave if we can't connect
            if (!client.IsConnected)
            {
                Console.WriteLine($"Couldn't connect to local host.");
                return;
            }

            // ... do something with the client

            // direct rpc call module chain
            var blockHash = await client.Chain.GetBlockHashAsync();

            // direct rpc call module system
            var name = await client.System.NameAsync();

            // extension storage rpc call
            var blocknumber = await client.SystemStorage.Number(CancellationToken.None);

            // extension extrinsic rpc call
            EnumMultiAddress address = new EnumMultiAddress();
            var account = new AccountId32();
            account.Create(Utils.GetPublicKeyFrom("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"));
            address.Create(MultiAddress.Id, account);
            var amount = new BaseCom<U128>(1000000000000);
            var extrinsicMethod = BalancesCalls.TransferKeepAlive(address, amount);

            string subscription = null;
            try
            {
                subscription = await client.Author.SubmitAndWatchExtrinsicAsync(ActionExtrinsicUpdate, extrinsicMethod, yourAccount, ChargeTransactionPayment.Default(), 64, CancellationToken.None);
                Console.WriteLine($"BalancesCalls.TransferKeepAlive: {subscription}");
            }
            catch (RemoteInvocationException e)
            {
                Log.Error("RemoteInvocationException: {0}", e.Message);
            }


            // disconnect from substrate node
            await client.CloseAsync();
        }

        private static void ActionExtrinsicUpdate(string subscription, ExtrinsicStatus status)
        {
            // call back for extrinsic update
        }
    }
}
⚠️ **GitHub.com Fallback** ⚠️