Compare commits

..

1 Commits

Author SHA1 Message Date
nicolas.dorier
5903865a3f small docker 2018-05-08 23:10:47 +09:00
292 changed files with 6257 additions and 28656 deletions

View File

@ -1,43 +0,0 @@
version: 2
jobs:
test:
machine:
- image: ubuntu-2004:202201-02
steps:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
docker:
docker:
- image: cimg/base:stable
steps:
- setup_remote_docker
- checkout
- run:
command: |
docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
docker buildx create --use
docker buildx build -t $DOCKERHUB_REPO:$LATEST_TAG --platform linux/amd64,linux/arm64,linux/arm/v7 --push .
workflows:
version: 2
build_and_test:
jobs:
- test
publish:
jobs:
- docker:
filters:
branches:
ignore: /.*/
# only act on version tags v1.0.0.88 or v1.0.2-1
# OR feature tags like abc
# OR features on specific versions like v1.0.0.88-abc-1
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/

View File

@ -1,7 +0,0 @@
#!/bin/sh
set -e
cd ../NBXplorer.Tests
docker-compose -v
docker-compose build
docker-compose run tests

View File

@ -121,4 +121,5 @@ bower_components
output
.vs
NBXplorer.Tests/
**/launchSettings.json

17
.gitattributes vendored
View File

@ -1,17 +0,0 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# Explicitly declare text files you want to always be normalized and converted
# to native line endings on checkout.
*.c text
*.h text
# Declare files that will always have CRLF line endings on checkout.
*.sln text eol=crlf
# Declare files that will always have CRLF line endings on checkout.
*.sh text eol=lf
# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary

4
.gitignore vendored
View File

@ -26,8 +26,6 @@ bld/
# Visual Studio 2015 cache/options directory
.vs/
# Visual Studio Code directory
.vscode/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
@ -48,6 +46,7 @@ dlldata.c
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
*_i.c
*_p.c
@ -287,4 +286,3 @@ __pycache__/
*.btm.cs
*.odx.cs
*.xsd.cs
/NBXplorer.Tests/Properties/launchSettings.json

View File

@ -1,21 +1,19 @@
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.301-noble AS builder
FROM microsoft/dotnet:2.1.300-rc1-sdk-alpine3.7 AS builder
WORKDIR /source
COPY NBXplorer/NBXplorer.csproj NBXplorer/NBXplorer.csproj
COPY NBXplorer.Client/NBXplorer.Client.csproj NBXplorer.Client/NBXplorer.Client.csproj
# Cache some dependencies
RUN cd NBXplorer && dotnet restore && cd ..
COPY . .
RUN cd NBXplorer && \
dotnet publish --output /app/ --configuration Release
dotnet add package ILLink.Tasks --version 0.1.5-preview-1461378 --source https://dotnet.myget.org/F/dotnet-core/api/v3/index.json && \
dotnet publish --output /app/ --configuration Release -r linux-musl-x64
FROM mcr.microsoft.com/dotnet/aspnet:10.0.9-noble
FROM microsoft/dotnet:2.1.0-rc1-runtime-deps-alpine3.7
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
RUN mkdir /datadir
ENV NBXPLORER_DATADIR=/datadir
VOLUME /datadir
COPY --from=builder "/app" .
ENTRYPOINT ["dotnet", "NBXplorer.dll"]
ENTRYPOINT ["./NBXplorer"]

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBXplorer.Client" Version="4.2.0" />
</ItemGroup>
</Project>

View File

@ -1,183 +0,0 @@
using NBitcoin;
using NBitcoin.RPC;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using System;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace MultiSig
{
class Program
{
class Party
{
public Party(Mnemonic mnemonic, string password, KeyPath accountKeyPath)
{
// Note: you could just generate the ExtKey with new ExtKey() and save extKey.GetWif(network) somewhere.
// But saving a mnemonic + password is well known UX
Mnemonic = mnemonic;
PartyName = password; //lazy yes
RootExtKey = mnemonic.DeriveExtKey(password);
AccountExtPubKey = RootExtKey.Derive(accountKeyPath).Neuter();
// The AccountKeyPath should be stored along the AccountExtPubKey
// This is the keypath + the hash of the root hd key.
// During signing, NBitcoin need this information to derive the RootExtKey to the address keypath properly.
AccountKeyPath = new RootedKeyPath(RootExtKey.GetPublicKey().GetHDFingerPrint(), accountKeyPath);
}
public string PartyName;
public Mnemonic Mnemonic;
public ExtPubKey AccountExtPubKey;
public ExtKey RootExtKey;
public RootedKeyPath AccountKeyPath;
}
// We will:
// 1. Create a multi sig wallet of Alice and Bob
// 2. Fund it with 1 BTC
// 3. Send 0.4 BTC to a random address from it
public static async Task Main(string[] args)
{
// Start bitcoind and NBXplorer in regtest:
// * Run "bitcoind -regtest"
// * Run ".\build.ps1", then ".\run.ps1 -regtest" in NBXplorer
var network = Network.RegTest;
var client = CreateNBXClient(network);
// Now let's simulate alice and bob in a 2-2 multisig
var alice = new Party(new Mnemonic(Wordlist.English), "Alice",
new KeyPath("1'/2'/3'"));
var bob = new Party(new Mnemonic(Wordlist.English), "Bob",
new KeyPath("5'/2'/3'"));
Console.WriteLine($"Alice should secretly save '{alice.Mnemonic}', and remember her password 'Alice'");
Console.WriteLine("---");
Console.WriteLine($"Alice should secretly save '{bob.Mnemonic}', and remember her password 'Bob'");
Console.WriteLine("---");
Console.WriteLine($"Alice should share '{alice.AccountExtPubKey.GetWif(network)}' with Bob");
Console.WriteLine("---");
Console.WriteLine($"Bob should share '{bob.AccountExtPubKey.GetWif(network)}' with Alice");
var factory = new DerivationStrategyFactory(network);
var derivationStrategy = factory.CreateMultiSigDerivationStrategy(new[]
{
alice.AccountExtPubKey.GetWif(network),
bob.AccountExtPubKey.GetWif(network)
}, 2, new DerivationStrategyOptions() { ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH });
Console.WriteLine("---");
Console.WriteLine($"The derivation strategy '{derivationStrategy}' represents all the data you need to know to track the multisig wallet");
// NBXplorer will start tracking this wallet.
await client.TrackAsync(derivationStrategy);
// This allow you to get events out of NBXPlorer
var evts = client.CreateLongPollingNotificationSession();
// Now let's fund the wallet
var address1 = (await client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit)).Address;
var rpc = new RPCClient(network);
// If that fail, your bitcoin node need some bitcoins
// bitcoin-cli -regtest getnewaddress
// bitcoin-cli -regtest generatetoaddress 101 <address>
await rpc.SendToAddressAsync(address1, Money.Coins(1.0m));
await WaitTransaction(evts, derivationStrategy);
Console.WriteLine("---");
Console.WriteLine("Sent some money to the multi sig wallet");
Console.WriteLine("---");
// You can list transactions
var txs = await client.GetTransactionsAsync(derivationStrategy);
Console.WriteLine($"Number of unconf transactions: {txs.UnconfirmedTransactions.Transactions.Count}");
Console.WriteLine("---");
var balance = await client.GetBalanceAsync(derivationStrategy);
Console.WriteLine($"Balance: {balance.Unconfirmed}");
Console.WriteLine("---");
var randomDestination = new Key().PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
var psbt = (await client.CreatePSBTAsync(derivationStrategy, new CreatePSBTRequest()
{
Destinations =
{
new CreatePSBTDestination()
{
Destination = randomDestination,
Amount = Money.Coins(0.4m),
SubstractFees = true // We will pay fee by sending to destination a bit less than 0.4 BTC
}
},
FeePreference = new FeePreference()
{
// 10 sat/byte. You can remove this in prod, as it will use bitcoin's core estimation.
ExplicitFeeRate = new FeeRate(10.0m)
}
})).PSBT;
var signedByAlice = Sign(alice, derivationStrategy, psbt);
Console.WriteLine("---");
var signedByBob = Sign (bob, derivationStrategy, psbt);
// OK both have signed
var fullySignedPSBT = signedByAlice.Combine(signedByBob);
fullySignedPSBT.Finalize();
var fullySignedTx = fullySignedPSBT.ExtractTransaction();
await client.BroadcastAsync(fullySignedTx);
// Let's wait NBX receives the tx
await WaitTransaction(evts, derivationStrategy);
balance = await client.GetBalanceAsync(derivationStrategy);
Console.WriteLine($"New balance: {balance.Unconfirmed}");
}
private static PSBT Sign(Party party, DerivationStrategyBase derivationStrategy, PSBT psbt)
{
psbt = psbt.Clone();
// NBXplorer does not have knowledge of the account key path, KeyPath are private information of each peer
// NBXplorer only derive 0/* and 1/* on top of provided account xpubs,
// This mean that the input keypaths in the PSBT are in the form 0/* (as if the account key was the root)
// RebaseKeyPaths modifies the PSBT by adding the AccountKeyPath in prefix of all the keypaths of the PSBT
// Note that this is not necessary to do this if the account key is the same as root key.
// Note that also that you don't have to do this, if you do not pass the account key path in the later SignAll call.
// however, this is best practice to rebase the PSBT before signing.
// If you sign with an offline device (hw wallet), the wallet would need the rebased PSBT.
psbt.RebaseKeyPaths(party.AccountExtPubKey, party.AccountKeyPath);
Console.WriteLine("A PSBT is a data structure with all information for a wallet to sign.");
var spend = psbt.GetBalance(derivationStrategy, party.AccountExtPubKey, party.AccountKeyPath);
Console.WriteLine($"{party.PartyName}, Do you agree to sign this transaction spending {spend}?");
// Ok I sign
psbt.SignAll(derivationStrategy, // What addresses to derive?
party.RootExtKey.Derive(party.AccountKeyPath), // With which account private keys?
party.AccountKeyPath); // What is the keypath of the account private key. If you did not rebased the keypath like before, you can remove this parameter
return psbt;
}
static async Task<NewTransactionEvent> WaitTransaction(LongPollingNotificationSession evts, DerivationStrategyBase derivationStrategy)
{
while (true)
{
var evt = await evts.NextEventAsync();
if (evt is NBXplorer.Models.NewTransactionEvent tx)
{
if (tx.DerivationStrategy == derivationStrategy)
return tx;
}
}
}
private static ExplorerClient CreateNBXClient(Network network)
{
NBXplorerNetworkProvider provider = new NBXplorerNetworkProvider(network.ChainName);
ExplorerClient client = new NBXplorer.ExplorerClient(provider.GetFromCryptoCode(network.NetworkSet.CryptoCode));
return client;
}
}
}

View File

