Skip to content

Commit 210b159

Browse files
committed
Added WaitForConfirmationAsync and WaitForNewOutputAsync
1 parent 2b3e708 commit 210b159

File tree

8 files changed

+166
-105
lines changed

8 files changed

+166
-105
lines changed

csharp/IotaWalletNet/IotaWalletNet.Application/Account.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,27 @@ public Account(IMediator mediator, string username, IWallet wallet)
5959
public IWallet Wallet { get; }
6060

6161

62+
public async Task<T> RetryAsyncFuncUntil<T>(Func<Task<T>> function, int intervalInMilliseconds, Func<T, bool> predicate, CancellationToken cancellationToken=default)
63+
{
64+
while(true)
65+
{
66+
T response = await function();
67+
68+
if(predicate(response))
69+
{
70+
return response;
71+
}
72+
73+
if (cancellationToken.IsCancellationRequested)
74+
return response;
75+
76+
await Task.Delay(intervalInMilliseconds);
77+
78+
if (cancellationToken.IsCancellationRequested)
79+
return response;
80+
}
81+
}
82+
6283
public async Task<SendOutputsResponse> SendOutputsAsync(List<IOutputType> outputs, TaggedDataPayload? taggedDataPayload = null)
6384
{
6485
return await _mediator.Send(new SendOutputsCommand(this, Username, outputs, taggedDataPayload));
@@ -69,9 +90,9 @@ public async Task<BuildBasicOutputResponse> BuildBasicOutputAsync(BuildBasicOutp
6990
return await _mediator.Send(new BuildBasicOutputCommand(buildBasicOutputData, Username, this));
7091
}
7192

72-
public async Task<Task> EnablePeriodicSyncing(int intervalInMilliSeconds, int count = 0)
93+
public async Task EnablePeriodicSyncing(int intervalInMilliSeconds, CancellationToken cancellationToken=default)
7394
{
74-
return await _mediator.Send(new EnablePeriodicSyncingCommand(this, intervalInMilliSeconds, count));
95+
await _mediator.Send(new EnablePeriodicSyncingCommand(this, intervalInMilliSeconds), cancellationToken);
7596
}
7697

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

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,16 @@
33

44
namespace IotaWalletNet.Application.AccountContext.Commands.EnablePeriodicSyncing
55
{
6-
public class EnablePeriodicSyncingCommand : IRequest<Task>
6+
public class EnablePeriodicSyncingCommand : IRequest
77
{
8-
public EnablePeriodicSyncingCommand(IAccount account, int intervalInMilliSeconds, int count)
8+
public EnablePeriodicSyncingCommand(IAccount account, int intervalInMilliSeconds)
99
{
1010
Account = account;
1111
IntervalInMilliSeconds = intervalInMilliSeconds;
12-
Count = count;
1312
}
1413

1514
public IAccount Account { get; set; }
1615

1716
public int IntervalInMilliSeconds { get; set; }
18-
public int Count { get; set; }
1917
}
2018
}
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,30 @@
1-
using IotaWalletNet.Application.Common.Interfaces;
2-
using MediatR;
1+
using MediatR;
2+
using Microsoft.Extensions.Logging;
33

44
namespace IotaWalletNet.Application.AccountContext.Commands.EnablePeriodicSyncing
55
{
6-
public class EnablePeriodicSyncingCommandHandler : IRequestHandler<EnablePeriodicSyncingCommand, Task>
6+
public class EnablePeriodicSyncingCommandHandler : IRequestHandler<EnablePeriodicSyncingCommand>
77
{
8-
public Task<Task> Handle(EnablePeriodicSyncingCommand request, CancellationToken cancellationToken)
8+
9+
public EnablePeriodicSyncingCommandHandler()
910
{
10-
static async Task SyncAndDelay(IAccount account, int interval)
11-
{
12-
Console.WriteLine(("Syncing..."));
13-
await account.SyncAccountAsync();
14-
await Task.Delay(interval);
15-
}
11+
}
1612

17-
Task periodicSyncingTask = Task.Run(async () =>
13+
public async Task<Unit> Handle(EnablePeriodicSyncingCommand request, CancellationToken cancellationToken=default)
14+
{
15+
while (true)
1816
{
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-
}
32-
});
17+
Console.WriteLine("SYNCING");
18+
await request.Account.SyncAccountAsync();
19+
20+
if (cancellationToken != CancellationToken.None && cancellationToken.IsCancellationRequested)
21+
return Unit.Value;
3322

34-
return Task.FromResult(periodicSyncingTask);
23+
await Task.Delay(request.IntervalInMilliSeconds);
3524

25+
if (cancellationToken != CancellationToken.None && cancellationToken.IsCancellationRequested)
26+
return Unit.Value;
27+
}
3628
}
3729
}
3830
}

