Giter Site home page Giter Site logo

substrategaming / substrate.net.api Goto Github PK

View Code? Open in Web Editor NEW
22.0 6.0 16.0 1.05 MB

Substrate .NET API Core for substrate-based nodes

Home Page: https://github.com/SubstrateGaming

License: GNU General Public License v3.0

C# 100.00%
substrate api csharp dotnet gaming polkadot rpc rust unity web3

substrate.net.api's Introduction

Substrate .NET API

Build Nuget GitHub issues license contributors Nuget

Substrate .NET API Core for substrate-based nodes darkfriend77_substrate_gaming

Substrate .NET API is written to provide maximum compatibility for Unity and C# Applications. Feedback, constructive criticism, and discussions are welcome and will help us to improve the API!

Target Frameworks

  • NETStandard2.0
  • NETStandard2.1
  • NET6

This is a substrate ecosystem project, to prepare a general Substrate Gaming SDK, that is usable within the Polkadot ecosystem and beyond. The main goal is to provide an easy entry point for Web3 Game Devs using Substrate as an infrastructure.

Important

Gaming Communities

Discord Banner 1

Projects using Substrate .NET API

In alphabetical order

substrate.net.api's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

substrate.net.api's Issues

Signed Extras is handled inside the NET API

Description

Since signed extras is handled inside the NET API, and it's static, we can't use different signed extras in the SDK for different nodes, this is causing currently issues in accessing Polakdot, Kusama and other Parachains that use the old ChargeTransactionPayment instead of the new ChargeAssetTxPayment.

image.png

Task

Make the signed extras able to be generated in the Toolchain from the metadata exposed.

ex. from Kusama

   "Extrinsic": {
      "TypeId": 768,
      "Version": 4,
      "SignedExtensions": [
        {
          "SignedIdentifier": "CheckNonZeroSender",
          "SignedExtType": 770,
          "AddSignedExtType": 57
        },
        {
          "SignedIdentifier": "CheckSpecVersion",
          "SignedExtType": 771,
          "AddSignedExtType": 4
        },
        {
          "SignedIdentifier": "CheckTxVersion",
          "SignedExtType": 772,
          "AddSignedExtType": 4
        },
        {
          "SignedIdentifier": "CheckGenesis",
          "SignedExtType": 773,
          "AddSignedExtType": 9
        },
        {
          "SignedIdentifier": "CheckMortality",
          "SignedExtType": 774,
          "AddSignedExtType": 9
        },
        {
          "SignedIdentifier": "CheckNonce",
          "SignedExtType": 776,
          "AddSignedExtType": 57
        },
        {
          "SignedIdentifier": "CheckWeight",
          "SignedExtType": 777,
          "AddSignedExtType": 57
        },
        {
          "SignedIdentifier": "ChargeTransactionPayment",
          "SignedExtType": 778,
          "AddSignedExtType": 57
        }
      ]
    },

ex. from Substrate

    "Extrinsic": {
      "TypeId": 664,
      "Version": 4,
      "SignedExtensions": [
        {
          "SignedIdentifier": "CheckNonZeroSender",
          "SignedExtType": 669,
          "AddSignedExtType": 29
        },
        {
          "SignedIdentifier": "CheckSpecVersion",
          "SignedExtType": 670,
          "AddSignedExtType": 4
        },
        {
          "SignedIdentifier": "CheckTxVersion",
          "SignedExtType": 671,
          "AddSignedExtType": 4
        },
        {
          "SignedIdentifier": "CheckGenesis",
          "SignedExtType": 672,
          "AddSignedExtType": 9
        },
        {
          "SignedIdentifier": "CheckMortality",
          "SignedExtType": 673,
          "AddSignedExtType": 9
        },
        {
          "SignedIdentifier": "CheckNonce",
          "SignedExtType": 675,
          "AddSignedExtType": 29
        },
        {
          "SignedIdentifier": "CheckWeight",
          "SignedExtType": 676,
          "AddSignedExtType": 29
        },
        {
          "SignedIdentifier": "ChargeAssetTxPayment",
          "SignedExtType": 677,
          "AddSignedExtType": 29
        }
      ]
    },

Acceptance Criteria

  • Toolchain can add a node specific signature of the extrinsic.

Update `Account` to be more fitting for External Wallets

