neo-modules icon indicating copy to clipboard operation
neo-modules copied to clipboard

Support NEP-11 in RpcClient

Open RichardBelsum opened this issue 3 years ago • 2 comments

Support NEP-11 in RpcClient

e.g.

Nep17API

  • Task<byte> DecimalsAsync(UInt160 scriptHash)
  • Task<string> SymbolAsync(UInt160 scriptHash)
  • Task<BigInteger>TotalSupplyAsync(UInt160 scriptHash)
  • Task<RpcNep11TokenInfo> GetTokenInfoAsync(UInt160 scriptHash)
  • Task<BigInteger> BalanceOfAsync(UInt160 scriptHash, UInt160 account)
  • Task<UInt160> OwnerOfAsync(UInt160 scriptHash, ByteString tokenId)
  • Task<RpcNep11Propertie[]> PropertiesAsync(UInt160 scriptHash, ByteString tokenId)
  • Task<RpcNep11TokenInfo[]> TokensAsync(UInt160 scriptHash)
  • Task<RpcNep11TokenInfo[]> TokensOfAsync(UInt160 scriptHash, ByteString owner)
  • Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, ByteString tokenId, object data = null)
  • Task<Transaction> CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, ByteString tokenId, object data = null)

RichardBelsum avatar Jul 01 '22 09:07 RichardBelsum

Tokens/TokensOf should return IAsyncEnumerable<ByteString>.

Possible implementation (as extension methods):

public static IAsyncEnumerable<ByteString> TokensOfAsync(this RpcClient rpcClient, UInt160 scriptHash, UInt160 owner)
{
    const string METHOD = "tokensOf";
    var script = Neo.VM.Helper.MakeScript(scriptHash, "tokensOf", owner);
    return rpcClient.ParseTokenResults(script, METHOD);
}

public static IAsyncEnumerable<ByteString> TokensAsync(this RpcClient rpcClient, UInt160 scriptHash)
{
    const string METHOD = "tokens";
    var script = Neo.VM.Helper.MakeScript(scriptHash, METHOD);
    return rpcClient.ParseTokenResults(script, METHOD);
}

static async IAsyncEnumerable<ByteString> ParseTokenResults(this RpcClient rpcClient, byte[] script, string method = null)
{
    method ??= "InvokeScript";
    var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false);
    if (result.State != Neo.VM.VMState.HALT)
    {
        var message = string.IsNullOrEmpty(result.Exception) ? $"{method} returned {result.State}" : result.Exception;
        throw new Exception(message);
    }
    if (!string.IsNullOrEmpty(result.Session)
        && result.Stack.Length > 0
        && TryGetIteratorId(result.Stack[0], out var iteratorId))
    {
        await foreach (var json in rpcClient.TraverseIteratorAsync(result.Session, iteratorId))
        {
            yield return (ByteString)StackItemFromJson(json);
        }
    }

    throw new Exception($"{method} returned unexpected results");

    static bool TryGetIteratorId(Neo.VM.Types.StackItem item, out string iteratorId)
    {
        if (item is Neo.VM.Types.InteropInterface interop)
        {
            var @object = interop.GetInterface<object>();
            if (@object is JObject json)
            {
                iteratorId = json["id"]?.AsString() ?? "";
                if (json["interface"]?.AsString() == "IIterator"
                    && !string.IsNullOrEmpty(iteratorId))
                {
                    return true;
                }
            }
        }

        iteratorId = string.Empty;
        return false;
    }
}

devhawk avatar Jul 07 '22 23:07 devhawk

And a possible implementation of PropertiesAsync

public static async Task<IReadOnlyDictionary<string, StackItem>> PropertiesAsync(this RpcClient rpcClient, UInt160 scriptHash, ByteString tokenId)
{
    const string METHOD = "properties";
    var script = Neo.VM.Helper.MakeScript(scriptHash, METHOD, tokenId.GetSpan().ToArray());
    var result = await rpcClient.InvokeScriptAsync(script);

    if (result.State != Neo.VM.VMState.HALT)
    {
        var message = string.IsNullOrEmpty(result.Exception) ? $"{METHOD} returned {result.State}" : result.Exception;
        throw new Exception(message);
    }
    if (result.Stack.Length > 0
        && result.Stack[0] is Neo.VM.Types.Map map)
    {
        return map.ToDictionary(kvp => kvp.Key.GetString(), kvp => kvp.Value);
    }

    throw new Exception($"{METHOD} returned unexpected results");
}

devhawk avatar Jul 07 '22 23:07 devhawk