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

NBX enhancements #431

Closed
wants to merge 16 commits into from
138 changes: 101 additions & 37 deletions NBXplorer.Client/ExplorerClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public void Track(DerivationStrategyBase strategy, CancellationToken cancellatio
}
public Task TrackAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default)
{
return TrackAsync(TrackedSource.Create(strategy), cancellation);
return TrackAsync(TrackedSource.Create(strategy), cancellation: cancellation);
}

public void Track(DerivationStrategyBase strategy, TrackWalletRequest trackDerivationRequest, CancellationToken cancellation = default)
Expand All @@ -285,14 +285,13 @@ public async Task TrackAsync(DerivationStrategyBase strategy, TrackWalletRequest

public void Track(TrackedSource trackedSource, CancellationToken cancellation = default)
{
TrackAsync(trackedSource, cancellation).GetAwaiter().GetResult();
TrackAsync(trackedSource, cancellation: cancellation).GetAwaiter().GetResult();
}
public Task TrackAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
public Task TrackAsync(TrackedSource trackedSource, TrackWalletRequest trackDerivationRequest = null, CancellationToken cancellation = default)
{
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));

return SendAsync<string>(HttpMethod.Post, null, GetBasePath(trackedSource), cancellation);
return SendAsync<string>(HttpMethod.Post, trackDerivationRequest, GetBasePath(trackedSource), cancellation);
}

private Exception UnSupported(TrackedSource trackedSource)
Expand All @@ -311,19 +310,20 @@ public GetBalanceResponse GetBalance(DerivationStrategyBase userDerivationScheme
}
public Task<GetBalanceResponse> GetBalanceAsync(DerivationStrategyBase userDerivationScheme, CancellationToken cancellation = default)
{
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{userDerivationScheme}/balance", cancellation);
return GetBalanceAsync(TrackedSource.Create(userDerivationScheme), cancellation);
}


public GetBalanceResponse GetBalance(BitcoinAddress address, CancellationToken cancellation = default)
{
return GetBalanceAsync(address, cancellation).GetAwaiter().GetResult();
}
public Task<GetBalanceResponse> GetBalanceAsync(BitcoinAddress address, CancellationToken cancellation = default)
{
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/addresses/{address}/balance", cancellation);
return GetBalanceAsync(TrackedSource.Create(address), cancellation);
}
public Task<GetBalanceResponse> GetBalanceAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
{
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/balance", cancellation);
}

public Task CancelReservationAsync(DerivationStrategyBase strategy, KeyPath[] keyPaths, CancellationToken cancellation = default)
{
return SendAsync<string>(HttpMethod.Post, keyPaths, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/cancelreservation", cancellation);
Expand Down Expand Up @@ -365,19 +365,8 @@ public Task<GetTransactionsResponse> GetTransactionsAsync(TrackedSource trackedS
{
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
if (trackedSource is DerivationSchemeTrackedSource dsts)
{
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}/transactions", cancellation);
}
else if (trackedSource is AddressTrackedSource asts)
{
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}/transactions", cancellation);
}
else
throw UnSupported(trackedSource);
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/transactions", cancellation);
}


public TransactionInformation GetTransaction(TrackedSource trackedSource, uint256 txId, CancellationToken cancellation = default)
{
return this.GetTransactionAsync(trackedSource, txId, cancellation).GetAwaiter().GetResult();
Expand All @@ -399,16 +388,23 @@ public Task<TransactionInformation> GetTransactionAsync(TrackedSource trackedSou
throw new ArgumentNullException(nameof(txId));
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
if (trackedSource is DerivationSchemeTrackedSource dsts)
{
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}/transactions/{txId}", cancellation);
}
else if (trackedSource is AddressTrackedSource asts)
{
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}/transactions/{txId}", cancellation);
}
else
throw UnSupported(trackedSource);
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/transactions/{txId}", cancellation);
}
public async Task AssociateScriptsAsync(TrackedSource trackedSource, AssociateScriptRequest[] scripts, CancellationToken cancellation = default)
{
if (scripts == null)
throw new ArgumentNullException(nameof(scripts));
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
await SendAsync(HttpMethod.Post, scripts, $"{GetBasePath(trackedSource)}/associate", cancellation);
}
public async Task ImportUTXOs(TrackedSource trackedSource, ImportUTXORequest request, CancellationToken cancellation = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
await SendAsync(HttpMethod.Post, request, $"{GetBasePath(trackedSource)}/import-utxos", cancellation);
}

