Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend token SDK with async methods & fixes #45

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Nexus.Sdk.Shared/Http/IResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
{
public interface IResponseHandler
{
public Task<T> HandleResponse<T>(HttpResponseMessage response) where T : class;
public Task<T> HandleResponse<T>(HttpResponseMessage response, CancellationToken cancellationToken = default) where T : class;

public Task HandleResponse(HttpResponseMessage response);
public Task HandleResponse(HttpResponseMessage response, CancellationToken cancellationToken = default);
}
}
8 changes: 4 additions & 4 deletions Nexus.Sdk.Shared/Http/NexusResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public NexusResponseHandler(ILogger? logger = null)
_logger = logger;
}

public async Task<T> HandleResponse<T>(HttpResponseMessage response) where T : class
public async Task<T> HandleResponse<T>(HttpResponseMessage response, CancellationToken cancellationToken = default) where T : class
{
var statusCode = response.StatusCode;
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);

_logger?.LogDebug("{statusCode} Response: {content}", statusCode, content);

Expand All @@ -43,7 +43,7 @@ public async Task<T> HandleResponse<T>(HttpResponseMessage response) where T : c
return responseObj.Values;
}

public async Task HandleResponse(HttpResponseMessage response)
public async Task HandleResponse(HttpResponseMessage response, CancellationToken cancellationToken = default)
{
var statusCode = response.StatusCode;

Expand All @@ -54,7 +54,7 @@ public async Task HandleResponse(HttpResponseMessage response)
_logger?.LogWarning("Did you configure your authentication provider using ConnectTo?");
}

var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
_logger?.LogError("Response: {content}", content);

var responseObj = JsonSingleton.GetInstance<NexusResponse>(content);
Expand Down
6 changes: 3 additions & 3 deletions Nexus.Sdk.Shared/Http/RequestBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public async Task<TResponse> ExecuteGet<TResponse>() where TResponse : class
return await _responseHandler.HandleResponse<TResponse>(response);
}

public async Task ExecutePost<TRequest>(TRequest request) where TRequest : class
public async Task ExecutePost<TRequest>(TRequest request, CancellationToken cancellationToken = default) where TRequest : class
{
var json = JsonSerializer.Serialize(request);
var path = BuildPath();
Expand All @@ -59,11 +59,11 @@ public async Task ExecutePost<TRequest>(TRequest request) where TRequest : class
var content = new StringContent(json, Encoding.UTF8, "application/json");

var postRequest = HttpRequestBuilder.BuildPostRequest(requestUri, content, _headers);
var response = await _httpClient.SendAsync(postRequest);
var response = await _httpClient.SendAsync(postRequest, cancellationToken);

ResetHeaders(); // reset headers

await _responseHandler.HandleResponse(response);
await _responseHandler.HandleResponse(response, cancellationToken);
}

public async Task<TResponse> ExecutePost<TRequest, TResponse>(TRequest request) where TRequest : class where TResponse : class
Expand Down
8 changes: 4 additions & 4 deletions Nexus.Sdk.Shared/Http/ResponseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public ResponseHandler(ILogger? logger = null)
_logger = logger;
}

public async Task<T> HandleResponse<T>(HttpResponseMessage response) where T : class
public async Task<T> HandleResponse<T>(HttpResponseMessage response, CancellationToken cancellationToken = default) where T : class
{
var statusCode = response.StatusCode;
var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);

_logger?.LogDebug("{statusCode} Response: {content}", statusCode, content);

Expand Down Expand Up @@ -47,7 +47,7 @@ public async Task<T> HandleResponse<T>(HttpResponseMessage response) where T : c
return responseObj;
}

public async Task HandleResponse(HttpResponseMessage response)
public async Task HandleResponse(HttpResponseMessage response, CancellationToken cancellationToken = default)
{
var statusCode = response.StatusCode;

Expand All @@ -58,7 +58,7 @@ public async Task HandleResponse(HttpResponseMessage response)
_logger?.LogWarning("Did you configure your authentication provider using ConnectTo?");
}

var content = await response.Content.ReadAsStringAsync();
var content = await response.Content.ReadAsStringAsync(cancellationToken);
_logger?.LogError("Response: {content}", content);

throw new ApiException((int)statusCode, "An error response was returned, please check the logs for details");
Expand Down
13 changes: 12 additions & 1 deletion Nexus.Sdk.Token.Tests/Helpers/MockTokenServerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ public Task<PagedResponse<TokenResponse>> GetTokens(IDictionary<string, string>?
throw new NotImplementedException();
}

public Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests)
/// <inheritdoc/>
public Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests, bool waitForCompletion = true, CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
Expand Down Expand Up @@ -326,5 +327,15 @@ public Task<PagedResponse<CustomerTraceResponse>> GetCustomerTrace(string custom
{
throw new NotImplementedException();
}

public Task<EnvelopeResponse> GetEnvelope(string code)
{
throw new NotImplementedException();
}

public Task<bool> WaitForCompletionAsync(string hash, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
}
5 changes: 4 additions & 1 deletion Nexus.Sdk.Token/Facades/Interfaces/ISubmitFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ public interface ISubmitFacade
{
public Task OnStellarAsync(IEnumerable<StellarSubmitSignatureRequest> requests);

public Task OnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests);
public Task OnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests,
bool awaitResult = true, CancellationToken cancellationToken = default);