In Polkadot.js api, the Signer interface contains SignPayload and SignRaw methods that are async.
Screenshot 2024-01-28 at 11 42 12

The IAccount only has 1 method for raw signing and it is not async.
Screenshot 2024-01-28 at 11 49 45

This has got many drawbacks:

  1. Just by using SignRaw, you do not expose the payload information to the account itself. When using an external wallet, it is better to expose the payload data so that the wallet knows what it is signing. This can prevent the user from signing a malicious extrinsic.
  2. Without the async methods, the account has to sign right away. The external wallets would have no time to decide to sign.

Example solution:

using Chaos.NaCl;
using Newtonsoft.Json;
using Schnorrkel;
using Substrate.NetApi.Model.Extrinsics;
using Substrate.NetApi.Model.Types.Base;
using System;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;

namespace Substrate.NetApi.Model.Types
{
    /// <summary>
    /// Represents a key type.
    /// </summary>
    public enum KeyType
    {
        Ed25519,
        Sr25519
    }

    /// <summary>
    /// Interface for an account.
    /// </summary>
    public interface IAccount
    {
        Task<byte[]> SignRawAsync(byte[] rawBytes);

        Task<byte[]> SignPayloadAsync(Payload payload);

        bool Verify(byte[] signature, byte[] publicKey, byte[] message);
    }

    /// <summary>
    /// Represents an account.
    /// </summary>
    public class Account : AccountId, IAccount
    {
        public KeyType KeyType { get; private set; }

        [JsonIgnore]
        public byte KeyTypeByte
        {
            get
            {
                switch (KeyType)
                {
                    case KeyType.Ed25519:
                        return 0;

                    case KeyType.Sr25519:
                        return 1;

                    default:
                        throw new NotSupportedException($"Unknown key type found '{KeyType}'.");
                }
            }
        }

        [JsonIgnore] public byte[] PrivateKey { get; private set; }

        /// <summary>
        /// Creates the specified key type with private key.
        /// </summary>
        /// <param name="keyType"></param>
        /// <param name="privateKey"></param>
        /// <param name="publicKey"></param>
        public void Create(KeyType keyType, byte[] privateKey, byte[] publicKey)
        {
            KeyType = keyType;
            PrivateKey = privateKey;
            base.Create(publicKey);
        }

        /// <summary>
        /// Creates the specified key type, without private key.
        /// </summary>
        /// <param name="keyType"></param>
        /// <param name="publicKey"></param>
        public void Create(KeyType keyType, byte[] publicKey)
        {
            Create(keyType, null, publicKey);
        }

        /// <summary>
        /// Builds the specified key type.
        /// </summary>
        /// <param name="keyType"></param>
        /// <param name="privateKey"></param>
        /// <param name="publicKey"></param>
        /// <returns></returns>
        public static Account Build(KeyType keyType, byte[] privateKey, byte[] publicKey)
        {
            var account = new Account();
            account.Create(keyType, privateKey, publicKey);
            return account;
        }

        /// <summary>
        /// Signs the specified message.
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
        public async Task<byte[]> SignRawAsync(byte[] message)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
        {
            switch (KeyType)
            {
                case KeyType.Ed25519:
                    return Ed25519.Sign(message, PrivateKey);

                case KeyType.Sr25519:
                    return Sr25519v091.SignSimple(Bytes, PrivateKey, message);

                default:
                    throw new NotSupportedException($"Unknown key type found '{KeyType}'.");
            }
        }

        /// <summary>
        /// Signs the specified extrinsic payload.
        /// </summary>
        /// <param name="payload"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        public async Task<byte[]> SignPayloadAsync(Payload payload)
        {
            var payloadBytes = payload.Encode();

            /// Payloads longer than 256 bytes are going to be `blake2_256`-hashed.
            if (payloadBytes.Length > 256) payloadBytes = HashExtension.Blake2(payloadBytes, 256);

            return await SignRawAsync(payloadBytes);
        }

        /// <summary>
        /// Verifies a signature from this account.
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public bool Verify(byte[] signature, byte[] message)
        {
            return Verify(signature, Bytes, message);
        }