@ -1,34 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiSig", "MultiSig\MultiSig.csproj", "{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|x64.ActiveCfg = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|x64.Build.0 = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|x86.ActiveCfg = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Debug|x86.Build.0 = Debug|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|Any CPU.Build.0 = Release|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|x64.ActiveCfg = Release|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|x64.Build.0 = Release|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|x86.ActiveCfg = Release|Any CPU
{DBB077D5-4F0A-4D09-8E73-5A9C56877DDB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@ -1,405 +0,0 @@
using NBitcoin;
using System;
using System.Collections.Generic;
namespace NBXplorer
{
public class AssetMoney : IComparable, IComparable<AssetMoney>, IEquatable<AssetMoney>, IMoney
{
long _Quantity;
public long Quantity
{
get
{
return _Quantity;
}
// used as a central point where long.MinValue checking can be enforced
private set
{
CheckLongMinValue(value);
_Quantity = value;
}
}
private static void CheckLongMinValue(long value)
{
if (value == long.MinValue)
throw new OverflowException("satoshis amount should be greater than long.MinValue");
}
private readonly uint256 _Id;
/// <summary>
/// AssetId of the current amount
/// </summary>
public uint256 AssetId
{
get
{
return _Id;
}
}
/// <summary>
/// Get absolute value of the instance
/// </summary>
/// <returns></returns>
public AssetMoney Abs()
{
var a = this;
if (a.Quantity < 0)
a = -a;
return a;
}
#region ctor
public AssetMoney(uint256 assetId)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
}
public AssetMoney(uint256 assetId, int quantity)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
Quantity = quantity;
}
public AssetMoney(uint256 assetId, uint quantity)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
Quantity = quantity;
}
public AssetMoney(uint256 assetId, long quantity)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
Quantity = quantity;
}
public AssetMoney(uint256 assetId, ulong quantity)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
// overflow check.
// ulong.MaxValue is greater than long.MaxValue
checked
{
Quantity = (long)quantity;
}
}
public AssetMoney(uint256 assetId, decimal amount, int divisibility)
{
if (assetId == null)
throw new ArgumentNullException(nameof(assetId));
_Id = assetId;
// sanity check. Only valid units are allowed
checked
{
int dec = Pow10(divisibility);
var satoshi = amount * dec;
Quantity = (long)satoshi;
}
}
#endregion
private static int Pow10(int divisibility)
{
if (divisibility < 0)
throw new ArgumentOutOfRangeException("divisibility", "divisibility should be higher than 0");
int dec = 1;
for (int i = 0; i < divisibility; i++)
{
dec = dec * 10;
}
return dec;
}
/// <summary>
/// Split the Money in parts without loss
/// </summary>
/// <param name="parts">The number of parts (must be more than 0)</param>
/// <returns>The splitted money</returns>
public IEnumerable<AssetMoney> Split(int parts)
{
if (parts <= 0)
throw new ArgumentOutOfRangeException("Parts should be more than 0", "parts");
long remain;
long result = DivRem(_Quantity, parts, out remain);
for (int i = 0; i < parts; i++)
{
yield return new AssetMoney(_Id, result + (remain > 0 ? 1 : 0));
remain--;
}
}
private static long DivRem(long a, long b, out long result)
{
result = a % b;
return a / b;
}
public decimal ToDecimal(int divisibility)
{
var dec = Pow10(divisibility);
// overflow safe because (long / int) always fit in decimal
// decimal operations are checked by default
return (decimal)Quantity / (int)dec;
}
#region IEquatable<AssetMoney> Members
public bool Equals(AssetMoney other)
{
if (other == null)
return false;
CheckAssetId(other, "other");
return _Quantity.Equals(other.Quantity);
}
internal void CheckAssetId(AssetMoney other, string param)
{
if (other.AssetId != AssetId)
throw new ArgumentException("AssetMoney instance of different assets can't be computed together", param);
}
public int CompareTo(AssetMoney other)
{
if (other == null)
return 1;
CheckAssetId(other, "other");
return _Quantity.CompareTo(other.Quantity);
}
#endregion
#region IComparable Members
public int CompareTo(object obj)
{
if (obj == null)
return 1;
AssetMoney m = obj as AssetMoney;
if (m != null)
return _Quantity.CompareTo(m.Quantity);
#if !NETSTANDARD1X
return _Quantity.CompareTo(obj);
#else
return _Quantity.CompareTo((long)obj);
#endif
}
#endregion
public static AssetMoney operator -(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return new AssetMoney(left.AssetId, checked(left.Quantity - right.Quantity));
}
public static AssetMoney operator -(AssetMoney left)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
return new AssetMoney(left.AssetId, checked(-left.Quantity));
}
public static AssetMoney operator +(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return new AssetMoney(left.AssetId, checked(left.Quantity + right.Quantity));
}
public static AssetMoney operator *(int left, AssetMoney right)
{
if (right == null)
throw new ArgumentNullException(nameof(right));
return new AssetMoney(right.AssetId, checked(left * right.Quantity));
}
public static AssetMoney operator *(AssetMoney right, int left)
{
if (right == null)
throw new ArgumentNullException(nameof(right));
return new AssetMoney(right.AssetId, checked(right.Quantity * left));
}
public static AssetMoney operator *(long left, AssetMoney right)
{
if (right == null)
throw new ArgumentNullException(nameof(right));
return new AssetMoney(right.AssetId, checked(left * right.Quantity));
}
public static AssetMoney operator *(AssetMoney right, long left)
{
if (right == null)
throw new ArgumentNullException(nameof(right));
return new AssetMoney(right.AssetId, checked(left * right.Quantity));
}
public static bool operator <(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return left.Quantity < right.Quantity;
}
public static bool operator >(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return left.Quantity > right.Quantity;
}
public static bool operator <=(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return left.Quantity <= right.Quantity;
}
public static bool operator >=(AssetMoney left, AssetMoney right)
{
if (left == null)
throw new ArgumentNullException(nameof(left));
if (right == null)
throw new ArgumentNullException(nameof(right));
left.CheckAssetId(right, "right");
return left.Quantity >= right.Quantity;
}
public override bool Equals(object obj)
{
AssetMoney item = obj as AssetMoney;
if (item == null)
return false;
if (item.AssetId != AssetId)
return false;
return _Quantity.Equals(item.Quantity);
}
public static bool operator ==(AssetMoney a, AssetMoney b)
{
if (Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
if (a.AssetId != b.AssetId)
return false;
return a.Quantity == b.Quantity;
}
public static bool operator !=(AssetMoney a, AssetMoney b)
{
return !(a == b);
}
public override int GetHashCode()
{
return Tuple.Create(_Quantity, AssetId).GetHashCode();
}
public override string ToString()
{
return String.Format("{0}-{1}", Quantity, AssetId);
}
public static AssetMoney Min(AssetMoney a, AssetMoney b)
{
if (a == null)
throw new ArgumentNullException(nameof(a));
if (b == null)
throw new ArgumentNullException(nameof(b));
a.CheckAssetId(b, "b");
if (a <= b)
return a;
return b;
}
#region IMoney Members
IMoney IMoney.Add(IMoney money)
{
var assetMoney = (AssetMoney)money;
return this + assetMoney;
}
IMoney IMoney.Sub(IMoney money)
{
var assetMoney = (AssetMoney)money;
return this - assetMoney;
}
IMoney IMoney.Negate()
{
return this * -1;
}
int IComparable.CompareTo(object obj)
{
return this.CompareTo(obj);
}
int IComparable<IMoney>.CompareTo(IMoney other)
{
return this.CompareTo(other);
}
bool IEquatable<IMoney>.Equals(IMoney other)
{
return this.Equals(other);
}
bool IMoney.IsCompatible(IMoney money)
{
if (money == null)
throw new ArgumentNullException(nameof(money));
AssetMoney assetMoney = money as AssetMoney;
if (assetMoney == null)
return false;
return assetMoney.AssetId == AssetId;
}
#endregion
#region IMoney Members
IEnumerable<IMoney> IMoney.Split(int parts)
{
return Split(parts);
}
#endregion
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NBXplorer
{

View File

@ -1,18 +0,0 @@
using NBitcoin.Crypto;
using NBitcoin.DataEncoders;
using System.Text;
namespace NBXplorer.Client
{
public static class DBUtils
{
public static string nbxv1_get_wallet_id(string cryptoCode, string addressOrDerivation)
{
return Encoders.Base64.EncodeData(Hashes.SHA256(new UTF8Encoding(false).GetBytes($"{cryptoCode}|{addressOrDerivation}")), 0, 21);
}
public static string nbxv1_get_descriptor_id(string cryptoCode, string strategy, string feature)
{
return Encoders.Base64.EncodeData(Hashes.SHA256(new UTF8Encoding(false).GetBytes($"{cryptoCode}|{strategy}|{feature}")), 0, 21);
}
}
}

View File

@ -1,49 +1,19 @@
#nullable enable
using NBitcoin;
using NBitcoin;
using System;
using System.Collections.Generic;
#if !NO_RECORD
using static NBitcoin.WalletPolicies.MiniscriptNode;
#endif
using System.Text;
namespace NBXplorer.DerivationStrategy
{
public class Derivation
{
public Derivation(Script scriptPubKey, Script? redeem = null)
{
ScriptPubKey = scriptPubKey;
Redeem = redeem;
}
public Script ScriptPubKey
{
get;
get; set;
}
public Script? Redeem
public Script Redeem
{
get; set;
}
}
public class KeyPathDerivation : Derivation
{
public KeyPathDerivation(KeyPath keyPath, Script scriptPubKey, Script? redeem = null)
: base(scriptPubKey, redeem)
{
KeyPath = keyPath;
}
public KeyPath KeyPath { get; }
}
#if !NO_RECORD
public class PolicyDerivation : Derivation
{
public PolicyDerivation(NBitcoin.WalletPolicies.DerivationResult details, Script scriptPubKey, Script? redeem = null)
: base(scriptPubKey, redeem)
{
Details = details;
}
public NBitcoin.WalletPolicies.DerivationResult Details { get; }
}
#endif
}

View File

@ -1,19 +1,32 @@
using NBitcoin;
#if !NO_RECORD
using NBitcoin.WalletPolicies;
#endif
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
namespace NBXplorer.DerivationStrategy
{
public class DerivationStrategyOptions
{
public ScriptPubKeyType ScriptPubKeyType { get; set; }
/// <summary>
/// If true, use P2SH (default: false)
/// </summary>
public bool P2SH
{
get; set;
}
/// <summary>
/// If false, use segwit (default: false)
/// </summary>
public bool Legacy
{
get; set;
}
/// <summary>
/// If true, in case of multisig, do not reorder the public keys of an address lexicographically (default: false)
@ -22,8 +35,6 @@ namespace NBXplorer.DerivationStrategy
{
get; set;
}
public ReadOnlyDictionary<string, string> AdditionalOptions { get; set; }
}
public class DerivationStrategyFactory
{
@ -38,24 +49,13 @@ namespace NBXplorer.DerivationStrategy
}
public DerivationStrategyFactory(Network network)
{
if (network == null)
if(network == null)
throw new ArgumentNullException(nameof(network));
_Network = network;
if (_Network.Consensus.SupportSegwit)
{
AuthorizedOptions.Add("p2sh");
}
if (_Network.Consensus.SupportTaproot)
{
AuthorizedOptions.Add("taproot");
}
AuthorizedOptions.Add("keeporder");
AuthorizedOptions.Add("legacy");
}
public HashSet<string> AuthorizedOptions { get; } = new HashSet<string>();
readonly Regex MultiSigRegex = new Regex("^([0-9]{1,2})-of(-[A-Za-z0-9]+)+$");
static DirectDerivationStrategy DummyPubKey = new DirectDerivationStrategy(new ExtKey().Neuter().GetWif(Network.RegTest)) { Segwit = false };
public DerivationStrategyBase Parse(string str)
{
var strategy = ParseCore(str);
@ -65,78 +65,25 @@ namespace NBXplorer.DerivationStrategy
private DerivationStrategyBase ParseCore(string str)
{
bool legacy = false;
ReadBool(ref str, "legacy", ref legacy);
bool p2sh = false;
ReadBool(ref str, "p2sh", ref p2sh);
bool keepOrder = false;
bool taproot = false;
ScriptPubKeyType type = ScriptPubKeyType.Segwit;
ReadBool(ref str, "keeporder", ref keepOrder);
IDictionary<string, string> optionsDictionary = new Dictionary<string, string>(5);
foreach (Match optionMatch in _OptionRegex.Matches(str))
{
var rawKey = optionMatch.Groups[1].Value.ToLowerInvariant();
var splitKey = rawKey.Split(new[]{'='}, StringSplitOptions.RemoveEmptyEntries);
var key = splitKey[0];
var value = splitKey.Length > 1 ? splitKey[1]: null;
if (!AuthorizedOptions.Contains(key))
throw new FormatException($"The option '{key}' is not supported by this network");
if (!Extensions.TryAdd(optionsDictionary, key, value))
throw new FormatException($"The option '{key}' is duplicated");
}
var hasOptions = optionsDictionary.Count != 0;
str = _OptionRegex.Replace(str, string.Empty);
if (optionsDictionary.Remove("legacy"))
{
legacy = true;
type = ScriptPubKeyType.Legacy;
}
if (optionsDictionary.Remove("p2sh"))
{
p2sh = true;
type = ScriptPubKeyType.SegwitP2SH;
}
if (optionsDictionary.Remove("keeporder"))
{
keepOrder = true;
}
if (optionsDictionary.Remove("taproot"))
{
taproot = true;
#pragma warning disable CS0618 // Type or member is obsolete
type = ScriptPubKeyType.TaprootBIP86;
#pragma warning restore CS0618 // Type or member is obsolete
}
if (!legacy && !_Network.Consensus.SupportSegwit)
throw new FormatException("Segwit is not supported you need to specify option '-[legacy]'");
if (legacy && p2sh)
throw new FormatException("The option 'legacy' is incompatible with 'p2sh'");
if (taproot)
{
if (!_Network.Consensus.SupportTaproot)
{
throw new FormatException("Taproot is not supported, you need to remove option '-[taproot]'");
}
else
{
if (p2sh)
throw new FormatException("The option 'taproot' is incompatible with 'p2sh'");
if (legacy)
throw new FormatException("The option 'taproot' is incompatible with 'legacy'");
if (keepOrder)
throw new FormatException("The option 'taproot' is incompatible with 'keeporder'");
}
}
if(!legacy && !_Network.Consensus.SupportSegwit)
throw new FormatException("Segwit is not supported");
var options = new DerivationStrategyOptions()
{
KeepOrder = keepOrder,
ScriptPubKeyType = type,
AdditionalOptions = new ReadOnlyDictionary<string, string>(optionsDictionary)
Legacy = legacy,
P2SH = p2sh
};
var match = MultiSigRegex.Match(str);
if (match.Success)
if(match.Success)
{
var sigCount = int.Parse(match.Groups[1].Value);
var pubKeys = match.Groups
@ -147,14 +94,6 @@ namespace NBXplorer.DerivationStrategy
.ToArray();
return CreateMultiSigDerivationStrategy(pubKeys, sigCount, options);
}
#if !NO_RECORD
else if (PolicyDerivationStrategy._MaybeMiniscript.IsMatch(str))
{
if (hasOptions)
throw new FormatException("The derivation scheme should not contain any option (such as -[legacy])");
return PolicyDerivationStrategy.Parse(str, _Network);
}
#endif
else
{
var key = _Network.Parse<BitcoinExtPubKey>(str);
@ -168,7 +107,7 @@ namespace NBXplorer.DerivationStrategy
/// <param name="publicKey">The public key of the wallet</param>
/// <param name="options">Derivation options</param>
/// <returns></returns>
public StandardDerivationStrategyBase CreateDirectDerivationStrategy(ExtPubKey publicKey, DerivationStrategyOptions options = null)
public DerivationStrategyBase CreateDirectDerivationStrategy(ExtPubKey publicKey, DerivationStrategyOptions options = null)
{
return CreateDirectDerivationStrategy(publicKey.GetWif(Network), options);
}
@ -179,41 +118,19 @@ namespace NBXplorer.DerivationStrategy
/// <param name="publicKey">The public key of the wallet</param>
/// <param name="options">Derivation options</param>
/// <returns></returns>
public StandardDerivationStrategyBase CreateDirectDerivationStrategy(BitcoinExtPubKey publicKey, DerivationStrategyOptions options = null)
public DerivationStrategyBase CreateDirectDerivationStrategy(BitcoinExtPubKey publicKey, DerivationStrategyOptions options = null)
{
options = options ?? new DerivationStrategyOptions();
StandardDerivationStrategyBase strategy = null;
#pragma warning disable CS0618 // Type or member is obsolete
if (options.ScriptPubKeyType != ScriptPubKeyType.TaprootBIP86)
#pragma warning restore CS0618 // Type or member is obsolete
{
strategy = new DirectDerivationStrategy(publicKey, options.ScriptPubKeyType != ScriptPubKeyType.Legacy, options.AdditionalOptions);
if (options.ScriptPubKeyType == ScriptPubKeyType.Segwit && !_Network.Consensus.SupportSegwit)
throw new InvalidOperationException("This crypto currency does not support segwit");
DerivationStrategyBase strategy = new DirectDerivationStrategy(publicKey) { Segwit = !options.Legacy };
if(!options.Legacy && !_Network.Consensus.SupportSegwit)
throw new InvalidOperationException("This crypto currency does not support segwit");
if (options.ScriptPubKeyType == ScriptPubKeyType.SegwitP2SH)
{
strategy = new P2SHDerivationStrategy(strategy, true);
}
}
else
if(options.P2SH && !options.Legacy)
{
if (!_Network.Consensus.SupportTaproot)
throw new InvalidOperationException("This crypto currency does not support taproot");
strategy = new TaprootDerivationStrategy(publicKey, options.AdditionalOptions);
strategy = new P2SHDerivationStrategy(strategy, true);
}
return strategy;
}
/// <summary>
/// Create a taproot signature derivation strategy from public key
/// </summary>
/// <param name="publicKey">The public key of the wallet</param>
/// <param name="options">Derivation options</param>
/// <returns></returns>
public TaprootDerivationStrategy CreateTaprootDerivationStrategy(BitcoinExtPubKey publicKey, ReadOnlyDictionary<string, string> options = null)
{
return new TaprootDerivationStrategy(publicKey, options);
}
/// <summary>
/// Create a multisig derivation strategy from public keys
@ -237,19 +154,33 @@ namespace NBXplorer.DerivationStrategy
public DerivationStrategyBase CreateMultiSigDerivationStrategy(BitcoinExtPubKey[] pubKeys, int sigCount, DerivationStrategyOptions options = null)
{
options = options ?? new DerivationStrategyOptions();
StandardDerivationStrategyBase derivationStrategy = new MultisigDerivationStrategy(sigCount, pubKeys.ToArray(), options.ScriptPubKeyType == ScriptPubKeyType.Legacy, !options.KeepOrder, options.AdditionalOptions);
if (options.ScriptPubKeyType == ScriptPubKeyType.Legacy)
DerivationStrategyBase derivationStrategy = new MultisigDerivationStrategy(sigCount, pubKeys.ToArray(), options.Legacy)
{
LexicographicOrder = !options.KeepOrder
};
if(options.Legacy)
return new P2SHDerivationStrategy(derivationStrategy, false);
if (!_Network.Consensus.SupportSegwit)
if(!_Network.Consensus.SupportSegwit)
throw new InvalidOperationException("This crypto currency does not support segwit");
derivationStrategy = new P2WSHDerivationStrategy(derivationStrategy);
if (options.ScriptPubKeyType == ScriptPubKeyType.SegwitP2SH)
if(options.P2SH)
{
derivationStrategy = new P2SHDerivationStrategy(derivationStrategy, true);
}
return derivationStrategy;
}
readonly static Regex _OptionRegex = new Regex(@"-\[([^ \]\-]+)\]");
private void ReadBool(ref string str, string attribute, ref bool value)
{
value = str.Contains($"[{attribute}]");
if(value)
{
str = str.Replace($"[{attribute}]", string.Empty);
str = str.Replace("--", "-");
if(str.EndsWith("-"))
str = str.Substring(0, str.Length - 1);
}
}
}
}

View File

@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using NBitcoin;
using NBitcoin.Crypto;
namespace NBXplorer.DerivationStrategy
{
public class DirectDerivationStrategy : StandardDerivationStrategyBase
public class DirectDerivationStrategy : DerivationStrategyBase
{
BitcoinExtPubKey _Root;
@ -21,9 +21,10 @@ namespace NBXplorer.DerivationStrategy
public bool Segwit
{
get;
set;
}
protected internal override string StringValueCore
protected override string StringValue
{
get
{
@ -37,23 +38,21 @@ namespace NBXplorer.DerivationStrategy
}
}
public DirectDerivationStrategy(BitcoinExtPubKey root, bool segwit, ReadOnlyDictionary<string, string> additionalOptions = null) : base(additionalOptions)
public DirectDerivationStrategy(BitcoinExtPubKey root)
{
if(root == null)
throw new ArgumentNullException(nameof(root));
_Root = root;
Segwit = segwit;
}
public override Derivation GetDerivation(KeyPath keyPath)
public override Derivation Derive(KeyPath keyPath)
{
var pubKey = _Root.ExtPubKey.Derive(keyPath).PubKey;
return new KeyPathDerivation(keyPath, Segwit ? pubKey.WitHash.ScriptPubKey : pubKey.Hash.ScriptPubKey);
return new Derivation() { ScriptPubKey = Segwit ? pubKey.WitHash.ScriptPubKey : pubKey.Hash.ScriptPubKey };
}
public override IEnumerable<ExtPubKey> GetExtPubKeys()
public override DerivationStrategyBase GetLineFor(KeyPath keyPath)
{
yield return _Root.ExtPubKey;
return new DirectDerivationStrategy(_Root.ExtPubKey.Derive(keyPath).GetWif(_Root.Network)) { Segwit = Segwit };
}
}
}

View File

@ -1,12 +1,8 @@
#nullable enable
using NBitcoin;
#if !NO_RECORD
using NBitcoin.WalletPolicies;
#endif
using NBitcoin;
using NBitcoin.Crypto;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace NBXplorer.DerivationStrategy
{
@ -14,132 +10,72 @@ namespace NBXplorer.DerivationStrategy
{
Change = 1,
Deposit = 0,
Direct = 2,
Custom = 3,
}
public abstract class StandardDerivationStrategyBase : DerivationStrategyBase, IHDScriptPubKey
{
internal StandardDerivationStrategyBase(ReadOnlyDictionary<string, string> additionalOptions) : base(additionalOptions)
{
}
public abstract Derivation GetDerivation(KeyPath keyPath);
public override DerivationLine GetLineFor(KeyPathTemplates keyPathTemplates, DerivationFeature feature)
=> new KeyPathTemplateDerivationLine(this, keyPathTemplates, feature);
Script IHDScriptPubKey.ScriptPubKey => GetDerivation(KeyPath.Empty).ScriptPubKey;
IHDScriptPubKey? IHDScriptPubKey.Derive(KeyPath keyPath) => keyPath.IsHardenedPath ? null : new HDScriptPubKey(this, keyPath);
class HDScriptPubKey(StandardDerivationStrategyBase Parent, KeyPath KeyPath) : IHDScriptPubKey
{
public Script ScriptPubKey => Parent.GetDerivation(KeyPath).ScriptPubKey;
public IHDScriptPubKey? Derive(KeyPath keyPath) => KeyPath.IsHardenedPath ? null : new HDScriptPubKey(Parent, KeyPath.Derive(keyPath));
}
}
Direct = 2
}
public abstract class DerivationStrategyBase
{
readonly ReadOnlyDictionary<string, string> Empty = new ReadOnlyDictionary<string, string>(new Dictionary<string, string>(0));
public ReadOnlyDictionary<string, string> AdditionalOptions { get; }
internal DerivationStrategyBase(ReadOnlyDictionary<string,string>? additionalOptions)
internal DerivationStrategyBase()
{
AdditionalOptions = additionalOptions ?? Empty;
}
public DerivationLine GetLineFor(DerivationFeature feature) => GetLineFor(KeyPathTemplates.Default, feature);
public abstract DerivationLine GetLineFor(KeyPathTemplates keyPathTemplates, DerivationFeature feature);
public static KeyPath GetKeyPath(DerivationFeature derivationFeature)
{
return derivationFeature == DerivationFeature.Direct ? new KeyPath() : new KeyPath((uint)derivationFeature);
}
public static DerivationFeature GetFeature(KeyPath path)
{
return path.Indexes.Length == 1 ? DerivationFeature.Direct : (DerivationFeature)path.Indexes[0];
}
public DerivationStrategyBase GetLineFor(DerivationFeature derivationFeature)
{
return derivationFeature == DerivationFeature.Direct ? this :
GetLineFor(GetKeyPath(derivationFeature));
}
protected internal abstract string StringValueCore
public abstract DerivationStrategyBase GetLineFor(KeyPath keyPath);
public Derivation Derive(uint i)
{
return Derive(new KeyPath(i));
}
public abstract Derivation Derive(KeyPath keyPath);
protected abstract string StringValue
{
get;
}
string? _StringValue;
string StringValue
public override bool Equals(object obj)
{
get
{
if (_StringValue == null)
{
if (AdditionalOptions.Count == 0)
_StringValue = StringValueCore;
else
_StringValue = $"{StringValueCore}{GetSuffixOptionsString()}";
}
return _StringValue;
}
DerivationStrategyBase item = obj as DerivationStrategyBase;
if(item == null)
return false;
return StringValue.Equals(item.StringValue);
}
public static bool operator ==(DerivationStrategyBase a, DerivationStrategyBase b)
{
if(System.Object.ReferenceEquals(a, b))
return true;
if(((object)a == null) || ((object)b == null))
return false;
return a.StringValue == b.StringValue;
}
private string GetSuffixOptionsString()
public static bool operator !=(DerivationStrategyBase a, DerivationStrategyBase b)
{
return string.Join("", new SortedDictionary<string, string>(AdditionalOptions).Select(pair => $"-[{pair.Key}{(string.IsNullOrEmpty(pair.Value)?string.Empty: $"={pair.Value}")}]"));
return !(a == b);
}
#nullable enable
public override bool Equals(object? obj) => obj is DerivationStrategyBase o && StringValue.Equals(o.StringValue);
public static bool operator ==(DerivationStrategyBase? a, DerivationStrategyBase? b) => a is null ? b is null : a.Equals(b);
public static bool operator !=(DerivationStrategyBase? a, DerivationStrategyBase? b) => !(a == b);
public override int GetHashCode() => StringValue.GetHashCode();
#nullable restore
public abstract IEnumerable<ExtPubKey> GetExtPubKeys();
public override int GetHashCode()
{
return StringValue.GetHashCode();
}
public override string ToString()
{
return StringValue;
}
}
#if !NO_RECORD
public class MiniscriptDerivationLine : DerivationLine
{
public MiniscriptDerivationLine(PolicyDerivationStrategy derivationStrategy, DerivationFeature derivationFeature) : base(derivationFeature)
{
DerivationStrategy = derivationStrategy;
Intent = ToAddressIntent(derivationFeature);
}
public static AddressIntent ToAddressIntent(DerivationFeature derivationFeature)
{
return derivationFeature switch
{
DerivationFeature.Change => AddressIntent.Change,
DerivationFeature.Deposit => AddressIntent.Deposit,
_ => throw new NotSupportedException("MiniscriptDerivationStrategy only support deposit and change features")
};
}
public PolicyDerivationStrategy DerivationStrategy { get; }
public AddressIntent Intent { get; }
public override Derivation Derive(uint index) => DerivationStrategy.GetDerivation(Intent, index);
}
#endif
public abstract class DerivationLine
{
protected DerivationLine(DerivationFeature feature)
{
Feature = feature;
}
public DerivationFeature Feature { get; }
public abstract Derivation Derive(uint index);
}
public class KeyPathTemplateDerivationLine : DerivationLine
{
public KeyPathTemplateDerivationLine(StandardDerivationStrategyBase derivationStrategyBase, KeyPathTemplates keyPathTemplates, DerivationFeature derivationFeature) : base(derivationFeature)
{
if (derivationStrategyBase == null)
throw new ArgumentNullException(nameof(derivationStrategyBase));
if (keyPathTemplates == null)
throw new ArgumentNullException(nameof(keyPathTemplates));
DerivationStrategyBase = derivationStrategyBase;
KeyPathTemplate = keyPathTemplates.GetKeyPathTemplate(derivationFeature);
}
public StandardDerivationStrategyBase DerivationStrategyBase { get; }
public KeyPathTemplate KeyPathTemplate { get; }
public override Derivation Derive(uint index)
{
var kp = KeyPathTemplate.GetKeyPath(index);
return DerivationStrategyBase.GetDerivation(kp);
}
}
}

View File

@ -1,34 +1,34 @@
using NBitcoin;
using System.Linq;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using NBitcoin.Crypto;
namespace NBXplorer.DerivationStrategy
{
public class MultisigDerivationStrategy : StandardDerivationStrategyBase
public class MultisigDerivationStrategy : DerivationStrategyBase
{
public bool LexicographicOrder
{
get;
get; set;
}
public int RequiredSignatures
{
get;
get; set;
}
static readonly Comparer<PubKey> LexicographicComparer = Comparer<PubKey>.Create((a, b) => Comparer<string>.Default.Compare(a?.ToHex(), b?.ToHex()));
public ReadOnlyCollection<BitcoinExtPubKey> Keys
public BitcoinExtPubKey[] Keys
{
get;
get; set;
}
protected internal override string StringValueCore
protected override string StringValue
{
get
{
@ -48,38 +48,41 @@ namespace NBXplorer.DerivationStrategy
}
}
internal MultisigDerivationStrategy(int reqSignature, BitcoinExtPubKey[] keys, bool isLegacy, bool lexicographicOrder,
ReadOnlyDictionary<string, string> additionalOptions) : base(additionalOptions)
internal MultisigDerivationStrategy(int reqSignature, BitcoinExtPubKey[] keys, bool isLegacy)
{
Keys = new ReadOnlyCollection<BitcoinExtPubKey>(keys);
Keys = keys;
RequiredSignatures = reqSignature;
LexicographicOrder = lexicographicOrder;
LexicographicOrder = true;
IsLegacy = isLegacy;
}
public bool IsLegacy
{
get;
get; private set;
}
public override Derivation GetDerivation(KeyPath keyPath)
private void WriteBytes(MemoryStream ms, byte[] v)
{
var pubKeys = new PubKey[this.Keys.Count];
Parallel.For(0, pubKeys.Length, i =>
{
pubKeys[i] = this.Keys[i].ExtPubKey.Derive(keyPath).PubKey;
});
ms.Write(v, 0, v.Length);
}
public override Derivation Derive(KeyPath keyPath)
{
var pubKeys = this.Keys.Select(s => s.ExtPubKey.Derive(keyPath).PubKey).ToArray();
if(LexicographicOrder)
{
Array.Sort(pubKeys, LexicographicComparer);
}
var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(RequiredSignatures, pubKeys);
return new KeyPathDerivation(keyPath, redeem);
return new Derivation() { ScriptPubKey = redeem };
}
public override IEnumerable<ExtPubKey> GetExtPubKeys()
public override DerivationStrategyBase GetLineFor(KeyPath keyPath)
{
return Keys.Select(k => k.ExtPubKey);
return new MultisigDerivationStrategy(RequiredSignatures, Keys.Select(k => k.ExtPubKey.Derive(keyPath).GetWif(k.Network)).ToArray(), IsLegacy)
{
LexicographicOrder = LexicographicOrder
};
}
}
}

View File

@ -1,13 +1,17 @@
using System;
using NBXplorer.DerivationStrategy;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin;
using NBitcoin.Crypto;
namespace NBXplorer.DerivationStrategy
{
public class P2SHDerivationStrategy : StandardDerivationStrategyBase
public class P2SHDerivationStrategy : DerivationStrategyBase
{
bool addSuffix;
internal P2SHDerivationStrategy(StandardDerivationStrategyBase inner, bool addSuffix):base(inner.AdditionalOptions)
internal P2SHDerivationStrategy(DerivationStrategyBase inner, bool addSuffix)
{
if(inner == null)
throw new ArgumentNullException(nameof(inner));
@ -15,33 +19,34 @@ namespace NBXplorer.DerivationStrategy
this.addSuffix = addSuffix;
}
public StandardDerivationStrategyBase Inner
public DerivationStrategyBase Inner
{
get; set;
}
protected internal override string StringValueCore
protected override string StringValue
{
get
{
if(addSuffix)
return Inner.StringValueCore + "-[p2sh]";
return Inner.ToString() + "-[p2sh]";
return Inner.ToString();
}
}
public override IEnumerable<ExtPubKey> GetExtPubKeys()
public override Derivation Derive(KeyPath keyPath)
{
return Inner.GetExtPubKeys();
var derivation = Inner.Derive(keyPath);
return new Derivation()
{
ScriptPubKey = derivation.ScriptPubKey.Hash.ScriptPubKey,
Redeem = derivation.Redeem ?? derivation.ScriptPubKey
};
}
public override Derivation GetDerivation(KeyPath keyPath)
public override DerivationStrategyBase GetLineFor(KeyPath keyPath)
{
var derivation = Inner.GetDerivation(keyPath);
return new KeyPathDerivation(
keyPath,
derivation.ScriptPubKey.Hash.ScriptPubKey,
derivation.Redeem ?? derivation.ScriptPubKey);
return new P2SHDerivationStrategy(Inner.GetLineFor(keyPath), addSuffix);
}
}
}

View File

@ -1,34 +1,42 @@
using System;
using NBXplorer.DerivationStrategy;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin;
using NBitcoin.Crypto;
namespace NBXplorer.DerivationStrategy
{
public class P2WSHDerivationStrategy : StandardDerivationStrategyBase
public class P2WSHDerivationStrategy : DerivationStrategyBase
{
internal P2WSHDerivationStrategy(StandardDerivationStrategyBase inner):base(inner.AdditionalOptions)
internal P2WSHDerivationStrategy(DerivationStrategyBase inner)
{
if(inner == null)
throw new ArgumentNullException(nameof(inner));
Inner = inner;
}
public StandardDerivationStrategyBase Inner
public DerivationStrategyBase Inner
{
get; set;
}
protected internal override string StringValueCore => Inner.ToString();
protected override string StringValue => Inner.ToString();
public override IEnumerable<ExtPubKey> GetExtPubKeys()
public override Derivation Derive(KeyPath keyPath)
{
return Inner.GetExtPubKeys();
var derivation = Inner.Derive(keyPath);
return new Derivation()
{
ScriptPubKey = derivation.ScriptPubKey.WitHash.ScriptPubKey,
Redeem = derivation.ScriptPubKey
};
}
public override Derivation GetDerivation(KeyPath keyPath)
public override DerivationStrategyBase GetLineFor(KeyPath keyPath)
{
var redeem = Inner.GetDerivation(keyPath).ScriptPubKey;
return new KeyPathDerivation(keyPath, redeem.WitHash.ScriptPubKey, redeem);
return new P2WSHDerivationStrategy(Inner.GetLineFor(keyPath));
}
}
}

View File

@ -1,212 +0,0 @@
#nullable enable
#if !NO_RECORD
using NBitcoin;
using NBitcoin.WalletPolicies;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
namespace NBXplorer.DerivationStrategy
{
public class PolicyDerivationStrategy : DerivationStrategyBase
{
internal static readonly Regex _MaybeMiniscript = new("^(wsh|sh|pkh|tr|wpkh)\\(");
public static bool TryParse(
string str,
Network network,
[MaybeNullWhen(false)] out PolicyDerivationStrategy strategy)
{
strategy = null;
if (!_MaybeMiniscript.IsMatch(str))
return false;
if (!WalletPolicy.TryParse(str, network, out var policy) || !IsValidPolicy(policy, out _))
return false;
strategy = new PolicyDerivationStrategy(policy, false);
return true;
}
public static PolicyDerivationStrategy Parse(string str, Network network)
{
if (!_MaybeMiniscript.IsMatch(str))
throw new FormatException("The policy should start by either wsh, sh, pkh, tr, wpkh");
var policy = WalletPolicy.Parse(str, network);
if (!IsValidPolicy(policy, out var err))
throw new FormatException(err);
return new PolicyDerivationStrategy(policy, false);
}
public PolicyDerivationStrategy(WalletPolicy policy) : this(policy, true)
{
}
PolicyDerivationStrategy(WalletPolicy policy, bool check) : base(null)
{
if (check && !IsValidPolicy(policy, out var error))
throw new ArgumentException(paramName: nameof(policy), message: error);
Policy = policy;
}
/// <summary>
/// Check that the policy should have at least one multi path node ([12345678]xpub/**) and no xpriv
/// </summary>
/// <param name="policy"></param>
/// <param name="error"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public static bool IsValidPolicy(WalletPolicy policy, [MaybeNullWhen(true)] out string error)
{
var v = new ValidPolicyVisitor();
policy.FullDescriptor.Visit(v);
error = v.Error;
return error is null;
}
class ValidPolicyVisitor : MiniscriptVisitor
{
public string? Error {
get
{
if (hasSecretKey)
return "The policy should not contain any xpriv key";
if (!hasMultiPathNode)
return "The policy should contain at least one multi path node ([12345678]xpub/**)";
return null;
}
}
private bool hasMultiPathNode;
private bool hasSecretKey;
public override void Visit(MiniscriptNode node)
{
if (node is MiniscriptNode.MultipathNode)
hasMultiPathNode = true;
else if (node is MiniscriptNode.HDKeyNode { Key: BitcoinExtKey })
hasSecretKey = true;
else
base.Visit(node);
}
}
public WalletPolicy Policy { get; }
private readonly DerivationCache cache = new();
private string? _str;
protected internal override string StringValueCore => _str ??= Policy.ToString(true);
public override IEnumerable<ExtPubKey> GetExtPubKeys()
=> Policy.KeyInformationVector.Select(kv => GetExtPubKey(kv.Key));
private ExtPubKey GetExtPubKey(IHDKey key)
=> key switch
{
ExtPubKey extPubKey => extPubKey,
BitcoinExtPubKey bitcoinExtPubKey => bitcoinExtPubKey.ExtPubKey,
ExtKey extKey => extKey.Neuter(),
BitcoinExtKey bitcoinExtKey => bitcoinExtKey.ExtKey.Neuter(),
_ => throw new NotSupportedException($"Unsupported key type: {key.GetType()}")
};
public NBXplorer.DerivationStrategy.Derivation GetDerivation(DerivationFeature feature, uint index)
=> GetDerivation(MiniscriptDerivationLine.ToAddressIntent(feature), index);
public NBXplorer.DerivationStrategy.Derivation GetDerivation(AddressIntent addressIntent, uint index)
{
var derived = Policy.FullDescriptor.Derive(new(addressIntent, [(int)index]) { DervivationCache = cache });
var scripts = derived[0].Miniscript.ToScripts();
return new PolicyDerivation(derived[0], scripts.ScriptPubKey, scripts.RedeemScript);
}
public override DerivationLine GetLineFor(KeyPathTemplates keyPathTemplates, DerivationFeature feature) => new MiniscriptDerivationLine(this, feature);
// Extract the multipath node from the hdkey
class MultipathNodeVisitor : MiniscriptVisitor
{
private readonly ExtPubKey _target;
public MiniscriptNode.MultipathNode? Result { get; set; }
public MultipathNodeVisitor(IHDKey target)
{
ArgumentNullException.ThrowIfNull(target);
_target = Normalize(target);
}
private static ExtPubKey Normalize(IHDKey target)
=> target switch
{
BitcoinExtKey extKey => extKey.Neuter().ExtPubKey,
ExtKey extKey => extKey.Neuter(),
BitcoinExtPubKey bitcoinExtPubKey => bitcoinExtPubKey.ExtPubKey,
ExtPubKey a => a,
_ => throw new NotSupportedException(target.GetType().ToString())
};
public override void Visit(MiniscriptNode node)
{
if (Result is not null)
return;
if (node is MiniscriptNode.MultipathNode { Target: MiniscriptNode.HDKeyNode hd } mp)
{
if (Normalize(hd.Key).Equals(_target))
Result = mp;
}
else
base.Visit(node);
}
}
class MiniscriptScriptPubKey : IHDScriptPubKey
{
private readonly PolicyDerivationStrategy _policyDerivationStrategy;
private readonly MiniscriptNode.MultipathNode _multipathNode;
private readonly KeyPath _keyPath;
public MiniscriptScriptPubKey(
PolicyDerivationStrategy policyDerivationStrategy,
MiniscriptNode.MultipathNode multipathNode,
KeyPath? keyPath = null,
DerivationCache? cache = null)
{
_policyDerivationStrategy = policyDerivationStrategy;
_multipathNode = multipathNode;
_keyPath = keyPath ?? KeyPath.Empty;
_cache = cache ?? new();
}
private readonly DerivationCache _cache;
public IHDScriptPubKey? Derive(KeyPath keyPath) =>
_keyPath.Derive(keyPath) is { Length: <= 2 } kp
&& (kp.Length == 0 || GetAddressIntent(kp.Indexes[0]) is not null)
&& !kp.IsHardenedPath
? new MiniscriptScriptPubKey(_policyDerivationStrategy, _multipathNode, kp, _cache) : null;
public Script ScriptPubKey
{
get
{
if (_keyPath is not { Indexes: [var intentIdx, var index], IsHardenedPath: false }
|| GetAddressIntent(intentIdx) is not {} intent)
throw new InvalidOperationException("Invalid keypath (it should be non hardened with two component)");
var derived = _policyDerivationStrategy.Policy.FullDescriptor.Derive(new(intent, new[] { (int)index })
{
DervivationCache = _cache
});
return derived[0].Miniscript.ToScripts().ScriptPubKey;
}
}
private AddressIntent? GetAddressIntent(uint intentIdx)
=> intentIdx == _multipathNode.DepositIndex ? AddressIntent.Deposit :
intentIdx == _multipathNode.ChangeIndex ? AddressIntent.Change : null;
}
public IHDScriptPubKey? GetHDScriptPubKey(IHDKey accountKey)
{
ArgumentNullException.ThrowIfNull(accountKey);
var visitor = new MultipathNodeVisitor(accountKey);
visitor.Visit(Policy.FullDescriptor.RootNode);
if (visitor.Result is null)
return null;
return new MiniscriptScriptPubKey(this, visitor.Result);
}
}
}
#endif

View File

@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using NBitcoin;
namespace NBXplorer.DerivationStrategy
{
public class TaprootDerivationStrategy : StandardDerivationStrategyBase
{
BitcoinExtPubKey _Root;
public ExtPubKey Root
{
get
{
return _Root;
}
}
protected internal override string StringValueCore
{
get
{
StringBuilder builder = new StringBuilder();
builder.Append(_Root.ToString());
builder.Append("-[taproot]");
return builder.ToString();
}
}
public TaprootDerivationStrategy(BitcoinExtPubKey root, ReadOnlyDictionary<string, string> additionalOptions = null) : base(additionalOptions)
{
if (root == null)
throw new ArgumentNullException(nameof(root));
_Root = root;
}
public override Derivation GetDerivation(KeyPath keyPath)
{
#if NO_SPAN
throw new NotSupportedException("Deriving taproot address is not supported on this platform.");
#else
var pubKey = _Root.ExtPubKey.Derive(keyPath).PubKey.GetTaprootFullPubKey();
return new KeyPathDerivation(keyPath, pubKey.ScriptPubKey);
#endif
}
public override IEnumerable<ExtPubKey> GetExtPubKeys()
{
yield return _Root.ExtPubKey;
}
}
}

View File

@ -1,5 +1,7 @@
using NBitcoin;
using System.Linq;
using NBitcoin.DataEncoders;
using NBitcoin.JsonConverters;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using System;
@ -12,15 +14,12 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
using NBitcoin.RPC;
using System.Runtime.CompilerServices;
using System.Linq;
namespace NBXplorer
{
public class ExplorerClient
{
public interface IAuth
internal interface IAuth
{
bool RefreshCache();
void SetAuthorization(HttpRequestMessage message);
@ -37,13 +36,11 @@ namespace NBXplorer
_CookieFilePath = path;
}
public string CookieFilePath => _CookieFilePath;
public bool RefreshCache()
{
try
{
var cookieData = File.ReadAllText(CookieFilePath);
var cookieData = File.ReadAllText(_CookieFilePath);
_CachedAuth = new AuthenticationHeaderValue("Basic", Encoders.Base64.EncodeData(Encoders.ASCII.DecodeData(cookieData)));
return true;
}
@ -57,7 +54,7 @@ namespace NBXplorer
public void SetWebSocketAuth(ClientWebSocket socket)
{
if (_CachedAuth != null)
if(_CachedAuth != null)
socket.Options.SetRequestHeader("Authorization", $"{_CachedAuth.Scheme} {_CachedAuth.Parameter}");
}
}
@ -77,57 +74,33 @@ namespace NBXplorer
}
}
public ExplorerClient(NBXplorerNetwork network, Uri serverAddress = null) : this(network, serverAddress, null)
{
}
public ExplorerClient(NBXplorerNetwork network, Uri serverAddress, IAuth customAuth)
public ExplorerClient(NBXplorerNetwork network, Uri serverAddress = null)
{
serverAddress = serverAddress ?? network.DefaultSettings.DefaultUrl;
if (network == null)
if(network == null)
throw new ArgumentNullException(nameof(network));
_Address = serverAddress;
_Network = network;
Serializer = new Serializer(network);
_Serializer = new Serializer(network.NBitcoinNetwork);
_CryptoCode = _Network.CryptoCode;
_Factory = Network.DerivationStrategyFactory;
if (customAuth == null)
{
SetCookieAuth(network.DefaultSettings.DefaultCookieFile);
}
else
{
_Auth = customAuth;
customAuth.RefreshCache();
}
_Factory = new DerivationStrategy.DerivationStrategyFactory(Network.NBitcoinNetwork);
SetCookieAuth(network.DefaultSettings.DefaultCookieFile);
}
public RPCClient RPCClient { get; private set; }
internal IAuth _Auth = new NullAuthentication();
public bool SetCookieAuth(string path)
{
if (path == null)
if(path == null)
throw new ArgumentNullException(nameof(path));
CookieAuthentication auth = new CookieAuthentication(path);
Auth = auth;
_Auth = auth;
return auth.RefreshCache();
}
public void SetNoAuth()
{
Auth = new NullAuthentication();
}
private void ConstructRPCClient()
{
RPCClient = new RPCClient(Auth is CookieAuthentication cookieAuthentication
? new RPCCredentialString()
{
CookieFile = cookieAuthentication.CookieFilePath
}
: new RPCCredentialString(), GetFullUri($"v1/cryptos/{_CryptoCode}/rpc"), _Network.NBitcoinNetwork);
_Auth = new NullAuthentication();
}
private readonly string _CryptoCode = "BTC";
@ -139,479 +112,203 @@ namespace NBXplorer
}
}
Serializer _Serializer;
DerivationStrategy.DerivationStrategyFactory _Factory;
public UTXOChanges GetUTXOs(DerivationStrategyBase extKey, CancellationToken cancellation = default)
public UTXOChanges GetUTXOs(DerivationStrategyBase extKey, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
return GetUTXOsAsync(extKey, cancellation).GetAwaiter().GetResult();
}
public Task<UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, CancellationToken cancellation = default)
{
if (extKey == null)
throw new ArgumentNullException(nameof(extKey));
return GetUTXOsAsync(TrackedSource.Create(extKey), cancellation);
}
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default)
{
return await SendAsync<TransactionResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/transactions/{txId}", cancellation).ConfigureAwait(false);
return GetUTXOsAsync(extKey, previousChange, longPolling, cancellation).GetAwaiter().GetResult();
}
public TransactionResult GetTransaction(uint256 txId, CancellationToken cancellation = default)
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
{
return await SendAsync<TransactionResult>(HttpMethod.Get, null, "v1/cryptos/{0}/transactions/" + txId, new[] { CryptoCode }, cancellation).ConfigureAwait(false);
}
public TransactionResult GetTransaction(uint256 txId, CancellationToken cancellation = default(CancellationToken))
{
return GetTransactionAsync(txId, cancellation).GetAwaiter().GetResult();
}
public async Task<PruneResponse> PruneAsync(DerivationStrategyBase extKey, PruneRequest pruneRequest, CancellationToken cancellation = default)
public Task<UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, UTXOChanges previousChange, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
if (extKey == null)
throw new ArgumentNullException(nameof(extKey));
return await SendAsync<PruneResponse>(HttpMethod.Post, pruneRequest, $"v1/cryptos/{CryptoCode}/derivations/{extKey}/prune", cancellation).ConfigureAwait(false);
return GetUTXOsAsync(extKey, previousChange?.Confirmed?.Bookmark, previousChange?.Unconfirmed?.Bookmark, longPolling, cancellation);
}
public PruneResponse Prune(DerivationStrategyBase extKey, PruneRequest pruneRequest, CancellationToken cancellation = default)
public UTXOChanges GetUTXOs(DerivationStrategyBase extKey, Bookmark confirmedBookmark, Bookmark unconfirmedBookmark, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
return PruneAsync(extKey, pruneRequest, cancellation).GetAwaiter().GetResult();
return GetUTXOsAsync(extKey, confirmedBookmark, unconfirmedBookmark, longPolling, cancellation).GetAwaiter().GetResult();
}
internal class RawStr
public NotificationSession CreateNotificationSession(CancellationToken cancellation = default(CancellationToken))
{
private string str;
public RawStr(string str)
{
this.str = str;
}
public override string ToString() => str;
};
internal static RawStr Raw(string str) => new RawStr(str);
public async Task ScanUTXOSetAsync(DerivationStrategyBase extKey, int? batchSize = null, int? gapLimit = null, int? fromIndex = null, CancellationToken cancellation = default)
{
if (extKey == null)
throw new ArgumentNullException(nameof(extKey));
List<string> args = new List<string>();
if (batchSize != null)
args.Add($"batchsize={batchSize.Value}");
if (gapLimit != null)
args.Add($"gaplimit={gapLimit.Value}");
if (fromIndex != null)
args.Add($"from={fromIndex.Value}");
var argsString = string.Join("&", args.ToArray());
if (argsString != string.Empty)
argsString = $"?{argsString}";
await SendAsync<bool>(HttpMethod.Post, null, $"v1/cryptos/{CryptoCode}/derivations/{extKey}/utxos/scan{Raw(argsString)}", cancellation).ConfigureAwait(false);
}
public void ScanUTXOSet(DerivationStrategyBase extKey, int? batchSize = null, int? gapLimit = null, int? fromIndex = null, CancellationToken cancellation = default)
{
ScanUTXOSetAsync(extKey, batchSize, gapLimit, fromIndex, cancellation).GetAwaiter().GetResult();
return CreateNotificationSessionAsync(cancellation).GetAwaiter().GetResult();
}
public async Task<ScanUTXOInformation> GetScanUTXOSetInformationAsync(DerivationStrategyBase extKey, CancellationToken cancellation = default)
public async Task<NotificationSession> CreateNotificationSessionAsync(CancellationToken cancellation = default(CancellationToken))
{
return await SendAsync<ScanUTXOInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{extKey}/utxos/scan", cancellation).ConfigureAwait(false);
}
public ScanUTXOInformation GetScanUTXOSetInformation(DerivationStrategyBase extKey, CancellationToken cancellation = default)
{
return GetScanUTXOSetInformationAsync(extKey, cancellation).GetAwaiter().GetResult();
}
public LongPollingNotificationSession CreateLongPollingNotificationSession(long lastEventId = 0)
{
return new LongPollingNotificationSession(lastEventId, this);
}
public WebsocketNotificationSession CreateWebsocketNotificationSession(CancellationToken cancellation = default)
{
return CreateWebsocketNotificationSessionAsync(cancellation).GetAwaiter().GetResult();
}
public async Task<WebsocketNotificationSession> CreateWebsocketNotificationSessionAsync(CancellationToken cancellation = default)
{
var session = new WebsocketNotificationSession(this);
await session.ConnectAsync(cancellation).ConfigureAwait(false);
return session;
}
public WebsocketNotificationSessionLegacy CreateWebsocketNotificationSessionLegacy(CancellationToken cancellation = default)
{
return CreateWebsocketNotificationSessionLegacyAsync(cancellation).GetAwaiter().GetResult();
}
public async Task<WebsocketNotificationSessionLegacy> CreateWebsocketNotificationSessionLegacyAsync(CancellationToken cancellation = default)
{
var session = new WebsocketNotificationSessionLegacy(this);
var session = new NotificationSession(this);
await session.ConnectAsync(cancellation).ConfigureAwait(false);
return session;
}
public UTXOChanges GetUTXOs(TrackedSource trackedSource, CancellationToken cancellation = default)
public Task<UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, Bookmark confirmedBookmark, Bookmark unconfirmedBookmark, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
return GetUTXOsAsync(trackedSource, cancellation).GetAwaiter().GetResult();
}
public Task<UTXOChanges> GetUTXOsAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
{
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
return SendAsync<UTXOChanges>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/utxos", cancellation);
return GetUTXOsAsync(extKey,
confirmedBookmark == null ? null as Bookmark[] : new Bookmark[] { confirmedBookmark },
unconfirmedBookmark == null ? null as Bookmark[] : new Bookmark[] { unconfirmedBookmark }, longPolling, cancellation);
}
public void WaitServerStarted(CancellationToken cancellation = default)
public async Task<UTXOChanges> GetUTXOsAsync(DerivationStrategyBase extKey, Bookmark[] confirmedBookmarks, Bookmark[] unconfirmedBookmarks, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
if(confirmedBookmarks != null)
parameters.Add("confirmedBookmarks", String.Join(",", confirmedBookmarks.Select(b => b.ToString())));
if(unconfirmedBookmarks != null)
parameters.Add("unconfirmedBookmarks", String.Join(",", unconfirmedBookmarks.Select(b => b.ToString())));
parameters.Add("longPolling", longPolling.ToString());
var query = String.Join("&", parameters.Select(p => p.Key + "=" + p.Value).ToArray());
return await SendAsync<UTXOChanges>(HttpMethod.Get, null, "v1/cryptos/{0}/derivations/{1}/utxos?" + query, new object[] { CryptoCode, extKey.ToString() }, cancellation).ConfigureAwait(false);
}
public void WaitServerStarted(CancellationToken cancellation = default(CancellationToken))
{
WaitServerStartedAsync(cancellation).GetAwaiter().GetResult();
}
public async Task WaitServerStartedAsync(CancellationToken cancellation = default)
public async Task WaitServerStartedAsync(CancellationToken cancellation = default(CancellationToken))
{
while (true)
while(true)
{
try
{
var status = await GetStatusAsync(cancellation).ConfigureAwait(false);
if (status.IsFullySynched)
if(status.IsFullySynched)
break;
await Task.Delay(10);
}
catch (OperationCanceledException) { throw; }
catch(OperationCanceledException) { throw; }
catch { }
cancellation.ThrowIfCancellationRequested();
}
}
public void Track(DerivationStrategyBase strategy, CancellationToken cancellation = default)
public void Track(DerivationStrategyBase strategy, CancellationToken cancellation = default(CancellationToken))
{
TrackAsync(strategy, cancellation).GetAwaiter().GetResult();
}
public Task TrackAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default)
public Task TrackAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default(CancellationToken))
{
return TrackAsync(TrackedSource.Create(strategy), cancellation: cancellation);
return SendAsync<string>(HttpMethod.Post, null, "v1/cryptos/{0}/derivations/{1}", new[] { CryptoCode, strategy.ToString() }, cancellation);
}
public void Track(DerivationStrategyBase strategy, TrackWalletRequest trackDerivationRequest, CancellationToken cancellation = default)
{
TrackAsync(strategy, trackDerivationRequest, cancellation).GetAwaiter().GetResult();
}
public async Task TrackAsync(DerivationStrategyBase strategy, TrackWalletRequest trackDerivationRequest, CancellationToken cancellation = default)
{
if (strategy == null)
throw new ArgumentNullException(nameof(strategy));
await SendAsync<string>(HttpMethod.Post, trackDerivationRequest, $"v1/cryptos/{CryptoCode}/derivations/{strategy}", cancellation).ConfigureAwait(false);
}
public void Track(TrackedSource trackedSource, CancellationToken cancellation = default)
{
TrackAsync(trackedSource, cancellation: cancellation).GetAwaiter().GetResult();
}
public Task TrackAsync(TrackedSource trackedSource, TrackWalletRequest trackDerivationRequest = null, CancellationToken cancellation = default)
{
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
return SendAsync<string>(HttpMethod.Post, trackDerivationRequest, GetBasePath(trackedSource), cancellation);
}
private Exception UnSupported(TrackedSource trackedSource)
{
return new NotSupportedException($"Unsupported {trackedSource.GetType().Name}");
}
public void CancelReservation(DerivationStrategyBase strategy, KeyPath[] keyPaths, CancellationToken cancellation = default)
public void CancelReservation(DerivationStrategyBase strategy, KeyPath[] keyPaths, CancellationToken cancellation = default(CancellationToken))
{
CancelReservationAsync(strategy, keyPaths, cancellation).GetAwaiter().GetResult();
}
public GetBalanceResponse GetBalance(DerivationStrategyBase userDerivationScheme, CancellationToken cancellation = default)
public Task CancelReservationAsync(DerivationStrategyBase strategy, KeyPath[] keyPaths, CancellationToken cancellation = default(CancellationToken))
{
return GetBalanceAsync(userDerivationScheme, cancellation).GetAwaiter().GetResult();
}
public Task<GetBalanceResponse> GetBalanceAsync(DerivationStrategyBase userDerivationScheme, CancellationToken cancellation = default)
{
return GetBalanceAsync(TrackedSource.Create(userDerivationScheme), cancellation);
return SendAsync<string>(HttpMethod.Post, keyPaths, "v1/cryptos/{0}/derivations/{1}/addresses/cancelreservation", new[] { CryptoCode, strategy.ToString() }, 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 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 async Task<bool> IsTrackedAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
{
var responseMessage = await SendAsync(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}", cancellation);
switch (responseMessage.StatusCode)
{
case HttpStatusCode.OK:
return true;
case HttpStatusCode.NotFound:
return false;
default:
await ParseResponse(responseMessage);
return false;
}
}
public Task CancelReservationAsync(DerivationStrategyBase strategy, KeyPath[] keyPaths, CancellationToken cancellation = default)
{
return SendAsync<string>(HttpMethod.Post, keyPaths, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/cancelreservation", cancellation);
}
public StatusResult GetStatus(CancellationToken cancellation = default)
public StatusResult GetStatus(CancellationToken cancellation = default(CancellationToken))
{
return GetStatusAsync(cancellation).GetAwaiter().GetResult();
}
public void Wipe(DerivationStrategyBase strategy, CancellationToken cancellation = default)
public Task<StatusResult> GetStatusAsync(CancellationToken cancellation = default(CancellationToken))
{
WipeAsync(strategy, cancellation).GetAwaiter().GetResult();
return SendAsync<StatusResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/status", null, cancellation);
}
public Task WipeAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default)
public GetTransactionsResponse GetTransactions(DerivationStrategyBase strategy, GetTransactionsResponse previous, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
if (strategy is null)
throw new ArgumentNullException(nameof(strategy));
return SendAsync<bool>(HttpMethod.Post, null, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/utxos/wipe", cancellation);
return GetTransactionsAsync(strategy, previous, longPolling, cancellation).GetAwaiter().GetResult();
}
public GetTransactionsResponse GetTransactions(DerivationStrategyBase strategy, Bookmark[] confirmedBookmarks, Bookmark[] unconfirmedBookmarks, Bookmark[] replacedBookmarks, bool longPolling = true, CancellationToken cancellation = default(CancellationToken))
{
return GetTransactionsAsync(strategy, confirmedBookmarks, unconfirmedBookmarks, replacedBookmarks, longPolling, cancellation).GetAwaiter().GetResult();
}
public Task<GetTransactionsResponse> GetTransactionsAsync(DerivationStrategyBase strategy, GetTransactionsResponse previous, bool longPolling, CancellationToken cancellation = default(CancellationToken))
{
return GetTransactionsAsync(strategy,
previous == null ? null : new[] { previous.ConfirmedTransactions.Bookmark },
previous == null ? null : new[] { previous.UnconfirmedTransactions.Bookmark },
previous == null ? null : new[] { previous.ReplacedTransactions.Bookmark }, longPolling, cancellation);
}
public Task<GetTransactionsResponse> GetTransactionsAsync(DerivationStrategyBase strategy, Bookmark[] confirmedBookmarks, Bookmark[] unconfirmedBookmarks, Bookmark[] replacedBookmarks, bool longPolling, CancellationToken cancellation = default(CancellationToken))
{
Dictionary<string, string> parameters = new Dictionary<string, string>();
if(confirmedBookmarks != null)
parameters.Add("confirmedBookmarks", String.Join(",", confirmedBookmarks.Select(b => b.ToString())));
if(unconfirmedBookmarks != null)
parameters.Add("unconfirmedBookmarks", String.Join(",", unconfirmedBookmarks.Select(b => b.ToString())));
if(replacedBookmarks != null)
parameters.Add("replacedBookmarks", String.Join(",", replacedBookmarks.Select(b => b.ToString())));
parameters.Add("longPolling", longPolling.ToString());
var query = String.Join("&", parameters.Select(p => p.Key + "=" + p.Value).ToArray());
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/transactions?" + query, null, cancellation);
}
public Task<StatusResult> GetStatusAsync(CancellationToken cancellation = default)
{
return SendAsync<StatusResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/status", cancellation);
}
public GetTransactionsResponse GetTransactions(DerivationStrategyBase strategy, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
{
return GetTransactionsAsync(strategy, from, to, cancellation).GetAwaiter().GetResult();
}
public GetTransactionsResponse GetTransactions(TrackedSource trackedSource, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
{
return GetTransactionsAsync(trackedSource, from, to, cancellation).GetAwaiter().GetResult();
}
public Task<GetTransactionsResponse> GetTransactionsAsync(DerivationStrategyBase strategy, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
{
return GetTransactionsAsync(TrackedSource.Create(strategy), from, to, cancellation);
}
public Task<GetTransactionsResponse> GetTransactionsAsync(TrackedSource trackedSource, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
{
string fromV = string.Empty;
string toV = string.Empty;
if (from is DateTimeOffset f)
{
fromV = NBitcoin.Utils.DateTimeToUnixTime(f).ToString();
}
if (to is DateTimeOffset t)
{
toV = NBitcoin.Utils.DateTimeToUnixTime(t).ToString();
}
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/transactions?from={fromV}&to={toV}", cancellation);
}
public TransactionInformation GetTransaction(TrackedSource trackedSource, uint256 txId, CancellationToken cancellation = default)
{
return this.GetTransactionAsync(trackedSource, txId, cancellation).GetAwaiter().GetResult();
}
public TransactionInformation GetTransaction(DerivationStrategyBase derivationStrategyBase, uint256 txId, CancellationToken cancellation = default)
{
return this.GetTransactionAsync(derivationStrategyBase, txId, cancellation).GetAwaiter().GetResult();
}
public Task<TransactionInformation> GetTransactionAsync(DerivationStrategyBase derivationStrategyBase, uint256 txId, CancellationToken cancellation = default)
{
if (derivationStrategyBase == null)
throw new ArgumentNullException(nameof(derivationStrategyBase));
return GetTransactionAsync(new DerivationSchemeTrackedSource(derivationStrategyBase), txId, cancellation);
}
public Task<TransactionInformation> GetTransactionAsync(TrackedSource trackedSource, uint256 txId, CancellationToken cancellation = default)
{
if (txId == null)
throw new ArgumentNullException(nameof(txId));
if (trackedSource == null)
throw new ArgumentNullException(nameof(trackedSource));
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/transactions/{txId}", cancellation);
}
public Task RescanAsync(RescanRequest rescanRequest, CancellationToken cancellation = default)
{
if (rescanRequest == null)
throw new ArgumentNullException(nameof(rescanRequest));
return SendAsync<byte[]>(HttpMethod.Post, rescanRequest, $"v1/cryptos/{CryptoCode}/rescan", cancellation);
}
public void Rescan(RescanRequest rescanRequest, CancellationToken cancellation = default)
{
RescanAsync(rescanRequest, cancellation).GetAwaiter().GetResult();
}
public KeyPathInformation GetUnused(DerivationStrategyBase strategy, DerivationFeature feature, int skip = 0, bool reserve = false, CancellationToken cancellation = default)
public KeyPathInformation GetUnused(DerivationStrategyBase strategy, DerivationFeature feature, int skip = 0, bool reserve = false, CancellationToken cancellation = default(CancellationToken))
{
return GetUnusedAsync(strategy, feature, skip, reserve, cancellation).GetAwaiter().GetResult();
}
public async Task<KeyPathInformation> GetUnusedAsync(DerivationStrategyBase strategy, DerivationFeature feature, int skip = 0, bool reserve = false, CancellationToken cancellation = default)
public async Task<KeyPathInformation> GetUnusedAsync(DerivationStrategyBase strategy, DerivationFeature feature, int skip = 0, bool reserve = false, CancellationToken cancellation = default(CancellationToken))
{
try
{
return await GetAsync<KeyPathInformation>($"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/unused?feature={feature}&skip={skip}&reserve={reserve}", cancellation).ConfigureAwait(false);
return await GetAsync<KeyPathInformation>($"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/unused?feature={feature}&skip={skip}&reserve={reserve}", null, cancellation).ConfigureAwait(false);
}
catch (NBXplorerException ex) when (ex.Error?.HttpCode == 404)
catch(NBXplorerException ex) when(ex.Error?.HttpCode == 404)
{
return null;
}
}
public KeyPathInformation GetKeyInformation(DerivationStrategyBase strategy, Script script, CancellationToken cancellation = default)
public async Task<KeyPathInformation[]> GetKeyInformationsAsync(Script script, CancellationToken cancellation = default(CancellationToken))
{
return GetKeyInformationAsync(strategy, script, cancellation).GetAwaiter().GetResult();
return await SendAsync<KeyPathInformation[]>(HttpMethod.Get, null, "v1/cryptos/{0}/scripts/" + script.ToHex(), new[] { CryptoCode }, cancellation).ConfigureAwait(false);
}
public async Task<KeyPathInformation> GetKeyInformationAsync(DerivationStrategyBase strategy, Script script, CancellationToken cancellation = default)
{
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);
}
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);
}
public KeyPathInformation[] GetKeyInformations(Script script, CancellationToken cancellation = default)
public KeyPathInformation[] GetKeyInformations(Script script, CancellationToken cancellation = default(CancellationToken))
{
return GetKeyInformationsAsync(script, cancellation).GetAwaiter().GetResult();
}
public GetFeeRateResult GetFeeRate(int blockCount, CancellationToken cancellation = default)
public GetFeeRateResult GetFeeRate(int blockCount, CancellationToken cancellation = default(CancellationToken))
{
return GetFeeRateAsync(blockCount, cancellation).GetAwaiter().GetResult();
}
public GetFeeRateResult GetFeeRate(int blockCount, FeeRate fallbackFeeRate, CancellationToken cancellation = default)
public GetFeeRateResult GetFeeRate(int blockCount, FeeRate fallbackFeeRate, CancellationToken cancellation = default(CancellationToken))
{
return GetFeeRateAsync(blockCount, fallbackFeeRate, cancellation).GetAwaiter().GetResult();
}
public async Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, FeeRate fallbackFeeRate, CancellationToken cancellation = default)
public async Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, FeeRate fallbackFeeRate, CancellationToken cancellation = default(CancellationToken))
{
try
{
return await GetAsync<GetFeeRateResult>($"v1/cryptos/{CryptoCode}/fees/{blockCount}", cancellation).ConfigureAwait(false);
return await GetAsync<GetFeeRateResult>("v1/cryptos/{0}/fees/{1}", new object[] { CryptoCode, blockCount }, cancellation).ConfigureAwait(false);
}
catch (NBXplorerException ex) when (fallbackFeeRate != null && ex.Error.Code == "fee-estimation-unavailable")
catch(NBXplorerException ex) when (fallbackFeeRate != null && ex.Error.Code == "fee-estimation-unavailable")
{
return new GetFeeRateResult() { BlockCount = blockCount, FeeRate = fallbackFeeRate };
}
}
public Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, CancellationToken cancellation = default)
public Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, CancellationToken cancellation = default(CancellationToken))
{
return GetAsync<GetFeeRateResult>($"v1/cryptos/{CryptoCode}/fees/{blockCount}", cancellation);
}
public CreatePSBTResponse CreatePSBT(DerivationStrategyBase derivationStrategy, CreatePSBTRequest request, CancellationToken cancellation = default)
{
return CreatePSBTAsync(derivationStrategy, request, cancellation).GetAwaiter().GetResult();
}
public Task<CreatePSBTResponse> CreatePSBTAsync(DerivationStrategyBase derivationStrategy, CreatePSBTRequest request, CancellationToken cancellation = default)
{
if (derivationStrategy == null)
throw new ArgumentNullException(nameof(derivationStrategy));
if (request == null)
throw new ArgumentNullException(nameof(request));
return this.SendAsync<CreatePSBTResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/derivations/{derivationStrategy}/psbt/create", cancellation);
return GetAsync<GetFeeRateResult>("v1/cryptos/{0}/fees/{1}", new object[] { CryptoCode, blockCount }, cancellation);
}
public UpdatePSBTResponse UpdatePSBT(UpdatePSBTRequest request, CancellationToken cancellation = default)
public BroadcastResult Broadcast(Transaction tx, CancellationToken cancellation = default(CancellationToken))
{
return UpdatePSBTAsync(request, cancellation).GetAwaiter().GetResult();
}
public Task<UpdatePSBTResponse> UpdatePSBTAsync(UpdatePSBTRequest request, CancellationToken cancellation = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
return this.SendAsync<UpdatePSBTResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/psbt/update", cancellation);
}
public BroadcastResult Broadcast(Transaction tx, CancellationToken cancellation = default)
{
return Broadcast(tx, false, cancellation);
}
public BroadcastResult Broadcast(Transaction tx, bool testMempoolAccept, CancellationToken cancellation = default)
{
return BroadcastAsync(tx, testMempoolAccept, cancellation).GetAwaiter().GetResult();
return BroadcastAsync(tx, cancellation).GetAwaiter().GetResult();
}
public Task<BroadcastResult> BroadcastAsync(Transaction tx, CancellationToken cancellation = default)
public Task<BroadcastResult> BroadcastAsync(Transaction tx, CancellationToken cancellation = default(CancellationToken))
{
return BroadcastAsync(tx, false, cancellation);
}
public Task<BroadcastResult> BroadcastAsync(Transaction tx, bool testMempoolAccept, CancellationToken cancellation = default)
{
return SendAsync<BroadcastResult>(HttpMethod.Post, tx.ToBytes(), $"v1/cryptos/{CryptoCode}/transactions?testMempoolAccept={testMempoolAccept}", cancellation);
}
public TMetadata GetMetadata<TMetadata>(DerivationStrategyBase derivationScheme, string key, CancellationToken cancellationToken = default)
{
return GetMetadataAsync<TMetadata>(derivationScheme, key, cancellationToken).GetAwaiter().GetResult();
}
public Task<TMetadata> GetMetadataAsync<TMetadata>(DerivationStrategyBase derivationScheme, string key, CancellationToken cancellationToken = default)
{
if (derivationScheme == null)
throw new ArgumentNullException(nameof(derivationScheme));
if (key == null)
throw new ArgumentNullException(nameof(key));
return GetAsync<TMetadata>($"v1/cryptos/{CryptoCode}/derivations/{derivationScheme}/metadata/{key}", cancellationToken);
}
public void SetMetadata<TMetadata>(DerivationStrategyBase derivationScheme, string key, TMetadata value, CancellationToken cancellationToken = default)
{
SetMetadataAsync<TMetadata>(derivationScheme, key, value, cancellationToken).GetAwaiter().GetResult();
}
public Task SetMetadataAsync<TMetadata>(DerivationStrategyBase derivationScheme, string key, TMetadata value, CancellationToken cancellationToken = default)
{
return SendAsync<string>(HttpMethod.Post, value, $"v1/cryptos/{CryptoCode}/derivations/{derivationScheme}/metadata/{key}", cancellationToken);
}
public Task<GenerateWalletResponse> GenerateWalletAsync(GenerateWalletRequest request = null, CancellationToken cancellationToken = default)
{
request ??= new GenerateWalletRequest();
return SendAsync<GenerateWalletResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/derivations", cancellationToken);
}
public GenerateWalletResponse GenerateWallet(GenerateWalletRequest request = null, CancellationToken cancellationToken = default)
{
request ??= new GenerateWalletRequest();
return GenerateWalletAsync(request, cancellationToken).GetAwaiter().GetResult();
}
public Task<GroupInformation> CreateGroupAsync(CancellationToken cancellationToken = default)
{
return SendAsync<GroupInformation>(HttpMethod.Post, null, $"v1/groups", cancellationToken);
}
public Task<GroupInformation> GetGroupAsync(string groupId, CancellationToken cancellationToken = default)
{
return SendAsync<GroupInformation>(HttpMethod.Get, null, $"v1/groups/{groupId}", cancellationToken);
}
public Task<GroupInformation> AddGroupChildrenAsync(string groupId, GroupChild[] children, CancellationToken cancellationToken = default)
{
return SendAsync<GroupInformation>(HttpMethod.Post, children, $"v1/groups/{groupId}/children", cancellationToken);
}
public Task<GroupInformation> RemoveGroupChildrenAsync(string groupId, GroupChild[] children, CancellationToken cancellationToken = default)
{
return SendAsync<GroupInformation>(HttpMethod.Delete, children, $"v1/groups/{groupId}/children", cancellationToken);
}
public Task AddGroupAddressAsync(string cryptoCode, string groupId, string[] addresses, CancellationToken cancellationToken = default)
{
return SendAsync<GroupInformation>(HttpMethod.Post, addresses, $"v1/cryptos/{cryptoCode}/groups/{groupId}/addresses", cancellationToken);
}
public async Task ImportUTXOs(string cryptoCode, ImportUTXORequest request, CancellationToken cancellation = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (cryptoCode == null)
throw new ArgumentNullException(nameof(cryptoCode));
await SendAsync(HttpMethod.Post, request, $"v1/cryptos/{cryptoCode}/rescan-utxos", cancellation);
return SendAsync<BroadcastResult>(HttpMethod.Post, tx.ToBytes(), "v1/cryptos/{0}/transactions", new[] { CryptoCode }, cancellation);
}
private static readonly HttpClient SharedClient = new HttpClient();
@ -645,126 +342,80 @@ namespace NBXplorer
{
get; set;
} = true;
public Serializer Serializer { get; private set; }
internal IAuth Auth
{
get => _Auth;
set
{
_Auth = value;
ConstructRPCClient();
}
}
static FormattableString EncodeUrlParameters(FormattableString url)
{
return FormattableStringFactory.Create(
url.Format,
url.GetArguments()
.Select(a =>
a is RawStr ? a :
a is FormattableString o ? EncodeUrlParameters(o) :
Uri.EscapeDataString(a?.ToString() ?? ""))
.ToArray());
}
internal string GetFullUri(FormattableString relativePath)
internal string GetFullUri(string relativePath, params object[] parameters)
{
relativePath = String.Format(relativePath, parameters ?? new object[0]);
var uri = Address.AbsoluteUri;
if (!uri.EndsWith("/", StringComparison.Ordinal))
if(!uri.EndsWith("/", StringComparison.Ordinal))
uri += "/";
uri += EncodeUrlParameters(relativePath).ToString();
if (!IncludeTransaction)
uri += relativePath;
if(!IncludeTransaction)
{
if (uri.IndexOf('?') == -1)
if(uri.IndexOf('?') == -1)
uri += $"?includeTransaction=false";
else
uri += $"&includeTransaction=false";
}
return uri;
}
private Task<T> GetAsync<T>(FormattableString relativePath, CancellationToken cancellation)
private Task<T> GetAsync<T>(string relativePath, object[] parameters, CancellationToken cancellation)
{
return SendAsync<T>(HttpMethod.Get, null, relativePath, cancellation);
return SendAsync<T>(HttpMethod.Get, null, relativePath, parameters, cancellation);
}
internal async Task<T> SendAsync<T>(HttpMethod method, object body, FormattableString relativePath, CancellationToken cancellation)
private async Task<T> SendAsync<T>(HttpMethod method, object body, string relativePath, object[] parameters, CancellationToken cancellation)
{
HttpRequestMessage message = CreateMessage(method, body, relativePath);
HttpRequestMessage message = CreateMessage(method, body, relativePath, parameters);
var result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
if ((int)result.StatusCode == 404)
if((int)result.StatusCode == 404)
{
return default(T);
}
if (result.StatusCode == HttpStatusCode.GatewayTimeout || result.StatusCode == HttpStatusCode.RequestTimeout)
if((int)result.StatusCode == 401)
{
throw new HttpRequestException($"HTTP error {(int)result.StatusCode}", new TimeoutException());
}
if ((int)result.StatusCode == 401)
{
if (Auth.RefreshCache())
if(_Auth.RefreshCache())
{
message = CreateMessage(method, body, relativePath);
result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
message = CreateMessage(method, body, relativePath, parameters);
result = await Client.SendAsync(message).ConfigureAwait(false);
}
}
return await ParseResponse<T>(result).ConfigureAwait(false);
}
internal async Task<HttpResponseMessage> 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);
}
}
return result;
}
internal HttpRequestMessage CreateMessage(HttpMethod method, object body, FormattableString relativePath)
internal HttpRequestMessage CreateMessage(HttpMethod method, object body, string relativePath, object[] parameters)
{
var uri = GetFullUri(relativePath);
var uri = GetFullUri(relativePath, parameters);
var message = new HttpRequestMessage(method, uri);
Auth.SetAuthorization(message);
if (body != null)
_Auth.SetAuthorization(message);
if(body != null)
{
if (body is byte[])
{
var content = new ByteArrayContent((byte[])body);
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
message.Content = content;
}
if(body is byte[])
message.Content = new ByteArrayContent((byte[])body);
else
message.Content = new StringContent(Serializer.ToString(body), Encoding.UTF8, "application/json");
message.Content = new StringContent(_Serializer.ToString(body), Encoding.UTF8, "application/json");
}
return message;
}
private async Task<T> ParseResponse<T>(HttpResponseMessage response)
{
using (response)
using(response)
{
if (response.IsSuccessStatusCode)
if (response.Content.Headers.ContentLength == 0)
if(response.IsSuccessStatusCode)
if(response.Content.Headers.ContentLength == 0)
return default(T);
else if (response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.Ordinal))
return Serializer.ToObject<T>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
else if (response.Content.Headers.ContentType.MediaType.Equals("application/octet-stream", StringComparison.Ordinal))
else if(response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.Ordinal))
return _Serializer.ToObject<T>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
else if(response.Content.Headers.ContentType.MediaType.Equals("application/octet-stream", StringComparison.Ordinal))
{
return (T)(object)await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
}
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
if(response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
response.EnsureSuccessStatusCode();
var error = Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
if (error == null)
var error = _Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
if(error == null)
response.EnsureSuccessStatusCode();
throw error.AsException();
}
@ -772,30 +423,17 @@ namespace NBXplorer
private async Task ParseResponse(HttpResponseMessage response)
{
using (response)
using(response)
{
if (response.IsSuccessStatusCode)
if(response.IsSuccessStatusCode)
return;
if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
if(response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
response.EnsureSuccessStatusCode();
var error = Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
if (error == null)
var error = _Serializer.ToObject<NBXplorerError>(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
if(error == null)
response.EnsureSuccessStatusCode();
throw error.AsException();
}
}
private FormattableString GetBasePath(TrackedSource trackedSource)
{
if (trackedSource is null)
throw new ArgumentNullException(nameof(trackedSource));
return trackedSource switch
{
DerivationSchemeTrackedSource dsts => $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}",
AddressTrackedSource asts => $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}",
GroupTrackedSource wts => $"v1/cryptos/{CryptoCode}/groups/{wts.GroupId}",
_ => $"v1/cryptos/{CryptoCode}/tracked-sources/{trackedSource}"
};
}
}
}

