WalletConnect / WalletConnectSharp

A C# implementation of the WalletConnect client
Apache License 2.0
139 stars 60 forks source link

error null reference in get request from dapp exchange intro file Sto… #197

Closed MassoudKargar closed 3 weeks ago

MassoudKargar commented 3 weeks ago

…re method Set property map

skibitsky commented 3 weeks ago

Hey @MassoudKargar,

Thank you for the PR! Could you please explain what exact issue it solves? The map is statically initialized, so I'm curious about the conditions that result in the null reference.

MassoudKargar commented 3 weeks ago

I was programming an inter-voltage connection that I wrote myself with a dapp exchange like pancakeswap, and when I received the eth_sendTransaction event, I was getting an error and I couldn't continue, and the system didn't react at all, that's why I started debugging your system and realized the problem. I think it is because of the new system of Asp .net 8 about enable that this feature has created the issue. However, I know that this small change can help solve the problem.

skibitsky commented 3 weeks ago

I am trying to understand why the map is null for you. It is automatically initialized with an empty dictionary when an instance of the Store object is created, even before Init() is called. The map is set to null only in the Dispose() method. This makes me think that you are trying to access the store after it has been disposed.

While your suggestion fixes the error message, you may encounter more issues later because you are operating on a disposed Store.

Could you share your code or explain how you initialize the SDK and handle session requests?

MassoudKargar commented 3 weeks ago

I am programming in blazor webassembly application


And I get this error

