Compare commits
433 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5147b5f261 | ||
|
|
6ad4941712 | ||
|
|
98d89f924a | ||
|
|
36e1596b5c | ||
|
|
83e0ab4f68 | ||
|
|
c24b47bb03 | ||
|
|
b9f324b2eb | ||
|
|
e0af3bf493 | ||
|
|
06349f9818 | ||
|
|
c46949ac60 | ||
|
|
5e570a84fb | ||
|
|
060d19f6a8 | ||
|
|
b83178be8d | ||
|
|
f72fc4d321 | ||
|
|
9242d645b6 | ||
|
|
05df9b5037 | ||
|
|
456717935c | ||
|
|
98df6b7fdd | ||
|
|
449568be9a | ||
|
|
abb5fd3a6c | ||
|
|
deb868b2c0 | ||
|
|
f0000ceab6 | ||
|
|
d9cba8b0b5 | ||
|
|
becdc3f47d | ||
|
|
aa462e7af1 | ||
|
|
34d6b0dcf3 | ||
|
|
87037e17e1 | ||
|
|
c806e9022e | ||
|
|
39b1861d7a | ||
|
|
a379506b46 | ||
|
|
68513b4668 | ||
|
|
802945291a | ||
|
|
3756b54138 | ||
|
|
9eeaf71195 | ||
|
|
0e4222a1f2 | ||
|
|
34831b2d85 | ||
|
|
aba8cdc8c6 | ||
|
|
ba76d3b059 | ||
|
|
26b3dddc28 | ||
|
|
1a38ffa497 | ||
|
|
2fb63ae18e | ||
|
|
206fd0017f | ||
|
|
5d78c7034c | ||
|
|
f226238779 | ||
|
|
05d8ed054e | ||
|
|
4ebd7c4c2f | ||
|
|
8670e58923 | ||
|
|
5ea76f64bb | ||
|
|
be086282ae | ||
|
|
58bde3c916 | ||
|
|
8af448f724 | ||
|
|
273333b040 | ||
|
|
d03465c240 | ||
|
|
58e9bba5a2 | ||
|
|
c58c60c9fa | ||
|
|
939a575c51 | ||
|
|
ca97104831 | ||
|
|
a51911f4ce | ||
|
|
e8316d959b | ||
|
|
be51a4af50 | ||
|
|
b7dc5b5c5f | ||
|
|
cdac2c264e | ||
|
|
e494a46de9 | ||
|
|
10ac6d65d8 | ||
|
|
7f84ef983c | ||
|
|
9a784b5cce | ||
|
|
dfe7ca478f | ||
|
|
ea4ac907b6 | ||
|
|
fa8bf56623 | ||
|
|
f4640c8c4f | ||
|
|
fdc8281e88 | ||
|
|
8597067cbe | ||
|
|
ae99da7299 | ||
|
|
4666c13a1b | ||
|
|
3118b71004 | ||
|
|
d61f3db4fa | ||
|
|
10e139c3d1 | ||
|
|
206165736a | ||
|
|
f68c50c4cf | ||
|
|
75cbba9427 | ||
|
|
1916ea7050 | ||
|
|
e43031411d | ||
|
|
7b7cd2314d | ||
|
|
4ce8ef6954 | ||
|
|
80f16ce8bc | ||
|
|
fed67b354f | ||
|
|
da251bdbe0 | ||
|
|
11a3564ab4 | ||
|
|
e3f7933ad1 | ||
|
|
8b9d5fbde7 | ||
|
|
916a52b49e | ||
|
|
2ed4f7850f | ||
|
|
a38ad24023 | ||
|
|
ab27e4b6ce | ||
|
|
e9a235c149 | ||
|
|
03c17c046d | ||
|
|
530a38030e | ||
|
|
9d796828f8 | ||
|
|
eaeeea22a9 | ||
|
|
5170fd92e1 | ||
|
|
e5eaf763e7 | ||
|
|
326d9c5e2b | ||
|
|
8ea14672dd | ||
|
|
4d81c236f5 | ||
|
|
a2afbd951e | ||
|
|
35b449a49f | ||
|
|
5ab3da5031 | ||
|
|
dbfd09e2f9 | ||
|
|
24d24f2f25 | ||
|
|
1eeb0ff3ef | ||
|
|
b31fa111b4 | ||
|
|
fb2691748f | ||
|
|
c64dc124b5 | ||
|
|
97214740c1 | ||
|
|
3e0b2047d3 | ||
|
|
2fa2ca69a0 | ||
|
|
f37ef1c2d5 | ||
|
|
f165b14c52 | ||
|
|
c249b842ef | ||
|
|
531f28817e | ||
|
|
654b103128 | ||
|
|
21122b4a1f | ||
|
|
e547b99a47 | ||
|
|
88f7d8248a | ||
|
|
a201004b42 | ||
|
|
0bf9492e1d | ||
|
|
8275c6effb | ||
|
|
3d746aeff9 | ||
|
|
8f7371dc47 | ||
|
|
048eab8625 | ||
|
|
d4068e7dec | ||
|
|
1352c8dcdf | ||
|
|
e2767a85c5 | ||
|
|
09d56b22be | ||
|
|
1ee089836d | ||
|
|
4c9db11e6c | ||
|
|
dddd825571 | ||
|
|
42c9dda280 | ||
|
|
9af5962b1e | ||
|
|
d5b56d3206 | ||
|
|
ddde4ae301 | ||
|
|
a76965d12c | ||
|
|
7fb40b5a81 | ||
|
|
2e75bdf65e | ||
|
|
ac6e63c20f | ||
|
|
5519caf601 | ||
|
|
1814138225 | ||
|
|
d612420059 | ||
|
|
f3aeea447d | ||
|
|
261a1e73a1 | ||
|
|
7c870bdeaa | ||
|
|
15a07a8b47 | ||
|
|
52258aecd1 | ||
|
|
b672a28c1c | ||
|
|
a33de3bec7 | ||
|
|
f98fa1addb | ||
|
|
b5b90871ae | ||
|
|
e937448b6b | ||
|
|
c84c0fce97 | ||
|
|
4456dd04d0 | ||
|
|
6ba0315de4 | ||
|
|
2c6418c058 | ||
|
|
f978e88b85 | ||
|
|
22b8e0b17a | ||
|
|
2bd46f7820 | ||
|
|
d03b511db6 | ||
|
|
0c9a3569e6 | ||
|
|
f200ea4930 | ||
|
|
dfef4e8c7e | ||
|
|
4bf8e25902 | ||
|
|
019f8c420a | ||
|
|
a2dc680338 | ||
|
|
72a102a8fc | ||
|
|
e7c701f3f7 | ||
|
|
7c710e34b7 | ||
|
|
efc9ae6444 | ||
|
|
b20d1567e0 | ||
|
|
21f5f04f64 | ||
|
|
825d5bc78d | ||
|
|
e83cfdb720 | ||
|
|
96633a3b7d | ||
|
|
900f99545d | ||
|
|
082373501e | ||
|
|
279521c507 | ||
|
|
2000affe7b | ||
|
|
7d70e72d91 | ||
|
|
9b6358221a | ||
|
|
5d4d028b0e | ||
|
|
07b1193237 | ||
|
|
56d2293c30 | ||
|
|
84305c84d8 | ||
|
|
fffa6f0bca | ||
|
|
15eee7ae47 | ||
|
|
a490c24351 | ||
|
|
6eb53ed8d2 | ||
|
|
6ca2560a1a | ||
|
|
f69581f69e | ||
|
|
190eabbc5e | ||
|
|
826dc2d4ac | ||
|
|
95f28ac578 | ||
|
|
8d309e4de5 | ||
|
|
9bcf963a4a | ||
|
|
85f508756a | ||
|
|
5ddba1d816 | ||
|
|
eca963def6 | ||
|
|
cfedff079d | ||
|
|
ceef9651f3 | ||
|
|
1d89e331ed | ||
|
|
7860ebbda4 | ||
|
|
2f855b5cc3 | ||
|
|
8ec608f2bb | ||
|
|
4260cb8e04 | ||
|
|
9b6c0010fb | ||
|
|
a01893d09d | ||
|
|
57048b0d05 | ||
|
|
9a64c246b8 | ||
|
|
fa26cd4fcf | ||
|
|
3b192971f0 | ||
|
|
02e4714bce | ||
|
|
342a16ad07 | ||
|
|
31a1ab4c89 | ||
|
|
5de2a002a0 | ||
|
|
9faab8f97d | ||
|
|
dbef90b927 | ||
|
|
30c1536323 | ||
|
|
ef1f78b9e9 | ||
|
|
d7a9af1d7a | ||
|
|
1ac249db79 | ||
|
|
c547f20d20 | ||
|
|
a9a8f56d73 | ||
|
|
42dbfdb59d | ||
|
|
f8707aca8b | ||
|
|
f76543311d | ||
|
|
8a051016f4 | ||
|
|
506388937e | ||
|
|
600a642ebc | ||
|
|
3cbb864713 | ||
|
|
25a3f579d8 | ||
|
|
a858d56552 | ||
|
|
06ad54141e | ||
|
|
ef84e74bbb | ||
|
|
3579fcd226 | ||
|
|
fbf8787ad0 | ||
|
|
f6e1a5c4df | ||
|
|
e03ef32ab9 | ||
|
|
26f23c0e75 | ||
|
|
299032a210 | ||
|
|
f2b91e3513 | ||
|
|
c7b5a73ea1 | ||
|
|
4385f75d39 | ||
|
|
a9059c1fba | ||
|
|
50532c4a92 | ||
|
|
73e6100a19 | ||
|
|
ebec46553c | ||
|
|
7cd88f88da | ||
|
|
0534c50898 | ||
|
|
70d608bc09 | ||
|
|
d0ad70f6cb | ||
|
|
f35be21e74 | ||
|
|
faf7621545 | ||
|
|
092018cb6f | ||
|
|
c2c4cb7709 | ||
|
|
994dbfe55b | ||
|
|
b1a14c1bd3 | ||
|
|
256f62385b | ||
|
|
bc92de9db4 | ||
|
|
8ada11e7cb | ||
|
|
f9a5cdc999 | ||
|
|
898258d87f | ||
|
|
f07649fe1c | ||
|
|
9dfd5f6adf | ||
|
|
cfb9aa9506 | ||
|
|
d7c2da3f11 | ||
|
|
9998c66492 | ||
|
|
5437af5975 | ||
|
|
a8c5bdc615 | ||
|
|
67edae79d0 | ||
|
|
32b048f0b6 | ||
|
|
ab8c365ae5 | ||
|
|
919a1f68d7 | ||
|
|
d42378ff91 | ||
|
|
b287ea5521 | ||
|
|
91e76440d0 | ||
|
|
64376a41e1 | ||
|
|
369d3579e9 | ||
|
|
795af96df2 | ||
|
|
28db6d419f | ||
|
|
f3d4508e69 | ||
|
|
b64b7abc04 | ||
|
|
64cfb2fc90 | ||
|
|
06aa2b5dcc | ||
|
|
9eeb5c6d82 | ||
|
|
8768d129b6 | ||
|
|
a064c60932 | ||
|
|
f2e9615f9b | ||
|
|
7dd960dfee | ||
|
|
578426da15 | ||
|
|
8d09c57c7d | ||
|
|
db3142b92f | ||
|
|
4a0e16861b | ||
|
|
b73cb2e110 | ||
|
|
ad90a241a6 | ||
|
|
7045800e48 | ||
|
|
b5592e0106 | ||
|
|
4874373783 | ||
|
|
194f16c7a5 | ||
|
|
c7b6885eed | ||
|
|
b0b7e98cb9 | ||
|
|
1c535e62d4 | ||
|
|
bdd32e1147 | ||
|
|
45f9a4adc9 | ||
|
|
f28bb22c7f | ||
|
|
000376564e | ||
|
|
416a0e7bd2 | ||
|
|
1f18ac5bd2 | ||
|
|
8332e05023 | ||
|
|
639ad9ba45 | ||
|
|
8e069c5b9b | ||
|
|
33bfb8b9e7 | ||
|
|
15f6c42245 | ||
|
|
23a8e335e4 | ||
|
|
0801eca1eb | ||
|
|
c6ae43adb2 | ||
|
|
0a09a4ba1e | ||
|
|
ebac7ce987 | ||
|
|
a4456088e8 | ||
|
|
a906570f08 | ||
|
|
6becdca483 | ||
|
|
086fc3b42c | ||
|
|
b9fd115c6c | ||
|
|
844af377e2 | ||
|
|
d17f60913a | ||
|
|
19c57353e6 | ||
|
|
a993e45266 | ||
|
|
4e13f90605 | ||
|
|
8a6f6dee9c | ||
|
|
00df1e03be | ||
|
|
f063979667 | ||
|
|
9f691db055 | ||
|
|
07323f16ea | ||
|
|
1ddf28ee2a | ||
|
|
fd61110a1e | ||
|
|
754278f473 | ||
|
|
af2ee4c09c | ||
|
|
381c90fc0d | ||
|
|
030084cdfa | ||
|
|
38ddc7d211 | ||
|
|
a1205d8f97 | ||
|
|
f381e00023 | ||
|
|
4447eea417 | ||
|
|
2fb8e2630f | ||
|
|
f7649f490a | ||
|
|
916329e4ff | ||
|
|
788e800b44 | ||
|
|
51a300b70d | ||
|
|
848c6c2665 | ||
|
|
9d601ec868 | ||
|
|
2a493b911a | ||
|
|
da8b7b7653 | ||
|
|
5cc4389cda | ||
|
|
b3fcda0cf6 | ||
|
|
7cf5f33443 | ||
|
|
b8a4a38184 | ||
|
|
dda0bac750 | ||
|
|
713ff5e256 | ||
|
|
4d32024b90 | ||
|
|
bc72700847 | ||
|
|
969b73a49e | ||
|
|
cd1f5df749 | ||
|
|
2075f2200f | ||
|
|
05235a6de0 | ||
|
|
5238f1f2c7 | ||
|
|
c8e9d2f135 | ||
|
|
72830a49c4 | ||
|
|
4128f8b068 | ||
|
|
0939c00d13 | ||
|
|
8e060f545d | ||
|
|
bbcb4e53e8 | ||
|
|
af9d7ce7a5 | ||
|
|
0595a87f22 | ||
|
|
a1dead87c3 | ||
|
|
e4700d21db | ||
|
|
52585b3348 | ||
|
|
4aa2cce05c | ||
|
|
37c49b356f | ||
|
|
ce7819bdc3 | ||
|
|
d0bb7cf9ac | ||
|
|
05e443fa38 | ||
|
|
3692c797fc | ||
|
|
ea99e3cc9e | ||
|
|
0b659c82e2 | ||
|
|
f8b4c650ba | ||
|
|
ba274bfeb1 | ||
|
|
afb16b5890 | ||
|
|
9091abff44 | ||
|
|
1fff142f6f | ||
|
|
9a08aef0fb | ||
|
|
229bf48bee | ||
|
|
55fbdcb9cd | ||
|
|
53eb9d08b8 | ||
|
|
9d235e28dd | ||
|
|
9fe95038a4 | ||
|
|
893ea4eb94 | ||
|
|
fea82a5f57 | ||
|
|
f7aaa1ae91 | ||
|
|
bdd741e753 | ||
|
|
79fbbbf523 | ||
|
|
86d00dd152 | ||
|
|
e15c7c3969 | ||
|
|
273fffa950 | ||
|
|
ee8f65b251 | ||
|
|
8707ac15a6 | ||
|
|
cb5877d959 | ||
|
|
813a49ad99 | ||
|
|
acf2db03e2 | ||
|
|
ccac0d6463 | ||
|
|
a03a897a0b | ||
|
|
4f9a0688b6 | ||
|
|
3944aca2c0 | ||
|
|
4a35012713 | ||
|
|
88a437fc60 | ||
|
|
6c708a4c07 | ||
|
|
79c9196f22 | ||
|
|
7d44d4cb74 | ||
|
|
89ada4a172 | ||
|
|
b96763eca4 | ||
|
|
6454ffcc80 | ||
|
|
b42e5dc031 | ||
|
|
de6d25edf7 | ||
|
|
edf3fab86a | ||
|
|
3726a56374 | ||
|
|
2a610db2d3 | ||
|
|
8d993ff85f |
@ -1,77 +1,28 @@
|
||||
version: 2
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/dotnet/core/sdk:3.1.100
|
||||
machine:
|
||||
- image: ubuntu-2004:202201-02
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
dotnet --info
|
||||
dotnet build -c Release
|
||||
dotnet test -c Release --no-build ./NBXplorer.Tests/NBXplorer.Tests.csproj --filter "Azure!=Azure&Broker!=RabbitMq" -v n < /dev/null
|
||||
cd .circleci && ./run-tests.sh
|
||||
|
||||
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
|
||||
amd64:
|
||||
machine:
|
||||
enabled: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -t $DOCKERHUB_REPO:latest-amd64 -f Dockerfile.linuxamd64 .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
|
||||
|
||||
arm32v7:
|
||||
machine:
|
||||
enabled: true
|
||||
docker:
|
||||
docker:
|
||||
- image: cimg/base:stable
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfile.linuxarm32v7 .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||
|
||||
arm64v8:
|
||||
machine:
|
||||
enabled: true
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
#
|
||||
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f Dockerfile.linuxarm64v8 .
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
|
||||
|
||||
multiarch:
|
||||
machine:
|
||||
enabled: true
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
# Turn on Experimental features
|
||||
sudo mkdir $HOME/.docker
|
||||
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
|
||||
#
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
#
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
|
||||
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
|
||||
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
|
||||
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
|
||||
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
|
||||
docker buildx create --use
|
||||
docker buildx build -t $DOCKERHUB_REPO:$LATEST_TAG --platform linux/amd64,linux/arm64,linux/arm/v7 --push .
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@ -81,33 +32,12 @@ workflows:
|
||||
|
||||
publish:
|
||||
jobs:
|
||||
- amd64:
|
||||
filters:
|
||||
# ignore any commit on any branch by default
|
||||
branches:
|
||||
ignore: /.*/
|
||||
# only act on version tags
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
- arm32v7:
|
||||
- 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]+)*/
|
||||
- arm64v8:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
- multiarch:
|
||||
requires:
|
||||
- amd64
|
||||
- arm32v7
|
||||
- arm64v8
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v[1-9]+(\.[0-9]+)*/
|
||||
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
|
||||
|
||||
7
.circleci/run-tests.sh
Executable file
7
.circleci/run-tests.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cd ../NBXplorer.Tests
|
||||
docker-compose -v
|
||||
docker-compose build
|
||||
docker-compose run tests
|
||||
@ -121,5 +121,4 @@ bower_components
|
||||
output
|
||||
|
||||
.vs
|
||||
NBXplorer.Tests/
|
||||
**/launchSettings.json
|
||||
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# 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
|
||||
@ -1,4 +1,4 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:3.1.413-bullseye AS builder
|
||||
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.301-noble AS builder
|
||||
WORKDIR /source
|
||||
COPY NBXplorer/NBXplorer.csproj NBXplorer/NBXplorer.csproj
|
||||
COPY NBXplorer.Client/NBXplorer.Client.csproj NBXplorer.Client/NBXplorer.Client.csproj
|
||||
@ -8,12 +8,14 @@ COPY . .
|
||||
RUN cd NBXplorer && \
|
||||
dotnet publish --output /app/ --configuration Release
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:3.1.19-bullseye-slim
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0.9-noble
|
||||
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 ["dotnet", "NBXplorer.dll"]
|
||||
@ -1,21 +0,0 @@
|
||||
# Note that we are using buster rather than bulleyes. Somehow, raspberry pi 4 doesn't like bulleyes.
|
||||
FROM mcr.microsoft.com/dotnet/sdk:3.1.413-buster 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
|
||||
|
||||
# Note that we are using buster rather than bulleyes. Somehow, raspberry pi 4 doesn't like bulleyes.
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:3.1.19-buster-slim-arm32v7
|
||||
|
||||
WORKDIR /datadir
|
||||
WORKDIR /app
|
||||
ENV NBXPLORER_DATADIR=/datadir
|
||||
VOLUME /datadir
|
||||
|
||||
COPY --from=builder "/app" .
|
||||
ENTRYPOINT ["dotnet", "NBXplorer.dll"]
|
||||
@ -1,21 +0,0 @@
|
||||
# This is a manifest image, will pull the image with the same arch as the builder machine
|
||||
FROM mcr.microsoft.com/dotnet/sdk:3.1.413-bullseye 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
|
||||
|
||||
# Force the builder machine to take make an arm runtime image. This is fine as long as the builder does not run any program
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:3.1.19-bullseye-slim-arm64v8
|
||||
|
||||
WORKDIR /datadir
|
||||
WORKDIR /app
|
||||
ENV NBXPLORER_DATADIR=/datadir
|
||||
VOLUME /datadir
|
||||
|
||||
COPY --from=builder "/app" .
|
||||
ENTRYPOINT ["dotnet", "NBXplorer.dll"]
|
||||
@ -2,11 +2,11 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="4.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
BIN
NBXplorer.Client/Bitcoin.png
Normal file
BIN
NBXplorer.Client/Bitcoin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
18
NBXplorer.Client/DBUtils.cs
Normal file
18
NBXplorer.Client/DBUtils.cs
Normal file
@ -0,0 +1,18 @@
|
||||
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,23 +1,49 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
#nullable enable
|
||||
using NBitcoin;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
#if !NO_RECORD
|
||||
using static NBitcoin.WalletPolicies.MiniscriptNode;
|
||||
#endif
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class Derivation
|
||||
{
|
||||
public Derivation()
|
||||
public Derivation(Script scriptPubKey, Script? redeem = null)
|
||||
{
|
||||
|
||||
ScriptPubKey = scriptPubKey;
|
||||
Redeem = redeem;
|
||||
}
|
||||
public Script ScriptPubKey
|
||||
{
|
||||
get; set;
|
||||
get;
|
||||
}
|
||||
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,11 +1,12 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
#if !NO_RECORD
|
||||
using NBitcoin.WalletPolicies;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
@ -22,7 +23,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
get; set;
|
||||
}
|
||||
|
||||
public ReadOnlyDictionary<string, bool> AdditionalOptions { get; set; }
|
||||
public ReadOnlyDictionary<string, string> AdditionalOptions { get; set; }
|
||||
}
|
||||
public class DerivationStrategyFactory
|
||||
{
|
||||
@ -55,7 +56,6 @@ namespace NBXplorer.DerivationStrategy
|
||||
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), false);
|
||||
public DerivationStrategyBase Parse(string str)
|
||||
{
|
||||
var strategy = ParseCore(str);
|
||||
@ -70,15 +70,20 @@ namespace NBXplorer.DerivationStrategy
|
||||
bool taproot = false;
|
||||
ScriptPubKeyType type = ScriptPubKeyType.Segwit;
|
||||
|
||||
Dictionary<string, bool> optionsDictionary = new Dictionary<string, bool>(5);
|
||||
IDictionary<string, string> optionsDictionary = new Dictionary<string, string>(5);
|
||||
foreach (Match optionMatch in _OptionRegex.Matches(str))
|
||||
{
|
||||
var key = optionMatch.Groups[1].Value.ToLowerInvariant();
|
||||
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 (!optionsDictionary.TryAdd(key, true))
|
||||
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"))
|
||||
{
|
||||
@ -128,7 +133,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
KeepOrder = keepOrder,
|
||||
ScriptPubKeyType = type,
|
||||
AdditionalOptions = new ReadOnlyDictionary<string, bool>(optionsDictionary)
|
||||
AdditionalOptions = new ReadOnlyDictionary<string, string>(optionsDictionary)
|
||||
};
|
||||
var match = MultiSigRegex.Match(str);
|
||||
if (match.Success)
|
||||
@ -142,6 +147,14 @@ 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);
|
||||
@ -155,7 +168,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
/// <param name="publicKey">The public key of the wallet</param>
|
||||
/// <param name="options">Derivation options</param>
|
||||
/// <returns></returns>
|
||||
public DerivationStrategyBase CreateDirectDerivationStrategy(ExtPubKey publicKey, DerivationStrategyOptions options = null)
|
||||
public StandardDerivationStrategyBase CreateDirectDerivationStrategy(ExtPubKey publicKey, DerivationStrategyOptions options = null)
|
||||
{
|
||||
return CreateDirectDerivationStrategy(publicKey.GetWif(Network), options);
|
||||
}
|
||||
@ -166,10 +179,10 @@ namespace NBXplorer.DerivationStrategy
|
||||
/// <param name="publicKey">The public key of the wallet</param>
|
||||
/// <param name="options">Derivation options</param>
|
||||
/// <returns></returns>
|
||||
public DerivationStrategyBase CreateDirectDerivationStrategy(BitcoinExtPubKey publicKey, DerivationStrategyOptions options = null)
|
||||
public StandardDerivationStrategyBase CreateDirectDerivationStrategy(BitcoinExtPubKey publicKey, DerivationStrategyOptions options = null)
|
||||
{
|
||||
options = options ?? new DerivationStrategyOptions();
|
||||
DerivationStrategyBase strategy = null;
|
||||
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
|
||||
@ -197,7 +210,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
/// <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, bool> options = null)
|
||||
public TaprootDerivationStrategy CreateTaprootDerivationStrategy(BitcoinExtPubKey publicKey, ReadOnlyDictionary<string, string> options = null)
|
||||
{
|
||||
return new TaprootDerivationStrategy(publicKey, options);
|
||||
}
|
||||
@ -224,7 +237,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
public DerivationStrategyBase CreateMultiSigDerivationStrategy(BitcoinExtPubKey[] pubKeys, int sigCount, DerivationStrategyOptions options = null)
|
||||
{
|
||||
options = options ?? new DerivationStrategyOptions();
|
||||
DerivationStrategyBase derivationStrategy = new MultisigDerivationStrategy(sigCount, pubKeys.ToArray(), options.ScriptPubKeyType == ScriptPubKeyType.Legacy, !options.KeepOrder, options.AdditionalOptions);
|
||||
StandardDerivationStrategyBase derivationStrategy = new MultisigDerivationStrategy(sigCount, pubKeys.ToArray(), options.ScriptPubKeyType == ScriptPubKeyType.Legacy, !options.KeepOrder, options.AdditionalOptions);
|
||||
if (options.ScriptPubKeyType == ScriptPubKeyType.Legacy)
|
||||
return new P2SHDerivationStrategy(derivationStrategy, false);
|
||||
|
||||
@ -237,19 +250,6 @@ namespace NBXplorer.DerivationStrategy
|
||||
}
|
||||
return derivationStrategy;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
readonly static Regex _OptionRegex = new Regex(@"-\[([^ \]\-]+)\]");
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,11 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class DirectDerivationStrategy : DerivationStrategyBase
|
||||
public class DirectDerivationStrategy : StandardDerivationStrategyBase
|
||||
{
|
||||
BitcoinExtPubKey _Root;
|
||||
|
||||
@ -38,22 +37,18 @@ namespace NBXplorer.DerivationStrategy
|
||||
}
|
||||
}
|
||||
|
||||
public DirectDerivationStrategy(BitcoinExtPubKey root, bool segwit, ReadOnlyDictionary<string, bool> additionalOptions = null) : base(additionalOptions)
|
||||
public DirectDerivationStrategy(BitcoinExtPubKey root, bool segwit, ReadOnlyDictionary<string, string> additionalOptions = null) : base(additionalOptions)
|
||||
{
|
||||
if(root == null)
|
||||
throw new ArgumentNullException(nameof(root));
|
||||
_Root = root;
|
||||
Segwit = segwit;
|
||||
}
|
||||
public override Derivation GetDerivation()
|
||||
{
|
||||
var pubKey = _Root.ExtPubKey.PubKey;
|
||||
return new Derivation() { ScriptPubKey = Segwit ? pubKey.WitHash.ScriptPubKey : pubKey.Hash.ScriptPubKey };
|
||||
}
|
||||
|
||||
public override DerivationStrategyBase GetChild(KeyPath keyPath)
|
||||
public override Derivation GetDerivation(KeyPath keyPath)
|
||||
{
|
||||
return new DirectDerivationStrategy(_Root.ExtPubKey.Derive(keyPath).GetWif(_Root.Network), Segwit, AdditionalOptions);
|
||||
var pubKey = _Root.ExtPubKey.Derive(keyPath).PubKey;
|
||||
return new KeyPathDerivation(keyPath, Segwit ? pubKey.WitHash.ScriptPubKey : pubKey.Hash.ScriptPubKey);
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtPubKey> GetExtPubKeys()
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
#nullable enable
|
||||
using NBitcoin;
|
||||
#if !NO_RECORD
|
||||
using NBitcoin.WalletPolicies;
|
||||
#endif
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
@ -15,40 +17,42 @@ namespace NBXplorer.DerivationStrategy
|
||||
Direct = 2,
|
||||
Custom = 3,
|
||||
}
|
||||
public abstract class DerivationStrategyBase : IHDScriptPubKey
|
||||
{
|
||||
ReadOnlyDictionary<string, bool> Empty = new ReadOnlyDictionary<string, bool>(new Dictionary<string, bool>(0));
|
||||
public ReadOnlyDictionary<string, bool> AdditionalOptions { get; }
|
||||
|
||||
internal DerivationStrategyBase(ReadOnlyDictionary<string,bool> additionalOptions)
|
||||
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));
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
AdditionalOptions = additionalOptions ?? Empty;
|
||||
}
|
||||
|
||||
public DerivationLine GetLineFor(KeyPathTemplate keyPathTemplate)
|
||||
{
|
||||
return new DerivationLine(this, keyPathTemplate);
|
||||
}
|
||||
public abstract DerivationStrategyBase GetChild(KeyPath keyPath);
|
||||
|
||||
public Derivation GetDerivation(uint i)
|
||||
{
|
||||
return GetChild(new KeyPath(i)).GetDerivation();
|
||||
}
|
||||
public Derivation GetDerivation(KeyPath keyPath)
|
||||
{
|
||||
if (keyPath == null || keyPath.Length == 0)
|
||||
return GetDerivation();
|
||||
return GetChild(keyPath).GetDerivation();
|
||||
}
|
||||
public abstract Derivation GetDerivation();
|
||||
public DerivationLine GetLineFor(DerivationFeature feature) => GetLineFor(KeyPathTemplates.Default, feature);
|
||||
public abstract DerivationLine GetLineFor(KeyPathTemplates keyPathTemplates, DerivationFeature feature);
|
||||
|
||||
protected internal abstract string StringValueCore
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
string _StringValue;
|
||||
string? _StringValue;
|
||||
string StringValue
|
||||
{
|
||||
get
|
||||
@ -66,98 +70,76 @@ namespace NBXplorer.DerivationStrategy
|
||||
|
||||
private string GetSuffixOptionsString()
|
||||
{
|
||||
return string.Join("", new SortedDictionary<string, bool>(AdditionalOptions).Where(pair => pair.Value).Select(pair => $"-[{pair.Key}]"));
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static bool operator !=(DerivationStrategyBase a, DerivationStrategyBase b)
|
||||
{
|
||||
return !(a == b);
|
||||
return string.Join("", new SortedDictionary<string, string>(AdditionalOptions).Select(pair => $"-[{pair.Key}{(string.IsNullOrEmpty(pair.Value)?string.Empty: $"={pair.Value}")}]"));
|
||||
}
|
||||
#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;
|
||||
}
|
||||
|
||||
Script IHDScriptPubKey.ScriptPubKey => GetDerivation().ScriptPubKey;
|
||||
IHDScriptPubKey IHDScriptPubKey.Derive(KeyPath keyPath)
|
||||
{
|
||||
return GetChild(keyPath);
|
||||
}
|
||||
|
||||
class HDRedeemScriptPubKey : IHDScriptPubKey
|
||||
{
|
||||
private readonly DerivationStrategyBase strategyBase;
|
||||
public HDRedeemScriptPubKey(DerivationStrategyBase strategyBase)
|
||||
{
|
||||
this.strategyBase = strategyBase;
|
||||
}
|
||||
public Script ScriptPubKey => strategyBase.GetDerivation().Redeem;
|
||||
|
||||
public bool CanDeriveHardenedPath()
|
||||
{
|
||||
return strategyBase.CanDeriveHardenedPath();
|
||||
}
|
||||
public IHDScriptPubKey Derive(KeyPath keyPath)
|
||||
{
|
||||
return strategyBase.GetChild(keyPath).AsHDRedeemScriptPubKey();
|
||||
}
|
||||
}
|
||||
public IHDScriptPubKey AsHDRedeemScriptPubKey()
|
||||
{
|
||||
return new HDRedeemScriptPubKey(this);
|
||||
}
|
||||
|
||||
public bool CanDeriveHardenedPath()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class DerivationLine
|
||||
#if !NO_RECORD
|
||||
public class MiniscriptDerivationLine : DerivationLine
|
||||
{
|
||||
public DerivationLine(DerivationStrategyBase derivationStrategyBase, KeyPathTemplate keyPathTemplate)
|
||||
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 (keyPathTemplate == null)
|
||||
throw new ArgumentNullException(nameof(keyPathTemplate));
|
||||
if (keyPathTemplates == null)
|
||||
throw new ArgumentNullException(nameof(keyPathTemplates));
|
||||
DerivationStrategyBase = derivationStrategyBase;
|
||||
KeyPathTemplate = keyPathTemplate;
|
||||
KeyPathTemplate = keyPathTemplates.GetKeyPathTemplate(derivationFeature);
|
||||
}
|
||||
|
||||
public DerivationStrategyBase DerivationStrategyBase { get; }
|
||||
public StandardDerivationStrategyBase DerivationStrategyBase { get; }
|
||||
public KeyPathTemplate KeyPathTemplate { get; }
|
||||
|
||||
DerivationStrategyBase _PreLine;
|
||||
|
||||
public Derivation Derive(uint index)
|
||||
public override Derivation Derive(uint index)
|
||||
{
|
||||
_PreLine = _PreLine ?? DerivationStrategyBase.GetChild(KeyPathTemplate.PreIndexes);
|
||||
return _PreLine.GetDerivation(new KeyPath(index).Derive(KeyPathTemplate.PostIndexes));
|
||||
var kp = KeyPathTemplate.GetKeyPath(index);
|
||||
return DerivationStrategyBase.GetDerivation(kp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
using NBitcoin;
|
||||
using System.Linq;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using NBitcoin.Crypto;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class MultisigDerivationStrategy : DerivationStrategyBase
|
||||
public class MultisigDerivationStrategy : StandardDerivationStrategyBase
|
||||
{
|
||||
public bool LexicographicOrder
|
||||
{
|
||||
@ -51,7 +49,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
}
|
||||
|
||||
internal MultisigDerivationStrategy(int reqSignature, BitcoinExtPubKey[] keys, bool isLegacy, bool lexicographicOrder,
|
||||
ReadOnlyDictionary<string, bool> additionalOptions) : base(additionalOptions)
|
||||
ReadOnlyDictionary<string, string> additionalOptions) : base(additionalOptions)
|
||||
{
|
||||
Keys = new ReadOnlyCollection<BitcoinExtPubKey>(keys);
|
||||
RequiredSignatures = reqSignature;
|
||||
@ -64,29 +62,19 @@ namespace NBXplorer.DerivationStrategy
|
||||
get;
|
||||
}
|
||||
|
||||
private void WriteBytes(MemoryStream ms, byte[] v)
|
||||
{
|
||||
ms.Write(v, 0, v.Length);
|
||||
}
|
||||
|
||||
public override Derivation GetDerivation()
|
||||
public override Derivation GetDerivation(KeyPath keyPath)
|
||||
{
|
||||
var pubKeys = new PubKey[this.Keys.Count];
|
||||
Parallel.For(0, pubKeys.Length, i =>
|
||||
{
|
||||
pubKeys[i] = this.Keys[i].ExtPubKey.PubKey;
|
||||
pubKeys[i] = this.Keys[i].ExtPubKey.Derive(keyPath).PubKey;
|
||||
});
|
||||
if(LexicographicOrder)
|
||||
{
|
||||
Array.Sort(pubKeys, LexicographicComparer);
|
||||
}
|
||||
var redeem = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(RequiredSignatures, pubKeys);
|
||||
return new Derivation() { ScriptPubKey = redeem };
|
||||
}
|
||||
|
||||
public override DerivationStrategyBase GetChild(KeyPath keyPath)
|
||||
{
|
||||
return new MultisigDerivationStrategy(RequiredSignatures, Keys.Select(k => k.ExtPubKey.Derive(keyPath).GetWif(k.Network)).ToArray(), IsLegacy, LexicographicOrder, AdditionalOptions);
|
||||
return new KeyPathDerivation(keyPath, redeem);
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtPubKey> GetExtPubKeys()
|
||||
|
||||
@ -1,17 +1,13 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class P2SHDerivationStrategy : DerivationStrategyBase
|
||||
public class P2SHDerivationStrategy : StandardDerivationStrategyBase
|
||||
{
|
||||
bool addSuffix;
|
||||
internal P2SHDerivationStrategy(DerivationStrategyBase inner, bool addSuffix):base(inner.AdditionalOptions)
|
||||
internal P2SHDerivationStrategy(StandardDerivationStrategyBase inner, bool addSuffix):base(inner.AdditionalOptions)
|
||||
{
|
||||
if(inner == null)
|
||||
throw new ArgumentNullException(nameof(inner));
|
||||
@ -19,7 +15,7 @@ namespace NBXplorer.DerivationStrategy
|
||||
this.addSuffix = addSuffix;
|
||||
}
|
||||
|
||||
public DerivationStrategyBase Inner
|
||||
public StandardDerivationStrategyBase Inner
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
@ -34,24 +30,18 @@ namespace NBXplorer.DerivationStrategy
|
||||
}
|
||||
}
|
||||
|
||||
public override Derivation GetDerivation()
|
||||
{
|
||||
var derivation = Inner.GetDerivation();
|
||||
return new Derivation()
|
||||
{
|
||||
ScriptPubKey = derivation.ScriptPubKey.Hash.ScriptPubKey,
|
||||
Redeem = derivation.Redeem ?? derivation.ScriptPubKey
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtPubKey> GetExtPubKeys()
|
||||
{
|
||||
return Inner.GetExtPubKeys();
|
||||
}
|
||||
|
||||
public override DerivationStrategyBase GetChild(KeyPath keyPath)
|
||||
public override Derivation GetDerivation(KeyPath keyPath)
|
||||
{
|
||||
return new P2SHDerivationStrategy(Inner.GetChild(keyPath), addSuffix);
|
||||
var derivation = Inner.GetDerivation(keyPath);
|
||||
return new KeyPathDerivation(
|
||||
keyPath,
|
||||
derivation.ScriptPubKey.Hash.ScriptPubKey,
|
||||
derivation.Redeem ?? derivation.ScriptPubKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +1,34 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class P2WSHDerivationStrategy : DerivationStrategyBase
|
||||
public class P2WSHDerivationStrategy : StandardDerivationStrategyBase
|
||||
{
|
||||
internal P2WSHDerivationStrategy(DerivationStrategyBase inner):base(inner.AdditionalOptions)
|
||||
internal P2WSHDerivationStrategy(StandardDerivationStrategyBase inner):base(inner.AdditionalOptions)
|
||||
{
|
||||
if(inner == null)
|
||||
throw new ArgumentNullException(nameof(inner));
|
||||
Inner = inner;
|
||||
}
|
||||
|
||||
public DerivationStrategyBase Inner
|
||||
public StandardDerivationStrategyBase Inner
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
protected internal override string StringValueCore => Inner.ToString();
|
||||
|
||||
public override Derivation GetDerivation()
|
||||
{
|
||||
var derivation = Inner.GetDerivation();
|
||||
return new Derivation()
|
||||
{
|
||||
ScriptPubKey = derivation.ScriptPubKey.WitHash.ScriptPubKey,
|
||||
Redeem = derivation.ScriptPubKey
|
||||
};
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtPubKey> GetExtPubKeys()
|
||||
{
|
||||
return Inner.GetExtPubKeys();
|
||||
}
|
||||
|
||||
public override DerivationStrategyBase GetChild(KeyPath keyPath)
|
||||
public override Derivation GetDerivation(KeyPath keyPath)
|
||||
{
|
||||
return new P2WSHDerivationStrategy(Inner.GetChild(keyPath));
|
||||
var redeem = Inner.GetDerivation(keyPath).ScriptPubKey;
|
||||
return new KeyPathDerivation(keyPath, redeem.WitHash.ScriptPubKey, redeem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
212
NBXplorer.Client/DerivationStrategy/PolicyDerivationStrategy.cs
Normal file
212
NBXplorer.Client/DerivationStrategy/PolicyDerivationStrategy.cs
Normal file
@ -0,0 +1,212 @@
|
||||
#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
|
||||
@ -3,11 +3,10 @@ using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace NBXplorer.DerivationStrategy
|
||||
{
|
||||
public class TaprootDerivationStrategy : DerivationStrategyBase
|
||||
public class TaprootDerivationStrategy : StandardDerivationStrategyBase
|
||||
{
|
||||
BitcoinExtPubKey _Root;
|
||||
|
||||
@ -30,27 +29,22 @@ namespace NBXplorer.DerivationStrategy
|
||||
}
|
||||
}
|
||||
|
||||
public TaprootDerivationStrategy(BitcoinExtPubKey root, ReadOnlyDictionary<string, bool> additionalOptions = null) : base(additionalOptions)
|
||||
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()
|
||||
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.PubKey.GetTaprootFullPubKey();
|
||||
return new Derivation() { ScriptPubKey = pubKey.ScriptPubKey };
|
||||
var pubKey = _Root.ExtPubKey.Derive(keyPath).PubKey.GetTaprootFullPubKey();
|
||||
return new KeyPathDerivation(keyPath, pubKey.ScriptPubKey);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override DerivationStrategyBase GetChild(KeyPath keyPath)
|
||||
{
|
||||
return new TaprootDerivationStrategy(_Root.ExtPubKey.Derive(keyPath).GetWif(_Root.Network), AdditionalOptions);
|
||||
}
|
||||
|
||||
public override IEnumerable<ExtPubKey> GetExtPubKeys()
|
||||
{
|
||||
yield return _Root.ExtPubKey;
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using NBitcoin;
|
||||
using System.Linq;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.JsonConverters;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using System;
|
||||
@ -15,7 +13,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.WebSockets;
|
||||
using NBitcoin.RPC;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -153,7 +152,7 @@ namespace NBXplorer
|
||||
}
|
||||
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<TransactionResult>(HttpMethod.Get, null, "v1/cryptos/{0}/transactions/" + txId, new[] { CryptoCode }, cancellation).ConfigureAwait(false);
|
||||
return await SendAsync<TransactionResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/transactions/{txId}", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public TransactionResult GetTransaction(uint256 txId, CancellationToken cancellation = default)
|
||||
@ -165,7 +164,7 @@ namespace NBXplorer
|
||||
{
|
||||
if (extKey == null)
|
||||
throw new ArgumentNullException(nameof(extKey));
|
||||
return await SendAsync<PruneResponse>(HttpMethod.Post, pruneRequest, "v1/cryptos/{0}/derivations/{1}/prune", new object[] { Network.CryptoCode, extKey }, cancellation).ConfigureAwait(false);
|
||||
return await SendAsync<PruneResponse>(HttpMethod.Post, pruneRequest, $"v1/cryptos/{CryptoCode}/derivations/{extKey}/prune", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public PruneResponse Prune(DerivationStrategyBase extKey, PruneRequest pruneRequest, CancellationToken cancellation = default)
|
||||
@ -173,6 +172,16 @@ namespace NBXplorer
|
||||
return PruneAsync(extKey, pruneRequest, cancellation).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
internal class RawStr
|
||||
{
|
||||
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)
|
||||
@ -187,7 +196,7 @@ namespace NBXplorer
|
||||
var argsString = string.Join("&", args.ToArray());
|
||||
if (argsString != string.Empty)
|
||||
argsString = $"?{argsString}";
|
||||
await SendAsync<bool>(HttpMethod.Post, null, "v1/cryptos/{0}/derivations/{1}/utxos/scan{2}", new object[] { Network.CryptoCode, extKey, argsString }, cancellation).ConfigureAwait(false);
|
||||
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)
|
||||
{
|
||||
@ -196,7 +205,7 @@ namespace NBXplorer
|
||||
|
||||
public async Task<ScanUTXOInformation> GetScanUTXOSetInformationAsync(DerivationStrategyBase extKey, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<ScanUTXOInformation>(HttpMethod.Get, null, "v1/cryptos/{0}/derivations/{1}/utxos/scan", new object[] { Network.CryptoCode, extKey }, cancellation).ConfigureAwait(false);
|
||||
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)
|
||||
@ -220,25 +229,28 @@ namespace NBXplorer
|
||||
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);
|
||||
await session.ConnectAsync(cancellation).ConfigureAwait(false);
|
||||
return session;
|
||||
}
|
||||
|
||||
public UTXOChanges GetUTXOs(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetUTXOsAsync(trackedSource, cancellation).GetAwaiter().GetResult();
|
||||
}
|
||||
public async Task<UTXOChanges> GetUTXOsAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
public Task<UTXOChanges> GetUTXOsAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
{
|
||||
|
||||
if (trackedSource == null)
|
||||
throw new ArgumentNullException(nameof(trackedSource));
|
||||
if (trackedSource is DerivationSchemeTrackedSource dsts)
|
||||
{
|
||||
return await SendAsync<UTXOChanges>(HttpMethod.Get, null, "v1/cryptos/{0}/derivations/{1}/utxos", new object[] { CryptoCode, dsts.DerivationStrategy.ToString() }, cancellation).ConfigureAwait(false);
|
||||
}
|
||||
else if (trackedSource is AddressTrackedSource asts)
|
||||
{
|
||||
return await SendAsync<UTXOChanges>(HttpMethod.Get, null, "v1/cryptos/{0}/addresses/{1}/utxos", new object[] { CryptoCode, asts.Address }, cancellation).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
throw UnSupported(trackedSource);
|
||||
return SendAsync<UTXOChanges>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/utxos", cancellation);
|
||||
}
|
||||
|
||||
public void WaitServerStarted(CancellationToken cancellation = default)
|
||||
@ -269,7 +281,7 @@ namespace NBXplorer
|
||||
}
|
||||
public Task TrackAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default)
|
||||
{
|
||||
return TrackAsync(TrackedSource.Create(strategy), cancellation);
|
||||
return TrackAsync(TrackedSource.Create(strategy), cancellation: cancellation);
|
||||
}
|
||||
|
||||
public void Track(DerivationStrategyBase strategy, TrackWalletRequest trackDerivationRequest, CancellationToken cancellation = default)
|
||||
@ -280,27 +292,18 @@ namespace NBXplorer
|
||||
{
|
||||
if (strategy == null)
|
||||
throw new ArgumentNullException(nameof(strategy));
|
||||
await SendAsync<string>(HttpMethod.Post, trackDerivationRequest, "v1/cryptos/{0}/derivations/{1}", new[] { CryptoCode, strategy.ToString() }, cancellation).ConfigureAwait(false);
|
||||
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).GetAwaiter().GetResult();
|
||||
TrackAsync(trackedSource, cancellation: cancellation).GetAwaiter().GetResult();
|
||||
}
|
||||
public Task TrackAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
public Task TrackAsync(TrackedSource trackedSource, TrackWalletRequest trackDerivationRequest = null, CancellationToken cancellation = default)
|
||||
{
|
||||
if (trackedSource == null)
|
||||
throw new ArgumentNullException(nameof(trackedSource));
|
||||
if (trackedSource is DerivationSchemeTrackedSource dsts)
|
||||
{
|
||||
return SendAsync<string>(HttpMethod.Post, null, "v1/cryptos/{0}/derivations/{1}", new[] { CryptoCode, dsts.DerivationStrategy.ToString() }, cancellation);
|
||||
}
|
||||
else if (trackedSource is AddressTrackedSource asts)
|
||||
{
|
||||
return SendAsync<string>(HttpMethod.Post, null, "v1/cryptos/{0}/addresses/{1}", new[] { CryptoCode, asts.Address.ToString() }, cancellation);
|
||||
}
|
||||
else
|
||||
throw UnSupported(trackedSource);
|
||||
return SendAsync<string>(HttpMethod.Post, trackDerivationRequest, GetBasePath(trackedSource), cancellation);
|
||||
}
|
||||
|
||||
private Exception UnSupported(TrackedSource trackedSource)
|
||||
@ -319,7 +322,7 @@ namespace NBXplorer
|
||||
}
|
||||
public Task<GetBalanceResponse> GetBalanceAsync(DerivationStrategyBase userDerivationScheme, CancellationToken cancellation = default)
|
||||
{
|
||||
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, "v1/cryptos/{0}/derivations/{1}/balance", new[] { CryptoCode, userDerivationScheme.ToString() }, cancellation);
|
||||
return GetBalanceAsync(TrackedSource.Create(userDerivationScheme), cancellation);
|
||||
}
|
||||
|
||||
|
||||
@ -329,12 +332,30 @@ namespace NBXplorer
|
||||
}
|
||||
public Task<GetBalanceResponse> GetBalanceAsync(BitcoinAddress address, CancellationToken cancellation = default)
|
||||
{
|
||||
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, "v1/cryptos/{0}/addresses/{1}/balance", new[] { CryptoCode, address.ToString() }, cancellation);
|
||||
return GetBalanceAsync(TrackedSource.Create(address), cancellation);
|
||||
}
|
||||
public Task<GetBalanceResponse> GetBalanceAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
{
|
||||
return SendAsync<GetBalanceResponse>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/balance", cancellation);
|
||||
}
|
||||
public 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/{0}/derivations/{1}/addresses/cancelreservation", new[] { CryptoCode, strategy.ToString() }, cancellation);
|
||||
return SendAsync<string>(HttpMethod.Post, keyPaths, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/cancelreservation", cancellation);
|
||||
}
|
||||
|
||||
public StatusResult GetStatus(CancellationToken cancellation = default)
|
||||
@ -349,40 +370,39 @@ namespace NBXplorer
|
||||
{
|
||||
if (strategy is null)
|
||||
throw new ArgumentNullException(nameof(strategy));
|
||||
return SendAsync<bool>(HttpMethod.Post, null, "v1/cryptos/{0}/derivations/{1}/utxos/wipe", new[] { CryptoCode, strategy.ToString() }, cancellation);
|
||||
return SendAsync<bool>(HttpMethod.Post, null, $"v1/cryptos/{CryptoCode}/derivations/{strategy}/utxos/wipe", cancellation);
|
||||
}
|
||||
|
||||
public Task<StatusResult> GetStatusAsync(CancellationToken cancellation = default)
|
||||
{
|
||||
return SendAsync<StatusResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/status", null, cancellation);
|
||||
return SendAsync<StatusResult>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/status", cancellation);
|
||||
}
|
||||
public GetTransactionsResponse GetTransactions(DerivationStrategyBase strategy, CancellationToken cancellation = default)
|
||||
public GetTransactionsResponse GetTransactions(DerivationStrategyBase strategy, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetTransactionsAsync(strategy, cancellation).GetAwaiter().GetResult();
|
||||
return GetTransactionsAsync(strategy, from, to, cancellation).GetAwaiter().GetResult();
|
||||
}
|
||||
public GetTransactionsResponse GetTransactions(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
public GetTransactionsResponse GetTransactions(TrackedSource trackedSource, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetTransactionsAsync(trackedSource, cancellation).GetAwaiter().GetResult();
|
||||
return GetTransactionsAsync(trackedSource, from, to, cancellation).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public Task<GetTransactionsResponse> GetTransactionsAsync(DerivationStrategyBase strategy, CancellationToken cancellation = default)
|
||||
public Task<GetTransactionsResponse> GetTransactionsAsync(DerivationStrategyBase strategy, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetTransactionsAsync(TrackedSource.Create(strategy), cancellation);
|
||||
return GetTransactionsAsync(TrackedSource.Create(strategy), from, to, cancellation);
|
||||
}
|
||||
public Task<GetTransactionsResponse> GetTransactionsAsync(TrackedSource trackedSource, CancellationToken cancellation = default)
|
||||
public Task<GetTransactionsResponse> GetTransactionsAsync(TrackedSource trackedSource, DateTimeOffset? from = null, DateTimeOffset? to = null, CancellationToken cancellation = default)
|
||||
{
|
||||
if (trackedSource == null)
|
||||
throw new ArgumentNullException(nameof(trackedSource));
|
||||
if (trackedSource is DerivationSchemeTrackedSource dsts)
|
||||
string fromV = string.Empty;
|
||||
string toV = string.Empty;
|
||||
if (from is DateTimeOffset f)
|
||||
{
|
||||
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}/transactions", null, cancellation);
|
||||
fromV = NBitcoin.Utils.DateTimeToUnixTime(f).ToString();
|
||||
}
|
||||
else if (trackedSource is AddressTrackedSource asts)
|
||||
if (to is DateTimeOffset t)
|
||||
{
|
||||
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}/transactions", null, cancellation);
|
||||
toV = NBitcoin.Utils.DateTimeToUnixTime(t).ToString();
|
||||
}
|
||||
else
|
||||
throw UnSupported(trackedSource);
|
||||
return SendAsync<GetTransactionsResponse>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/transactions?from={fromV}&to={toV}", cancellation);
|
||||
}
|
||||
|
||||
|
||||
@ -407,23 +427,14 @@ namespace NBXplorer
|
||||
throw new ArgumentNullException(nameof(txId));
|
||||
if (trackedSource == null)
|
||||
throw new ArgumentNullException(nameof(trackedSource));
|
||||
if (trackedSource is DerivationSchemeTrackedSource dsts)
|
||||
{
|
||||
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/derivations/{dsts.DerivationStrategy}/transactions/{txId}", null, cancellation);
|
||||
}
|
||||
else if (trackedSource is AddressTrackedSource asts)
|
||||
{
|
||||
return SendAsync<TransactionInformation>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/addresses/{asts.Address}/transactions/{txId}", null, cancellation);
|
||||
}
|
||||
else
|
||||
throw UnSupported(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", null, cancellation);
|
||||
return SendAsync<byte[]>(HttpMethod.Post, rescanRequest, $"v1/cryptos/{CryptoCode}/rescan", cancellation);
|
||||
}
|
||||
|
||||
public void Rescan(RescanRequest rescanRequest, CancellationToken cancellation = default)
|
||||
@ -440,7 +451,7 @@ namespace NBXplorer
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetAsync<KeyPathInformation>($"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/unused?feature={feature}&skip={skip}&reserve={reserve}", null, cancellation).ConfigureAwait(false);
|
||||
return await GetAsync<KeyPathInformation>($"v1/cryptos/{CryptoCode}/derivations/{strategy}/addresses/unused?feature={feature}&skip={skip}&reserve={reserve}", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
catch (NBXplorerException ex) when (ex.Error?.HttpCode == 404)
|
||||
{
|
||||
@ -455,16 +466,17 @@ namespace NBXplorer
|
||||
|
||||
public async Task<KeyPathInformation> GetKeyInformationAsync(DerivationStrategyBase strategy, Script script, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<KeyPathInformation>(HttpMethod.Get, null, "v1/cryptos/{0}/derivations/{1}/scripts/" + script.ToHex(), new object[] { CryptoCode, strategy }, cancellation).ConfigureAwait(false);
|
||||
return await GetKeyInformationAsync(new DerivationSchemeTrackedSource(strategy), script, cancellation).ConfigureAwait(false);
|
||||
}
|
||||
public async Task<KeyPathInformation> GetKeyInformationAsync(TrackedSource trackedSource, Script script, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<KeyPathInformation>(HttpMethod.Get, null, $"{GetBasePath(trackedSource)}/scripts/{script.ToHex()}", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetKeyInformationAsync(DerivationStrategyBase strategy, Script script) instead")]
|
||||
public async Task<KeyPathInformation[]> GetKeyInformationsAsync(Script script, CancellationToken cancellation = default)
|
||||
{
|
||||
return await SendAsync<KeyPathInformation[]>(HttpMethod.Get, null, "v1/cryptos/{0}/scripts/" + script.ToHex(), new[] { CryptoCode }, cancellation).ConfigureAwait(false);
|
||||
return await SendAsync<KeyPathInformation[]>(HttpMethod.Get, null, $"v1/cryptos/{CryptoCode}/scripts/{script.ToHex()}", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[Obsolete("Use GetKeyInformation(DerivationStrategyBase strategy, Script script) instead")]
|
||||
public KeyPathInformation[] GetKeyInformations(Script script, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetKeyInformationsAsync(script, cancellation).GetAwaiter().GetResult();
|
||||
@ -483,7 +495,7 @@ namespace NBXplorer
|
||||
{
|
||||
try
|
||||
{
|
||||
return await GetAsync<GetFeeRateResult>("v1/cryptos/{0}/fees/{1}", new object[] { CryptoCode, blockCount }, cancellation).ConfigureAwait(false);
|
||||
return await GetAsync<GetFeeRateResult>($"v1/cryptos/{CryptoCode}/fees/{blockCount}", cancellation).ConfigureAwait(false);
|
||||
}
|
||||
catch (NBXplorerException ex) when (fallbackFeeRate != null && ex.Error.Code == "fee-estimation-unavailable")
|
||||
{
|
||||
@ -492,7 +504,7 @@ namespace NBXplorer
|
||||
}
|
||||
public Task<GetFeeRateResult> GetFeeRateAsync(int blockCount, CancellationToken cancellation = default)
|
||||
{
|
||||
return GetAsync<GetFeeRateResult>("v1/cryptos/{0}/fees/{1}", new object[] { CryptoCode, blockCount }, cancellation);
|
||||
return GetAsync<GetFeeRateResult>($"v1/cryptos/{CryptoCode}/fees/{blockCount}", cancellation);
|
||||
}
|
||||
public CreatePSBTResponse CreatePSBT(DerivationStrategyBase derivationStrategy, CreatePSBTRequest request, CancellationToken cancellation = default)
|
||||
{
|
||||
@ -504,7 +516,7 @@ namespace NBXplorer
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
return this.SendAsync<CreatePSBTResponse>(HttpMethod.Post, request, "v1/cryptos/{0}/derivations/{1}/psbt/create", new object[] { CryptoCode, derivationStrategy }, cancellation);
|
||||
return this.SendAsync<CreatePSBTResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/derivations/{derivationStrategy}/psbt/create", cancellation);
|
||||
}
|
||||
|
||||
public UpdatePSBTResponse UpdatePSBT(UpdatePSBTRequest request, CancellationToken cancellation = default)
|
||||
@ -515,7 +527,7 @@ namespace NBXplorer
|
||||
{
|
||||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
return this.SendAsync<UpdatePSBTResponse>(HttpMethod.Post, request, "v1/cryptos/{0}/psbt/update", new object[] { CryptoCode }, cancellation);
|
||||
return this.SendAsync<UpdatePSBTResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/psbt/update", cancellation);
|
||||
}
|
||||
public BroadcastResult Broadcast(Transaction tx, CancellationToken cancellation = default)
|
||||
{
|
||||
@ -533,7 +545,7 @@ namespace NBXplorer
|
||||
|
||||
public Task<BroadcastResult> BroadcastAsync(Transaction tx, bool testMempoolAccept, CancellationToken cancellation = default)
|
||||
{
|
||||
return SendAsync<BroadcastResult>(HttpMethod.Post, tx.ToBytes(), "v1/cryptos/{0}/transactions?testMempoolAccept={1}", new[] { CryptoCode, testMempoolAccept.ToString() }, cancellation);
|
||||
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)
|
||||
@ -546,7 +558,7 @@ namespace NBXplorer
|
||||
throw new ArgumentNullException(nameof(derivationScheme));
|
||||
if (key == null)
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
return GetAsync<TMetadata>("v1/cryptos/{0}/derivations/{1}/metadata/{2}", new object[] { CryptoCode, derivationScheme, key }, cancellationToken);
|
||||
return GetAsync<TMetadata>($"v1/cryptos/{CryptoCode}/derivations/{derivationScheme}/metadata/{key}", cancellationToken);
|
||||
}
|
||||
|
||||
public void SetMetadata<TMetadata>(DerivationStrategyBase derivationScheme, string key, TMetadata value, CancellationToken cancellationToken = default)
|
||||
@ -556,13 +568,13 @@ namespace NBXplorer
|
||||
|
||||
public Task SetMetadataAsync<TMetadata>(DerivationStrategyBase derivationScheme, string key, TMetadata value, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return SendAsync<string>(HttpMethod.Post, value, "v1/cryptos/{0}/derivations/{1}/metadata/{2}", new object[] { CryptoCode, derivationScheme, key }, cancellationToken);
|
||||
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/{0}/derivations", new object[] { CryptoCode }, cancellationToken);
|
||||
return SendAsync<GenerateWalletResponse>(HttpMethod.Post, request, $"v1/cryptos/{CryptoCode}/derivations", cancellationToken);
|
||||
}
|
||||
|
||||
public GenerateWalletResponse GenerateWallet(GenerateWalletRequest request = null, CancellationToken cancellationToken = default)
|
||||
@ -571,6 +583,37 @@ namespace NBXplorer
|
||||
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);
|
||||
}
|
||||
|
||||
private static readonly HttpClient SharedClient = new HttpClient();
|
||||
internal HttpClient Client = SharedClient;
|
||||
|
||||
@ -614,13 +657,23 @@ namespace NBXplorer
|
||||
}
|
||||
}
|
||||
|
||||
internal string GetFullUri(string relativePath, params object[] parameters)
|
||||
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)
|
||||
{
|
||||
relativePath = String.Format(relativePath, parameters ?? new object[0]);
|
||||
var uri = Address.AbsoluteUri;
|
||||
if (!uri.EndsWith("/", StringComparison.Ordinal))
|
||||
uri += "/";
|
||||
uri += relativePath;
|
||||
uri += EncodeUrlParameters(relativePath).ToString();
|
||||
if (!IncludeTransaction)
|
||||
{
|
||||
if (uri.IndexOf('?') == -1)
|
||||
@ -630,13 +683,13 @@ namespace NBXplorer
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
private Task<T> GetAsync<T>(string relativePath, object[] parameters, CancellationToken cancellation)
|
||||
private Task<T> GetAsync<T>(FormattableString relativePath, CancellationToken cancellation)
|
||||
{
|
||||
return SendAsync<T>(HttpMethod.Get, null, relativePath, parameters, cancellation);
|
||||
return SendAsync<T>(HttpMethod.Get, null, relativePath, cancellation);
|
||||
}
|
||||
internal async Task<T> SendAsync<T>(HttpMethod method, object body, string relativePath, object[] parameters, CancellationToken cancellation)
|
||||
internal async Task<T> SendAsync<T>(HttpMethod method, object body, FormattableString relativePath, CancellationToken cancellation)
|
||||
{
|
||||
HttpRequestMessage message = CreateMessage(method, body, relativePath, parameters);
|
||||
HttpRequestMessage message = CreateMessage(method, body, relativePath);
|
||||
var result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
|
||||
if ((int)result.StatusCode == 404)
|
||||
{
|
||||
@ -650,16 +703,35 @@ namespace NBXplorer
|
||||
{
|
||||
if (Auth.RefreshCache())
|
||||
{
|
||||
message = CreateMessage(method, body, relativePath, parameters);
|
||||
result = await Client.SendAsync(message).ConfigureAwait(false);
|
||||
message = CreateMessage(method, body, relativePath);
|
||||
result = await Client.SendAsync(message, cancellation).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
return await ParseResponse<T>(result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal HttpRequestMessage CreateMessage(HttpMethod method, object body, string relativePath, object[] parameters)
|
||||
internal async Task<HttpResponseMessage> SendAsync(HttpMethod method, object body, FormattableString relativePath, CancellationToken cancellation)
|
||||
{
|
||||
var uri = GetFullUri(relativePath, parameters);
|
||||
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)
|
||||
{
|
||||
var uri = GetFullUri(relativePath);
|
||||
var message = new HttpRequestMessage(method, uri);
|
||||
Auth.SetAuthorization(message);
|
||||
if (body != null)
|
||||
@ -712,5 +784,18 @@ namespace NBXplorer
|
||||
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,11 +1,8 @@
|
||||
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;
|
||||
|
||||
@ -19,6 +16,11 @@ namespace NBXplorer
|
||||
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>();
|
||||
foreach(var v in values)
|
||||
{
|
||||
@ -35,27 +37,6 @@ namespace NBXplorer
|
||||
}
|
||||
}
|
||||
|
||||
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 async Task CloseSocket(this WebSocket socket, WebSocketCloseStatus status, string statusDescription, CancellationToken cancellation = default)
|
||||
{
|
||||
try
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
using NBitcoin;
|
||||
using System.Linq;
|
||||
using NBitcoin.JsonConverters;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
|
||||
namespace NBXplorer.JsonConverters
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
using NBitcoin;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
||||
namespace NBXplorer.JsonConverters
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,6 @@
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.JsonConverters
|
||||
{
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Reflection;
|
||||
using NBXplorer.Models;
|
||||
using NBitcoin.JsonConverters;
|
||||
|
||||
@ -4,7 +4,6 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.JsonConverters
|
||||
{
|
||||
|
||||
@ -4,7 +4,6 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.JsonConverters
|
||||
{
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NBXplorer.Models;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -77,7 +75,7 @@ namespace NBXplorer
|
||||
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{parametersString}", null, cancellation);
|
||||
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>()
|
||||
@ -96,7 +94,7 @@ namespace NBXplorer
|
||||
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{parametersString}", null, cancellation);
|
||||
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>()
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin.RPC;
|
||||
using NBitcoin.RPC;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
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>
|
||||
@ -35,6 +38,10 @@ namespace NBXplorer.Models
|
||||
/// </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>();
|
||||
@ -61,10 +68,15 @@ namespace NBXplorer.Models
|
||||
/// </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 BitcoinAddress ExplicitChangeAddress { get; set; }
|
||||
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)
|
||||
@ -99,9 +111,66 @@ namespace NBXplorer.Models
|
||||
/// </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
|
||||
{
|
||||
public BitcoinAddress Destination { get; set; }
|
||||
/// <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>
|
||||
@ -118,11 +187,11 @@ namespace NBXplorer.Models
|
||||
public class FeePreference
|
||||
{
|
||||
/// <summary>
|
||||
/// An explicit fee rate for the transaction in Satoshi per vBytes (Mutually exclusive with: BlockTarget, ExplicitFee, FallbackFeeRate)
|
||||
/// 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, ExplicitFeeRate, FallbackFeeRate)
|
||||
/// An explicit fee for the transaction in Satoshi (Mutually exclusive with: BlockTarget, FallbackFeeRate)
|
||||
/// </summary>
|
||||
public Money ExplicitFee { get; set; }
|
||||
/// <summary>
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBXplorer.JsonConverters;
|
||||
using NBitcoin;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
@ -18,7 +15,9 @@ namespace NBXplorer.Models
|
||||
[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,15 +1,12 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBXplorer.JsonConverters;
|
||||
|
||||
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))]
|
||||
@ -20,7 +17,8 @@ namespace NBXplorer.Models
|
||||
public BitcoinExtKey AccountHDKey { get; set; }
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.KeyPathJsonConverter))]
|
||||
public NBitcoin.RootedKeyPath AccountKeyPath { get; set; }
|
||||
public DerivationStrategyBase DerivationScheme { get; set; }
|
||||
public string AccountDescriptor { get; set; }
|
||||
public StandardDerivationStrategyBase DerivationScheme { get; set; }
|
||||
|
||||
public Mnemonic GetMnemonic()
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
@ -64,11 +63,11 @@ namespace NBXplorer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public int Confirmations
|
||||
public long Confirmations
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public int? Height
|
||||
public long? Height
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
@ -90,10 +89,10 @@ namespace NBXplorer.Models
|
||||
get; set;
|
||||
} = new List<MatchedOutput>();
|
||||
|
||||
public List<MatchedOutput> Inputs
|
||||
public List<MatchedInput> Inputs
|
||||
{
|
||||
get; set;
|
||||
} = new List<MatchedOutput>();
|
||||
} = new List<MatchedInput>();
|
||||
public DateTimeOffset Timestamp
|
||||
{
|
||||
get;
|
||||
@ -107,5 +106,6 @@ namespace NBXplorer.Models
|
||||
public uint256 ReplacedBy { get; set; }
|
||||
public uint256 Replacing { get; set; }
|
||||
public bool Replaceable { get; set; }
|
||||
public TransactionMetadata Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
22
NBXplorer.Client/Models/GroupInformation.cs
Normal file
22
NBXplorer.Client/Models/GroupInformation.cs
Normal file
@ -0,0 +1,22 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
10
NBXplorer.Client/Models/ImportUTXORequest.cs
Normal file
10
NBXplorer.Client/Models/ImportUTXORequest.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NBXplorer.Models;
|
||||
|
||||
public class ImportUTXORequest
|
||||
{
|
||||
[JsonProperty("UTXOs")]
|
||||
public OutPoint[] Utxos { get; set; }
|
||||
}
|
||||
@ -1,29 +1,13 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class KeyPathInformation
|
||||
{
|
||||
public KeyPathInformation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public KeyPathInformation(Derivation derivation, DerivationSchemeTrackedSource derivationStrategy, DerivationFeature feature, KeyPath keyPath, NBXplorerNetwork network)
|
||||
{
|
||||
ScriptPubKey = derivation.ScriptPubKey;
|
||||
Redeem = derivation.Redeem;
|
||||
TrackedSource = derivationStrategy;
|
||||
DerivationStrategy = derivationStrategy.DerivationStrategy;
|
||||
Feature = feature;
|
||||
KeyPath = keyPath;
|
||||
Address = network.CreateAddress(derivationStrategy.DerivationStrategy, keyPath, ScriptPubKey);
|
||||
}
|
||||
public TrackedSource TrackedSource { get; set; }
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public DerivationFeature Feature
|
||||
@ -51,9 +35,10 @@ namespace NBXplorer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public int GetIndex()
|
||||
{
|
||||
return (int)KeyPath.Indexes[KeyPath.Indexes.Length - 1];
|
||||
}
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public int? Index { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public IDictionary<string, JToken> AdditionalData { get; set; } = new Dictionary<string, JToken>();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -72,25 +71,6 @@ namespace NBXplorer
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryMatchTemplate(KeyPath keyPath, out uint index)
|
||||
{
|
||||
index = 0;
|
||||
if (keyPath.Length != 1 + PreIndexes.Length + PostIndexes.Length)
|
||||
return false;
|
||||
for (int i = 0; i < PreIndexes.Length; i++)
|
||||
{
|
||||
if (PreIndexes[i] != keyPath[i])
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < PostIndexes.Length; i++)
|
||||
{
|
||||
if (PostIndexes[i] != keyPath[i + 1 + PreIndexes.Length])
|
||||
return false;
|
||||
}
|
||||
index = keyPath[PreIndexes.Length];
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseCore(string i, out uint index)
|
||||
{
|
||||
if (i.Length == 0)
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -15,14 +13,7 @@ namespace NBXplorer
|
||||
private readonly KeyPathTemplate customKeyPathTemplate;
|
||||
private static readonly KeyPathTemplates _Default = new KeyPathTemplates();
|
||||
private readonly DerivationFeature[] derivationFeatures;
|
||||
|
||||
public static KeyPathTemplates Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Default;
|
||||
}
|
||||
}
|
||||
public static KeyPathTemplates Default => _Default;
|
||||
|
||||
private KeyPathTemplates() : this(null)
|
||||
{
|
||||
@ -31,81 +22,27 @@ namespace NBXplorer
|
||||
public KeyPathTemplates(KeyPathTemplate customKeyPathTemplate)
|
||||
{
|
||||
this.customKeyPathTemplate = customKeyPathTemplate;
|
||||
List<DerivationFeature> derivationFeatures = new List<DerivationFeature>();
|
||||
derivationFeatures.Add(DerivationFeature.Deposit);
|
||||
derivationFeatures.Add(DerivationFeature.Change);
|
||||
derivationFeatures.Add(DerivationFeature.Direct);
|
||||
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)
|
||||
{
|
||||
switch (derivationFeature)
|
||||
=> derivationFeature switch
|
||||
{
|
||||
case DerivationFeature.Deposit:
|
||||
return depositKeyPathTemplate;
|
||||
case DerivationFeature.Change:
|
||||
return changeKeyPathTemplate;
|
||||
case DerivationFeature.Direct:
|
||||
return directKeyPathTemplate;
|
||||
case DerivationFeature.Custom when customKeyPathTemplate != null:
|
||||
return customKeyPathTemplate;
|
||||
default:
|
||||
throw new NotSupportedException(derivationFeature.ToString());
|
||||
}
|
||||
}
|
||||
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 KeyPathTemplate GetKeyPathTemplate(KeyPath keyPath)
|
||||
{
|
||||
if (keyPath == null)
|
||||
throw new ArgumentNullException(nameof(keyPath));
|
||||
|
||||
if (depositKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return depositKeyPathTemplate;
|
||||
}
|
||||
else if (changeKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return changeKeyPathTemplate;
|
||||
}
|
||||
else if (directKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return directKeyPathTemplate;
|
||||
}
|
||||
else if (customKeyPathTemplate != null && customKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return customKeyPathTemplate;
|
||||
}
|
||||
else
|
||||
throw new ArgumentException(paramName: nameof(keyPath), message: "No template match this keypath");
|
||||
}
|
||||
|
||||
public DerivationFeature GetDerivationFeature(KeyPath keyPath)
|
||||
{
|
||||
if (depositKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return DerivationFeature.Deposit;
|
||||
}
|
||||
else if (changeKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return DerivationFeature.Change;
|
||||
}
|
||||
else if (directKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return DerivationFeature.Direct;
|
||||
}
|
||||
else if (customKeyPathTemplate != null && customKeyPathTemplate.TryMatchTemplate(keyPath, out _))
|
||||
{
|
||||
return DerivationFeature.Custom;
|
||||
}
|
||||
else
|
||||
throw new ArgumentException(paramName: nameof(keyPath), message: "No template match this keypath");
|
||||
}
|
||||
public IEnumerable<DerivationFeature> GetSupportedDerivationFeatures()
|
||||
{
|
||||
return derivationFeatures;
|
||||
}
|
||||
public IEnumerable<DerivationFeature> GetSupportedDerivationFeatures() => derivationFeatures;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
@ -28,6 +26,13 @@ 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;
|
||||
@ -40,6 +45,10 @@ namespace NBXplorer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public string Reason
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public NBXplorerException AsException()
|
||||
{
|
||||
|
||||
@ -22,6 +22,8 @@ namespace NBXplorer.Models
|
||||
get; set;
|
||||
}
|
||||
|
||||
public long Confirmations { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public override string EventType => "newblock";
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
@ -29,6 +28,10 @@ namespace NBXplorer.Models
|
||||
get; set;
|
||||
}
|
||||
|
||||
public List<MatchedInput> Inputs
|
||||
{
|
||||
get; set;
|
||||
} = new List<MatchedInput>();
|
||||
public List<MatchedOutput> Outputs
|
||||
{
|
||||
get; set;
|
||||
@ -37,6 +40,8 @@ namespace NBXplorer.Models
|
||||
[JsonIgnore]
|
||||
public override string EventType => "newtransaction";
|
||||
|
||||
public List<uint256> Replacing { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var conf = (BlockId == null ? "unconfirmed" : "confirmed");
|
||||
@ -61,6 +66,19 @@ namespace NBXplorer.Models
|
||||
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,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class PruneRequest
|
||||
{
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class PruneResponse
|
||||
{
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
@ -51,11 +48,6 @@ namespace NBXplorer.Models
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public double RepositoryPingTime
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public bool IsFullySynched
|
||||
{
|
||||
get; set;
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
@ -14,22 +13,30 @@ namespace NBXplorer.Models
|
||||
{
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
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;
|
||||
@ -37,7 +44,6 @@ namespace NBXplorer.Models
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
TrackedSource item = obj as TrackedSource;
|
||||
@ -93,6 +99,58 @@ namespace NBXplorer.Models
|
||||
{
|
||||
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
|
||||
@ -117,8 +175,6 @@ namespace NBXplorer.Models
|
||||
|
||||
public static bool TryParse(ReadOnlySpan<char> strSpan, out TrackedSource addressTrackedSource, Network network)
|
||||
{
|
||||
if (strSpan == null)
|
||||
throw new ArgumentNullException(nameof(strSpan));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
addressTrackedSource = null;
|
||||
@ -156,8 +212,6 @@ namespace NBXplorer.Models
|
||||
|
||||
public static bool TryParse(ReadOnlySpan<char> strSpan, out DerivationSchemeTrackedSource derivationSchemeTrackedSource, NBXplorerNetwork network)
|
||||
{
|
||||
if (strSpan == null)
|
||||
throw new ArgumentNullException(nameof(strSpan));
|
||||
if (network == null)
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
derivationSchemeTrackedSource = null;
|
||||
@ -186,5 +240,12 @@ namespace NBXplorer.Models
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
40
NBXplorer.Client/Models/TransactionMetadata.cs
Normal file
40
NBXplorer.Client/Models/TransactionMetadata.cs
Normal file
@ -0,0 +1,40 @@
|
||||
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,14 +1,12 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
public class TransactionResult
|
||||
{
|
||||
int _Confirmations;
|
||||
public int Confirmations
|
||||
long _Confirmations;
|
||||
public long Confirmations
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -46,7 +44,7 @@ namespace NBXplorer.Models
|
||||
}
|
||||
}
|
||||
|
||||
public int? Height
|
||||
public long? Height
|
||||
{
|
||||
get;
|
||||
set;
|
||||
@ -56,5 +54,8 @@ namespace NBXplorer.Models
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public uint256 ReplacedBy { get; set; }
|
||||
|
||||
public TransactionMetadata Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
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
|
||||
{
|
||||
@ -46,6 +45,11 @@ namespace NBXplorer.Models
|
||||
}
|
||||
}
|
||||
|
||||
public List<UTXO> SpentUnconfirmed
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = new List<UTXO>();
|
||||
|
||||
UTXOChange _Confirmed = new UTXOChange();
|
||||
public UTXOChange Confirmed
|
||||
@ -89,7 +93,7 @@ namespace NBXplorer.Models
|
||||
|
||||
public Key[] GetKeys(ExtKey extKey, bool excludeUnconfirmedUTXOs = false)
|
||||
{
|
||||
return GetUnspentUTXOs(excludeUnconfirmedUTXOs).Select(u => extKey.Derive(u.KeyPath).PrivateKey).ToArray();
|
||||
return GetUnspentUTXOs(excludeUnconfirmedUTXOs).Where(u => u.KeyPath is not null).Select(u => extKey.Derive(u.KeyPath).PrivateKey).ToArray();
|
||||
}
|
||||
}
|
||||
public class UTXOChange
|
||||
@ -155,13 +159,30 @@ namespace NBXplorer.Models
|
||||
if (Value is Money v)
|
||||
{
|
||||
var coin = new Coin(Outpoint, new TxOut(v, ScriptPubKey));
|
||||
if (derivationStrategy != null)
|
||||
if (Redeem is not null)
|
||||
{
|
||||
var derivation = derivationStrategy.GetDerivation(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);
|
||||
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;
|
||||
}
|
||||
@ -197,7 +218,10 @@ namespace NBXplorer.Models
|
||||
}
|
||||
}
|
||||
|
||||
public BitcoinAddress Address { get; set; }
|
||||
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
|
||||
public Script Redeem { get; set; }
|
||||
IMoney _Value;
|
||||
public IMoney Value
|
||||
{
|
||||
@ -241,17 +265,19 @@ namespace NBXplorer.Models
|
||||
}
|
||||
|
||||
|
||||
uint _Confirmations;
|
||||
public int Confirmations
|
||||
long _Confirmations;
|
||||
public long Confirmations
|
||||
{
|
||||
get
|
||||
{
|
||||
return checked((int)_Confirmations);
|
||||
return checked((long)_Confirmations);
|
||||
}
|
||||
set
|
||||
{
|
||||
_Confirmations = checked((uint)value);
|
||||
_Confirmations = checked((long)value);
|
||||
}
|
||||
}
|
||||
|
||||
public int KeyIndex { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer.Models
|
||||
{
|
||||
|
||||
@ -1,35 +1,43 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.1;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>net10.0;netstandard2.1</TargetFrameworks>
|
||||
<Company>Digital Garage</Company>
|
||||
<Version>4.1.3</Version>
|
||||
<Version>5.0.6</Version>
|
||||
<Copyright>Copyright © Digital Garage 2017</Copyright>
|
||||
<Description>Client API for the minimalist HD Wallet Tracker NBXplorer</Description>
|
||||
<PackageIconUrl>https://aois.blob.core.windows.net/public/Bitcoin.png</PackageIconUrl>
|
||||
<PackageIcon>Bitcoin.png</PackageIcon>
|
||||
<PackageTags>bitcoin</PackageTags>
|
||||
<PackageProjectUrl>https://github.com/dgarage/NBXplorer/</PackageProjectUrl>
|
||||
<PackageProjectUrl>https://github.com/btcpayserver/NBXplorer/</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<RepositoryUrl>https://github.com/dgarage/NBXplorer</RepositoryUrl>
|
||||
<RepositoryUrl>https://github.com/btcpayserver/NBXplorer</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<LangVersion>12</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<DefineConstants>$(DefineConstants);NO_SPAN</DefineConstants>
|
||||
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' ">
|
||||
<DefineConstants>$(DefineConstants);NO_RECORD</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<DebugType>portable</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<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="6.0.18" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="3.0.8" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="System.Net.WebSockets.Client" Version="4.3.2" />
|
||||
<PackageReference Include="NBitcoin" Version="10.0.6" />
|
||||
<PackageReference Include="NBitcoin.Altcoins" Version="6.0.3" />
|
||||
</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>
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -59,7 +57,7 @@ namespace NBXplorer
|
||||
if (_Settings.TryGetValue(networkType, out v))
|
||||
return v;
|
||||
var settings = new NBXplorerDefaultSettings();
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", GetFolderName(networkType), false);
|
||||
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 :
|
||||
@ -70,5 +68,48 @@ namespace NBXplorer
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -17,6 +13,10 @@ namespace NBXplorer
|
||||
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;
|
||||
@ -50,6 +50,7 @@ namespace NBXplorer
|
||||
internal set;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public virtual BitcoinAddress CreateAddress(DerivationStrategyBase derivationStrategy, KeyPath keyPath, Script scriptPubKey)
|
||||
{
|
||||
return scriptPubKey.GetDestinationAddress(NBitcoinNetwork);
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -14,7 +12,6 @@ namespace NBXplorer
|
||||
MinRPCVersion = 140200,
|
||||
ChainLoadingTimeout = TimeSpan.FromHours(1),
|
||||
ChainCacheLoadingTimeout = TimeSpan.FromMinutes(2),
|
||||
SupportCookieAuthentication = false,
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin.Altcoins.Elements;
|
||||
using NBitcoin.Crypto;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
#if !NO_RECORD
|
||||
using NBitcoin.WalletPolicies;
|
||||
#endif
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -20,28 +22,77 @@ namespace NBXplorer
|
||||
{
|
||||
var factory = base.CreateStrategyFactory();
|
||||
factory.AuthorizedOptions.Add("unblinded");
|
||||
factory.AuthorizedOptions.Add("slip77");
|
||||
return factory;
|
||||
}
|
||||
|
||||
public override BitcoinAddress CreateAddress(DerivationStrategyBase derivationStrategy, KeyPath keyPath, Script scriptPubKey)
|
||||
public BitcoinAddress BlindIfNeeded(DerivationStrategyBase derivationStrategy, BitcoinAddress address, KeyPath keyPath)
|
||||
{
|
||||
if (derivationStrategy.Unblinded())
|
||||
{
|
||||
return base.CreateAddress(derivationStrategy, keyPath, scriptPubKey);
|
||||
}
|
||||
var blindingPubKey = GenerateBlindingKey(derivationStrategy, keyPath).PubKey;
|
||||
return new BitcoinBlindedAddress(blindingPubKey, base.CreateAddress(derivationStrategy, keyPath, scriptPubKey));
|
||||
if (derivationStrategy.Unblinded() || address is BitcoinBlindedAddress)
|
||||
return address;
|
||||
var blindingPubKey = GenerateBlindingKey(derivationStrategy, keyPath, address.ScriptPubKey, NBitcoinNetwork).PubKey;
|
||||
return new BitcoinBlindedAddress(blindingPubKey, address);
|
||||
}
|
||||
|
||||
public static Key GenerateBlindingKey(DerivationStrategyBase derivationStrategy, KeyPath keyPath)
|
||||
[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");
|
||||
}
|
||||
var blindingKey = new Key(derivationStrategy.GetChild(keyPath).GetChild(new KeyPath("0")).GetDerivation()
|
||||
.ScriptPubKey.WitHash.ToBytes());
|
||||
return blindingKey;
|
||||
|
||||
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)
|
||||
@ -63,7 +114,11 @@ namespace NBXplorer
|
||||
{
|
||||
public static bool Unblinded(this DerivationStrategyBase derivationStrategyBase)
|
||||
{
|
||||
return derivationStrategyBase.AdditionalOptions.TryGetValue("unblinded", out var unblinded) is true && unblinded;
|
||||
return derivationStrategyBase.AdditionalOptions.TryGetValue("unblinded", out _);
|
||||
}
|
||||
public static bool Slip77(this DerivationStrategyBase derivationStrategyBase ,out string key)
|
||||
{
|
||||
return derivationStrategyBase.AdditionalOptions.TryGetValue("slip77", out key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
24
NBXplorer.Client/NBXplorerNetworkProvider.Pepecoin.cs
Normal file
24
NBXplorer.Client/NBXplorerNetworkProvider.Pepecoin.cs
Normal file
@ -0,0 +1,24 @@
|
||||
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,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
using NBitcoin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -13,6 +13,7 @@ namespace NBXplorer
|
||||
InitBitcore(networkType);
|
||||
InitLitecoin(networkType);
|
||||
InitDogecoin(networkType);
|
||||
InitPepecoin(networkType);
|
||||
InitBCash(networkType);
|
||||
InitGroestlcoin(networkType);
|
||||
InitBGold(networkType);
|
||||
|
||||
128
NBXplorer.Client/NBitcoinNBXplorerExtensions.cs
Normal file
128
NBXplorer.Client/NBitcoinNBXplorerExtensions.cs
Normal file
@ -0,0 +1,128 @@
|
||||
#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,7 +1,4 @@
|
||||
using NBXplorer.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -16,3 +13,4 @@ namespace NBXplorer
|
||||
public abstract Task<NewEventBase> NextEventAsync(CancellationToken cancellation = default);
|
||||
}
|
||||
}
|
||||
|
||||
9
NBXplorer.Client/PushNuget.sh
Executable file
9
NBXplorer.Client/PushNuget.sh
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
rm -rf "bin/Release/"
|
||||
dotnet pack --configuration Release --include-symbols -p:SymbolPackageFormat=snupkg
|
||||
package=$(find ./bin/Release -name "*.nupkg" -type f | head -n 1)
|
||||
dotnet nuget push "${package[0]}" --source "https://api.nuget.org/v3/index.json" --api-key "$NUGET_API_KEY"
|
||||
ver=$(basename "${package[0]}" | sed -E 's/NBXplorer\.Client\.([0-9]+(\.[0-9]+){1,3}).*/\1/')
|
||||
git tag -a "Client/v$ver" -m "Client/$ver"
|
||||
git push origin "Client/v$ver"
|
||||
@ -3,8 +3,7 @@ using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using NBXplorer.JsonConverters;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
@ -25,10 +24,13 @@ namespace NBXplorer
|
||||
{
|
||||
if (settings == null)
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
Settings.DateParseHandling = DateParseHandling.None;
|
||||
NBitcoin.JsonConverters.Serializer.RegisterFrontConverters(settings, Network);
|
||||
if (_Network != null)
|
||||
{
|
||||
settings.Converters.Insert(0, new JsonConverters.CachedSerializer(_Network));
|
||||
settings.Converters.Insert(1, new PSBTDestinationJsonConverter(_Network.NBitcoinNetwork));
|
||||
settings.Converters.Add(new JsonConverters.KeyPathTemplateJsonConverter());
|
||||
}
|
||||
ReplaceConverter<NBitcoin.JsonConverters.MoneyJsonConverter>(settings, new NBXplorer.JsonConverters.MoneyJsonConverter());
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace NBXplorer
|
||||
{
|
||||
|
||||
@ -1,79 +1,22 @@
|
||||
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 WebsocketNotificationSession : NotificationSessionBase, IDisposable
|
||||
public class WebsocketNotificationSessionLegacy : WebsocketNotificationSession
|
||||
{
|
||||
|
||||
private readonly ExplorerClient _Client;
|
||||
public ExplorerClient Client
|
||||
protected override FormattableString GetConnectPath() => $"v1/cryptos/{_Client.CryptoCode}/connect";
|
||||
internal WebsocketNotificationSessionLegacy(ExplorerClient client) : base(client)
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Client;
|
||||
}
|
||||
}
|
||||
internal WebsocketNotificationSession(ExplorerClient client)
|
||||
{
|
||||
if(client == null)
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
_Client = client;
|
||||
}
|
||||
|
||||
internal async Task ConnectAsync(CancellationToken cancellation)
|
||||
{
|
||||
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
|
||||
{
|
||||
if(!_Client.Auth.RefreshCache())
|
||||
throw;
|
||||
socket = await ConnectAsyncCore(uri, cancellation);
|
||||
}
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings();
|
||||
new Serializer(_Client.Network).ConfigureSerializer(settings);
|
||||
_MessageListener = new WebsocketMessageListener(socket, settings);
|
||||
}
|
||||
|
||||
private async Task<ClientWebSocket> ConnectAsyncCore(string uri, CancellationToken cancellation)
|
||||
{
|
||||
var socket = new ClientWebSocket();
|
||||
_Client.Auth.SetWebSocketAuth(socket);
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(new Uri(uri, UriKind.Absolute), cancellation).ConfigureAwait(false);
|
||||
}
|
||||
catch { socket.Dispose(); throw; }
|
||||
return socket;
|
||||
}
|
||||
|
||||
private static string ToWebsocketUri(string uri)
|
||||
{
|
||||
if(uri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
uri = uri.Replace("https://", "wss://");
|
||||
if(uri.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||
uri = uri.Replace("http://", "ws://");
|
||||
return uri;
|
||||
}
|
||||
|
||||
WebsocketMessageListener _MessageListener;
|
||||
UTF8Encoding UTF8 = new UTF8Encoding(false, true);
|
||||
|
||||
public void ListenNewBlock(CancellationToken cancellation = default)
|
||||
{
|
||||
ListenNewBlockAsync(cancellation).GetAwaiter().GetResult();
|
||||
@ -130,7 +73,7 @@ namespace NBXplorer
|
||||
|
||||
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);
|
||||
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)
|
||||
@ -143,6 +86,70 @@ namespace NBXplorer
|
||||
return _MessageListener.Send(new Models.NewTransactionEventRequest() { TrackedSources = trackedSources.Select(d => d.ToString()).ToArray(), CryptoCode = _Client.CryptoCode }, null, cancellation);
|
||||
}
|
||||
|
||||
}
|
||||
public class WebsocketNotificationSession : NotificationSessionBase, IDisposable
|
||||
{
|
||||
|
||||
protected readonly ExplorerClient _Client;
|
||||
public ExplorerClient Client
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Client;
|
||||
}
|
||||
}
|
||||
internal WebsocketNotificationSession(ExplorerClient client)
|
||||
{
|
||||
if(client == null)
|
||||
throw new ArgumentNullException(nameof(client));
|
||||
_Client = client;
|
||||
}
|
||||
|
||||
internal async Task ConnectAsync(CancellationToken cancellation)
|
||||
{
|
||||
var uri = _Client.GetFullUri(GetConnectPath());
|
||||
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
|
||||
{
|
||||
if (!_Client.Auth.RefreshCache())
|
||||
throw;
|
||||
socket = await ConnectAsyncCore(uri, cancellation);
|
||||
}
|
||||
JsonSerializerSettings settings = new JsonSerializerSettings();
|
||||
new Serializer(_Client.Network).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);
|
||||
try
|
||||
{
|
||||
await socket.ConnectAsync(new Uri(uri, UriKind.Absolute), cancellation).ConfigureAwait(false);
|
||||
}
|
||||
catch { socket.Dispose(); throw; }
|
||||
return socket;
|
||||
}
|
||||
|
||||
private static string ToWebsocketUri(string uri)
|
||||
{
|
||||
if(uri.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
uri = uri.Replace("https://", "wss://");
|
||||
if(uri.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||
uri = uri.Replace("http://", "ws://");
|
||||
return uri;
|
||||
}
|
||||
|
||||
protected WebsocketMessageListener _MessageListener;
|
||||
|
||||
public override Task<NewEventBase> NextEventAsync(CancellationToken cancellation = default)
|
||||
{
|
||||
return _MessageListener.NextMessageAsync(cancellation);
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NBXplorer
|
||||
namespace NBXplorer
|
||||
{
|
||||
public class WellknownMetadataKeys
|
||||
{
|
||||
@ -10,6 +6,8 @@ namespace NBXplorer
|
||||
public const string Mnemonic = nameof(Mnemonic);
|
||||
public const string MasterHDKey = nameof(MasterHDKey);
|
||||
public const string AccountHDKey = nameof(AccountHDKey);
|
||||
public const string AccountDescriptor = nameof(AccountDescriptor);
|
||||
public const string Birthdate = nameof(Birthdate);
|
||||
public const string AccountKeyPath = nameof(AccountKeyPath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,18 +3,13 @@ using System.Linq;
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Net.Http;
|
||||
using Xunit.Abstractions;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using NBitcoin.Crypto;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NBXplorer.Analytics;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBitcoin.Altcoins;
|
||||
|
||||
namespace NBXplorer.Tests
|
||||
{
|
||||
@ -135,7 +130,7 @@ namespace NBXplorer.Tests
|
||||
catch { }
|
||||
}
|
||||
using var client = new HttpClient();
|
||||
var resp = await client.GetAsync($"https://api.qbit.ninja/blocks/{blockId}?format=raw");
|
||||
var resp = await client.GetAsync($"https://mempool.space/api/block/{blockId}/raw");
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var bytes = await resp.Content.ReadAsByteArrayAsync();
|
||||
var block = Block.Load(bytes, Network.Main);
|
||||
|
||||
@ -1,86 +0,0 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace NBXplorer.Tests
|
||||
{
|
||||
public class CustomServer : IDisposable
|
||||
{
|
||||
public static int FreeTcpPort()
|
||||
{
|
||||
TcpListener l = new TcpListener(IPAddress.Loopback, 0);
|
||||
l.Start();
|
||||
int port = ((IPEndPoint)l.LocalEndpoint).Port;
|
||||
l.Stop();
|
||||
return port;
|
||||
}
|
||||
|
||||
TaskCompletionSource<bool> _Evt = null;
|
||||
IWebHost _Host = null;
|
||||
CancellationTokenSource _Closed = new CancellationTokenSource();
|
||||
public CustomServer()
|
||||
{
|
||||
var port = FreeTcpPort();
|
||||
_Host = new WebHostBuilder()
|
||||
.Configure(app =>
|
||||
{
|
||||
app.Run(req =>
|
||||
{
|
||||
while(_Act == null)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
_Closed.Token.ThrowIfCancellationRequested();
|
||||
}
|
||||
_Act(req);
|
||||
_Act = null;
|
||||
_Evt.TrySetResult(true);
|
||||
req.Response.StatusCode = 200;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
})
|
||||
.UseKestrel()
|
||||
.UseUrls("http://127.0.0.1:" + port)
|
||||
.Build();
|
||||
_Host.Start();
|
||||
}
|
||||
|
||||
public Uri GetUri()
|
||||
{
|
||||
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
|
||||
}
|
||||
|
||||
Action<HttpContext> _Act;
|
||||
public void ProcessNextRequest(Action<HttpContext> act)
|
||||
{
|
||||
var source = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
|
||||
cancellation.Token.Register(() => source.TrySetCanceled());
|
||||
source = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_Evt = source;
|
||||
_Act = act;
|
||||
try
|
||||
{
|
||||
_Evt.Task.GetAwaiter().GetResult();
|
||||
}
|
||||
catch(TaskCanceledException)
|
||||
{
|
||||
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_Closed.Cancel();
|
||||
_Host.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
1093
NBXplorer.Tests/DatabaseTests.cs
Normal file
1093
NBXplorer.Tests/DatabaseTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
13
NBXplorer.Tests/Docker/servers.json
Normal file
13
NBXplorer.Tests/Docker/servers.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"Servers": {
|
||||
"1": {
|
||||
"Name": "docker_postgres",
|
||||
"Group": "Servers",
|
||||
"Host": "postgres",
|
||||
"Port": 5432,
|
||||
"MaintenanceDB": "postgres",
|
||||
"Username": "postgres",
|
||||
"SSLMode": "disable"
|
||||
}
|
||||
}
|
||||
}
|
||||
6
NBXplorer.Tests/Dockerfile
Normal file
6
NBXplorer.Tests/Dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0.301-noble AS builder
|
||||
WORKDIR /source
|
||||
COPY . .
|
||||
RUN cd NBXplorer.Tests && dotnet build
|
||||
WORKDIR /source/NBXplorer.Tests
|
||||
ENTRYPOINT ["./tests-entrypoint.sh"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user