View File

@ -1,8 +1,10 @@
using NBitcoin;
using NBitcoin.RPC;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -10,34 +12,84 @@ namespace NBXplorer
{
public static class ExtensionsClient
{
public static IEnumerable<IList<T>> Batch<T>(this IEnumerable<T> values, int size)
static ExtensionsClient()
{
if (size <= 0)
throw new ArgumentOutOfRangeException(nameof(size));
if (values == null)
throw new ArgumentNullException(nameof(values));
if (values is IList<T> l && l.Count <= size)
{
yield return l;
yield break;
}
var batch = new List<T>();
_TypeByName = new Dictionary<string, Type>();
_NameByType = new Dictionary<Type, string>();
Add("newblock", typeof(Models.NewBlockEvent));
Add("subscribeblock", typeof(Models.NewBlockEventRequest));
Add("subscribetransaction", typeof(Models.NewTransactionEventRequest));
Add("newtransaction", typeof(Models.NewTransactionEvent));
}
static Dictionary<string, Type> _TypeByName;
static Dictionary<Type, string> _NameByType;
private static void Add(string typeName, Type type)
{
_TypeByName.Add(typeName, type);
_NameByType.Add(type, typeName);
}
public static IEnumerable<T[]> Batch<T>(this IEnumerable<T> values, int size)
{
var batch = new T[size];
int index = 0;
foreach(var v in values)
{
batch.Add(v);
if(size == batch.Count)
batch[index++] = v;
if(index == batch.Length)
{
yield return batch;
batch = new List<T>();
batch = new T[size];
index = 0;
}
}
if(batch.Count != 0)
if(index != 0)
{
Array.Resize(ref batch, index);
yield return batch;
}
}
public static async Task CloseSocket(this WebSocket socket, WebSocketCloseStatus status, string statusDescription, CancellationToken cancellation = default)
public static ArraySegment<T> Slice<T>(this ArraySegment<T> array, int index)
{
if((uint)index > (uint)array.Count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return new ArraySegment<T>(array.Array, array.Offset + index, array.Count - index);
}
public static ArraySegment<T> Slice<T>(this ArraySegment<T> array, int index, int count)
{
if((uint)index > (uint)array.Count || (uint)count > (uint)(array.Count - index))
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return new ArraySegment<T>(array.Array, array.Offset + index, count);
}
public static object ParseNotificationMessage(string str, JsonSerializerSettings settings)
{
if(str == null)
throw new ArgumentNullException(nameof(str));
JObject jobj = JObject.Parse(str);
var type = (jobj["type"] as JValue)?.Value<string>();
if(type == null)
throw new FormatException("'type' property not found");
if(!_TypeByName.TryGetValue(type, out Type typeObject))
throw new FormatException("unknown 'type'");
var data = (jobj["data"] as JObject);
if(data == null)
throw new FormatException("'data' property not found");
return JsonConvert.DeserializeObject(data.ToString(), typeObject, settings);
}
public static async Task CloseSocket(this WebSocket socket, WebSocketCloseStatus status, string statusDescription, CancellationToken cancellation = default(CancellationToken))
{
try
{
@ -61,18 +113,10 @@ namespace NBXplorer
finally { socket.Dispose(); }
}
public static async Task<uint256[]> EnsureGenerateAsync(this RPCClient client, int blockCount)
public static string GetNotificationMessageTypeName(Type type)
{
uint256[] blockIds = new uint256[blockCount];
int generated = 0;
while(generated < blockCount)
{
foreach(var id in await client.GenerateAsync(blockCount - generated).ConfigureAwait(false))
{
blockIds[generated++] = id;
}
}
return blockIds;
_NameByType.TryGetValue(type, out string name);
return name;
}
}
}

View File

@ -0,0 +1,36 @@
using NBitcoin;
using NBXplorer.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace NBXplorer.JsonConverters
{
public class BookmarkJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return
typeof(Bookmark).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.Null)
return null;
return new Bookmark(new uint160(reader.Value.ToString()));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var b = value as Bookmark;
if(b != null)
{
writer.WriteValue(b.ToString());
}
}
}
}