public Task RescanAsync(RescanRequest rescanRequest, CancellationToken cancellation = default)
Expand Down Expand Up @@ -447,16 +443,18 @@ public KeyPathInformation GetKeyInformation(DerivationStrategyBase strategy, Scr

public async Task<KeyPathInformation> GetKeyInformationAsync(DerivationStrategyBase strategy, Script script, CancellationToken cancellation = default)
{
return await SendAsync<KeyPathInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/scripts/{script.ToHex()}", cancellation).ConfigureAwait(false);
return await GetKeyInformationAsync(new DerivationSchemeTrackedSource(strategy), script, cancellation).ConfigureAwait(false);
}
public async Task<KeyPathInformation> GetKeyInformationAsync(TrackedSource trackedSource, Script script, CancellationToken cancellation = default)
{
return await SendAsync<KeyPathInformation>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/scripts/{script.ToHex()}", cancellation).ConfigureAwait(false);
}

[Obsolete("Use GetKeyInformationAsync(DerivationStrategyBase strategy, Script script) instead")]
public async Task<KeyPathInformation[]> GetKeyInformationsAsync(Script script, CancellationToken cancellation = default)
{
return await SendAsync<KeyPathInformation[]>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/scripts/{script.ToHex()}", cancellation).ConfigureAwait(false);
}

[Obsolete("Use GetKeyInformation(DerivationStrategyBase strategy, Script script) instead")]
public KeyPathInformation[] GetKeyInformations(Script script, CancellationToken cancellation = default)
{
return GetKeyInformationsAsync(script, cancellation).GetAwaiter().GetResult();
Expand Down Expand Up @@ -563,6 +561,53 @@ public GenerateWalletResponse GenerateWallet(GenerateWalletRequest request = nul
return GenerateWalletAsync(request, cancellationToken).GetAwaiter().GetResult();
}

public async Task<TrackedSource[]> GetChildWallets(TrackedSource trackedSource,
CancellationToken cancellation = default)
{
return await GetAsync<TrackedSource[]>( $"{GetBasePath(trackedSource)}/children", cancellation);
}
public async Task<TrackedSource[]> GetParentWallets(TrackedSource trackedSource,
CancellationToken cancellation = default)
{
return await GetAsync<TrackedSource[]>( $"{GetBasePath(trackedSource)}/parents", cancellation);
}
public async Task AddChildWallet(TrackedSource trackedSource, TrackedSource childWallet, CancellationToken cancellation = default)
{
var request = new TrackedSourceRequest()
{
TrackedSource = childWallet
};
await SendAsync(HttpMethod.Post, request, $"{GetBasePath(trackedSource)}/children", cancellation);
}

public async Task AddParentWallet(TrackedSource trackedSource, TrackedSource parentWallet,
CancellationToken cancellation = default)
{
var request = new TrackedSourceRequest()
{
TrackedSource = parentWallet
};
await SendAsync(HttpMethod.Post, request, $"{GetBasePath(trackedSource)}/parents", cancellation);
}
public async Task RemoveChildWallet(TrackedSource trackedSource, TrackedSource childWallet, CancellationToken cancellation = default)
{
var request = new TrackedSourceRequest()
{
TrackedSource = childWallet
};
await SendAsync(HttpMethod.Delete, request, $"{GetBasePath(trackedSource)}/children", cancellation);
}

public async Task RemoveParentWallet(TrackedSource trackedSource, TrackedSource parentWallet,
CancellationToken cancellation = default)
{
var request = new TrackedSourceRequest()
{
TrackedSource = parentWallet
};
await SendAsync(HttpMethod.Delete, request, $"{GetBasePath(trackedSource)}/parents", cancellation);
}

private static readonly HttpClient SharedClient = new HttpClient();
internal HttpClient Client = SharedClient;

Expand Down Expand Up @@ -653,12 +698,30 @@ internal async Task<T> SendAsync<T>(HttpMethod method, object body, FormattableS
if (Auth.RefreshCache())
{
message = CreateMessage(method, body, relativePath);
result = await Client.SendAsync(message).ConfigureAwait(false);
result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
}
}
return await ParseResponse<T>(result).ConfigureAwait(false);
}
internal async Task SendAsync(HttpMethod method, object body, FormattableString relativePath, CancellationToken cancellation)
{
var message = CreateMessage(method, body, relativePath);
var result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);

if (result.StatusCode == HttpStatusCode.GatewayTimeout || result.StatusCode == HttpStatusCode.RequestTimeout)
{
throw new HttpRequestException($"HTTP error {(int)result.StatusCode}", new TimeoutException());
}
if ((int)result.StatusCode == 401)
{
if (Auth.RefreshCache())
{
message = CreateMessage(method, body, relativePath);
result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
}
}
await ParseResponse(result).ConfigureAwait(false);
}
internal HttpRequestMessage CreateMessage(HttpMethod method, object body, FormattableString relativePath)
{
var uri = GetFullUri(relativePath);
Expand Down Expand Up @@ -721,7 +784,8 @@ private FormattableString GetBasePath(TrackedSource trackedSource)
{
DerivationSchemeTrackedSource dsts => $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}",
AddressTrackedSource asts => $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}",
_ => throw UnSupported(trackedSource)
WalletTrackedSource wts => $"v1/cryptos/{CryptoCode}/wallets/{wts.WalletId}",
_ => $"v1/cryptos/{CryptoCode}/tracked-sources/{trackedSource}",
};
}
}
Expand Down
2 changes: 2 additions & 0 deletions NBXplorer.Client/Models/GenerateWalletRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ public class GenerateWalletRequest
public bool ImportKeysToRPC { get; set; }
public bool SavePrivateKeys { get; set; }
public Dictionary<string, string> AdditionalOptions { get; set; }

