Compare commits
1 Commits
master
...
smalldocke
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5903865a3f |
@ -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-]+)/
|
||||
@ -1,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cd ../NBXplorer.Tests
|
||||
docker-compose -v
|
||||
docker-compose build
|
||||
docker-compose run tests
|
||||
@ -121,4 +121,5 @@ bower_components
|
||||
output
|
||||
|
||||
.vs
|
||||
NBXplorer.Tests/
|
||||
**/launchSettings.json
|
||||
17
.gitattributes
vendored
17
.gitattributes
vendored
@ -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
4
.gitignore
vendored
@ -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
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@ -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"]
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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 |
@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
NBXplorer.Client/JsonConverters/BookmarkJsonConverter.cs
Normal file
36
NBXplorer.Client/JsonConverters/BookmarkJsonConverter.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
40
NBXplorer.Client/JsonConverters/FeeRateJsonConverter.cs
Normal file
40
NBXplorer.Client/JsonConverters/FeeRateJsonConverter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
60
NBXplorer.Client/Models/Bookmark.cs
Normal file
60
NBXplorer.Client/Models/Bookmark.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
using NBitcoin.RPC;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin.RPC;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NBXplorer.Models;
|
||||
|
||||
public class ImportUTXORequest
|
||||
{
|
||||
[JsonProperty("UTXOs")]
|
||||
public OutPoint[] Utxos { get; set; }
|
||||
}
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class PruneRequest
|
||||
{
|
||||
public double? DaysToKeep { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class PruneResponse
|
||||
{
|
||||
public int TotalPruned { get; set; }
|
||||
}
|
||||
}
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
29
NBXplorer.Client/Models/TransactionMatch.cs
Normal file
29
NBXplorer.Client/Models/TransactionMatch.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
@ -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>();
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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'")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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
Loading…
Reference in New Issue
Block a user