View File

@ -1,88 +0,0 @@
using System.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
namespace NBXplorer.JsonConverters
{
/// <summary>
/// Cache serialization of costly base58 structures
/// </summary>
public class CachedSerializer : JsonConverter
{
class CachedConverter
{
public CachedConverter(JsonConverter converter)
{
Converter = converter;
}
public JsonConverter Converter { get; }
ConcurrentDictionary<string, object> cachedStrings = new ConcurrentDictionary<string, object>();
int total = 0;
public object ReadJson(string str, JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (cachedStrings.TryGetValue(str, out var v))
return v;
v = Converter.ReadJson(reader, objectType, existingValue, serializer);
if (cachedStrings.TryAdd(str, v))
{
Interlocked.Increment(ref total);
if (total > 20)
{
cachedStrings.Clear();
total = 0;
}
}
return v;
}
public void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Converter.WriteJson(writer, value, serializer);
}
internal bool CanConvert(Type objectType)
{
return Converter.CanConvert(objectType);
}
}
public CachedSerializer(NBXplorerNetwork network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
cachedConverter.Add(new CachedConverter(new DerivationStrategyJsonConverter(network.DerivationStrategyFactory)));
cachedConverter.Add(new CachedConverter(new TrackedSourceJsonConverter(network)));
}
public override bool CanConvert(Type objectType)
{
return GetConverter(objectType) != null;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var str = reader.Value.ToString();
return GetConverter(objectType).ReadJson(str, reader, objectType, existingValue, serializer);
}
private CachedConverter GetConverter(Type objectType)
{
return cachedConverter.FirstOrDefault(s => s.CanConvert(objectType));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
return;
var converter = GetConverter(value.GetType());
converter.WriteJson(writer, value, serializer);
}
List<CachedConverter> cachedConverter = new List<CachedConverter>();
}
}