        /// <summary>
        /// Verifies the specified signature.
        /// </summary>
        /// <param name="signature"></param>
        /// <param name="publicKey"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        /// <exception cref="NotSupportedException"></exception>
        public bool Verify(byte[] signature, byte[] publicKey, byte[] message)
        {
            switch (KeyType)
            {
                case KeyType.Ed25519:
                    return Ed25519.Verify(signature, message, publicKey);

                case KeyType.Sr25519:
                    return Sr25519v091.Verify(signature, publicKey, message);

                default:
                    throw new NotSupportedException($"Unknown key type found '{KeyType}'.");
            }
        }
    }
}

Problems with this solution:

  • It requires other methods to also become async. This would potentially break existing codes.

`tokenDecimals` is causing errors when connecting to certain parachains

Newtonsoft.Json.JsonSerializationException: Deserializing JSON-RPC result to type Properties failed with JsonReaderException: Error reading integer. Unexpected token: StartArray. Path 'result.tokenDecimals'.
 ---> Newtonsoft.Json.JsonReaderException: Error reading integer. Unexpected token: StartArray. Path 'result.tokenDecimals'.
   at Newtonsoft.Json.JsonReader.ReadAsInt32()
   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)
   at Newtonsoft.Json.Linq.JToken.ToObject[T](JsonSerializer jsonSerializer)
   at StreamJsonRpc.JsonMessageFormatter.JsonRpcResult.GetResult[T]()
   --- End of inner exception stack trace ---
   at StreamJsonRpc.JsonMessageFormatter.JsonRpcResult.GetResult[T]()
   at StreamJsonRpc.JsonRpc.InvokeCoreAsync[TResult](RequestId id, String targetName, IReadOnlyList`1 arguments, IReadOnlyList`1 positionalArgumentDeclaredTypes, IReadOnlyDictionary`2 namedArgumentDeclaredTypes, CancellationToken cancellationToken, Boolean isParameterObject)
   at Substrate.NetApi.SubstrateClient.InvokeAsync[T](String method, Object parameters, CancellationToken token)
   at Substrate.NetApi.Modules.System.PropertiesAsync(CancellationToken token)
   at Substrate.NetApi.SubstrateClient.ConnectAsync(Boolean useMetaData, Boolean standardSubstrate, CancellationToken token)
   at Substrate.NetApi.SubstrateClient.ConnectAsync(Boolean useMetaData, CancellationToken token)
   at Substrate.NetApi.SubstrateClient.ConnectAsync()
   at PlutoWallet.Model.AjunaExt.SubstrateClientExt.ConnectAsync() in /Users/rostislavlitovkin/Programming/PlutoWallet/PlutoWallet.Model/AjunaExt/SubstrateClientExt.cs:line 83
   at PlutoWalletTests.AssetsTests.Setup() in /Users/rostislavlitovkin/Programming/PlutoWallet/PlutoWalletTests/AssetsTests.cs:line 27

I am getting the following error when Connecting to Bifrost wss://bifrost-polkadot.api.onfinality.io/public-ws rpc:

try
{
    var endpoint = PlutoWallet.Constants.Endpoints.GetEndpointDictionary["bifrost"];

    //string bestWebSecket = await WebSocketModel.GetFastestWebSocketAsync(endpoint.URLs);

    client = new SubstrateClientExt(
        endpoint, // This is my modification, can be ignored
        new Uri("wss://bifrost-polkadot.api.onfinality.io/public-ws"),
        Substrate.NetApi.Model.Extrinsics.ChargeTransactionPayment.Default());

    await client.ConnectAsync();
}
catch (Exception ex)
{
    // Prints the error written at the top
    Console.WriteLine(ex);
}

How to call extrinsics?

I am a Unity developer and I was exploring your packages to connect my game to the Polkadot blockchain. I am still a novice when it comes to blockchain. I was able to connect to the chain using our WebSocket URL and was also able to query all the chain state functions - such as block number, account info, etc.
The next task that I need to perform is calling the extrinsic from Unity. Could you please guide me on how to do this using the Ajuna.NetAPI?
Thanks in advance.

Unable to fetch All Assets

I am trying to fetch All Assets info from Astar parachain.

The endpoint I am using: wss://rpc.astar.network

I am calling assets.asset with

out any parameters. The result should look like this:

Screenshot 2023-05-06 at 12 08 05

However when I try to fetch the data using the code bellow, I receive null instead of a string containing some data.

 var str = await client.InvokeAsync<string>(
                            "state_getStorage",
                            new object[] { "0x682a59d51ab9e48a8c8cc418ff9708d2d34371a193a751eea5883e9553457b2e", null },
                            CancellationToken.None
                        );

// This is true
messagePopup.Text = (str == null).ToString();

SubstrateClient connection event handlers

Problem:
I am currently unable to inform users in my application about the connection status of the SubstrateClient.

Solution:
Introduce EventHandler<T>s that are fired whenever client connects, disconnects, or is reconnecting...

These 5 event handlers would be useful:
onConnect
onDisconnect
onReconnecting
onReconnected
onReconnectFailed

What are the correct DLL files that i need to include / consider when using the Ajuna.NetApi?

I am building a game in unity. I tried installing Ajuna.NetApi via Nuget package manager. The errors that I get are related to multiple copies of the same DLL files present in the project. As unity does not accept the same copies I'm a bit confused as to whether deleting copies will break the code or not.
I tried to use the files from this example Unity3dExample DLL files. It did not work.
Is there any way I can get the correct list of all the DLL files for Ajuna.NetApi?

Add Value and Value2 in BaseEnumType

Hey,

When we handle Events / Errors, we use BaseEnumExt<> generic class based on Enum index and generic parameter.
Here is the file : BaseEnumExt

Each BaseEnumExt inherit from this mother class which is empty :

public abstract class BaseEnumType : BaseType
    {
    }

Value and Value2 properties are defined in each child class like this :

public class BaseEnumExt<T0, T1> : BaseEnumType
       where T0 : Enum
       where T1 : IType, new()
    {
        public override string TypeName() => typeof(T0).Name;

        // ...

        [JsonConverter(typeof(StringEnumConverter))]
        public T0 Value { get; set; }

        public IType Value2 { get; set; }
    }

Is there any way to put Value and Value2 in BaseEnumType ?
Of course for Value2 it is obvious, but Value can not be declare as :

public abstract class BaseEnumType : BaseType
    {
        public Enum Value { get; set; }
        public IType Value2 { get; set; }
    }

Currently, I have created an extension method in my project to handle this with reflexion:

public static class BaseEnumTypeExtension
    {
        public static Enum? GetValue(this BaseEnumType sender)
        {
            var prp = sender.GetType().GetProperty("Value");
            return (Enum?)prp?.GetValue(sender);
        }

        public static IType? GetValue2(this BaseEnumType sender)
        {
            var prp = sender.GetType().GetProperty("Value2");
            return (IType?)prp?.GetValue(sender);
        }

        public static T GetValue<T>(this BaseEnum<T> sender) where T : Enum
        {
            var prp = sender.GetType().GetProperty("Value");
            return (T)prp?.GetValue(sender);
        }
    }

But it seems not the best way to handle it.
@darkfriend77, @RostislavLitovkin what do you think ? :)

Usage Examples

Firstly, thank you for all of your work on this library!

I'm looking at using this library for a project but I'm struggling a bit in figuring out how to use it. I think an example that showed how to (for instance) load a signer, compose and sign an extrinsic, and submit the extrinsic to a chain would be very helpful. As a reference, the examples in py-substrate-interface do a good job at this.

Unable to make a pallet call

Hello, I have been trying to submit an extrinsic. Could you please help me with what is wrong?

           var method = new Method((byte)SelectedPallet.Index, (byte)SelectedCall.Index, Encoding.ASCII.GetBytes(Args));

            var client = new SubstrateClient(new Uri(Preferences.Get("selectedNetwork", "wss://rpc.polkadot.io")), null);
            await client.ConnectAsync();

            Console.WriteLine("Connected");
            await client.Author.SubmitExtrinsicAsync(method, KeysModel.GetAccount(), null, 64);

.Index is of type long - so basically a number

Args is of type string

selected network is a local one 127.0.0.1
(If I try to fetch metadata, it works)

GetAccount() code looks like this:

public static string GetPublicKey()
        {
            return Preferences.Get("publicKey", "Error - no pubKey");
        }

public static Account GetAccount()
        {
            return Account.Build(
                KeyType.Ed25519,
                Utils.HexToByteArray(Preferences.Get("privateKey", ""), true),
                Utils.HexToByteArray(GetPublicKey(), true));
        }

Also here are 2 images that might also help. I am trying to call a custom pallet. First picture is from a wallet app that I am trying to make, the second one is from substrate front end template, where everything works as it should.

Screenshot 2023-02-06 at 20 33 47

Screenshot 2023-02-06 at 20 32 47

Implement interfaces for BaseType

We should consider adding interface abstraction to BaseType.
This would apply to classes like BaseTuple, BaseVec, and BaseEnumExt etc., which handle values with genericity.

Genericity force on manipulate concrete type, offering the benefit of strongly typed classes.
However, this approach comes with a drawback: we're unable to handle a list of events or a tuple of 'IType'.

The advantages of implementing this are as follows:

  • In applications with multiple layers, there are instances where a particular layer doesn't require knowledge of the concrete type (due to the absence of operations or modifications).
  • Reduce complexicity
  • Support for multiple pallet versions.

Let's engage in further discussion about this! ๐Ÿ‘
I will link my PR very soon.

Unhandled SignedExtra, for future use.

Signed Extra

let extra: runtime::SignedExtra = (
	frame_system::CheckNonZeroSender::<runtime::Runtime>::new(),
	frame_system::CheckSpecVersion::<runtime::Runtime>::new(),
	frame_system::CheckTxVersion::<runtime::Runtime>::new(),
	frame_system::CheckGenesis::<runtime::Runtime>::new(),
	frame_system::CheckEra::<runtime::Runtime>::from(sp_runtime::generic::Era::mortal(
		period,
		best_block.saturated_into(),
	)),
	frame_system::CheckNonce::<runtime::Runtime>::from(nonce),
	frame_system::CheckWeight::<runtime::Runtime>::new(),
	pallet_asset_tx_payment::ChargeAssetTxPayment::<runtime::Runtime>::from(0, None),
);

let raw_payload = runtime::SignedPayload::from_raw(
	call.clone(),
	extra.clone(),
	(
		(),
		runtime::VERSION.spec_version,
		runtime::VERSION.transaction_version,
		genesis_hash,
		best_hash,
		(),
		(),
		(),
	),
);
  • CheckNonZeroSender
  • CheckSpecVersion
  • CheckTxVersion
  • CheckGenesis
  • CheckEra
  • CheckNonce,
  • CheckWeight
  • ChargeAssetTxPayment

CheckNonZeroSender & CheckWeight haven't been included on our side, for now, we need to make sure they can be added.

Change the structure of Signing

Right now, to sing a message, you need to write this:

byte[] signature;
switch (account.KeyType)
{
    case KeyType.Ed25519:
        signature = Ed25519.Sign(message, account.PrivateKey);
        break;

    case KeyType.Sr25519:
        signature = Sr25519v091.SignSimple(account.Bytes, account.PrivateKey, message);
        break;

    default:
        throw new Exception($"Unknown key type found '{account.KeyType}'.");
}

This always needs the exposure of the private key right in the code.

I propose a change to instead keep signing inside of an Account. It would work the same way as in polkadot.js api:

// PRIVATE KEY TYPE + RAW SIGNATURE
const signature = alice.sign(message, { withType: true });

This could be done like this:

  1. Create IAccount interface to allow foreign account formats to be used for signing. (This could be useful for a very elegant Plutonication integration)
public interface IAccount {
  ...Address
  ...KeyType
  ...Sign()
  ... more?
}
  1. Implement IAccount for Account type.

  2. Change the public static UnCheckedExtrinsic SubmitExtrinsic(bool signed, Account account, Method method, Era era, uint nonce, ChargeType charge, Hash genesis, Hash startEra, RuntimeVersion runtime) method to support IAccount interface instead of Account type.

Unable to connect to the node. Error on initializing the SubstrateClientExt.

I used the Ajuna.SDK toolchain to create all the scaffolded projects and dragged all the necessary *.dll files into the plugins folder as instructed. I have created a SubstrateManager class to handle/manage all the interactions with the generated classes bundled in the SubstrateClientExt's dll file. Upon initializing the SubstrateClientExt I am running into the following error:

MissingMethodException: Method not found: void Ajuna.NetApi.SubstrateClient..ctor(System.Uri)
SubstrateManager.Start () (at Assets/Scripts/SubstrateManager.cs:26)

Could someone please help me resolve this? Thank you!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.