Skip to content

Commit 3b65b1a

Browse files
authored
Merge pull request #163 from IOTA-NET/162-need-ability-to-await-for-transaction-to-be-included
162 need ability to await for transaction to be included
2 parents 3fe35c4 + 4772c3a commit 3b65b1a

File tree

10 files changed

+264
-12
lines changed

10 files changed

+264
-12
lines changed

csharp/IotaWalletNet/IotaWalletNet.Application/Account.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ public async Task<BuildBasicOutputResponse> BuildBasicOutputAsync(BuildBasicOutp
6969
return await _mediator.Send(new BuildBasicOutputCommand(buildBasicOutputData, Username, this));
7070
}
7171

72-
public async Task<Task> EnablePeriodicSyncing(int intervalInMilliSeconds)
72+
public async Task<Task> EnablePeriodicSyncing(int intervalInMilliSeconds, int count = 0)
7373
{
74-
return await _mediator.Send(new EnablePeriodicSyncingCommand(this, intervalInMilliSeconds));
74+
return await _mediator.Send(new EnablePeriodicSyncingCommand(this, intervalInMilliSeconds, count));
7575
}
7676

7777
public async Task<GetOutputsWithAdditionalUnlockConditionsResponse> GetOutputsWithAdditionalUnlockConditionsAsync(OutputTypeToClaim outputTypeToClaim)