View File

@ -1,7 +1,10 @@
using System.Reflection;
using NBitcoin;
using System.Reflection;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin.JsonConverters;
namespace NBXplorer.JsonConverters

View File

@ -0,0 +1,40 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace NBXplorer.JsonConverters
{
public class FeeRateJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return
typeof(FeeRate).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if(reader.TokenType == JsonToken.Null)
return null;
if(reader.TokenType != JsonToken.Integer)
return null;
var value = (long)reader.Value;
return new FeeRate(Money.Satoshis(value), 1);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var feeRate = value as FeeRate;
if(feeRate != null)
{
writer.WriteValue(feeRate.GetFee(1).Satoshi);
}
}
}
}

View File

@ -1,25 +0,0 @@
using Newtonsoft.Json;
using System;
namespace NBXplorer.JsonConverters
{
public class KeyPathTemplateJsonConverter : JsonConverter<KeyPathTemplate>
{
public override KeyPathTemplate ReadJson(JsonReader reader, Type objectType, KeyPathTemplate existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new NBitcoin.JsonConverters.JsonObjectException($"Unexpected json token type, expected String, actual {reader.TokenType}", reader);
if (!KeyPathTemplate.TryParse((string)reader.Value, out var template))
throw new NBitcoin.JsonConverters.JsonObjectException($"Invalid KeyPathTemplate", reader);
return template;
}
public override void WriteJson(JsonWriter writer, KeyPathTemplate value, JsonSerializer serializer)
{
if (value is KeyPathTemplate kt)
writer.WriteValue(value.ToString());
}
}
}

View File

@ -1,76 +0,0 @@
using Newtonsoft.Json;
using System.Linq;
using System.Reflection;
using System;
using NBitcoin;
using NBitcoin.JsonConverters;
namespace NBXplorer.JsonConverters
{
public class MoneyJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(IMoney).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
class AssetCoinJson
{
public uint256 AssetId { get; set; }
public long? Value { get; set; }
public AssetMoney ToAssetMoney(string path)
{
if (AssetId == null)
throw new JsonObjectException("'assetId' is missing", path);
if (Value is null)
throw new JsonObjectException("'value' is missing", path);
return new AssetMoney(AssetId, Value.Value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
if (reader.TokenType == JsonToken.Null)
return null;
AssertJsonType(reader, new[] { JsonToken.Integer, JsonToken.StartObject, JsonToken.StartArray });
if (reader.TokenType == JsonToken.Integer)
{
return new Money((long)reader.Value);
}
else if (reader.TokenType == JsonToken.StartObject)
{
return serializer.Deserialize<AssetCoinJson>(reader).ToAssetMoney(reader.Path);
}
else
{
return new MoneyBag(serializer.Deserialize<AssetCoinJson[]>(reader).Select(c => c.ToAssetMoney(reader.Path)).ToArray());
}
}
catch (InvalidCastException)
{
throw new JsonObjectException("Money amount should be in satoshi", reader);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Money v)
writer.WriteValue(v.Satoshi);
else if (value is AssetMoney av)
{
serializer.Serialize(writer, new AssetCoinJson() { Value = av.Quantity, AssetId = av.AssetId });
}
else if (value is MoneyBag mb)
{
serializer.Serialize(writer, mb.OfType<AssetMoney>().Select(av2 => new AssetCoinJson() { Value = av2.Quantity, AssetId = av2.AssetId }).ToArray());
}
}
static void AssertJsonType(JsonReader reader, JsonToken[] anyExpectedTypes)
{
if (!anyExpectedTypes.Contains(reader.TokenType))
throw new JsonObjectException($"Unexpected json token type, expected are {string.Join(", ", anyExpectedTypes)} and actual is {reader.TokenType}", reader);
}
}
}

View File

@ -1,54 +0,0 @@
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.JsonConverters;
using NBXplorer.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace NBXplorer.JsonConverters
{
public class PSBTDestinationJsonConverter : JsonConverter
{
public PSBTDestinationJsonConverter(Network network)
{
Network = network;
}
public Network Network { get; }
public override bool CanConvert(Type objectType)
{
return typeof(PSBTDestination).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
{
throw new JsonObjectException($"Unexpected json token type, expected is {JsonToken.String} and actual is {reader.TokenType}", reader);
}
var str = reader.Value.ToString();
try
{
return PSBTDestination.Parse(str, Network);
}
catch (FormatException ex)
{
throw new JsonObjectException(ex.Message, reader);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is not null)
{
writer.WriteValue(value.ToString());
}
}
}
}

View File

@ -1,50 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
namespace NBXplorer.JsonConverters
{
public class ScriptPubKeyTypeConverter : JsonConverter
{
static ScriptPubKeyTypeConverter()
{
_ScriptPubKeyType = new Dictionary<string, ScriptPubKeyType>()
{
{ "Legacy", ScriptPubKeyType.Legacy },
{ "Segwit", ScriptPubKeyType.Segwit },
{ "SegwitP2SH", ScriptPubKeyType.SegwitP2SH },
#pragma warning disable CS0618 // Type or member is obsolete
{ "Taproot", ScriptPubKeyType.TaprootBIP86 }
#pragma warning restore CS0618 // Type or member is obsolete
};
_ScriptPubKeyTypeReverse = _ScriptPubKeyType.ToDictionary(kv => kv.Value, kv => kv.Key);
}
public override bool CanConvert(Type objectType)
{
return typeof(NBitcoin.WordCount).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new NBitcoin.JsonConverters.JsonObjectException($"Unexpected json token type, expected String, actual {reader.TokenType}", reader);
if (!_ScriptPubKeyType.TryGetValue((string)reader.Value, out var result))
throw new NBitcoin.JsonConverters.JsonObjectException($"Invalid ScriptPubKeyType, possible values {string.Join(", ", _ScriptPubKeyType.Keys.ToArray())} (defaut: 12)", reader);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is ScriptPubKeyType t)
writer.WriteValue(_ScriptPubKeyTypeReverse[t]);
}
readonly static Dictionary<string, ScriptPubKeyType> _ScriptPubKeyType;
readonly static Dictionary<ScriptPubKeyType, string> _ScriptPubKeyTypeReverse;
}
}

View File

@ -1,46 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Reflection;
using NBXplorer.Models;
using NBitcoin.JsonConverters;
namespace NBXplorer.JsonConverters
{
public class TrackedSourceJsonConverter : JsonConverter
{
public TrackedSourceJsonConverter(NBXplorerNetwork network)
{
Network = network;
}
public NBXplorerNetwork Network { get; }
public override bool CanConvert(Type objectType)
{
return
typeof(TrackedSource).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
return null;
if (TrackedSource.TryParse(reader.Value.ToString(), out var v, Network))
return v;
throw new JsonObjectException("Invalid TrackedSource", reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var trackedSource = value as TrackedSource;
if (trackedSource != null)
{
writer.WriteValue(trackedSource.ToString());
}
}
}
}

View File

@ -1,57 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
namespace NBXplorer.JsonConverters
{
public class WordcountJsonConverter : JsonConverter
{
static WordcountJsonConverter()
{
_Wordcount = new Dictionary<long, WordCount>()
{
{ 18, WordCount.Eighteen },
{ 15, WordCount.Fifteen },
{ 12, WordCount.Twelve },
{ 24, WordCount.TwentyFour },
{ 21, WordCount.TwentyOne }
};
_WordcountReverse = _Wordcount.ToDictionary(kv => kv.Value, kv => kv.Key);
}
public override bool CanConvert(Type objectType)
{
return typeof(NBitcoin.WordCount).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()) ||
typeof(NBitcoin.WordCount?).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return default;
if (reader.TokenType != JsonToken.Integer)
throw new NBitcoin.JsonConverters.JsonObjectException($"Unexpected json token type, expected Integer, actual {reader.TokenType}", reader);
if (!_Wordcount.TryGetValue((long)reader.Value, out var result))
throw new NBitcoin.JsonConverters.JsonObjectException($"Invalid WordCount, possible values {string.Join(", ", _Wordcount.Keys.ToArray())} (defaut: 12)", reader);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is WordCount wc)
writer.WriteValue(_WordcountReverse[wc]);
}
readonly static Dictionary<long, WordCount> _Wordcount = new Dictionary<long, WordCount>()
{
{ 18, WordCount.Eighteen },
{ 15, WordCount.Fifteen },
{ 12, WordCount.Twelve },
{ 24, WordCount.TwentyFour },
{ 21, WordCount.TwentyOne }
};
readonly static Dictionary<WordCount, long> _WordcountReverse;
}
}

View File

@ -1,49 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
namespace NBXplorer.JsonConverters
{
public class WordlistJsonConverter : JsonConverter
{
static WordlistJsonConverter()
{
_Wordlists = new Dictionary<string, Wordlist>(StringComparer.OrdinalIgnoreCase)
{
{ "English", Wordlist.English },
{ "French", Wordlist.French },
{ "Japanese", Wordlist.Japanese },
{ "Spanish", Wordlist.Spanish },
{ "ChineseSimplified", Wordlist.ChineseSimplified }
};
_WordlistsReverse = _Wordlists.ToDictionary(kv => kv.Value, kv => kv.Key);
}
public override bool CanConvert(Type objectType)
{
return typeof(Wordlist).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new NBitcoin.JsonConverters.JsonObjectException($"Unexpected json token type, expected String, actual {reader.TokenType}", reader);
if (!_Wordlists.TryGetValue((string)reader.Value, out var result))
throw new NBitcoin.JsonConverters.JsonObjectException($"Invalid wordlist, possible values {string.Join(", ", _Wordlists.Keys.ToArray())} (defaut: English)", reader);
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Wordlist wl)
writer.WriteValue(_WordlistsReverse[wl]);
}
readonly static Dictionary<string, Wordlist> _Wordlists;
readonly static Dictionary<Wordlist, string> _WordlistsReverse;
}
}

View File

@ -1,105 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using NBXplorer.Models;
namespace NBXplorer
{
public class LongPollingNotificationSession : NotificationSessionBase
{
public LongPollingNotificationSession(long lastEventId, ExplorerClient client)
{
LastEventId = lastEventId;
Client = client;
}
public long LastEventId { get; private set; }
public ExplorerClient Client { get; }
Queue<NewEventBase> _EventsToProcess = new Queue<NewEventBase>();
public override async Task<NewEventBase> NextEventAsync(CancellationToken cancellation = default)
{
retry:
long evtId = 0;
lock (_EventsToProcess)
{
evtId = LastEventId;
if (_EventsToProcess.Count > 0)
{
var evt = _EventsToProcess.Dequeue();
LastEventId = evt.EventId;
return evt;
}
}
NewEventBase[] evts = null;
try
{
evts = await GetEventsAsync(evtId, 30, true, cancellation);
}
catch(HttpRequestException ex) when (ex.InnerException is TimeoutException)
{
goto retry;
}
catch(OperationCanceledException) when (!cancellation.IsCancellationRequested)
{
goto retry;
}
lock (_EventsToProcess)
{
if (_EventsToProcess.Count != 0)
goto retry;
foreach (var item in evts)
{
_EventsToProcess.Enqueue(item);
}
}
goto retry;
}
public NewEventBase[] GetEvents(long lastEventId = 0, int? limit = null, bool longPolling = false, CancellationToken cancellation = default)
{
return GetEventsAsync(lastEventId, limit, longPolling, cancellation).GetAwaiter().GetResult();
}
public async Task<NewEventBase[]> GetEventsAsync(long lastEventId = 0, int? limit = null, bool longPolling = false, CancellationToken cancellation = default)
{
List<string> parameters = new List<string>();
if (lastEventId != 0)
parameters.Add($"lastEventId={lastEventId}");
if (limit != null)
parameters.Add($"limit={limit.Value}");
if (longPolling)
parameters.Add($"longPolling={longPolling}");
var parametersString = parameters.Count == 0 ? string.Empty : $"?{String.Join("&", parameters.ToArray<object>())}";
var evts = await Client.SendAsync<JArray>(HttpMethod.Get, null, $"v1/cryptos/{Client.CryptoCode}/events{ExplorerClient.Raw(parametersString)}", cancellation);
var evtsObj = evts.Select(ev => NewEventBase.ParseEvent((JObject)ev, Client.Serializer.Settings))
.OfType<NewEventBase>()
.ToArray();
return evtsObj;
}
public NewEventBase[] GetLatestEvents(int limit = 10, CancellationToken cancellation = default)
{
return GetLatestEventsAsync(limit, cancellation).GetAwaiter().GetResult();
}
public async Task<NewEventBase[]> GetLatestEventsAsync(int limit = 10, CancellationToken cancellation = default)
{
List<string> parameters = new List<string>();
if (limit != 10)
parameters.Add($"limit={limit}");
var parametersString = parameters.Count == 0 ? string.Empty : $"?{String.Join("&", parameters.ToArray<object>())}";
var evts = await Client.SendAsync<JArray>(HttpMethod.Get, null, $"v1/cryptos/{Client.CryptoCode}/events/latest{ExplorerClient.Raw(parametersString)}", cancellation);
var evtsObj = evts.Select(ev => NewEventBase.ParseEvent((JObject)ev, Client.Serializer.Settings))
.OfType<NewEventBase>()
.ToArray();
return evtsObj;
}
}
}

View File

@ -0,0 +1,60 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class Bookmark
{
public Bookmark(uint160 value)
{
if(value == null)
throw new ArgumentNullException(nameof(value));
_Value = value;
}
private uint160 _Value;
private static readonly Bookmark _Start = new Bookmark(uint160.Zero);
public static Bookmark Start
{
get
{
return _Start;
}
}
public override bool Equals(object obj)
{
Bookmark item = obj as Bookmark;
if(item == null)
return false;
return _Value.Equals(item._Value);
}
public static bool operator ==(Bookmark a, Bookmark b)
{
if(System.Object.ReferenceEquals(a, b))
return true;
if(((object)a == null) || ((object)b == null))
return false;
return a._Value == b._Value;
}
public static bool operator !=(Bookmark a, Bookmark b)
{
return !(a == b);
}
public override int GetHashCode()
{
return _Value.GetHashCode();
}
public override string ToString()
{
return _Value.ToString();
}
}
}

View File

@ -1,4 +1,7 @@
using NBitcoin.RPC;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin.RPC;
namespace NBXplorer.Models
{

View File

@ -1,206 +0,0 @@
using NBitcoin;
using NBitcoin.DataEncoders;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public class CreatePSBTRequest
{
[JsonProperty("PSBTVersion")]
public int? PSBTVersion { get; set; }
/// <summary>
/// A seed to specific to get a deterministic PSBT (useful for tests)
/// </summary>
public int? Seed { get; set; }
/// <summary>
/// The version of the transaction (Optional, default to 1)
/// </summary>
public uint? Version { get; set; }
/// <summary>
/// The timelock of the transaction, activate RBF if not null (Optional: null, nLockTime to 0)
/// </summary>
public LockTime? LockTime { get; set; }
/// <summary>
/// Discourage fee sniping (Default: true)
/// </summary>
public bool? DiscourageFeeSniping { get; set; }
/// <summary>
/// Whether the include the global xpub in the PSBT (default: false)
/// </summary>
public bool? IncludeGlobalXPub { get; set; }
/// <summary>
/// Whether this transaction should use RBF or not.
/// </summary>
public bool? RBF { get; set; }
/// <summary>
/// Whether this transaction should merge the outputs.
/// </summary>
public bool? MergeOutputs { get; set; }
/// <summary>
/// The destinations where to send the money
/// </summary>
public List<CreatePSBTDestination> Destinations { get; set; } = new List<CreatePSBTDestination>();
/// <summary>
/// Fee settings
/// </summary>
public FeePreference FeePreference { get; set; }
/// <summary>
/// Whether the creation of this PSBT will reserve a new change address
/// </summary>
public bool ReserveChangeAddress { get; set; }
/// <summary>
/// Default to 0, the minimum confirmations a UTXO need to be selected. (by default unconfirmed and confirmed UTXO will be used)
/// </summary>
public int MinConfirmations { get; set; }
/// <summary>
/// Do not select the following outpoints for creating the PSBT (default to empty)
/// </summary>
public List<OutPoint> ExcludeOutpoints { get; set; }
/// <summary>
/// Only select the following outpoints for creating the PSBT (default to null)
/// </summary>
public List<OutPoint> IncludeOnlyOutpoints { get; set; }
/// <summary>
/// If `true`, all the UTXOs that have been selected will be used as input in the PSBT. (default to false)
/// </summary>
public bool? SpendAllMatchingOutpoints { get; set; }
/// <summary>
/// Use a specific change address (Optional, default: null, mutually exclusive with ReserveChangeAddress)
/// </summary>
public PSBTDestination ExplicitChangeAddress { get; set; }
/// <summary>
/// Rebase the hdkey paths (if no rebase, the key paths are relative to the xpub that NBXplorer knows about)
/// This transform (PubKey0, 0/0, accountFingerprint) by (PubKey0, m/49'/0'/0/0, masterFingerprint)
/// </summary>
public List<PSBTRebaseKeyRules> RebaseKeyPaths { get; set; }
/// <summary>
/// Under this value, UTXO's will be ignored.
/// </summary>
public Money MinValue { get; set; }
/// <summary>
/// Disabling the randomization of unspecified parameters to match the network's fingerprint distribution
/// </summary>
public bool? DisableFingerprintRandomization { get; set; }
/// <summary>
/// Attempt setting non_witness_utxo for all inputs even if they are segwit.
/// </summary>
public bool AlwaysIncludeNonWitnessUTXO { get; set; }
}
public class PSBTRebaseKeyRules
{
/// <summary>
/// The account key to rebase
/// </summary>
public BitcoinExtPubKey AccountKey { get; set; }
/// <summary>
/// The path from the root to the account key
/// </summary>
public RootedKeyPath AccountKeyPath { get; set; }
}
public class ScriptDestination : IDestination
{
public ScriptDestination(Script scriptPubKey)
{
ScriptPubKey = scriptPubKey;
}
public Script ScriptPubKey { get; }
}
public abstract class PSBTDestination
{
public static implicit operator PSBTDestination(Script script) => new ScriptType(script);
public static implicit operator PSBTDestination(BitcoinAddress address) => new AddressType(address);
public class ScriptType : PSBTDestination
{
public ScriptType(Script scriptPubKey)
{
if (scriptPubKey is null)
throw new ArgumentNullException(nameof(scriptPubKey));
ScriptPubKey = scriptPubKey;
}
public override Script ScriptPubKey { get; }
public override string ToString() => ScriptPubKey.ToHex();
}
public class AddressType : PSBTDestination
{
public AddressType(BitcoinAddress address)
{
if (address is null)
throw new ArgumentNullException(nameof(address));
Address = address;
}
public BitcoinAddress Address { get; }
public override Script ScriptPubKey => Address.ScriptPubKey;
public override string ToString() => Address.ToString();
}
public abstract Script ScriptPubKey { get; }
public static PSBTDestination Create(Script script) => new ScriptType(script);
public static PSBTDestination Create(BitcoinAddress address) => new AddressType(address);
public static PSBTDestination Parse(string str, Network network)
{
if (str is null)
throw new ArgumentNullException(nameof(str));
if (network is null)
throw new ArgumentNullException(nameof(network));
if (HexEncoder.IsWellFormed(str))
return new ScriptType(Script.FromHex(str));
else
{
return new AddressType(BitcoinAddress.Create(str, network));
}
}
}
public class CreatePSBTDestination
{
/// <summary>
/// The destination as an address or a script. (in hex)
/// </summary>
public PSBTDestination Destination { get; set; }
/// <summary>
/// Will Send this amount to this destination (Mutually exclusive with: SweepAll)
/// </summary>
public Money Amount { get; set; }
/// <summary>
/// Will substract the fees of this transaction to this destination (Mutually exclusive with: SweepAll)
/// </summary>
public bool SubstractFees { get; set; }
/// <summary>
/// Will sweep all the balance of your wallet to this destination (Mutually exclusive with: Amount, SubstractFees)
/// </summary>
public bool SweepAll { get; set; }
}
public class FeePreference
{
/// <summary>
/// An explicit fee rate for the transaction in Satoshi per vBytes (Mutually exclusive with: BlockTarget, FallbackFeeRate)
/// </summary>
public FeeRate ExplicitFeeRate { get; set; }
/// <summary>
/// An explicit fee for the transaction in Satoshi (Mutually exclusive with: BlockTarget, FallbackFeeRate)
/// </summary>
public Money ExplicitFee { get; set; }
/// <summary>
/// A number of blocks after which the user expect one confirmation (Mutually exclusive with: ExplicitFeeRate, ExplicitFee)
/// </summary>
public int? BlockTarget { get; set; }
/// <summary>
/// If the NBXplorer's node does not have proper fee estimation, this specific rate will be use in Satoshi per vBytes. (Mutually exclusive with: ExplicitFeeRate, ExplicitFee)
/// </summary>
public FeeRate FallbackFeeRate { get; set; }
}
}

View File

@ -1,18 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
namespace NBXplorer.Models
{
public class CreatePSBTResponse
{
[JsonProperty("psbt")]
public PSBT PSBT { get; set; }
public BitcoinAddress ChangeAddress { get; set; }
public CreatePSBTSuggestions Suggestions { get; set; }
}
public class CreatePSBTSuggestions
{
public bool ShouldEnforceLowR { get; set; }
}
}

View File

@ -1,23 +0,0 @@
using System;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public class GenerateWalletRequest
{
public int AccountNumber { get; set; }
public string ExistingMnemonic { get; set; }
[JsonConverter(typeof(NBXplorer.JsonConverters.WordlistJsonConverter))]
public NBitcoin.Wordlist WordList { get; set; }
[JsonConverter(typeof(NBXplorer.JsonConverters.WordcountJsonConverter))]
public NBitcoin.WordCount? WordCount { get; set; }
[JsonConverter(typeof(NBXplorer.JsonConverters.ScriptPubKeyTypeConverter))]
public NBitcoin.ScriptPubKeyType? ScriptPubKeyType { get; set; }
public string Passphrase { get; set; }
[Obsolete("We will remove this feature in a future release.")]
public bool ImportKeysToRPC { get; set; }
public bool SavePrivateKeys { get; set; }
public Dictionary<string, string> AdditionalOptions { get; set; }
}
}