public Task<bool> WaitForCompletionAsync(string code, CancellationToken cancellationToken = default);
}
10 changes: 8 additions & 2 deletions Nexus.Sdk.Token/Facades/SubmitFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ public async Task OnStellarAsync(IEnumerable<StellarSubmitSignatureRequest> requ
await _provider.SubmitOnStellarAsync(requests);
}

public async Task OnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests)
public async Task OnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests,
bool awaitResult = true, CancellationToken cancellationToken = default)
{
await _provider.SubmitOnAlgorandAsync(requests);
await _provider.SubmitOnAlgorandAsync(requests, awaitResult, cancellationToken);
}

public async Task<bool> WaitForCompletionAsync(string code, CancellationToken cancellationToken = default)
{
return await _provider.WaitForCompletionAsync(code, cancellationToken);
}
}
22 changes: 19 additions & 3 deletions Nexus.Sdk.Token/ITokenServerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,11 +214,13 @@
Task SubmitOnStellarAsync(IEnumerable<StellarSubmitSignatureRequest> requests);

/// <summary>
///
/// Submit a signature for a token operation specific to Algorand
/// </summary>
/// <param name="request"></param>
/// <param name="requests">Collection of signatures to send</param>
/// <param name="awaitResult">If true, the method will await for the submit to be fully processed</param>
/// <returns></returns>
Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests);
Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests, bool awaitResult = true,
CancellationToken cancellationToken = default);

/// <summary>
///
Expand Down Expand Up @@ -316,6 +318,20 @@
/// Get payment method data
/// </summary>
/// <param name="paymentMethodCode">Unique identifier of the payment method.</param>
Task<PaymentMethodsResponse> GetPaymentMethod(string paymentMethodCode);

Check warning on line 321 in Nexus.Sdk.Token/ITokenServerProvider.cs

View workflow job for this annotation

GitHub Actions / build

'ITokenServerProvider.GetPaymentMethod(string)' hides inherited member 'IServerProvider.GetPaymentMethod(string)'. Use the new keyword if hiding was intended.

/// <summary>
/// Get envelope
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
Task<EnvelopeResponse> GetEnvelope(string code);

/// <summary>
/// Check for completion of an envelope by periodically polling the server
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
Task<bool> WaitForCompletionAsync(string code, CancellationToken cancellationToken = default);
}
}
3 changes: 3 additions & 0 deletions Nexus.Sdk.Token/Requests/TokenRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ public record AlgorandTokenSettings

[JsonPropertyName("authorizationRevocable")]
public bool AuthorizationRevocable { get; set; } = true;

[JsonPropertyName("authorizationRequired")]
public bool AuthorizationRequired { get; set; } = true;
}

public record StellarTokens
Expand Down
14 changes: 14 additions & 0 deletions Nexus.Sdk.Token/Responses/EnvelopeResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Nexus.Sdk.Token.Responses;

public class EnvelopeResponse
{
public string Code { get; set; }

Check warning on line 5 in Nexus.Sdk.Token/Responses/EnvelopeResponse.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'Code' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string hash { get; set; }

Check warning on line 6 in Nexus.Sdk.Token/Responses/EnvelopeResponse.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'hash' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string envelope { get; set; }

Check warning on line 7 in Nexus.Sdk.Token/Responses/EnvelopeResponse.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable property 'envelope' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
public string status { get; set; }
public string type { get; set; }
public string created { get; set; }
public string validUntil { get; set; }
public string memo { get; set; }
public string message { get; set; }
}
64 changes: 55 additions & 9 deletions Nexus.Sdk.Token/TokenServerProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public async Task<SignableResponse> CancelOrder(string orderCode)
/// <returns></returns>
public async Task<SignableResponse> ConnectAccountToTokenAsync(string accountCode, string tokenCode, string? customerIPAddress = null)
{
return await ConnectAccountToTokensAsync(accountCode, new string[] { tokenCode }, customerIPAddress);
return await ConnectAccountToTokensAsync(accountCode, [tokenCode], customerIPAddress);
}