csharp/IotaWalletNet/IotaWalletNet.Application/Common/Extensions/TransactionExtensions.cs

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,83 @@ namespace IotaWalletNet.Application.Common.Extensions
88
{
99
public static class TransactionExtensions
1010
{
11-
private static SemaphoreSlim MUTEX = new SemaphoreSlim(1);
12-
private static string? TRANSACTION_ID = null;
11+
private static SemaphoreSlim CONFIRMATION_MUTEX = new SemaphoreSlim(1);
12+
private static string? CONFIRMATION_TRANSACTION_ID = null;
1313
private static Action<IWalletEvent>? WAIT_CONFIRMATION_SET_RESULT;
1414

15-
public static async Task WaitForConfirmationAsync(this Transaction transaction, IWallet wallet)
15+
private static SemaphoreSlim NEW_OUTPUT_MUTEX = new SemaphoreSlim(1);
16+
private static string? NEW_OUTPUT_TRANSACTION_ID = null;
17+
private static Action<IWalletEvent>? WAIT_NEW_OUTPUT_SET_RESULT;
18+
19+
public static async Task WaitForNewOutputAsync(this Transaction transaction, IAccount account)
1620
{
17-
await MUTEX.WaitAsync();
21+
await NEW_OUTPUT_MUTEX.WaitAsync();
22+
23+
IWallet wallet = account.Wallet;
24+
NEW_OUTPUT_TRANSACTION_ID = transaction.TransactionId;
25+
TaskCompletionSource<IWalletEvent> taskCompletionSource = new TaskCompletionSource<IWalletEvent>();
26+
Task<IWalletEvent> waitNewOutputTask = taskCompletionSource.Task;
27+
WAIT_NEW_OUTPUT_SET_RESULT = taskCompletionSource.SetResult;
28+
wallet.WalletEventReceived += Account_NewOuputWalletEventReceived;
29+
await waitNewOutputTask;
1830

19-
wallet.WalletEventReceived += Account_TransactionInclusionWalletEventReceived;
20-
TRANSACTION_ID = transaction.TransactionId;
31+
wallet.WalletEventReceived -= Account_NewOuputWalletEventReceived;
32+
WAIT_NEW_OUTPUT_SET_RESULT = null;
33+
NEW_OUTPUT_TRANSACTION_ID = null;
34+
35+
NEW_OUTPUT_MUTEX.Release();
36+
}
37+
public static async Task WaitForConfirmationAsync(this Transaction transaction, IAccount account)
38+
{
39+
await CONFIRMATION_MUTEX.WaitAsync();
40+
41+
IWallet wallet = account.Wallet;
42+
43+
CONFIRMATION_TRANSACTION_ID = transaction.TransactionId;
2144

2245
TaskCompletionSource<IWalletEvent> taskCompletionSource = new TaskCompletionSource<IWalletEvent>();
2346
Task<IWalletEvent> waitConfirmationTask = taskCompletionSource.Task;
2447
WAIT_CONFIRMATION_SET_RESULT = taskCompletionSource.SetResult;
25-
48+
wallet.WalletEventReceived += Account_TransactionInclusionWalletEventReceived;
2649
await waitConfirmationTask;
2750

2851
wallet.WalletEventReceived -= Account_TransactionInclusionWalletEventReceived;
2952
WAIT_CONFIRMATION_SET_RESULT = null;
30-
TRANSACTION_ID = null;
53+
CONFIRMATION_TRANSACTION_ID = null;
3154

32-
MUTEX.Release();
55+
CONFIRMATION_MUTEX.Release();
3356
}
3457

3558
public static void Account_TransactionInclusionWalletEventReceived(object? sender, IWalletEvent? walletEvent)
3659
{
3760
if (walletEvent!.Event.Type == WalletEventTypes.TransactionInclusion.ToString())
3861
{
39-
if (WAIT_CONFIRMATION_SET_RESULT == null || TRANSACTION_ID == null)
62+
if (WAIT_CONFIRMATION_SET_RESULT == null || CONFIRMATION_TRANSACTION_ID == null)
4063
return;
4164

4265
TransactionInclusionWalletEventType? transactionInclusionWalletEvent = walletEvent!.Event as TransactionInclusionWalletEventType;
4366

44-
if (transactionInclusionWalletEvent!.TransactionInclusion.TransactionId == TRANSACTION_ID && transactionInclusionWalletEvent.TransactionInclusion.InclusionState == "Confirmed")
67+
if (transactionInclusionWalletEvent!.TransactionInclusion.TransactionId == CONFIRMATION_TRANSACTION_ID && transactionInclusionWalletEvent.TransactionInclusion.InclusionState == "Confirmed")
4568
{
4669
WAIT_CONFIRMATION_SET_RESULT(walletEvent);
4770
}
4871
}
4972
}
73+
74+
public static void Account_NewOuputWalletEventReceived(object? sender, IWalletEvent? walletEvent)
75+
{
76+
if (walletEvent!.Event.Type == WalletEventTypes.NewOutput.ToString())
77+
{
78+
if (WAIT_NEW_OUTPUT_SET_RESULT == null || NEW_OUTPUT_TRANSACTION_ID == null)
79+
return;
80+
81+
NewOutputWalletEventType? newOutputWalletEventType = walletEvent!.Event as NewOutputWalletEventType;
82+
83+
if(newOutputWalletEventType!.NewOutput.Output.Metadata.TransactionId == NEW_OUTPUT_TRANSACTION_ID)
84+
{
85+
WAIT_NEW_OUTPUT_SET_RESULT(walletEvent);
86+
}
87+
}
88+
}
5089
}
5190
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ 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, int count=0);
77+
Task EnablePeriodicSyncing(int intervalInMilliSeconds, CancellationToken cancellationToken=default);
7878
Task<BuildBasicOutputResponse> BuildBasicOutputAsync(BuildBasicOutputData buildBasicOutputData);
7979
Task<SendOutputsResponse> SendOutputsAsync(List<IOutputType> outputs, TaggedDataPayload? taggedDataPayload = null);
80+
Task<T> RetryAsyncFuncUntil<T>(Func<Task<T>> function, int intervalInMilliseconds, Func<T, bool> predicate, CancellationToken cancellationToken = default);
8081
}
8182
}