public TrackedSource ParentWallet { get; set; }
}
}
18 changes: 18 additions & 0 deletions NBXplorer.Client/Models/ImportUTXORequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using NBitcoin;
using Newtonsoft.Json.Linq;

namespace NBXplorer.Models;

public class ImportUTXORequest
{
public OutPoint[] Utxos { get; set; }

public MerkleBlock[] Proofs { get; set; }
}

public class AssociateScriptRequest
{
public IDestination Destination { get; set; }
public bool Used { get; set; }
public JObject Metadata { get; set; }
}
2 changes: 2 additions & 0 deletions NBXplorer.Client/Models/TrackWalletRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public class TrackWalletRequest
{
public TrackDerivationOption[] DerivationOptions { get; set; }
public bool Wait { get; set; } = false;

public TrackedSource ParentWallet { get; set; } = null;
}

public class TrackDerivationOption
Expand Down
40 changes: 40 additions & 0 deletions NBXplorer.Client/Models/TrackedSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public static bool TryParse(string str, out TrackedSource trackedSource, NBXplor
return false;
trackedSource = addressTrackedSource;
}
else if (strSpan.StartsWith("WALLET:".AsSpan(), StringComparison.Ordinal))
{
if (!WalletTrackedSource.TryParse(strSpan, out var walletTrackedSource))
return false;
trackedSource = walletTrackedSource;
}
else
{
return false;
Expand Down Expand Up @@ -97,6 +103,40 @@ public static TrackedSource Parse(string str, NBXplorerNetwork network)
}
}

public class WalletTrackedSource : TrackedSource
{
public string WalletId { get; }

public WalletTrackedSource(string walletId)
{
WalletId = walletId;
}

public static bool TryParse(ReadOnlySpan<char> strSpan, out WalletTrackedSource walletTrackedSource)
{
if (strSpan == null)
throw new ArgumentNullException(nameof(strSpan));
walletTrackedSource = null;
if (!strSpan.StartsWith("WALLET:".AsSpan(), StringComparison.Ordinal))
return false;
try
{
walletTrackedSource = new WalletTrackedSource(strSpan.Slice("WALLET:".Length).ToString());
return true;
}
catch { return false; }
}

public override string ToString()
{
return "WALLET:" + WalletId;
}
public override string ToPrettyString()
{
return WalletId;
}
}

public class AddressTrackedSource : TrackedSource, IDestination
{
// Note that we should in theory access BitcoinAddress. But parsing BitcoinAddress is very expensive, so we keep storing plain strings
Expand Down
6 changes: 6 additions & 0 deletions NBXplorer.Client/Models/TrackedSourceRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace NBXplorer.Models;

public class TrackedSourceRequest
{
public TrackedSource TrackedSource { get; set; }
}
Loading