/// <summary>
Expand Down Expand Up @@ -465,7 +465,7 @@ public async Task<TaxonomySchemaResponse> CreateTaxonomySchema(string code, stri
/// <returns></returns>
public async Task<CreateTokenResponse> CreateTokenOnAlgorand(AlgorandTokenDefinition definition, AlgorandTokenSettings? settings = null, string? customerIPAddress = null)
{
return await CreateTokensOnAlgorand(new AlgorandTokenDefinition[] { definition }, settings, customerIPAddress);
return await CreateTokensOnAlgorand([definition], settings, customerIPAddress);
}

public async Task<CreateTokenResponse> CreateTokensOnAlgorand(IEnumerable<AlgorandTokenDefinition> definitions, AlgorandTokenSettings? settings = null, string? customerIPAddress = null)
Expand Down Expand Up @@ -753,17 +753,23 @@ public async Task<PagedResponse<TokenOperationResponse>> GetTokenPayments(IDicti
return await builder.ExecuteGet<PagedResponse<TokenOperationResponse>>();
}

/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests)
/// <inheritdoc/>
public async Task SubmitOnAlgorandAsync(IEnumerable<AlgorandSubmitSignatureRequest> requests, bool awaitResult = true,
CancellationToken cancellationToken = default)
{
foreach (var request in requests)
{
var builder = new RequestBuilder(_client, _handler, _logger).SetSegments("token", "envelope", "signature", "submit");
await builder.ExecutePost(request);
await builder.ExecutePost(request, cancellationToken);

}

if (awaitResult)
{
await Task.WhenAll(requests.Select(async request =>
{
await WaitForCompletionAsync(request.TransactionHash, cancellationToken);
}));
}
}

Expand All @@ -781,6 +787,46 @@ public async Task SubmitOnStellarAsync(IEnumerable<StellarSubmitSignatureRequest
}
}

/// <summary>
/// Wait for the completion of a token operation.
/// It must be fully processed and either Completed, Failed or Cancelled before returning.
/// </summary>
/// <param name="code">Unique Nexus identifier of the token operation or transaction hash</param>
public async Task<bool> WaitForCompletionAsync(string code, CancellationToken cancellationToken = default)
{
while (true)
{
// get the status of the envelope
var envelope = await GetEnvelope(code);

// if the status is completed, break
var env = envelope;

if (env != null)
{
if (env.status == "Completed")
{
return true;
}

if (env.status is "Failed" or "Cancelled")
{
return false;
}
}

// else delay
await Task.Delay(1000, cancellationToken);
}
}

public async Task<EnvelopeResponse> GetEnvelope(string code)
{
var builder = new RequestBuilder(_client, _handler, _logger)
.SetSegments("token", "envelope", code);
return await builder.ExecuteGet<EnvelopeResponse>();
}

/// <summary>
///
/// </summary>
Expand Down
39 changes: 36 additions & 3 deletions Nexus.Token.Algorand.Examples/AlgorandExamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public async Task<string> CreateAccountAsync(string customerCode)
var request = new CreateCustomerRequestBuilder(customerCode, "Trusted", "EUR")
.Build();

string customerIPAddress = "127.1.0.0";
string customerIPAddress = null;

var customer = await _tokenServer.Customers.Create(request, customerIPAddress);

Expand All @@ -51,7 +51,13 @@ public async Task CreateAssetTokenAsync(string tokenCode, string tokenName)
{
var definition = AlgorandTokenDefinition.TokenizedAsset(tokenCode, tokenName, 1000, 0);

var response = await _tokenServer.Tokens.CreateOnAlgorand(definition);
var response = await _tokenServer.Tokens.CreateOnAlgorand(definition, new AlgorandTokenSettings
{
// TODO: forcing to get to fix an issue about having frozen assets
AuthorizationRequired = false,
AuthorizationRevocable = true,
ClawbackEnabled = true
});
var token = response.Tokens.First();

_logger.LogWarning("A new token was generated with the following issuer address: {issuerAddress}", token.IssuerAddress);
Expand Down Expand Up @@ -124,7 +130,22 @@ public async Task FundAccountAsync(string encryptedPrivateKey, string tokenCode,

var signableResponse = await _tokenServer.Accounts.ConnectToTokenAsync(kp.GetAccountCode(), tokenCode);
var signedResponse = kp.Sign(signableResponse);
await _tokenServer.Submit.OnAlgorandAsync(signedResponse);
await _tokenServer.Submit.OnAlgorandAsync(signedResponse, false);

_logger.LogInformation("Successfully submitted the Account Connect request.");
_logger.LogInformation("Waiting for completion...");

// wait to be connected
var result = await _tokenServer.Submit.WaitForCompletionAsync(signableResponse.BlockchainResponse.Code);

if (result)
{
_logger.LogInformation("Account successfully opted in");
}
else
{
_logger.LogWarning("Account failed to opt in");
}
}

await _tokenServer.Operations.CreateFundingAsync(kp.GetAccountCode(), tokenCode, amount);
Expand Down Expand Up @@ -170,6 +191,18 @@ public async Task PaymentAsync(string encryptedSenderPrivateKey, string encrypte
var signableResponse = await _tokenServer.Accounts.ConnectToTokenAsync(receiver.GetAccountCode(), tokenCode);
var signedResponse = receiver.Sign(signableResponse);
await _tokenServer.Submit.OnAlgorandAsync(signedResponse);

// wait to be connected
var result = await _tokenServer.Submit.WaitForCompletionAsync(signableResponse.BlockchainResponse.Code);

if (result)
{
_logger.LogInformation("Account successfully opted in");
}
else
{
_logger.LogWarning("Account failed to opt in");
}
}

{
Expand Down
Loading