csharp/IotaWalletNet/IotaWalletNet.Application/IotaWalletNet.Application.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<PackageProjectUrl>https://github.com/IOTA-NET/IotaWallet.NET</PackageProjectUrl>
1616
<RepositoryType>git</RepositoryType>
1717
<RepositoryUrl>https://github.com/IOTA-NET/IotaWallet.NET</RepositoryUrl>
18+
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
1819
</PropertyGroup>
1920

2021
<ItemGroup>

csharp/IotaWalletNet/IotaWalletNet.Domain/IotaWalletNet.Domain.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<PackageProjectUrl>https://github.com/IOTA-NET/IotaWallet.NET</PackageProjectUrl>
2121
<RepositoryType>git</RepositoryType>
2222
<RepositoryUrl>https://github.com/IOTA-NET/IotaWallet.NET</RepositoryUrl>
23-
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
23+
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
2424

2525
</PropertyGroup>
2626

csharp/IotaWalletNet/IotaWalletNet.Main/Examples/Events/WaitForTransactionConfirmation/WaitForTransactionConfirmationExample.cs

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
using IotaWalletNet.Application.AccountContext.Commands.SendAmount;
1+
using IotaWalletNet.Application.AccountContext.Commands.MintNfts;
2+
using IotaWalletNet.Application.AccountContext.Commands.SendAmount;
23
using IotaWalletNet.Application.AccountContext.Commands.SyncAccount;
34
using IotaWalletNet.Application.AccountContext.Queries.GetBalance;
45
using IotaWalletNet.Application.Common.Extensions;
56
using IotaWalletNet.Application.Common.Interfaces;
7+
using IotaWalletNet.Domain.Common.Extensions;
68
using IotaWalletNet.Domain.Common.Interfaces;
79
using IotaWalletNet.Domain.Common.Models.Coin;
10+
using IotaWalletNet.Domain.Common.Models.Nft;
11+
using IotaWalletNet.Domain.Common.Models.Output;
812
using IotaWalletNet.Domain.Common.Models.Transaction;
913
using Microsoft.Extensions.DependencyInjection;
14+
using MimeMapping;
1015
using Newtonsoft.Json;
1116
using static IotaWalletNet.Application.WalletContext.Queries.GetAccount.GetAccountQueryHandler;
1217
using static IotaWalletNet.Domain.Common.Models.Events.EventTypes;
@@ -24,66 +29,70 @@ public static async Task Run()
2429
IServiceProvider serviceProvider = services.BuildServiceProvider();
2530