View File

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
namespace NBXplorer.Models
{
public class GenerateWalletResponse
{
public string TrackedSource { get; set; }
public string Mnemonic { get; set; }
public string Passphrase { get; set; }
[JsonConverter(typeof(NBXplorer.JsonConverters.WordlistJsonConverter))]
public NBitcoin.Wordlist WordList { get; set; }
[JsonConverter(typeof(NBXplorer.JsonConverters.WordcountJsonConverter))]
public NBitcoin.WordCount WordCount { get; set; }
public BitcoinExtKey MasterHDKey { get; set; }
public BitcoinExtKey AccountHDKey { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
public NBitcoin.RootedKeyPath AccountKeyPath { get; set; }
public string AccountDescriptor { get; set; }
public StandardDerivationStrategyBase DerivationScheme { get; set; }
public Mnemonic GetMnemonic()
{
return new Mnemonic(Mnemonic, WordList);
}
}
}

View File

@ -1,29 +0,0 @@
using NBitcoin;
namespace NBXplorer.Models
{
public class GetBalanceResponse
{
/// <summary>
/// How the confirmed balance would be updated once all the unconfirmed transactions were confirmed.
/// </summary>
public IMoney Unconfirmed { get; set; }
/// <summary>
/// The balance of all funds in confirmed transactions.
/// </summary>
public IMoney Confirmed { get; set; }
/// <summary>
/// The total of funds owned (ie, `confirmed + unconfirmed`)
/// </summary>
public IMoney Total { get; set; }
/// <summary>
/// The total unspendable funds (ie, coinbase reward which need 100 confirmations before being spendable)
/// </summary>
public IMoney Immature { get; set; }
/// <summary>
/// The total spendable balance. (ie, `total - immature`)
/// </summary>
public IMoney Available { get; set; }
}
}

View File

@ -1,4 +1,7 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{

View File

@ -2,16 +2,22 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class GetTransactionsResponse
{
public class GetTransactionsResponse
{
public int Height
{
get; set;
}
public bool HasChanges()
{
return ConfirmedTransactions.HasChanges() || UnconfirmedTransactions.HasChanges() || ReplacedTransactions.HasChanges();
}
public TransactionInformationSet ConfirmedTransactions
{
get; set;
@ -22,11 +28,6 @@ namespace NBXplorer.Models
get; set;
} = new TransactionInformationSet();
public TransactionInformationSet ImmatureTransactions
{
get; set;
} = new TransactionInformationSet();
public TransactionInformationSet ReplacedTransactions
{
get; set;
@ -35,15 +36,27 @@ namespace NBXplorer.Models
public class TransactionInformationSet
{
public Bookmark KnownBookmark
{
get; set;
}
public Bookmark Bookmark
{
get; set;
}
public List<TransactionInformation> Transactions
{
get; set;
} = new List<TransactionInformation>();
public bool HasChanges()
{
return KnownBookmark != Bookmark;
}
}
public class TransactionInformationMatch
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public KeyPath KeyPath
{
get; set;
@ -63,16 +76,11 @@ namespace NBXplorer.Models
{
get; set;
}
public long Confirmations
public int Confirmations
{
get; set;
}
public long? Height
{
get; set;
}
public bool IsMature
public int? Height
{
get; set;
}
@ -84,28 +92,24 @@ namespace NBXplorer.Models
{
get; set;
}
public List<MatchedOutput> Outputs
public List<TransactionInformationMatch> Outputs
{
get; set;
} = new List<MatchedOutput>();
} = new List<TransactionInformationMatch>();
public List<MatchedInput> Inputs
public List<TransactionInformationMatch> Inputs
{
get; set;
} = new List<MatchedInput>();
} = new List<TransactionInformationMatch>();
public DateTimeOffset Timestamp
{
get;
set;
}
public IMoney BalanceChange
public Money BalanceChange
{
get;
set;
}
public uint256 ReplacedBy { get; set; }
public uint256 Replacing { get; set; }
public bool Replaceable { get; set; }
public TransactionMetadata Metadata { get; set; }
}
}

View File

@ -1,22 +0,0 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class GroupChild
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public string CryptoCode { get; set; }
public string TrackedSource { get; set; }
}
public class GroupInformation
{
public string TrackedSource { get; set; }
public string GroupId { get; set; }
public GroupChild[] Children { get; set; }
public GroupChild AsGroupChild() => new () { TrackedSource = TrackedSource };
}
}

View File

@ -1,10 +0,0 @@
using NBitcoin;
using Newtonsoft.Json;
namespace NBXplorer.Models;
public class ImportUTXORequest
{
[JsonProperty("UTXOs")]
public OutPoint[] Utxos { get; set; }
}

View File

@ -1,25 +1,23 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class KeyPathInformation
{
public TrackedSource TrackedSource { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public DerivationFeature Feature
{
get; set;
}
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public DerivationStrategyBase DerivationStrategy
{
get; set;
}
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public KeyPath KeyPath
{
get; set;
@ -28,17 +26,9 @@ namespace NBXplorer.Models
{
get; set;
}
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public BitcoinAddress Address { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Script Redeem
{
get; set;
}
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? Index { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
}
}

View File

@ -1,132 +0,0 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NBXplorer
{
public class KeyPathTemplate
{
private readonly KeyPath preIndexes;
private readonly KeyPath postIndexes;
public KeyPath PostIndexes => postIndexes;
public KeyPath PreIndexes => preIndexes;
public static KeyPathTemplate Parse(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (!TryParse(path, out var v))
throw new FormatException("Invalid keypath template");
return v;
}
public static bool TryParse(string path, out KeyPathTemplate keyPathTemplate)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
bool isValid = true;
uint[] preIndices = null, postIndices = null;
bool isPreIndex = true;
int count = 0;
List<uint> indices = new List<uint>();
foreach (var p in path
.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(p => p != "m"))
{
if (p == "*")
{
if (isPreIndex)
{
isPreIndex = false;
count++;
preIndices = indices.ToArray();
indices.Clear();
}
else
{
isValid = false;
}
}
else
{
isValid &= TryParseCore(p, out var i);
if(isValid)
indices.Add(i);
count++;
}
}
if (count > 255)
isValid = false;
isValid &= preIndices != null;
if (!isValid)
{
keyPathTemplate = null;
return false;
}
postIndices = indices.ToArray();
keyPathTemplate = new KeyPathTemplate(preIndices, postIndices);
return true;
}
private static bool TryParseCore(string i, out uint index)
{
if (i.Length == 0)
{
index = 0;
return false;
}
bool hardened = i[i.Length - 1] == '\'' || i[i.Length - 1] == 'h';
var nonhardened = hardened ? i.Substring(0, i.Length - 1) : i;
if (!uint.TryParse(nonhardened, out index))
return false;
if (hardened)
{
if (index >= 0x80000000u)
{
index = 0;
return false;
}
index = index | 0x80000000u;
return true;
}
else
{
return true;
}
}
public KeyPathTemplate(uint[] preIndexes, uint[] postIndexes) : this(new KeyPath(preIndexes ?? Array.Empty<uint>()),
new KeyPath(postIndexes ?? Array.Empty<uint>()))
{
}
public KeyPathTemplate(KeyPath preIndexes, KeyPath postIndexes)
{
this.preIndexes = preIndexes ?? new KeyPath();
this.postIndexes = postIndexes ?? new KeyPath();
}
public KeyPath GetKeyPath(uint index)
{
return PreIndexes.Derive(index).Derive(PostIndexes);
}
public KeyPath GetKeyPath(int index, bool hardened)
{
return PreIndexes.Derive(index, hardened).Derive(PostIndexes);
}
public override string ToString()
{
StringBuilder builder = new StringBuilder(PreIndexes.Length + PostIndexes.Length + 20);
if (PreIndexes.Length != 0)
builder.Append($"{PreIndexes}/*");
else
builder.Append("*");
if (PostIndexes.Length != 0)
builder.Append($"/{PostIndexes}");
return builder.ToString();
}
}
}

View File

@ -1,48 +0,0 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
namespace NBXplorer
{
public class KeyPathTemplates
{
private static readonly KeyPathTemplate depositKeyPathTemplate = KeyPathTemplate.Parse("0/*");
private static readonly KeyPathTemplate changeKeyPathTemplate = KeyPathTemplate.Parse("1/*");
private static readonly KeyPathTemplate directKeyPathTemplate = KeyPathTemplate.Parse("*");
private readonly KeyPathTemplate customKeyPathTemplate;
private static readonly KeyPathTemplates _Default = new KeyPathTemplates();
private readonly DerivationFeature[] derivationFeatures;
public static KeyPathTemplates Default => _Default;
private KeyPathTemplates() : this(null)
{
}
public KeyPathTemplates(KeyPathTemplate customKeyPathTemplate)
{
this.customKeyPathTemplate = customKeyPathTemplate;
List<DerivationFeature> derivationFeatures = new List<DerivationFeature>
{
DerivationFeature.Deposit,
DerivationFeature.Change,
DerivationFeature.Direct
};
if (customKeyPathTemplate != null)
derivationFeatures.Add(DerivationFeature.Custom);
this.derivationFeatures = derivationFeatures.ToArray();
}
public KeyPathTemplate GetKeyPathTemplate(DerivationFeature derivationFeature)
=> derivationFeature switch
{
DerivationFeature.Deposit => depositKeyPathTemplate,
DerivationFeature.Change => changeKeyPathTemplate,
DerivationFeature.Direct => directKeyPathTemplate,
DerivationFeature.Custom when customKeyPathTemplate != null => customKeyPathTemplate,
_ => throw new NotSupportedException($"The derivation feature {derivationFeature} is not supported by the key path templates.")
};
public IEnumerable<DerivationFeature> GetSupportedDerivationFeatures() => derivationFeatures;
}
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
@ -26,13 +28,6 @@ namespace NBXplorer.Models
Code = code;
Message = message;
}
public NBXplorerError(int httpCode, string code, string message, string reason)
{
HttpCode = httpCode;
Code = code;
Message = message;
Reason = reason;
}
public int HttpCode
{
get; set;
@ -45,10 +40,6 @@ namespace NBXplorer.Models
{
get; set;
}
public string Reason
{
get; set;
}
public NBXplorerException AsException()
{

View File

@ -1,13 +1,12 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class NewBlockEvent : NewEventBase
{
public NewBlockEvent()
{
}
public class NewBlockEvent
{
public int Height
{
get; set;
@ -21,15 +20,10 @@ namespace NBXplorer.Models
{
get; set;
}
public long Confirmations { get; set; }
[JsonIgnore]
public override string EventType => "newblock";
public override string ToString()
public string CryptoCode
{
return $"{CryptoCode}: New block {Hash} ({Height})";
get;
set;
}
}
}

View File

@ -1,7 +1,20 @@
namespace NBXplorer.Models
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class NewBlockEventRequest : NewEventBase
{
public override string EventType => "subscribeblock";
public class NewBlockEventRequest
{
public NewBlockEventRequest()
{
}
public String CryptoCode
{
get; set;
}
}
}

View File

@ -1,108 +0,0 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public abstract class NewEventBase
{
static NewEventBase()
{
_TypeByName = new Dictionary<string, Type>();
_NameByType = new Dictionary<Type, string>();
Add("newblock", typeof(Models.NewBlockEvent));
Add("subscribeblock", typeof(Models.NewBlockEventRequest));
Add("subscribetransaction", typeof(Models.NewTransactionEventRequest));
Add("newtransaction", typeof(Models.NewTransactionEvent));
}
static Dictionary<string, Type> _TypeByName;
static Dictionary<Type, string> _NameByType;
private static void Add(string typeName, Type type)
{
_TypeByName.Add(typeName, type);
_NameByType.Add(type, typeName);
}
public static string GetEventTypeName(Type type)
{
_NameByType.TryGetValue(type, out string name);
return name;
}
[JsonIgnore]
public abstract string EventType { get; }
public string CryptoCode
{
get;
set;
}
[JsonIgnore]
public long EventId { get; set; }
public JObject ToJObject(JsonSerializerSettings settings)
{
var typeName = GetEventTypeName(this.GetType());
if (typeName == null)
throw new InvalidOperationException($"{this.GetType().Name} does not have an associated typeName");
JObject jobj = new JObject();
var serialized = JsonConvert.SerializeObject(this, settings);
var data = JObject.Parse(serialized);
if(this.EventId != 0)
jobj.Add(new JProperty("eventId", new JValue(EventId)));
jobj.Add(new JProperty("type", new JValue(typeName)));
jobj.Add(new JProperty("data", data));
return jobj;
}
public static NewEventBase ParseEvent(string str, JsonSerializerSettings settings)
{
if (str == null)
throw new ArgumentNullException(nameof(str));
JObject jobj = JObject.Parse(str);
return ParseEvent(jobj, settings);
}
public static NewEventBase ParseEvent(JObject jobj, JsonSerializerSettings settings)
{
if (jobj == null)
throw new ArgumentNullException(nameof(jobj));
var type = (jobj["type"] as JValue)?.Value<string>();
if (type == null)
throw new FormatException("'type' property not found");
bool unknown = false;
if (!_TypeByName.TryGetValue(type, out Type typeObject))
{
unknown = true;
typeObject = typeof(UnknownEvent);
}
var data = (jobj["data"] as JObject);
if (data == null)
throw new FormatException("'data' property not found");
NewEventBase evt = null;
if (unknown)
{
var unk = new UnknownEvent(type);
unk.Data = data;
unk.CryptoCode = data["cryptoCode"]?.Value<string>();
evt = unk;
}
else
{
evt = (NewEventBase)JsonConvert.DeserializeObject(data.ToString(), typeObject, settings);
}
if(jobj["eventId"] != null)
{
evt.EventId = jobj["eventId"].Value<long>();
}
return evt;
}
public string ToJson(JsonSerializerSettings settings)
{
return JsonConvert.SerializeObject(this, settings);
}
}
}

View File

@ -1,23 +1,18 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
namespace NBXplorer.Models
{
public class NewTransactionEvent : NewEventBase
public class NewTransactionEvent
{
public uint256 BlockId
{
get; set;
}
public TrackedSource TrackedSource { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public DerivationStrategyBase DerivationStrategy
{
get; set;
@ -28,57 +23,24 @@ namespace NBXplorer.Models
get; set;
}
public List<MatchedInput> Inputs
public List<KeyPathInformation> Outputs
{
get; set;
} = new List<MatchedInput>();
public List<MatchedOutput> Outputs
} = new List<KeyPathInformation>();
public List<KeyPathInformation> Inputs
{
get; set;
} = new List<MatchedOutput>();
[JsonIgnore]
public override string EventType => "newtransaction";
public List<uint256> Replacing { get; set; }
public override string ToString()
} = new List<KeyPathInformation>();
public string CryptoCode
{
var conf = (BlockId == null ? "unconfirmed" : "confirmed");
get;
set;
}
string strategy = TrackedSource.ToPrettyString();
var txId = TransactionData.TransactionHash.ToString();
txId = txId.Substring(0, 6) + "..." + txId.Substring(txId.Length - 6);
string keyPathSuffix = string.Empty;
var keyPaths = Outputs.Select(v => v.KeyPath?.ToString()).Where(k => k != null).ToArray();
if (keyPaths.Length != 0)
{
keyPathSuffix = $" ({String.Join(", ", keyPaths)})";
}
return $"{CryptoCode}: {strategy} matching {conf} transaction {txId}{keyPathSuffix}";
public TransactionMatch AsMatch()
{
return new TransactionMatch() { DerivationStrategy = DerivationStrategy, Inputs = Inputs, Outputs = Outputs, Transaction = TransactionData.Transaction };
}
}
public class MatchedOutput
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public KeyPath KeyPath { get; set; }
public Script ScriptPubKey { get; set; }
public int Index { get; set; }
public int KeyIndex { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public DerivationFeature? Feature { get; set; }
public IMoney Value { get; set; }
public BitcoinAddress Address { get; set; }
}
public class MatchedInput : MatchedOutput
{
public int InputIndex { get; set; }
public uint256 TransactionId
{
get; set;
}
}
}
}

View File

@ -1,12 +1,23 @@
namespace NBXplorer.Models
{
public class NewTransactionEventRequest : NewEventBase
{
public override string EventType => "subscribetransaction";
public string[] DerivationSchemes { get; set; }
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Text;
public string[] TrackedSources { get; set; }
public bool? ListenAllTrackedSource { get; set; }
public bool? ListenAllDerivationSchemes { get; set; }
namespace NBXplorer.Models
{
public class NewTransactionEventRequest
{
public NewTransactionEventRequest()
{
}
public string CryptoCode
{
get; set;
}
public string[] DerivationSchemes
{
get; set;
}
}
}

View File

@ -1,7 +0,0 @@
namespace NBXplorer.Models
{
public class PruneRequest
{
public double? DaysToKeep { get; set; }
}
}

View File

@ -1,7 +0,0 @@
namespace NBXplorer.Models
{
public class PruneResponse
{
public int TotalPruned { get; set; }
}
}

View File

@ -1,28 +0,0 @@
using NBitcoin;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public class RescanRequest
{
public class TransactionToRescan
{
public uint256 BlockId
{
get; set;
}
public uint256 TransactionId
{
get; set;
}
public Transaction Transaction
{
get; set;
}
}
public List<TransactionToRescan> Transactions
{
get; set;
} = new List<TransactionToRescan>();
}
}

View File

@ -1,96 +0,0 @@
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public enum ScanUTXOStatus
{
Queued,
Pending,
Error,
Complete
}
public class ScanUTXOProgress
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.UnixDateTimeConverter))]
public DateTimeOffset StartedAt { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.UnixDateTimeConverter))]
public DateTimeOffset? CompletedAt { get; set; }
public int Found { get; set; }
public int BatchNumber { get; set; }
public int RemainingBatches { get; set; }
public int CurrentBatchProgress { get; set; }
public int OverallProgress { get; set; }
public int RemainingSeconds { get; set; }
public int From { get; set; }
public int Count { get; set; }
public int TotalSearched { get; set; }
public int? TotalSizeOfUTXOSet { get; set; }
public void UpdateOverallProgress(DateTimeOffset? now = null)
{
now = now.HasValue ? now : DateTimeOffset.UtcNow;
double percentPerBatch = 100.0 / (RemainingBatches + BatchNumber + 1);
double batchPercent = BatchNumber * percentPerBatch;
double insideBatchPercent = CurrentBatchProgress * (percentPerBatch / 100.0);
var overallProgress = batchPercent + insideBatchPercent;
var timeSpent = now.Value - StartedAt;
var secondsRemaining = ((100 - overallProgress) / 100.0) * timeSpent.TotalSeconds;
OverallProgress = (int)Math.Round(overallProgress);
RemainingSeconds = (int)Math.Round(secondsRemaining);
}
public void UpdateRemainingBatches(int gapLimit)
{
int highestIndex = -1;
foreach(var index in HighestKeyIndexFound)
{
if(index.Value != null)
{
if (index.Value > highestIndex)
highestIndex = index.Value.Value;
}
}
int gapLimitIndex = highestIndex + gapLimit;
var totalBatchesRequired = (gapLimitIndex / Count) + 1;
RemainingBatches = totalBatchesRequired - (BatchNumber + 1);
}
public Dictionary<DerivationFeature, int?> HighestKeyIndexFound { get; set; } = new Dictionary<DerivationFeature, int?>();
public ScanUTXOProgress Clone()
{
return new ScanUTXOProgress()
{
Found = Found,
BatchNumber = BatchNumber,
CurrentBatchProgress = CurrentBatchProgress,
From = From,
Count = Count,
HighestKeyIndexFound = new Dictionary<DerivationFeature, int?>(HighestKeyIndexFound),
TotalSearched = TotalSearched,
OverallProgress = OverallProgress,
RemainingBatches = RemainingBatches,
CompletedAt = CompletedAt,
StartedAt = StartedAt,
RemainingSeconds = RemainingSeconds
};
}
}
public class ScanUTXOInformation
{
public string Error { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.UnixDateTimeConverter))]
public DateTimeOffset QueuedAt { get; set; }
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public ScanUTXOStatus Status { get; set; }
public ScanUTXOProgress Progress { get; set; }
}
}

View File