csharp/IotaWalletNet/IotaWalletNet.Application/AccountContext/Commands/EnablePeriodicSyncing/EnablePeriodicSyncingCommand.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ namespace IotaWalletNet.Application.AccountContext.Commands.EnablePeriodicSyncin
55
{
66
public class EnablePeriodicSyncingCommand : IRequest<Task>
77
{
8-
public EnablePeriodicSyncingCommand(IAccount account, int intervalInMilliSeconds)
8+
public EnablePeriodicSyncingCommand(IAccount account, int intervalInMilliSeconds, int count)
99
{
1010
Account = account;
1111
IntervalInMilliSeconds = intervalInMilliSeconds;
12+
Count = count;
1213
}
1314

1415
public IAccount Account { get; set; }
1516

1617
public int IntervalInMilliSeconds { get; set; }
17-
18+
public int Count { get; set; }
1819
}
1920
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,38 @@
1-
using MediatR;
1+
using IotaWalletNet.Application.Common.Interfaces;
2+
using MediatR;
23

34
namespace IotaWalletNet.Application.AccountContext.Commands.EnablePeriodicSyncing
45
{
56
public class EnablePeriodicSyncingCommandHandler : IRequestHandler<EnablePeriodicSyncingCommand, Task>
67
{
78
public Task<Task> Handle(EnablePeriodicSyncingCommand request, CancellationToken cancellationToken)
89
{
10+
static async Task SyncAndDelay(IAccount account, int interval)
11+
{
12+
Console.WriteLine(("Syncing..."));
13+
await account.SyncAccountAsync();
14+
await Task.Delay(interval);
15+
}
16+
917
Task periodicSyncingTask = Task.Run(async () =>
1018
{
11-
await request.Account.SyncAccountAsync();
12-
await Task.Delay(request.IntervalInMilliSeconds);
19+
if (request.Count <= 0)
20+
{
21+
while (true)
22+
await SyncAndDelay(request.Account, request.IntervalInMilliSeconds);
23+
}
24+
else
25+
{
26+
while(request.Count != 0)
27+
{
28+
await SyncAndDelay(request.Account, request.IntervalInMilliSeconds);
29+
request.Count--;
30+
}
31+
}
1332
});
1433

1534
return Task.FromResult(periodicSyncingTask);
35+
1636
}
1737
}
1838
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using IotaWalletNet.Application.Common.Interfaces;
2+
using IotaWalletNet.Domain.Common.Interfaces;
3+
using IotaWalletNet.Domain.Common.Models.Events.WalletEventTypes;
4+
using IotaWalletNet.Domain.Common.Models.Transaction;
5+
using static IotaWalletNet.Domain.Common.Models.Events.EventTypes;
6+
7+
namespace IotaWalletNet.Application.Common.Extensions
8+
{
9+
public static class TransactionExtensions
10+
{
11+
private static SemaphoreSlim MUTEX = new SemaphoreSlim(1);
12+
private static string? TRANSACTION_ID = null;
13+
private static Action<IWalletEvent>? WAIT_CONFIRMATION_SET_RESULT;
14+
15+
public static async Task WaitForConfirmationAsync(this Transaction transaction, IWallet wallet)
16+
{
17+
await MUTEX.WaitAsync();
18+
19+
wallet.WalletEventReceived += Account_TransactionInclusionWalletEventReceived;
20+
TRANSACTION_ID = transaction.TransactionId;
21+
22+
TaskCompletionSource<IWalletEvent> taskCompletionSource = new TaskCompletionSource<IWalletEvent>();
23+
Task<IWalletEvent> waitConfirmationTask = taskCompletionSource.Task;
24+
WAIT_CONFIRMATION_SET_RESULT = taskCompletionSource.SetResult;
25+
26+
await waitConfirmationTask;
27+
28+
wallet.WalletEventReceived -= Account_TransactionInclusionWalletEventReceived;
29+
WAIT_CONFIRMATION_SET_RESULT = null;
30+
TRANSACTION_ID = null;
31+
32+
MUTEX.Release();
33+
}
34+
35+
public static void Account_TransactionInclusionWalletEventReceived(object? sender, IWalletEvent? walletEvent)
36+
{
37+
if (walletEvent!.Event.Type == WalletEventTypes.TransactionInclusion.ToString())
38+
{
39+
if (WAIT_CONFIRMATION_SET_RESULT == null || TRANSACTION_ID == null)
40+
return;
41+
42+
TransactionInclusionWalletEventType? transactionInclusionWalletEvent = walletEvent!.Event as TransactionInclusionWalletEventType;
43+
44+
if (transactionInclusionWalletEvent!.TransactionInclusion.TransactionId == TRANSACTION_ID && transactionInclusionWalletEvent.TransactionInclusion.InclusionState == "Confirmed")
45+
{
46+
WAIT_CONFIRMATION_SET_RESULT(walletEvent);
47+
}
48+
}
49+
}
50+
}
51+
}

csharp/IotaWalletNet/IotaWalletNet.Application/Common/Interfaces/IAccount.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public interface IAccount : IRustBridgeCommunicator
7474
Task<SendMicroAmountResponse> SendMicroAmountAsync(List<AddressWithMicroAmount> addressWithMicroAmounts, TaggedDataPayload? taggedDataPayload = null);
7575
SendMicroAmountBuilder SendMicroAmountUsingBuilder();
7676
Task<GetOutputsWithAdditionalUnlockConditionsResponse> GetOutputsWithAdditionalUnlockConditionsAsync(OutputTypeToClaim outputTypeToClaim);
77-
Task<Task> EnablePeriodicSyncing(int intervalInMilliSeconds);
77+
Task<Task> EnablePeriodicSyncing(int intervalInMilliSeconds, int count=0);
7878
Task<BuildBasicOutputResponse> BuildBasicOutputAsync(BuildBasicOutputData buildBasicOutputData);
7979
Task<SendOutputsResponse> SendOutputsAsync(List<IOutputType> outputs, TaggedDataPayload? taggedDataPayload = null);
8080
}

csharp/IotaWalletNet/IotaWalletNet.Main/Examples/Events/Subscribe/EventsExample.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static async Task Run()
5757
//TODO unsubscribe from events
5858

5959
//Let's retrieve our cookiemonster account
60-
(GetAccountResponse accountResponse, IAccount? account) = await wallet.GetAccountAsync("cosokiemonster");
60+
(GetAccountResponse accountResponse, IAccount? account) = await wallet.GetAccountAsync("cookiemonster");
6161
Console.WriteLine($"GetAccountAsync: {accountResponse}");
6262

6363
if (account == null)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Wait for Transaction Confirmation
2+
3+
## Code Example
4+
5+
The following example will:
6+
7+
1. Load our wallet
8+
2. Subscribe to events
9+
3. Enable Periodic Syncing
10+
3. Send a transaction and call `WaitForConfirmationAsync` on the transaction
11+
12+
13+
```cs
14+
public static class WaitForTransactionConfirmationExample
15+
{
16+
public static async Task Run()
17+
{
18+
//Register all of the dependencies into a collection of services
19+
IServiceCollection services = new ServiceCollection().AddIotaWalletServices();
20+
21+
//Install services to service provider which is used for dependency injection
22+
IServiceProvider serviceProvider = services.BuildServiceProvider();
23+
24+
//Use serviceprovider to create a scope, which disposes of all services at end of scope
25+
using (IServiceScope scope = serviceProvider.CreateScope())
26+
{
27+
//Request IWallet service from service provider
28+
IWallet wallet = scope.ServiceProvider.GetRequiredService<IWallet>();
29+
30+
//Build wallet using a fluent-style configuration api
31+
wallet = wallet
32+
.ConfigureWalletOptions()
33+
.SetCoinType(TypeOfCoin.Shimmer)
34+
.SetStoragePath("./walletdb")
35+
.Then()
36+
.ConfigureClientOptions()
37+
.AddNodeUrl("https://api.testnet.shimmer.network")
38+
.SetFaucetUrl("https://faucet.testnet.shimmer.network")
39+
.IsFallbackToLocalPow()
40+
.IsLocalPow()
41+
.Then()
42+
.ConfigureSecretManagerOptions()
43+
.SetPassword("password")
44+
.SetSnapshotPath("./mystronghold")
45+
.Then()
46+
.Initialize();
47+
48+
//We can subscrive to all events using WalletEventTypes.AllEvents
49+
//Howevever for this example, is only focussed on waiting for a transaction to complete.
50+
//Hence only the TransactionInclusion event is of interest.
51+
wallet.SubscribeToEvents(WalletEventTypes.TransactionInclusion);
52+
53+
54+
//Let's retrieve our cookiemonster account
55+
(GetAccountResponse accountResponse, IAccount? account) = await wallet.GetAccountAsync("cookiemonster");
56+
57+
//We can also opt for periodic syncing of our account,
58+
//so that we don't have to worry about manual syncing
59+
//Below, we want to sync periodically 30 times.
60+
//Set count to 0 for forever periodic syncing
61+
account.EnablePeriodicSyncing(intervalInMilliSeconds: 3000, count: 30);
62+
63+
GetBalanceResponse getBalanceResponse = await account.GetBalanceAsync();
64+
Console.WriteLine($"Current balance is : {getBalanceResponse.Payload!.BaseCoin.Total}");
65+
66+
//Let's send 1 shimmer, which is 1,000,000 Glow
67+
string receiverAddress = "rms1qz9f7vecqscfynnxacyzefwvpza0wz3r0lnnwrc8r7qhx65s5x7rx2fln5q";
68+
69+
SendAmountResponse sendAmountResponse = await account.SendAmountUsingBuilder()
70+
.AddAddressAndAmount(receiverAddress, 1000000)
71+
.SendAmountAsync();
72+
Transaction transaction = sendAmountResponse.Payload!;
73+
74+
//We will setup the event handler for you and let you proceed once we receive
75+
//confirmation from the node that the transactionid has been confirmed.
76+
await transaction.WaitForConfirmationAsync(wallet);
77+
78+
getBalanceResponse = await account.GetBalanceAsync();
79+
Console.WriteLine($"New balance is : {getBalanceResponse.Payload!.BaseCoin.Total}");
80+
81+
82+
await Task.Delay(200 * 1000);
83+
}
84+
85+
}
86+
87+
}
88+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using IotaWalletNet.Application.AccountContext.Commands.SendAmount;
2+
using IotaWalletNet.Application.AccountContext.Commands.SyncAccount;
3+
using IotaWalletNet.Application.AccountContext.Queries.GetBalance;
4+
using IotaWalletNet.Application.Common.Extensions;
5+
using IotaWalletNet.Application.Common.Interfaces;
6+
using IotaWalletNet.Domain.Common.Interfaces;
7+
using IotaWalletNet.Domain.Common.Models.Coin;
8+
using IotaWalletNet.Domain.Common.Models.Transaction;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Newtonsoft.Json;
11+
using static IotaWalletNet.Application.WalletContext.Queries.GetAccount.GetAccountQueryHandler;
12+
using static IotaWalletNet.Domain.Common.Models.Events.EventTypes;
13+
14+
namespace IotaWalletNet.Main.Examples.Events.WaitForTransactionConfirmation
15+
{
16+
public static class WaitForTransactionConfirmationExample
17+
{
18+
public static async Task Run()
19+
{
20+
//Register all of the dependencies into a collection of services
21+
IServiceCollection services = new ServiceCollection().AddIotaWalletServices();
22+
23+
//Install services to service provider which is used for dependency injection
24+
IServiceProvider serviceProvider = services.BuildServiceProvider();
25+
26+
//Use serviceprovider to create a scope, which disposes of all services at end of scope
27+
using (IServiceScope scope = serviceProvider.CreateScope())
28+
{
29+
//Request IWallet service from service provider
30+
IWallet wallet = scope.ServiceProvider.GetRequiredService<IWallet>();
31+
32+
//Build wallet using a fluent-style configuration api
33+
wallet = wallet
34+
.ConfigureWalletOptions()
35+
.SetCoinType(TypeOfCoin.Shimmer)
36+
.SetStoragePath("./walletdb")
37+
.Then()
38+
.ConfigureClientOptions()
39+
.AddNodeUrl("https://api.testnet.shimmer.network")
40+
.SetFaucetUrl("https://faucet.testnet.shimmer.network")
41+
.IsFallbackToLocalPow()
42+
.IsLocalPow()
43+
.Then()
44+
.ConfigureSecretManagerOptions()
45+
.SetPassword("password")
46+
.SetSnapshotPath("./mystronghold")
47+
.Then()
48+
.Initialize();
49+
50+
//We can subscrive to all events using WalletEventTypes.AllEvents
51+
//Howevever for this example, is only focussed on waiting for a transaction to complete.
52+
//Hence only the TransactionInclusion event is of interest.
53+
wallet.SubscribeToEvents(WalletEventTypes.TransactionInclusion);
54+
55+
56+
//Let's retrieve our cookiemonster account
57+
(GetAccountResponse accountResponse, IAccount? account) = await wallet.GetAccountAsync("cookiemonster");
58+
59+
//We can also opt for periodic syncing of our account,
60+
//so that we don't have to worry about manual syncing
61+
//Below, we want to sync periodically 30 times.
62+
//Set count to 0 for forever periodic syncing
63+
account.EnablePeriodicSyncing(intervalInMilliSeconds: 3000, count: 30);
64+
65+
GetBalanceResponse getBalanceResponse = await account.GetBalanceAsync();
66+
Console.WriteLine($"Current balance is : {getBalanceResponse.Payload!.BaseCoin.Total}");
67+
68+
//Let's send 1 shimmer, which is 1,000,000 Glow
69+
string receiverAddress = "rms1qz9f7vecqscfynnxacyzefwvpza0wz3r0lnnwrc8r7qhx65s5x7rx2fln5q";
70+
71+
SendAmountResponse sendAmountResponse = await account.SendAmountUsingBuilder()
72+
.AddAddressAndAmount(receiverAddress, 1000000)
73+
.SendAmountAsync();
74+
Transaction transaction = sendAmountResponse.Payload!;
75+
76+
//We will setup the event handler for you and let you proceed once we receive
77+
//confirmation from the node that the transactionid has been confirmed.
78+
await transaction.WaitForConfirmationAsync(wallet);
79+
80+
getBalanceResponse = await account.GetBalanceAsync();
81+
Console.WriteLine($"New balance is : {getBalanceResponse.Payload!.BaseCoin.Total}");
82+
83+
84+
await Task.Delay(200 * 1000);
85+
}
86+
87+
}
88+
89+
}
90+
}

csharp/IotaWalletNet/IotaWalletNet.Main/Program.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using IotaWalletNet.Main.Examples.Outputs_and_Transactions.Periodic_Syncing;
1+
using IotaWalletNet.Main.Examples.Events.WaitForTransactionConfirmation;
2+
using IotaWalletNet.Main.Examples.Outputs_and_Transactions.Periodic_Syncing;
23

34
namespace IotaWalletNet.Main
45
{
@@ -35,7 +36,8 @@ private static async Task Main(string[] args)
3536

3637
//await ClaimOutputsExample.Run();
3738

38-
await PeriodicSyncingExample.Run();
39+
//await PeriodicSyncingExample.Run();
40+
await WaitForTransactionConfirmationExample.Run();
3941
}
4042

4143
}

csharp/IotaWalletNet/IotaWalletNet.Tests/Common/Interfaces/DependencyTestBase.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class DependencyTestBase : IDisposable
1212
protected const string DEFAULT_FAUCET_URL = @"https://faucet.testnet.shimmer.network";
1313
protected const string ANOTHER_WALLET_ADDRESS = "rms1qz8wf6jrchvsfmcnsfhlf6s53x3u85y0j4hvwth9a5ff3xhrxtmvvyc9ae7";
1414
protected const int SLEEP_DURATION_SECONDS_TRANSACTION = 10;
15-
protected const int SLEEP_DURATION_SECONDS_FAUCET = 10;
15+
protected const int SLEEP_DURATION_SECONDS_FAUCET = 15;
1616
protected const int SLEEP_DURATION_SECONDS_API_RATE_LIMIT = 0;
1717
protected List<string> filesCreated;
1818
public DependencyTestBase()

0 commit comments

Comments
 (0)