diff --git a/SharedBuildProperties.props b/SharedBuildProperties.props index 4e7056fc..dae136f2 100644 --- a/SharedBuildProperties.props +++ b/SharedBuildProperties.props @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> Solnet - 5.0.6 + 5.0.7 Copyright 2022 © Solnet blockmountain blockmountain diff --git a/src/Solnet.Programs/Abstract/BaseClient.cs b/src/Solnet.Programs/Abstract/BaseClient.cs index c670b98e..cd1b9828 100644 --- a/src/Solnet.Programs/Abstract/BaseClient.cs +++ b/src/Solnet.Programs/Abstract/BaseClient.cs @@ -166,5 +166,18 @@ protected async Task SubscribeAccount(string accountAddres return res; } + + + /// + /// Confirms a transaction - same method as web3.js. + /// + /// The hash of the transaction. + /// The last valid block height of the blockhash used in the transaction. + /// The state commitment to consider when querying the ledger state. + /// Returns null if the transaction wasn't confirmed, otherwise returns the confirmation slot and possible transaction error. + public async Task> ConfirmTransaction(string hash, ulong validBlockHeight, Commitment commitment = Commitment.Finalized) + { + return await TransactionConfirmationUtils.ConfirmTransaction(this.RpcClient, this.StreamingRpcClient, hash, validBlockHeight, commitment); + } } } \ No newline at end of file diff --git a/src/Solnet.Rpc/TransactionUtils.cs b/src/Solnet.Rpc/TransactionUtils.cs new file mode 100644 index 00000000..55fa1f64 --- /dev/null +++ b/src/Solnet.Rpc/TransactionUtils.cs @@ -0,0 +1,95 @@ +using Solnet.Rpc.Messages; +using Solnet.Rpc.Models; +using Solnet.Rpc.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Rpc +{ + /// + /// Utilities to check transaction confirmation status. + /// + public static class TransactionConfirmationUtils + { + /// + /// Confirms a transaction - same method as web3.js. + /// + /// The rpc client instance. + /// The streaming rpc client instance. + /// The hash of the transaction. + /// The last valid block height of the blockhash used in the transaction. + /// The state commitment to consider when querying the ledger state. + /// Returns null if the transaction wasn't confirmed, otherwise returns the confirmation slot and possible transaction error. + public static async Task> ConfirmTransaction(IRpcClient rpc, IStreamingRpcClient streamingRpcClient, + string hash, ulong validBlockHeight, Commitment commitment = Commitment.Finalized) + { + TaskCompletionSource t = new(); + ResponseValue result = null; + + var s = await streamingRpcClient.SubscribeSignatureAsync(hash, (s, e) => + { + result = e; + t.SetResult(); + }, + commitment); + + var checkTask = Task.Run(async () => + { + var currHeight = await rpc.GetBlockHeightAsync(commitment); + while (currHeight.Result < validBlockHeight) + { + await Task.Delay(1000); + currHeight = await rpc.GetBlockHeightAsync(commitment); + } + }); + + + Task.WaitAny(t.Task, checkTask); + + if (!t.Task.IsCompleted) + { + await s.UnsubscribeAsync(); + } + + return result; + } + + /// + /// Confirms a transaction - old web3.js using constant timeout based on commitment parameter. + /// + /// The rpc client instance. + /// The streaming rpc client instance. + /// The hash of the transaction. + /// The state commitment to consider when querying the ledger state. + /// Returns null if the transaction wasn't confirmed, otherwise returns the confirmation slot and possible transaction error. + public static async Task> ConfirmTransaction(IRpcClient rpc, IStreamingRpcClient streamingRpcClient, + string hash, Commitment commitment = Commitment.Finalized) + { + TaskCompletionSource t = new(); + ResponseValue result = null; + + var s = await streamingRpcClient.SubscribeSignatureAsync(hash, (s, e) => + { + result = e; + t.SetResult(); + }, + commitment); + + var timeout = commitment == Commitment.Finalized ? TimeSpan.FromSeconds(60) : TimeSpan.FromSeconds(30); + var delay = Task.Delay(timeout); + + Task.WaitAny(t.Task, delay); + + if (!t.Task.IsCompleted) + { + await s.UnsubscribeAsync(); + } + + return result; + } + + } +}