2631
//Use serviceprovider to create a scope, which disposes of all services at end of scope
27-
using (IServiceScope scope = serviceProvider.CreateScope())
32+
using IServiceScope serviceScope = serviceProvider.CreateScope();
33+
34+
//Request IWallet service from service provider
35+
IWallet wallet = serviceScope.ServiceProvider.GetRequiredService<IWallet>();
36+
37+
//Build wallet using a fluent-style configuration api
38+
wallet = wallet
39+
.ConfigureClientOptions()
40+
.AddNodeUrl("https://api.testnet.shimmer.network")
41+
.SetFaucetUrl("https://faucet.testnet.shimmer.network")
42+
.IsFallbackToLocalPow()
43+
.Then()
44+
.ConfigureSecretManagerOptions()
45+
.SetSnapshotPath("./mystronghold")
46+
.SetPassword("password")
47+
.Then()
48+
.ConfigureWalletOptions()
49+
.SetStoragePath("./walletdb")
50+
.SetCoinType(TypeOfCoin.Shimmer)
51+
.Then()
52+
.Initialize();
53+
54+
//Subscrive to events, particularly transaction inclusion
55+
//For simplicity, we included all events
56+
wallet.SubscribeToEvents(WalletEventTypes.AllEvents);
57+
58+
//Let's retrieve our cookiemonster account
59+
(_, IAccount? account) = await wallet.GetAccountAsync("cookiemonster");
60+
61+
//Let's enable periodic syncing every 5 esconds
62+
//We can cancel the periodic syncing with the tokenSource
63+
CancellationTokenSource tokenSource = new CancellationTokenSource();
64+
account.EnablePeriodicSyncing(intervalInMilliSeconds: 5000, tokenSource.Token);
65+
66+
//Let's create a simple random nft that just contains textual data
67+
NftIrc27 metadata = new NftIrc27(KnownMimeTypes.Text, "Random NFT", "www.random.com");
68+
List<NftOptions> nftOptions = new List<NftOptions>()
2869
{
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-
70+
new NftOptions(){ ImmutableMetadata = JsonConvert.SerializeObject(metadata).ToHexString() }
71+
};
72+
MintNftsResponse mintNftsResponse = await account.MintNftsAsync(nftOptions);
73+
74+
//Now lets obtain the transaction object and wait for its confirmation as well as the new outputs to be
75+
//added to our wallet.
76+
//The below could only be achieved if you have subscribed to the transaction inclusion event.
77+
//You also have to sync in order to receive updates. Thus, we have enabled periodic syncing to help us.
78+
Transaction transaction = mintNftsResponse.Payload!;
79+
string transactionId = transaction.TransactionId;
80+
Task waitConfirmationTask = transaction.WaitForConfirmationAsync(account);
81+
Task waitNewOutputTask = transaction.WaitForNewOutputAsync(account);
82+
await Task.WhenAll(waitConfirmationTask, waitNewOutputTask);
83+
84+
//Success,the transaction have been confirmed and new outputs have arrived to our wallet!
85+
Console.WriteLine("Finished waiting");
86+
87+
//Let's stop our periodic syncing
88+
tokenSource.Cancel();
89+
90+
await account.SyncAccountAsync();
91+
92+
List<OutputData> outputs = (await account.GetUnspentOutputsAsync()).Payload!;
93+
var newOutputs = outputs.Where(x => x.Metadata.TransactionId == transactionId && x.Output.Type == 6);
94+
Console.WriteLine(newOutputs.Count());
95+
await Task.Delay(200 * 1000);
8796
}
8897

8998
}

0 commit comments

Comments
 (0)