@ -1,5 +1,8 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
@ -32,22 +35,18 @@ namespace NBXplorer.Models
get;
set;
}
public string[] ExternalAddresses { get; set; }
public NodeCapabilities Capabilities { get; set; }
}
public class NodeCapabilities
{
public bool CanScanTxoutSet { get; set; }
public bool CanSupportSegwit { get; set; }
public bool CanSupportTaproot { get; set; }
public bool CanSupportTransactionCheck { get; set; }
}
public class StatusResult
public class StatusResult
{
public BitcoinStatus BitcoinStatus
{
get; set;
}
public double RepositoryPingTime
{
get;
set;
}
public bool IsFullySynched
{
get; set;
@ -62,13 +61,8 @@ namespace NBXplorer.Models
get;
set;
}
public string InstanceName
{
get;
set;
}
[JsonConverter(typeof(NBitcoin.JsonConverters.ChainNameJsonConverter))]
public ChainName NetworkType
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public NetworkType NetworkType
{
get;
set;

View File

@ -1,19 +0,0 @@
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
namespace NBXplorer.Models
{
public class TrackWalletRequest
{
public TrackDerivationOption[] DerivationOptions { get; set; }
public bool Wait { get; set; } = false;
}
public class TrackDerivationOption
{
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public DerivationFeature? Feature { get; set; }
public int? MinAddresses { get; set; }
public int? MaxAddresses { get; set; }
}
}

View File

@ -1,251 +0,0 @@
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
namespace NBXplorer.Models
{
public abstract class TrackedSource
{
public static bool TryParse(string str, out TrackedSource trackedSource, NBXplorerNetwork network)
{
if (str == null)
throw new ArgumentNullException(nameof(str));
trackedSource = null;
var strSpan = str.AsSpan();
if (strSpan.StartsWith("DERIVATIONSCHEME:".AsSpan(), StringComparison.Ordinal))
{
if (network is null)
return false;
if (!DerivationSchemeTrackedSource.TryParse(strSpan, out var derivationSchemeTrackedSource, network))
return false;
trackedSource = derivationSchemeTrackedSource;
}
else if (strSpan.StartsWith("ADDRESS:".AsSpan(), StringComparison.Ordinal))
{
if (network is null)
return false;
if (!AddressTrackedSource.TryParse(strSpan, out var addressTrackedSource, network.NBitcoinNetwork))
return false;
trackedSource = addressTrackedSource;
}
else if (strSpan.StartsWith("GROUP:".AsSpan(), StringComparison.Ordinal))
{
if (!GroupTrackedSource.TryParse(strSpan, out var walletTrackedSource))
return false;
trackedSource = walletTrackedSource;
}
else
{
return false;
}
return true;
}
public override bool Equals(object obj)
{
TrackedSource item = obj as TrackedSource;
if (item == null)
return false;
return ToString().Equals(item.ToString());
}
public static bool operator ==(TrackedSource a, TrackedSource b)
{
if (System.Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
return a.ToString() == b.ToString();
}
public static bool operator !=(TrackedSource a, TrackedSource b)
{
return !(a == b);
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public static DerivationSchemeTrackedSource Create(DerivationStrategyBase strategy)
{
if (strategy == null)
throw new ArgumentNullException(nameof(strategy));
return new DerivationSchemeTrackedSource(strategy);
}
public static AddressTrackedSource Create(BitcoinAddress address)
{
if (address == null)
throw new ArgumentNullException(nameof(address));
return new AddressTrackedSource(address);
}
public static AddressTrackedSource Create(Script scriptPubKey, Network network)
{
if (scriptPubKey == null)
throw new ArgumentNullException(nameof(scriptPubKey));
if (network == null)
throw new ArgumentNullException(nameof(network));
var address = scriptPubKey.GetDestinationAddress(network);
if (address == null)
throw new ArgumentException(paramName: nameof(scriptPubKey), message: $"{nameof(scriptPubKey)} can't be translated on an address on {network.Name}");
return new AddressTrackedSource(address);
}
public virtual string ToPrettyString()
{
return ToString();
}
public static TrackedSource Parse(string str, NBXplorerNetwork network)
{
if (!TryParse(str, out var trackedSource, network))
throw new FormatException("Invalid TrackedSource");
return trackedSource;
}
}
public class GroupTrackedSource : TrackedSource
{
public string GroupId { get; }
public static GroupTrackedSource Generate()
{
Span<byte> r = stackalloc byte[13];
// 13 is most consistent on number of chars and more than we need to avoid generating twice same id
RandomNumberGenerator.Fill(r);
return new GroupTrackedSource(Encoders.Base58.EncodeData(r));
}
public GroupTrackedSource(string groupId)
{
GroupId = groupId;
}
public static bool TryParse(ReadOnlySpan<char> trackedSource, out GroupTrackedSource walletTrackedSource)
{
walletTrackedSource = null;
if (!trackedSource.StartsWith("GROUP:".AsSpan(), StringComparison.Ordinal))
return false;
try
{
walletTrackedSource = new GroupTrackedSource(trackedSource.Slice("GROUP:".Length).ToString());
return true;
}
catch { return false; }
}
public override string ToString()
{
return "GROUP:" + GroupId;
}
public override string ToPrettyString()
{
return "G:" + GroupId;
}
public static GroupTrackedSource Parse(string trackedSource)
{
return TryParse(trackedSource, out var g) ? g : throw new FormatException("Invalid group tracked source format");
}
}
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
public AddressTrackedSource(BitcoinAddress address)
{
if (address == null)
throw new ArgumentNullException(nameof(address));
_FullAddressString = "ADDRESS:" + address;
Address = address;
}
string _FullAddressString;
public BitcoinAddress Address
{
get;
}
public Script ScriptPubKey => Address.ScriptPubKey;
public static bool TryParse(ReadOnlySpan<char> strSpan, out TrackedSource addressTrackedSource, Network network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
addressTrackedSource = null;
if (!strSpan.StartsWith("ADDRESS:".AsSpan(), StringComparison.Ordinal))
return false;
try
{
addressTrackedSource = new AddressTrackedSource(BitcoinAddress.Create(strSpan.Slice("ADDRESS:".Length).ToString(), network));
return true;
}
catch { return false; }
}
public override string ToString()
{
return _FullAddressString;
}
public override string ToPrettyString()
{
return Address.ToString();
}
}
public class DerivationSchemeTrackedSource : TrackedSource
{
public DerivationSchemeTrackedSource(DerivationStrategy.DerivationStrategyBase derivationStrategy)
{
if (derivationStrategy == null)
throw new ArgumentNullException(nameof(derivationStrategy));
DerivationStrategy = derivationStrategy;
}
public DerivationStrategy.DerivationStrategyBase DerivationStrategy { get; }
public static bool TryParse(ReadOnlySpan<char> strSpan, out DerivationSchemeTrackedSource derivationSchemeTrackedSource, NBXplorerNetwork network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
derivationSchemeTrackedSource = null;
if (!strSpan.StartsWith("DERIVATIONSCHEME:".AsSpan(), StringComparison.Ordinal))
return false;
try
{
var factory = network.DerivationStrategyFactory;
var derivationScheme = factory.Parse(strSpan.Slice("DERIVATIONSCHEME:".Length).ToString());
derivationSchemeTrackedSource = new DerivationSchemeTrackedSource(derivationScheme);
return true;
}
catch { return false; }
}
public override string ToString()
{
return "DERIVATIONSCHEME:" + DerivationStrategy.ToString();
}
public override string ToPrettyString()
{
var strategy = DerivationStrategy.ToString();
if (strategy.Length > 35)
{
strategy = strategy.Substring(0, 10) + "..." + strategy.Substring(strategy.Length - 20);
}
return strategy;
}
#if !NO_RECORD
public IEnumerable<DerivationFeature> GetDerivationFeatures(KeyPathTemplates keyPathTemplates)
=> DerivationStrategy is PolicyDerivationStrategy ? new[] { DerivationFeature.Deposit, DerivationFeature.Change } : keyPathTemplates.GetSupportedDerivationFeatures();
#else
public IEnumerable<DerivationFeature> GetDerivationFeatures(KeyPathTemplates keyPathTemplates)
=> keyPathTemplates.GetSupportedDerivationFeatures();
#endif
}
}

View File

@ -0,0 +1,29 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class TransactionMatch
{
public DerivationStrategyBase DerivationStrategy
{
get; set;
}
public Transaction Transaction
{
get; set;
}
public List<KeyPathInformation> Outputs
{
get; set;
} = new List<KeyPathInformation>();
public List<KeyPathInformation> Inputs
{
get; set;
} = new List<KeyPathInformation>();
}
}

View File

@ -1,40 +0,0 @@
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class TransactionMetadata
{
public class ChunkMetadata
{
[JsonProperty("fees", DefaultValueHandling = DefaultValueHandling.Ignore)]
[JsonConverter(typeof(NBXplorer.JsonConverters.MoneyJsonConverter))]
public Money Fees { get; set; }
[JsonProperty("weight", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int Weight { get; set; }
[JsonProperty("feeRate", DefaultValueHandling = DefaultValueHandling.Ignore)]
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
}
[JsonProperty("vsize", DefaultValueHandling = DefaultValueHandling.Ignore)]
public int? VirtualSize { get; set; }
[JsonProperty("fees", DefaultValueHandling = DefaultValueHandling.Ignore)]
[JsonConverter(typeof(NBXplorer.JsonConverters.MoneyJsonConverter))]
public Money Fees { get; set; }
[JsonProperty("feeRate", DefaultValueHandling = DefaultValueHandling.Ignore)]
[JsonConverter(typeof(NBitcoin.JsonConverters.FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
public ChunkMetadata Chunk { get; set; }
public static TransactionMetadata Parse(string json) => JsonConvert.DeserializeObject<TransactionMetadata>(json);
public string ToString(bool indented) => JsonConvert.SerializeObject(this, indented ? Formatting.Indented : Formatting.None);
public override string ToString() => ToString(true);
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
}
}

View File

@ -1,20 +1,22 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{
public class TransactionResult
{
long _Confirmations;
public long Confirmations
uint _Confirmations;
public int Confirmations
{
get
{
return _Confirmations;
return checked((int)_Confirmations);
}
set
{
_Confirmations = value;
_Confirmations = checked((uint)value);
}
}
@ -30,7 +32,7 @@ namespace NBXplorer.Models
_BlockId = value;
}
}
public uint256 TransactionHash { get; set; }
Transaction _Transaction;
public Transaction Transaction
{
@ -44,7 +46,7 @@ namespace NBXplorer.Models
}
}
public long? Height
public int? Height
{
get;
set;
@ -54,8 +56,5 @@ namespace NBXplorer.Models
get;
set;
}
public uint256 ReplacedBy { get; set; }
public TransactionMetadata Metadata { get; set; }
}
}

View File

@ -1,19 +1,18 @@
using NBitcoin;
using System.Linq;
using NBitcoin.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin.Crypto;
using System.IO;
using Newtonsoft.Json;
using NBXplorer.DerivationStrategy;
#if !NO_RECORD
using NBitcoin.WalletPolicies;
#endif
namespace NBXplorer.Models
{
public class UTXOChanges
{
public TrackedSource TrackedSource { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public DerivationStrategyBase DerivationStrategy
{
get; set;
@ -45,11 +44,6 @@ namespace NBXplorer.Models
}
}
public List<UTXO> SpentUnconfirmed
{
get;
set;
} = new List<UTXO>();
UTXOChange _Confirmed = new UTXOChange();
public UTXOChange Confirmed
@ -64,41 +58,69 @@ namespace NBXplorer.Models
}
}
public bool HasChanges
{
get
{
return Confirmed.HasChanges || Unconfirmed.HasChanges;
}
}
public Coin[] GetUnspentCoins(bool excludeUnconfirmedUTXOs = false)
{
return GetUnspentCoins(excludeUnconfirmedUTXOs ? 1 : 0);
if(Confirmed.KnownBookmark != null || Unconfirmed.KnownBookmark != null)
throw new InvalidOperationException("This UTXOChanges is partial, it is calculate the unspent coins");
return GetUnspentUTXOs(excludeUnconfirmedUTXOs).Select(c => c.AsCoin(DerivationStrategy)).ToArray();
}
public Coin[] GetUnspentCoins(int minConfirmations)
public UTXO[] GetUnspentUTXOs(bool excludeUnconfirmedUTXOs = false)
{
return GetUnspentUTXOs(minConfirmations).Select(c => c.AsCoin(DerivationStrategy)).ToArray();
}
public UTXO[] GetUnspentUTXOs(int minConf)
{
var excludeUnconfirmedUTXOs = minConf > 0;
Dictionary<OutPoint, UTXO> received = new Dictionary<OutPoint, UTXO>();
foreach (var utxo in Confirmed.UTXOs.Where(u => u.Confirmations >= minConf).Concat(excludeUnconfirmedUTXOs ? (IEnumerable<UTXO>)Array.Empty<UTXO>() : Unconfirmed.UTXOs))
foreach(var utxo in Confirmed.UTXOs.Concat(excludeUnconfirmedUTXOs ? (IEnumerable<UTXO>)Array.Empty<UTXO>() : Unconfirmed.UTXOs))
{
received.TryAdd(utxo.Outpoint, utxo);
}
foreach (var utxo in Confirmed.SpentOutpoints.Concat(Unconfirmed.SpentOutpoints))
foreach(var utxo in Confirmed.SpentOutpoints.Concat(Unconfirmed.SpentOutpoints))
{
received.Remove(utxo);
}
return received.Values.ToArray();
}
public UTXO[] GetUnspentUTXOs(bool excludeUnconfirmedUTXOs = false)
{
return GetUnspentUTXOs(excludeUnconfirmedUTXOs ? 1 : 0);
}
public Key[] GetKeys(ExtKey extKey, bool excludeUnconfirmedUTXOs = false)
{
return GetUnspentUTXOs(excludeUnconfirmedUTXOs).Where(u => u.KeyPath is not null).Select(u => extKey.Derive(u.KeyPath).PrivateKey).ToArray();
return GetUnspentUTXOs(excludeUnconfirmedUTXOs).Select(u => extKey.Derive(u.KeyPath).PrivateKey).ToArray();
}
}
public class UTXOChange
{
Bookmark _KnownBookmark;
public Bookmark KnownBookmark
{
get
{
return _KnownBookmark;
}
set
{
_KnownBookmark = value;
}
}
Bookmark _Bookmark = null;
public Bookmark Bookmark
{
get
{
return _Bookmark;
}
set
{
_Bookmark = value;
}
}
List<UTXO> _UTXOs = new List<UTXO>();
public List<UTXO> UTXOs
{
@ -124,6 +146,14 @@ namespace NBXplorer.Models
_SpentOutpoints = value;
}
}
public bool HasChanges
{
get
{
return KnownBookmark != Bookmark || UTXOs.Count != 0 || SpentOutpoints.Count != 0;
}
}
}
public class UTXO
@ -133,18 +163,15 @@ namespace NBXplorer.Models
}
public UTXO(ICoin coin)
public UTXO(Coin coin)
{
Outpoint = coin.Outpoint;
Index = (int)coin.Outpoint.N;
TransactionHash = coin.Outpoint.Hash;
Value = coin.Amount;
Value = coin.TxOut.Value;
ScriptPubKey = coin.TxOut.ScriptPubKey;
}
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public DerivationFeature? Feature
public DerivationFeature Feature
{
get; set;
}
@ -156,37 +183,16 @@ namespace NBXplorer.Models
public Coin AsCoin(DerivationStrategy.DerivationStrategyBase derivationStrategy)
{
if (Value is Money v)
var coin = new Coin(Outpoint, new TxOut(Value, ScriptPubKey));
if(derivationStrategy != null)
{
var coin = new Coin(Outpoint, new TxOut(v, ScriptPubKey));
if (Redeem is not null)
{
coin = coin.ToScriptCoin(Redeem);
}
else
{
DerivationStrategy.Derivation derivation = null;
if (derivationStrategy is StandardDerivationStrategyBase kd && KeyPath is not null)
{
derivation = kd.GetDerivation(KeyPath);
}
#if !NO_RECORD
else if (derivationStrategy is PolicyDerivationStrategy md && Feature is { } f)
{
derivation = md.GetDerivation(f, (uint)KeyIndex);
}
#endif
if (derivation is not null)
{
if (derivation.ScriptPubKey != coin.ScriptPubKey)
throw new InvalidOperationException($"This Derivation Strategy does not own this coin");
if (derivation.Redeem != null)
coin = coin.ToScriptCoin(derivation.Redeem);
}
}
return coin;
var derivation = derivationStrategy.Derive(KeyPath);
if(derivation.ScriptPubKey != coin.ScriptPubKey)
throw new InvalidOperationException($"This Derivation Strategy does not own this coin");
if(derivation.Redeem != null)
coin = coin.ToScriptCoin(derivation.Redeem);
}
return null;
return coin;
}
OutPoint _Outpoint = new OutPoint();
@ -202,8 +208,7 @@ namespace NBXplorer.Models
}
}
public int Index { get; set; }
public uint256 TransactionHash { get; set; }
Script _ScriptPubKey;
public Script ScriptPubKey
@ -218,12 +223,9 @@ namespace NBXplorer.Models
}
}
public BitcoinAddress Address { get; set; }
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public Script Redeem { get; set; }
IMoney _Value;
public IMoney Value
Money _Value;
public Money Value
{
get
{
@ -237,7 +239,6 @@ namespace NBXplorer.Models
KeyPath _KeyPath;
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public KeyPath KeyPath
{
get
@ -265,19 +266,17 @@ namespace NBXplorer.Models
}
long _Confirmations;
public long Confirmations
uint _Confirmations;
public int Confirmations
{
get
{
return checked((long)_Confirmations);
return checked((int)_Confirmations);
}
set
{
_Confirmations = checked((long)value);
_Confirmations = checked((uint)value);
}
}
public int KeyIndex { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using Newtonsoft.Json.Linq;
namespace NBXplorer.Models
{
public class UnknownEvent : NewEventBase
{
public UnknownEvent()
{
}
public UnknownEvent(string eventType)
{
_EventType = eventType;
}
string _EventType;
public override string EventType => _EventType;
public JObject Data { get; set; }
}
}

View File

@ -1,37 +0,0 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace NBXplorer.Models
{
public class UpdatePSBTRequest
{
[JsonProperty("psbt")]
public PSBT PSBT { get; set; }
public DerivationStrategyBase DerivationScheme { get; set; }
/// <summary>
/// Rebase the hdkey paths (if no rebase, the key paths are relative to the xpub that NBXplorer knows about)
/// This transform (PubKey0, 0/0, accountFingerprint) by (PubKey0, m/49'/0'/0/0, masterFingerprint)
/// </summary>
public List<PSBTRebaseKeyRules> RebaseKeyPaths { get; set; }
/// <summary>
/// Attempt setting non_witness_utxo for all inputs even if they are segwit.
/// </summary>
public bool AlwaysIncludeNonWitnessUTXO { get; set; }
/// <summary>
/// Whether the include the global xpub in the PSBT (default: false)
/// </summary>
public bool? IncludeGlobalXPub { get; set; }
}
public class UpdatePSBTResponse
{
[JsonProperty("psbt")]
public PSBT PSBT { get; set; }
}
}

View File

@ -1,43 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0;netstandard2.1</TargetFrameworks>
<TargetFramework>netcoreapp2.1</TargetFramework>
<Company>Digital Garage</Company>
<Version>5.0.6</Version>
<Version>1.0.2.7</Version>
<Copyright>Copyright © Digital Garage 2017</Copyright>
<Description>Client API for the minimalist HD Wallet Tracker NBXplorer</Description>
<PackageIcon>Bitcoin.png</PackageIcon>
<PackageIconUrl>https://aois.blob.core.windows.net/public/Bitcoin.png</PackageIconUrl>
<PackageTags>bitcoin</PackageTags>
<PackageProjectUrl>https://github.com/btcpayserver/NBXplorer/</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/btcpayserver/NBXplorer</RepositoryUrl>
<PackageProjectUrl>https://github.com/dgarage/NBXplorer/</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/dgarage/NBXplorer/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryUrl>https://github.com/dgarage/NBXplorer/</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageReadmeFile>README.md</PackageReadmeFile>
<LangVersion>12</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
<DefineConstants>$(DefineConstants);NO_RECORD</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591;1573;1572;1584;1570;3021</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="10.0.6" />
<PackageReference Include="NBitcoin.Altcoins" Version="6.0.3" />
<PackageReference Include="NBitcoin" Version="4.1.1.6" />
<PackageReference Include="NBitcoin.Altcoins" Version="1.0.1.4" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="NBXplorer.Tests" />
</ItemGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
<None Include="Bitcoin.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.9" />
</ItemGroup>
</Project>

View File

@ -2,26 +2,44 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace NBXplorer
{
public class NBXplorerDefaultSettings
{
public static string GetFolderName(ChainName chainName)
static NBXplorerDefaultSettings()
{
if (chainName == null)
throw new ArgumentNullException(nameof(chainName));
if (chainName == ChainName.Mainnet)
return "Main";
if (chainName == ChainName.Testnet)
return "TestNet";
if (chainName == ChainName.Regtest)
return "RegTest";
return chainName.ToString();
_Settings = new Dictionary<NetworkType, NBXplorerDefaultSettings>();
foreach(var networkType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
{
var settings = new NBXplorerDefaultSettings();
_Settings.Add(networkType, settings);
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", GetFolderName(networkType));
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultCookieFile = Path.Combine(settings.DefaultDataDirectory, ".cookie");
settings.DefaultPort = (networkType == NetworkType.Mainnet ? 24444 :
networkType == NetworkType.Regtest ? 24446 :
networkType == NetworkType.Testnet ? 24445 : throw new NotSupportedException(networkType.ToString()));
settings.DefaultUrl = new Uri($"http://127.0.0.1:{settings.DefaultPort}/", UriKind.Absolute);
}
}
static Dictionary<ChainName, NBXplorerDefaultSettings> _Settings = new Dictionary<ChainName, NBXplorerDefaultSettings>();
public static string GetFolderName(NetworkType networkType)
{
switch(networkType)
{
case NetworkType.Mainnet:
return "Main";
case NetworkType.Regtest:
return "RegTest";
case NetworkType.Testnet:
return "TestNet";
}
throw new NotSupportedException();
}
static Dictionary<NetworkType, NBXplorerDefaultSettings> _Settings;
public string DefaultDataDirectory
{
get;
@ -48,68 +66,9 @@ namespace NBXplorer
set;
}
public static NBXplorerDefaultSettings GetDefaultSettings(ChainName networkType)
public static NBXplorerDefaultSettings GetDefaultSettings(NetworkType networkType)
{
if (_Settings.TryGetValue(networkType, out var v))
return v;
lock (_Settings)
{
if (_Settings.TryGetValue(networkType, out v))
return v;
var settings = new NBXplorerDefaultSettings();
settings.DefaultDataDirectory = GetDirectory("NBXplorer", GetFolderName(networkType), false);
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultCookieFile = Path.Combine(settings.DefaultDataDirectory, ".cookie");
settings.DefaultPort = (networkType == ChainName.Mainnet ? 24444 :
networkType == ChainName.Regtest ? 24446 :
networkType == ChainName.Testnet ? 24445 : 24447);
settings.DefaultUrl = new Uri($"http://127.0.0.1:{settings.DefaultPort}/", UriKind.Absolute);
_Settings.Add(networkType, settings);
return settings;
}
}
static string GetDirectory(string appDirectory, string subDirectory, bool createIfNotExists = true)
{
string directory = null;
var home = Environment.GetEnvironmentVariable("HOME");
var localAppData = Environment.GetEnvironmentVariable("APPDATA");
if (!string.IsNullOrEmpty(home) && string.IsNullOrEmpty(localAppData))
{
directory = home;
directory = Path.Combine(directory, "." + appDirectory.ToLowerInvariant());
}
else
{
if (!string.IsNullOrEmpty(localAppData))
{
directory = localAppData;
directory = Path.Combine(directory, appDirectory);
}
else if (createIfNotExists)
{
throw new DirectoryNotFoundException("Could not find suitable datadir environment variables HOME or APPDATA are not set");
}
else
return string.Empty;
}
if (createIfNotExists)
{
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
directory = Path.Combine(directory, subDirectory);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
}
else
{
directory = Path.Combine(directory, subDirectory);
}
return directory;
return _Settings[networkType];
}
}
}

View File

@ -1,22 +1,20 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace NBXplorer
{
public class NBXplorerNetwork
{
internal NBXplorerNetwork(INetworkSet networkSet, ChainName networkType)
public NBXplorerNetwork(INetworkSet networkSet, NBitcoin.NetworkType networkType)
{
NBitcoinNetwork = networkSet.GetNetwork(networkType);
CryptoCode = networkSet.CryptoCode;
DefaultSettings = NBXplorerDefaultSettings.GetDefaultSettings(networkType);
}
public static uint256 UnknownTxId = uint256.Parse("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
public static string UnknownAssetId = uint256.Parse("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").ToString();
public static AssetMoney UnknownAssetMoney = new AssetMoney(uint256.Parse("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), 1);
public bool IsElement => NBitcoinNetwork.NetworkSet == NBitcoin.Altcoins.Liquid.Instance;
public Network NBitcoinNetwork
{
get;
@ -39,80 +37,31 @@ namespace NBXplorer
private set;
}
internal virtual DerivationStrategyFactory CreateStrategyFactory()
{
return new DerivationStrategy.DerivationStrategyFactory(NBitcoinNetwork);
}
public DerivationStrategy.DerivationStrategyFactory DerivationStrategyFactory
{
get;
internal set;
}
[Obsolete]
public virtual BitcoinAddress CreateAddress(DerivationStrategyBase derivationStrategy, KeyPath keyPath, Script scriptPubKey)
{
return scriptPubKey.GetDestinationAddress(NBitcoinNetwork);
}
public bool SupportCookieAuthentication
{
get;
internal set;
} = true;
private Serializer _Serializer;
public Serializer Serializer
{
get
{
_Serializer ??= new Serializer(this);
return _Serializer;
}
}
public JsonSerializerSettings JsonSerializerSettings
{
get
{
return Serializer.Settings;
}
}
public TimeSpan ChainLoadingTimeout
{
get;
set;
} = TimeSpan.FromMinutes(15);
public TimeSpan ChainCacheLoadingTimeout
public bool SupportEstimatesSmartFee
{
get;
set;
} = TimeSpan.FromSeconds(30);
/// <summary>
/// Minimum blocks to keep if pruning is activated
/// </summary>
public int MinBlocksToKeep
{
get; set;
} = 288;
public KeyPath CoinType { get; internal set; }
} = true;
public override string ToString()
{
return CryptoCode.ToString();
}
public virtual ExplorerClient CreateExplorerClient(Uri uri)
{
return new ExplorerClient(this, uri);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitAlthash(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Althash.Instance, networkType)
{
MinRPCVersion = 169900,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("172'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetALTHASH()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Althash.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitArgoneum(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Argoneum.Instance, networkType)
{
MinRPCVersion = 1040000,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("421'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetAGM()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Argoneum.Instance.CryptoCode);
}
}
}

View File

@ -1,15 +1,17 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitBCash(ChainName networkType)
public partial class NBXplorerNetworkProvider
{
private void InitBCash(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.BCash.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("145'") : new KeyPath("1'")
MinRPCVersion = 140200
});
}

View File

@ -4,12 +4,11 @@ namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitBGold(ChainName networkType)
private void InitBGold(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.BGold.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
MinRPCVersion = 140200
});
}

View File

@ -1,15 +1,17 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitBitcoin(ChainName networkType)
private void InitBitcoin(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Bitcoin.Instance, networkType)
{
MinRPCVersion = 150000,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'")
MinRPCVersion = 150000
});
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitBitcore(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Bitcore.Instance, networkType)
{
MinRPCVersion = 80007,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetBTX()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Bitcore.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitChaincoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Chaincoin.Instance, networkType)
{
MinRPCVersion = 160400,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("711'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetCHC()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Chaincoin.Instance.CryptoCode);
}
}
}

View File

@ -1,20 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitColossus(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Colossus.Instance, networkType)
{
MinRPCVersion = 1010000
});
}
public NBXplorerNetwork GetCOLX()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Colossus.Instance.CryptoCode);
}
}
}