Debugging hotkey: Shift+Alt+D (when application has focus) [MONO] /w/1/s/src/mono/mono/metadata/icall.c:6177 [MONO] /w/1/s/src/mono/mono/metadata/icall.c:6186 (null) Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at WalletConnectSharp.Core.Controllers.Store2[[System.Int64, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[WalletConnectSharp.Sign.Models.PendingRequestStruct, WalletConnectSharp.Sign, Version=2.3.7.0, Culture=neutral, PublicKeyToken=null]].Set(Int64 key, PendingRequestStruct value) at WalletConnectSharp.Sign.Engine.WalletConnectSharp.Sign.Interfaces.IEnginePrivate.SetPendingSessionRequest(PendingRequestStruct pendingRequest) at WalletConnectSharp.Sign.Models.SessionRequestEventHandler2.d6[[BlazorApp1.Pages.Home.EthGetTransactionReceiptWrapper, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null],[BlazorApp1.Pages.Home.EthSignTransactionResponse, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() at WalletConnectSharp.Sign.Models.TypedEventHandler`2.d34[[WalletConnectSharp.Sign.Models.Engine.Methods.SessionRequest1[[BlazorApp1.Pages.Home.EthGetTransactionReceiptWrapper, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], WalletConnectSharp.Sign, Version=2.3.7.0, Culture=neutral, PublicKeyToken=null],[BlazorApp1.Pages.Home.EthSignTransactionResponse, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() at WalletConnectSharp.Core.Controllers.TypedMessageHandler.<>c__DisplayClass18_02.<gRequestCallback|0>d[[WalletConnectSharp.Sign.Models.Engine.Methods.SessionRequest`1[[BlazorApp1.Pages.Home.EthGetTransactionReceiptWrapper, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], WalletConnectSharp.Sign, Version=2.3.7.0, Culture=neutral, PublicKeyToken=null],[BlazorApp1.Pages.Home.EthSignTransactionResponse, BlazorApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]].MoveNext() --- End of stack trace from previous location --- at System.Threading.Tasks.Task.<>c.b128_1(Object state) at System.Threading.QueueUserWorkItemCallbackDefaultContext.Execute() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading.ThreadPool.BackgroundJob


And this is my code


@page "/" @using Newtonsoft.Json @using WalletConnectSharp.Common.Utils @using WalletConnectSharp.Network.Models @using WalletConnectSharp.Sign @using WalletConnectSharp.Sign.Models @using WalletConnectSharp.Sign.Models.Engine.Events @using WalletConnectSharp.Storage

Home

Hello, world!

@if (dataset) { <input type="text" @bind="@Uri" @bind:event="oninput" @bind:after="OnConnectUrl" /> }

Welcome to your new app. @code { public string? Uri { get; set; } public static string? Uris { get; set; } static readonly string words = "...."; Wallet? wallet; static Account? account; WalletConnectSignClient? client; ProposalStruct? proposalStruct; Engine? engin; bool dataset; public Dictionary<int, string>? ChainList { get; set; } } @functions { protected override async Task OnInitializedAsync() { ChainList = new Dictionary<int, string>() { { 56, "https://binance.llamarpc.com" }, { 97, "https://endpoints.omniatech.io/v1/bsc/testnet/public" }, { 11155111, "https://endpoints.omniatech.io/v1/eth/sepolia/public" }, }; dataset = false; await InitData(); }

private async Task InitData()
{
    wallet = new Wallet(words, null);
    account = wallet.GetAccount(0);

    var dappOptions = new SignClientOptions()
        {
            ProjectId = "....",
            Metadata = new Metadata()
            {
                Description = "An example dapp to showcase WalletConnectSharpv2",
                Icons = new[] { "https://walletconnect.com/meta/favicon.ico" },
                Name = "WalletConnectSharpv2 Dapp Example",
                Url = "https://walletconnect.com",
            },
            Storage = new InMemoryStorage(),
            Name = "my-app",
        };

    client = await WalletConnectSignClient.Init(dappOptions);
    dataset = true;
}

public async Task OnConnectUrl()
{
    if (client != null)
    {
        try
        {
            client.PendingRequests.Dispose();
            client.SessionProposed += SessionProposed;
            proposalStruct = await client.Pair(Uri);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}

private async void SessionProposed(object? sender, SessionProposalEvent e)
{
    string[]? manespacesMethods = e.Proposal.OptionalNamespaces.Values.Select(s => s.Methods).FirstOrDefault();
    engin = sender as Engine;
    if (engin != null && client != null && account != null)
    {
        engin.SessionRequestEvents<EthGetTransactionReceiptWrapper, EthSignTransactionResponse>().OnRequest += OnRequestEthGetTransactionReceiptWrapper;
        var approveData = await client.Approve(e.Proposal, new[] { account.Address });
        await approveData.Acknowledged();
    }
}

private async Task OnRequestEthGetTransactionReceiptWrapper(RequestEventArgs<EthGetTransactionReceiptWrapper, EthSignTransactionResponse> requestData)
{
    try
    {
        var chainId = int.Parse(client.AddressProvider.DefaultChainId.Split(':')[1]);
        var ethGetTransaction = requestData.Request.Params.FirstOrDefault();
        var account = new Account(Home.account.PrivateKey);
        Web3 web3 = new(new Account(account.PrivateKey), url: ChainList[chainId]) { TransactionManager = { UseLegacyAsDefault = true, } };
        var tx = new TransactionInput()
            {
                Data = ethGetTransaction.Data,
                From = ethGetTransaction.From,
                To = ethGetTransaction.To,
                Gas = new HexBigInteger(ethGetTransaction.Gas),
                Value = new HexBigInteger(ethGetTransaction.Value),
                ChainId = new HexBigInteger($"0x{chainId}")
            };
        var receipt = await web3.Eth.TransactionManager.TransactionReceiptService.DeployContractAndWaitForReceiptAsync(tx);
        await engin.Respond<EthGetTransactionReceiptWrapper, EthSignTransactionResponse>(requestData.Topic, new JsonRpcResponse<EthSignTransactionResponse>(requestData.Request.Id, new Error(), new EthSignTransactionResponse(receipt)));
        requestData.Response = receipt;
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

[RpcResponseOptions(Clock.ONE_MINUTE, 99999)]
[JsonConverter(typeof(ConvertToStr<TransactionReceipt>))]
public class EthSignTransactionResponse
{
    private readonly TransactionReceipt _response;

    public EthSignTransactionResponse(TransactionReceipt response)
    {
        _response = response;
    }

    public static implicit operator EthSignTransactionResponse(TransactionReceipt response)
    {
        if (response == null)
            return null;

        return new EthSignTransactionResponse(response);
    }

    public override string ToString()
    {
        return _response.BlockHash;
    }
}

public class ConvertToStr<T> : JsonConverter where T : class
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(string) || objectType == typeof(T);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Deserialize the value as usual
        return serializer.Deserialize<string>(reader) as T;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value.ToString());
    }
}
[RpcMethod("eth_sendTransaction"), RpcRequestOptions(Clock.ONE_MINUTE, 99999)]
public class EthGetTransactionReceiptWrapper : List<EthGetTransaction?>
{
}

public class EthGetTransaction
{
    [JsonProperty("from")]
    public string? From { get; set; }

    [JsonProperty("to")]
    public string? To { get; set; }

    [JsonProperty("data")]
    public string? Data { get; set; }

    [JsonProperty("gas")]
    public string? Gas { get; set; }

    [JsonProperty("value")]
    public string? Value { get; set; }
}

}

skibitsky commented 3 weeks ago

@MassoudKargar I believe the issue might be in your OnConnectUrl method, specifically with disposing client.PendingRequests. You should dispose the client when it is no longer needed and don't dispose individual parts of the client.

MassoudKargar commented 3 weeks ago

I did not understand exactly, can you give me a sample code or give an example please?

skibitsky commented 3 weeks ago

@MassoudKargar from your code:

public async Task OnConnectUrl()
{
    if (client != null)
    {
        try
        {
            client.PendingRequests.Dispose(); // ← REMOVE
            client.SessionProposed += SessionProposed;
            proposalStruct = await client.Pair(Uri);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
}
MassoudKargar commented 3 weeks ago

Thanks, the problem is solved, but I don't remember why I used such a code. But I have another problem and thank you for your help. I have a problem with the response for the OnRequestEthGetTransactionReceiptWrapper method for the eth_sendTransaction event. Is it possible for you to give me an example? I get this error on pancakeswap.finance service

Swap failed: Unknown error: "Request expired. Please try again.". Try increasing your slippage tolerance. Dismiss

skibitsky commented 3 weeks ago

@MassoudKargar could you please open an issue? In the issue please specify if you're building a wallet or an app.