View File

@ -1,15 +1,17 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitDash(ChainName networkType)
private void InitDash(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Dash.Instance, networkType)
{
MinRPCVersion = 120000,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("5'") : new KeyPath("1'")
MinRPCVersion = 120000
});
}

View File

@ -1,18 +1,20 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitDogecoin(ChainName networkType)
private void InitDogecoin(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Dogecoin.Instance, networkType)
{
MinRPCVersion = 140200,
ChainLoadingTimeout = TimeSpan.FromHours(1),
ChainCacheLoadingTimeout = TimeSpan.FromMinutes(2),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
SupportCookieAuthentication = false,
SupportEstimatesSmartFee = false
});
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitFeathercoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Feathercoin.Instance, networkType)
{
MinRPCVersion = 160000,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetFTC()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Feathercoin.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitGobyte(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.GoByte.Instance, networkType)
{
MinRPCVersion = 120204,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("176'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetGBX()
{
return GetFromCryptoCode(NBitcoin.Altcoins.GoByte.Instance.CryptoCode);
}
}
}

View File

@ -1,16 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitGroestlcoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Groestlcoin.Instance, networkType)
{
MinRPCVersion = 150000,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
});
}
}
}

View File

@ -1,124 +0,0 @@
using NBitcoin;
using System;
using NBitcoin.Altcoins.Elements;
using NBitcoin.Crypto;
using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy;
#if !NO_RECORD
using NBitcoin.WalletPolicies;
#endif
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
public class LiquidNBXplorerNetwork : NBXplorerNetwork
{
internal LiquidNBXplorerNetwork(INetworkSet networkSet, ChainName networkType) : base(networkSet, networkType)
{
}
internal override DerivationStrategyFactory CreateStrategyFactory()
{
var factory = base.CreateStrategyFactory();
factory.AuthorizedOptions.Add("unblinded");
factory.AuthorizedOptions.Add("slip77");
return factory;
}
public BitcoinAddress BlindIfNeeded(DerivationStrategyBase derivationStrategy, BitcoinAddress address, KeyPath keyPath)
{
if (derivationStrategy.Unblinded() || address is BitcoinBlindedAddress)
return address;
var blindingPubKey = GenerateBlindingKey(derivationStrategy, keyPath, address.ScriptPubKey, NBitcoinNetwork).PubKey;
return new BitcoinBlindedAddress(blindingPubKey, address);
}
[Obsolete]
public override BitcoinAddress CreateAddress(DerivationStrategyBase derivationStrategy, KeyPath keyPath, Script scriptPubKey)
{
var addr = scriptPubKey.GetDestinationAddress(NBitcoinNetwork);
return BlindIfNeeded(derivationStrategy, addr, keyPath);
}
public static Key GenerateSlip77BlindingKeyFromMnemonic(Mnemonic mnemonic, Script script)
{
var seed = mnemonic.DeriveSeed();
var slip21 = Slip21Node.FromSeed(seed);
var slip77 = slip21.GetSlip77Node();
return slip77.DeriveSlip77BlindingKey(script);
}
public static Key GenerateSlip77BlindingKeyFromMasterBlindingKey(Key masterBlindingKey, Script script)
{
return new Key(Hashes.HMACSHA256(masterBlindingKey.ToBytes(), script.ToBytes()));
}
public static Key GenerateBlindingKey(DerivationStrategyBase derivationStrategy, KeyPath keyPath, Script scriptPubKey, Network network)
{
if (derivationStrategy.Unblinded())
{
throw new InvalidOperationException("This derivation scheme is set to only track unblinded addresses");
}
if (derivationStrategy.Slip77(out var key))
{
if (HexEncoder.IsWellFormed(key))
{
return GenerateSlip77BlindingKeyFromMasterBlindingKey(new Key(Encoders.Hex.DecodeData(key)), scriptPubKey);
}
try
{
return GenerateSlip77BlindingKeyFromMasterBlindingKey(Key.Parse(key, network), scriptPubKey);
}
catch (Exception)
{
// ignored
}
try
{
var data = new Mnemonic(key);
return GenerateSlip77BlindingKeyFromMnemonic(data, scriptPubKey);
}
catch (Exception)
{
// ignored
}
throw new InvalidOperationException("The key provided for slip77 derivation was invalid.");
}
else if (derivationStrategy is StandardDerivationStrategyBase kpd && keyPath is not null)
{
var blindingKey = new Key(kpd.GetDerivation(keyPath.Derive(new KeyPath(0))).ScriptPubKey.WitHash.ToBytes());
return blindingKey;
}
throw new InvalidOperationException("-[blinded] doesn't work on miniscript derivation strategies, use [slip77=key] instead");
}
}
private void InitLiquid(ChainName networkType)
{
Add(new LiquidNBXplorerNetwork(NBitcoin.Altcoins.Liquid.Instance, networkType)
{
MinRPCVersion = 150000,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
});
}
public NBXplorerNetwork GetLBTC()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Liquid.Instance.CryptoCode);
}
}
public static class LiquidDerivationStrategyOptionsExtensions
{
public static bool Unblinded(this DerivationStrategyBase derivationStrategyBase)
{
return derivationStrategyBase.AdditionalOptions.TryGetValue("unblinded", out _);
}
public static bool Slip77(this DerivationStrategyBase derivationStrategyBase ,out string key)
{
return derivationStrategyBase.AdditionalOptions.TryGetValue("slip77", out key);
}
}
}

View File

@ -1,15 +1,17 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitLitecoin(ChainName networkType)
private void InitLitecoin(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Litecoin.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
MinRPCVersion = 140200
});
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitMonacoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Monacoin.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetMONA()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Monacoin.Instance.CryptoCode);
}
}
}

View File

@ -1,20 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitMonetaryUnit(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.MonetaryUnit.Instance, networkType)
{
MinRPCVersion = 70702
});
}
public NBXplorerNetwork GetMUE()
{
return GetFromCryptoCode(NBitcoin.Altcoins.MonetaryUnit.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitMonoeci(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Monoeci.Instance, networkType)
{
MinRPCVersion = 120203,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1998'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetXMCC()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Monoeci.Instance.CryptoCode);
}
}
}

View File

@ -1,24 +0,0 @@
using NBitcoin;
using System;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitPepecoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Pepecoin.Instance, networkType)
{
MinRPCVersion = 10000,
ChainLoadingTimeout = TimeSpan.FromHours(1),
ChainCacheLoadingTimeout = TimeSpan.FromMinutes(2),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3434'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetPEPE()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Pepecoin.Instance.CryptoCode);
}
}
}

View File

@ -1,15 +1,17 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitPolis(ChainName networkType)
private void InitPolis(NetworkType networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Polis.Instance, networkType)
{
MinRPCVersion = 1030000,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
MinRPCVersion = 1030000
});
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitQtum(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Qtum.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("2301'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetQTUM()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Qtum.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitTerracoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Terracoin.Instance, networkType)
{
MinRPCVersion = 120204,
CoinType = networkType == ChainName.Mainnet ? new KeyPath("83'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetTRC()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Terracoin.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitUfo(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Ufo.Instance, networkType)
{
MinRPCVersion = 150000,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetUFO()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Ufo.Instance.CryptoCode);
}
}
}

View File

@ -1,21 +0,0 @@
using NBitcoin;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
private void InitViacoin(ChainName networkType)
{
Add(new NBXplorerNetwork(NBitcoin.Altcoins.Viacoin.Instance, networkType)
{
MinRPCVersion = 140200,
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
});
}
public NBXplorerNetwork GetVIA()
{
return GetFromCryptoCode(NBitcoin.Altcoins.Viacoin.Instance.CryptoCode);
}
}
}

View File

@ -1,44 +1,29 @@
using NBitcoin;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer
{
public partial class NBXplorerNetworkProvider
{
public NBXplorerNetworkProvider(ChainName networkType)
public NBXplorerNetworkProvider(NetworkType networkType)
{
NetworkType = networkType;
InitArgoneum(networkType);
InitBitcoin(networkType);
InitBitcore(networkType);
InitLitecoin(networkType);
InitDogecoin(networkType);
InitPepecoin(networkType);
InitBCash(networkType);
InitGroestlcoin(networkType);
InitBGold(networkType);
InitDash(networkType);
InitTerracoin(networkType);
InitPolis(networkType);
InitMonacoin(networkType);
InitFeathercoin(networkType);
InitUfo(networkType);
InitViacoin(networkType);
InitMonoeci(networkType);
InitGobyte(networkType);
InitColossus(networkType);
InitChaincoin(networkType);
InitLiquid(networkType);
InitQtum(networkType);
InitAlthash(networkType);
InitMonetaryUnit(networkType);
foreach (var chain in _Networks.Values)
NetworkType = networkType;
foreach(var chain in _Networks.Values)
{
chain.DerivationStrategyFactory ??= chain.CreateStrategyFactory();
chain.DerivationStrategyFactory = new DerivationStrategy.DerivationStrategyFactory(chain.NBitcoinNetwork);
}
}
public ChainName NetworkType
public NetworkType NetworkType
{
get;
private set;
@ -46,7 +31,7 @@ namespace NBXplorer
public NBXplorerNetwork GetFromCryptoCode(string cryptoCode)
{
_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out NBXplorerNetwork network);
_Networks.TryGetValue(cryptoCode, out NBXplorerNetwork network);
return network;
}
@ -58,8 +43,6 @@ namespace NBXplorer
Dictionary<string, NBXplorerNetwork> _Networks = new Dictionary<string, NBXplorerNetwork>();
private void Add(NBXplorerNetwork network)
{
if (network.NBitcoinNetwork == null)
return;
_Networks.Add(network.CryptoCode, network);
}
}

View File

@ -1,128 +0,0 @@
#nullable enable
using System;
using System.Collections.Generic;
using NBXplorer.DerivationStrategy;
namespace NBitcoin;
public static class NBitcoinNBXplorerExtensions
{
/// <summary>
/// Filter the keys which contains the <paramref name="accountKey"/> and <paramref name="accountKeyPath"/> in the HDKeys and whose input/output
/// the same scriptPubKeys as <paramref name="derivationStrategy"/>.
/// </summary>
/// <param name="psbt">The PSBT from which to get the keys</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <param name="accountKeyPath">The account key path</param>
/// <returns>HD Keys matching master root key</returns>
public static IEnumerable<PSBTHDKeyMatch> HDKeysFor(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey,
RootedKeyPath? accountKeyPath)
{
if (ToHDScriptPubKey(derivationStrategy, accountKey) is {} hd)
return psbt.HDKeysFor(hd, accountKey, accountKeyPath);
return Array.Empty<PSBTHDKeyMatch>();
}
/// <summary>
/// Filter the keys that contain the <paramref name="accountKey"/> in the HDKeys and whose input/output
/// the same scriptPubKeys as <paramref name="derivationStrategy"/>.
/// </summary>
/// <param name="psbt">The PSBT from which to get the keys</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <returns>HD Keys matching master root key</returns>
public static IEnumerable<PSBTHDKeyMatch> HDKeysFor(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey)
=> HDKeysFor(psbt, derivationStrategy, accountKey, null);
/// <summary>
/// Filter the keys which contains the <paramref name="accountKey"/> and <paramref name="accountKeyPath"/> in the HDKeys and whose input/output
/// the same scriptPubKeys as <paramref name="derivationStrategy"/>.
/// </summary>
/// <param name="coin">The coins to get the keys from</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <param name="accountKeyPath">The account key path</param>
/// <returns>HD Keys matching master root key</returns>
public static IEnumerable<PSBTHDKeyMatch> HDKeysFor(this PSBTCoin coin, DerivationStrategyBase derivationStrategy, IHDKey accountKey,
RootedKeyPath? accountKeyPath)
{
if (ToHDScriptPubKey(derivationStrategy, accountKey) is {} hd)
return coin.HDKeysFor(hd, accountKey, accountKeyPath);
return Array.Empty<PSBTHDKeyMatch>();
}
static IHDScriptPubKey? ToHDScriptPubKey(DerivationStrategyBase derivationStrategy, IHDKey accountKey)
{
if (derivationStrategy is null)
throw new ArgumentNullException(nameof(derivationStrategy));
if (derivationStrategy is StandardDerivationStrategyBase standard)
return standard;
#if !NO_RECORD
else if (derivationStrategy is PolicyDerivationStrategy policy && policy.GetHDScriptPubKey(accountKey) is IHDScriptPubKey hd)
return hd;
#endif
return null;
}
/// <summary>
/// Filter the keys that contain the <paramref name="accountKey"/> in the HDKeys and whose input/output
/// the same scriptPubKeys as <paramref name="derivationStrategy"/>.
/// </summary>
/// <param name="coin">The coins to get the keys from</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <returns>HD Keys matching master root key</returns>
public static IEnumerable<PSBTHDKeyMatch> HDKeysFor(this PSBTCoin coin, DerivationStrategyBase derivationStrategy, IHDKey accountKey)
=> HDKeysFor(coin, derivationStrategy, accountKey, null);
/// <summary>
/// Get the balance change if you were signing this transaction.
/// </summary>
/// <param name="psbt">The PSBT from which to get the balance</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <param name="accountKeyPath">The account key path</param>
/// <returns>The balance change</returns>
public static Money GetBalance(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey, RootedKeyPath? accountKeyPath)
{
if (ToHDScriptPubKey(derivationStrategy, accountKey) is {} hd)
return psbt.GetBalance(hd, accountKey, accountKeyPath);
return Money.Zero;
}
/// <summary>
/// Get the balance change if you were signing this transaction.
/// </summary>
/// <param name="psbt">The PSBT from which to get the balance</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key that will be used to sign (i.e., 49'/0'/0')</param>
/// <returns>The balance change</returns>
public static Money GetBalance(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey)
=> GetBalance(psbt, derivationStrategy, accountKey, null);
/// <summary>
/// Sign all inputs that derive addresses from <paramref name="derivationStrategy"/> and that need to be signed by <paramref name="accountKey"/>.
/// </summary>
/// <param name="psbt">The PSBT to sign</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key with which to sign</param>
/// <param name="accountKeyPath">The account key path (eg. [masterFP]/49'/0'/0')</param>
/// <returns>The signed PSBT</returns>
public static PSBT SignAll(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey, RootedKeyPath? accountKeyPath)
{
if (ToHDScriptPubKey(derivationStrategy, accountKey) is {} hd)
return psbt.SignAll(hd, accountKey, accountKeyPath);
return psbt;
}
/// <summary>
/// Sign all inputs that derive addresses from <paramref name="derivationStrategy"/> and that need to be signed by <paramref name="accountKey"/>.
/// </summary>
/// <param name="psbt">The PSBT to sign</param>
/// <param name="derivationStrategy">The derivation scheme</param>
/// <param name="accountKey">The account key with which to sign</param>
/// <returns>The signed PSBT</returns>
public static PSBT SignAll(this PSBT psbt, DerivationStrategyBase derivationStrategy, IHDKey accountKey)
=> SignAll(psbt, derivationStrategy, accountKey, null);
}

View File

@ -1,96 +1,21 @@
using NBitcoin;
using System.Linq;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin.Protocol;
namespace NBXplorer
{
public class WebsocketNotificationSessionLegacy : WebsocketNotificationSession
{
protected override FormattableString GetConnectPath() => $"v1/cryptos/{_Client.CryptoCode}/connect";
internal WebsocketNotificationSessionLegacy(ExplorerClient client) : base(client)
{
}
public void ListenNewBlock(CancellationToken cancellation = default)
{
ListenNewBlockAsync(cancellation).GetAwaiter().GetResult();
}
public Task ListenNewBlockAsync(CancellationToken cancellation = default)
{
return _MessageListener.Send(new Models.NewBlockEventRequest() { CryptoCode = _Client.CryptoCode }, null, cancellation);
}
/// <summary>
/// Listen all derivation schemes
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public void ListenAllDerivationSchemes(bool allCryptoCodes = false, CancellationToken cancellation = default)
{
ListenAllDerivationSchemesAsync(allCryptoCodes, cancellation).GetAwaiter().GetResult();
}
/// <summary>
/// Listen all derivation schemes
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public Task ListenAllDerivationSchemesAsync(bool allCryptoCodes = false, CancellationToken cancellation = default)
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { CryptoCode = allCryptoCodes ? "*" : _Client.CryptoCode }, null, cancellation);
}
/// <summary>
/// Listen all tracked source
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public void ListenAllTrackedSource(bool allCryptoCodes = false, CancellationToken cancellation = default)
{
ListenAllTrackedSourceAsync(allCryptoCodes, cancellation).GetAwaiter().GetResult();
}
/// <summary>
/// Listen all tracked source
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public Task ListenAllTrackedSourceAsync(bool allCryptoCodes = false, CancellationToken cancellation = default)
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { CryptoCode = allCryptoCodes ? "*" : _Client.CryptoCode, ListenAllTrackedSource = true }, null, cancellation);
}
public void ListenDerivationSchemes(DerivationStrategyBase[] derivationSchemes, CancellationToken cancellation = default)
{
ListenDerivationSchemesAsync(derivationSchemes, cancellation).GetAwaiter().GetResult();
}
public Task ListenDerivationSchemesAsync(DerivationStrategyBase[] derivationSchemes, CancellationToken cancellation = default)
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { DerivationSchemes = derivationSchemes.Select(d => d.ToString()).ToArray(), CryptoCode = _Client.CryptoCode }, null, cancellation);
}
public void ListenTrackedSources(TrackedSource[] trackedSources, CancellationToken cancellation = default)
{
ListenTrackedSourcesAsync(trackedSources, cancellation).GetAwaiter().GetResult();
}
public Task ListenTrackedSourcesAsync(TrackedSource[] trackedSources, CancellationToken cancellation = default)
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { TrackedSources = trackedSources.Select(d => d.ToString()).ToArray(), CryptoCode = _Client.CryptoCode }, null, cancellation);
}
}
public class WebsocketNotificationSession : NotificationSessionBase, IDisposable
public class NotificationSession : IDisposable
{
protected readonly ExplorerClient _Client;
private readonly ExplorerClient _Client;
public ExplorerClient Client
{
get
@ -98,7 +23,7 @@ namespace NBXplorer
return _Client;
}
}
internal WebsocketNotificationSession(ExplorerClient client)
internal NotificationSession(ExplorerClient client)
{
if(client == null)
throw new ArgumentNullException(nameof(client));
@ -107,30 +32,28 @@ namespace NBXplorer
internal async Task ConnectAsync(CancellationToken cancellation)
{
var uri = _Client.GetFullUri(GetConnectPath());
var uri = _Client.GetFullUri($"v1/cryptos/{_Client.CryptoCode}/connect", null);
uri = ToWebsocketUri(uri);
WebSocket socket = null;
try
{
socket = await ConnectAsyncCore(uri, cancellation);
}
catch (WebSocketException) // For some reason the ErrorCode is not properly set, so we can check for error 401
catch(WebSocketException) // For some reason the ErrorCode is not properly set, so we can check for error 401
{
if (!_Client.Auth.RefreshCache())
if(!_Client._Auth.RefreshCache())
throw;
socket = await ConnectAsyncCore(uri, cancellation);
}
JsonSerializerSettings settings = new JsonSerializerSettings();
new Serializer(_Client.Network).ConfigureSerializer(settings);
new Serializer(_Client.Network.NBitcoinNetwork).ConfigureSerializer(settings);
_MessageListener = new WebsocketMessageListener(socket, settings);
}
protected virtual FormattableString GetConnectPath() => $"v1/cryptos/connect?cryptoCode={_Client.Network.CryptoCode}";
private async Task<ClientWebSocket> ConnectAsyncCore(string uri, CancellationToken cancellation)
{
var socket = new ClientWebSocket();
_Client.Auth.SetWebSocketAuth(socket);
_Client._Auth.SetWebSocketAuth(socket);
try
{
await socket.ConnectAsync(new Uri(uri, UriKind.Absolute), cancellation).ConfigureAwait(false);
@ -148,14 +71,58 @@ namespace NBXplorer
return uri;
}
protected WebsocketMessageListener _MessageListener;
WebsocketMessageListener _MessageListener;
UTF8Encoding UTF8 = new UTF8Encoding(false, true);
public override Task<NewEventBase> NextEventAsync(CancellationToken cancellation = default)
public void ListenNewBlock(CancellationToken cancellation = default(CancellationToken))
{
ListenNewBlockAsync(cancellation).GetAwaiter().GetResult();
}
public Task ListenNewBlockAsync(CancellationToken cancellation = default(CancellationToken))
{
return _MessageListener.Send(new Models.NewBlockEventRequest() { CryptoCode = _Client.CryptoCode }, cancellation);
}
/// <summary>
/// Listen all derivation schemes
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public void ListenAllDerivationSchemes(bool allCryptoCodes = false, CancellationToken cancellation = default(CancellationToken))
{
ListenAllDerivationSchemesAsync(allCryptoCodes, cancellation).GetAwaiter().GetResult();
}
/// <summary>
/// Listen all derivation schemes
/// </summary>
/// <param name="allCryptoCodes">If true, all derivation schemes of all crypto code will get a notification (default: false)</param>
/// <param name="cancellation">Cancellation token</param>
public Task ListenAllDerivationSchemesAsync(bool allCryptoCodes = false, CancellationToken cancellation = default(CancellationToken))
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { CryptoCode = allCryptoCodes ? "*" : _Client.CryptoCode }, cancellation);
}
public void ListenDerivationSchemes(DerivationStrategyBase[] derivationSchemes, CancellationToken cancellation = default(CancellationToken))
{
ListenDerivationSchemesAsync(derivationSchemes, cancellation).GetAwaiter().GetResult();
}
public Task ListenDerivationSchemesAsync(DerivationStrategyBase[] derivationSchemes, CancellationToken cancellation = default(CancellationToken))
{
return _MessageListener.Send(new Models.NewTransactionEventRequest() { DerivationSchemes = derivationSchemes.Select(d=>d.ToString()).ToArray(), CryptoCode = _Client.CryptoCode }, cancellation);
}
public object NextEvent(CancellationToken cancellation = default(CancellationToken))
{
return NextEventAsync(cancellation).GetAwaiter().GetResult();
}
public Task<object> NextEventAsync(CancellationToken cancellation = default(CancellationToken))
{
return _MessageListener.NextMessageAsync(cancellation);
}
public Task DisposeAsync(CancellationToken cancellation = default)
public Task DisposeAsync(CancellationToken cancellation = default(CancellationToken))
{
return _MessageListener.DisposeAsync(cancellation);
}

View File

@ -1,16 +0,0 @@
using NBXplorer.Models;
using System.Threading;
using System.Threading.Tasks;
namespace NBXplorer
{
public abstract class NotificationSessionBase
{
public NewEventBase NextEvent(CancellationToken cancellation = default)
{
return NextEventAsync(cancellation).GetAwaiter().GetResult();
}
public abstract Task<NewEventBase> NextEventAsync(CancellationToken cancellation = default);
}
}

Some files were not shown because too many files have changed in this diff Show More