Compare commits

...

433 Commits

Author SHA1 Message Date
Nicolas Dorier
5147b5f261
bump deps 2026-06-10 10:12:06 +09:00
Nicolas Dorier
6ad4941712
Merge pull request #544 from s373nZ/docker-curl
Add `curl` to Docker image to support health checks
2026-06-02 09:07:36 +09:00
se7enz
98d89f924a
docker: Add curl to Docker image to support health checks 2026-05-26 09:52:03 +02:00
Nicolas Dorier
36e1596b5c
Faster tests 2026-04-24 14:14:09 +09:00
Nicolas Dorier
83e0ab4f68
Relax xpub validation 2026-04-24 12:25:52 +09:00
Nicolas Dorier
c24b47bb03
Deprecate import into RPC 2026-04-24 09:05:55 +09:00
Nicolas Dorier
b9f324b2eb
bump testframework 2026-04-23 15:33:14 +09:00
Nicolas Dorier
e0af3bf493
Fix broken links 2026-04-23 15:23:49 +09:00
Nicolas Dorier
06349f9818
Trying to speedup tests 2026-04-23 14:51:28 +09:00
Nicolas Dorier
c46949ac60
Merge pull request #542 from dgarage/chunk
Add chunk fee and weight to transaction metadata
2026-04-22 18:14:34 +09:00
Nicolas Dorier
5e570a84fb
Add chunk fee and weight to transaction metadata 2026-04-22 18:03:29 +09:00
Nicolas Dorier
060d19f6a8
bump 2026-04-22 11:13:19 +09:00
Nicolas Dorier
b83178be8d
Fix tests 2026-04-22 10:51:31 +09:00
Nicolas Dorier
f72fc4d321
Remove some error during tests 2026-04-22 09:59:01 +09:00
Nicolas Dorier
9242d645b6
Trying to speed up tests 2026-04-22 09:25:43 +09:00
Nicolas Dorier
05df9b5037
Decrease spams in test logs 2026-04-22 09:06:09 +09:00
Nicolas Dorier
456717935c
Fix tests 2026-04-22 09:03:32 +09:00
Nicolas Dorier
98df6b7fdd
Improve error messages for RPC wallet errors 2026-04-22 08:54:33 +09:00
Nicolas Dorier
449568be9a
Bump test to 31.0 2026-04-22 08:47:51 +09:00
Nicolas Dorier
abb5fd3a6c
bump bitcoin core in tests 2026-04-21 21:21:35 +09:00
Nicolas Dorier
deb868b2c0
Add PushNuget.sh 2026-04-21 10:38:46 +09:00
Nicolas Dorier
f0000ceab6
bump libs 2026-04-21 10:30:00 +09:00
Nicolas Dorier
d9cba8b0b5
bump nbt 2026-04-11 09:17:57 +09:00
Nicolas Dorier
becdc3f47d
Fix DOGE getting stuck 2026-04-10 22:33:18 +09:00
Nicolas Dorier
aa462e7af1
bump 2026-04-10 11:28:07 +09:00
Nicolas Dorier
34d6b0dcf3
Potentially fix dogecoin bug, improve logs when node connection fail with exception 2026-04-10 11:27:47 +09:00
Nicolas Dorier
87037e17e1
Improve logs at startup 2026-04-10 10:44:46 +09:00
Nicolas Dorier
c806e9022e
Do not use static logger in tests 2026-04-10 09:44:04 +09:00
Nicolas Dorier
39b1861d7a
Fix test 2026-04-10 09:27:13 +09:00
Nicolas Dorier
a379506b46
Port code to dotnet10.0 style 2026-04-10 09:18:44 +09:00
Nicolas Dorier
68513b4668
Remove message broker leftover 2026-04-10 09:09:44 +09:00
Nicolas Dorier
802945291a
Remove message broker dependencies 2026-04-10 08:55:28 +09:00
Nicolas Dorier
3756b54138
Fix compatibility with core 31.0 2026-04-10 08:17:05 +09:00
Nicolas Dorier
9eeaf71195
bump 2026-04-03 11:05:43 +09:00
Nicolas Dorier
0e4222a1f2
Merge pull request #539 from Unknown-Kush/master
Add Pepecoin
2026-04-03 11:04:13 +09:00
Unknown-Kush
34831b2d85 Add Pepecoin support 2026-04-02 09:51:55 -04:00
Unknown-Kush
aba8cdc8c6 bump NBitcoin.Altcoins 2026-04-02 09:24:52 -04:00
Nicolas Dorier
ba76d3b059
bump dockerfile 2026-04-01 10:07:47 +09:00
Nicolas Dorier
26b3dddc28
bump nbx 2026-03-11 09:54:42 +09:00
Nicolas Dorier
1a38ffa497
Bump libraries 2026-03-11 09:54:42 +09:00
Nicolas Dorier
2fb63ae18e
Merge pull request #537 from NicolasDorier/retry-flaky-rpc
Retry flaky idempotent RPC requests
2026-03-11 09:44:29 +09:00
Nicolas Dorier
206fd0017f
Fix .net10 mention 2026-03-11 09:42:01 +09:00
Nicolas Dorier
5d78c7034c
Retry flaky idempotent RPC requests 2026-03-11 09:32:27 +09:00
Nicolas Dorier
f226238779
Add publish-docker.sh 2026-03-11 09:15:22 +09:00
Nicolas Dorier
05d8ed054e bump 2025-12-26 11:05:07 +09:00
Nicolas Dorier
4ebd7c4c2f
Update to .NET10.0 and bump libs (#534) 2025-12-20 23:29:30 +09:00
Nicolas Dorier
8670e58923 bump postgres 2025-12-18 16:51:01 +09:00
nicolas.dorier
5ea76f64bb
bump NBitcoin.Testframework 2025-09-09 22:06:10 +09:00
nicolas.dorier
be086282ae
Allow feature tag to version on docker 2025-09-06 11:29:45 +09:00
nicolas.dorier
58bde3c916
bump asp.net 2025-09-06 11:25:36 +09:00
nicolas.dorier
8af448f724
bump 2025-08-27 19:41:21 +09:00
Nicolas Dorier
273333b040
Fix documentation AI slop, allow more format for broadcasting (#529) 2025-08-27 11:40:53 +01:00
Nicolas Dorier
d03465c240
Fix incorrect doc for GetUTXOs (#530) 2025-08-27 11:40:33 +01:00
nicolas.dorier
58e9bba5a2
Fix: Unable to delete a ADDRESS: item in a group 2025-08-27 19:40:09 +09:00
nicolas.dorier
c58c60c9fa
bump 2025-08-25 10:09:38 +09:00
nicolas.dorier
939a575c51
Fix: Periodic tasks would sometimes stop firing 2025-08-25 10:09:37 +09:00
nicolas.dorier
ca97104831
bump 2025-07-02 12:48:43 +09:00
gruve-p
a51911f4ce
Bump GRS to 29.0 (#517) 2025-07-02 12:32:49 +09:00
Nicolas Dorier
e8316d959b
Add key index to events (#525) 2025-07-02 12:32:33 +09:00
nicolas.dorier
be51a4af50
Add PSBTCoin.HDKeysFor extension method 2025-06-09 17:54:18 +09:00
nicolas.dorier
b7dc5b5c5f
Add PSBT.HDKeysFor for derivation schemes 2025-06-09 17:41:08 +09:00
nicolas.dorier
cdac2c264e
Add overloads to SignAll and GetBalance PSBT 2025-06-09 17:27:20 +09:00
nicolas.dorier
e494a46de9
Bump NBX 2025-06-09 16:56:54 +09:00
nicolas.dorier
10ac6d65d8
Bump NBitcoin 2025-06-09 16:56:02 +09:00
nicolas.dorier
7f84ef983c
Fix doc anchor 2025-06-02 18:57:15 +09:00
nicolas.dorier
9a784b5cce
Major bump version for NBXplorer.Client 2025-06-02 16:56:43 +09:00
nicolas.dorier
dfe7ca478f
Pass keypath when possible to Derivation 2025-06-02 16:36:10 +09:00
nicolas.dorier
ea4ac907b6
Add convenience overload for GetLineFor 2025-06-02 16:28:23 +09:00
nicolas.dorier
fa8bf56623
bump 2025-06-02 11:12:14 +09:00
Nicolas Dorier
f4640c8c4f
Support miniscript derivation scheme (#523)
* Support miniscript derivation scheme

* Adjustements

* fix
2025-06-02 11:11:28 +09:00
nicolas.dorier
fdc8281e88
Bump NBitcoin 2025-05-15 13:43:36 +09:00
nicolas.dorier
8597067cbe
bump NBitcoin 2025-05-15 10:35:22 +09:00
nicolas.dorier
ae99da7299
Simplify code, fix another bug which can stall sync 2025-04-05 10:53:41 +09:00
nicolas.dorier
4666c13a1b
bump 2025-04-04 23:16:04 +09:00
nicolas.dorier
3118b71004
Fix: NBXplorer stalling every 2000 blocks 2025-04-04 23:07:41 +09:00
nicolas.dorier
d61f3db4fa
bump nbxplorer 2025-04-04 22:28:04 +09:00
nicolas.dorier
10e139c3d1
For liquid blinded addresses returns blindingKey in KeyInformation 2025-04-04 22:22:15 +09:00
nicolas.dorier
206165736a
Add doc for the fullschema 2025-04-02 21:43:58 +09:00
nicolas.dorier
f68c50c4cf
Fix C# comment 2025-03-11 17:23:02 +09:00
Nicolas Dorier
75cbba9427
Get transaction data from peer when possible (#511) 2025-03-06 17:40:09 +09:00
Nicolas Dorier
1916ea7050
Add PSBTVersion to CreatePSBT (#510) 2025-03-05 18:26:38 +09:00
nicolas.dorier
e43031411d
bump nbx 2025-02-17 18:00:30 +09:00
nicolas.dorier
7b7cd2314d
Fix regression from .21 2025-02-09 23:53:44 +09:00
nicolas.dorier
4ce8ef6954
CreatePSBT: explicitFee shouldn't be mutually exclusive with feeRate settings 2025-02-09 23:10:44 +09:00
nicolas.dorier
80f16ce8bc
Allow CreatePSBT.ExplicitChangeAddress to be an hex script 2025-01-30 17:34:29 +09:00
nicolas.dorier
fed67b354f
bump 2025-01-30 17:27:20 +09:00
Nicolas Dorier
da251bdbe0
CreatePSBT accept raw scripts (#509) 2025-01-30 17:27:00 +09:00
nicolas.dorier
11a3564ab4
Fix: Feature property was always null. Exception thrown if PSBTDest.Value is null 2025-01-30 16:18:51 +09:00
nicolas.dorier
e3f7933ad1
Fixup GetTransaction crash in case an unknown entry isn't in the mempool 2025-01-30 15:39:20 +09:00
nicolas.dorier
8b9d5fbde7
Bump, and nicer error message if amount are negative in create PSBT 2025-01-30 15:36:46 +09:00
Nicolas Dorier
916a52b49e
Add fee and mempool information to txs fetched in the mempool (#508) 2025-01-30 14:21:25 +09:00
Nicolas Dorier
2ed4f7850f
Bump Bitcoin Core and NBitcoin (#506) 2025-01-17 00:07:31 +09:00
Nicolas Dorier
a38ad24023
Bump Bitcoin Core in tests (#505) 2025-01-13 12:06:19 +09:00
nicolas.dorier
ab27e4b6ce
bump 2025-01-06 19:09:02 +09:00
Nicolas Dorier
e9a235c149
New route for subscribing to events via websockets (#503) 2025-01-06 19:08:41 +09:00
nicolas.dorier
03c17c046d
Improve doc 2024-12-27 19:15:47 +09:00
nicolas.dorier
530a38030e
Fix: Issue when using custom key templates after scanning utxosets 2024-12-27 16:41:12 +09:00
nicolas.dorier
9d796828f8
Refactoring: Do not use keyPathTemplates directly 2024-12-27 16:31:54 +09:00
Nicolas Dorier
eaeeea22a9
Remove dependencies to keytemplates (#502) 2024-12-27 16:16:12 +09:00
nicolas.dorier
5170fd92e1
Returns KeyPathInformation.Index and reuse queries 2024-12-25 21:48:08 +09:00
nicolas.dorier
e5eaf763e7
Make doc a bit more user-friendly by using default postgres port (Fix #498) 2024-12-19 12:23:59 +09:00
Nicolas Dorier
326d9c5e2b
Fix: Taproot PSBT's outputs were not including internal key and taproot hd information (#495) 2024-12-05 15:20:51 +09:00
Nicolas Dorier
8ea14672dd
Bump dependencies (#494) 2024-12-03 14:40:37 +09:00
nicolas.dorier
4d81c236f5
Remove deps 2024-11-30 10:30:12 +09:00
nicolas.dorier
a2afbd951e
More docs 2024-11-29 14:27:00 +09:00
nicolas.dorier
35b449a49f
Fix build 2024-11-29 14:13:57 +09:00
nicolas.dorier
5ab3da5031
Improve docs, show version on startup 2024-11-29 14:09:03 +09:00
nicolas.dorier
dbfd09e2f9
bump 2024-11-29 10:02:06 +09:00
Nicolas Dorier
24d24f2f25
Fix timeout errors, Fix NBXplorer hanging when exiting (#491) 2024-11-29 10:01:21 +09:00
nicolas.dorier
1eeb0ff3ef
Fixup doc 2024-11-29 10:01:01 +09:00
nicolas.dorier
b31fa111b4
Add doc 2024-11-29 10:00:25 +09:00
nicolas.dorier
fb2691748f
Reword doc 2024-11-28 22:32:44 +09:00
nicolas.dorier
c64dc124b5
Better docs 2024-11-28 22:30:11 +09:00
nicolas.dorier
97214740c1
Avoid crash on useless config parameters (#470) 2024-11-28 22:10:07 +09:00
nicolas.dorier
3e0b2047d3
Avoid crash on useless config parameters (#470) 2024-11-28 21:20:01 +09:00
nicolas.dorier
2fa2ca69a0
bump 2024-11-28 19:27:22 +09:00
nicolas.dorier
f37ef1c2d5
Improve doc for groups 2024-11-28 14:34:05 +09:00
Nicolas Dorier
f165b14c52
GetTransactions can be called with from/to timestamp filter (#490) 2024-11-28 14:25:59 +09:00
Andrew Camilleri
c249b842ef
Import utxo (#465) 2024-11-28 13:38:27 +09:00
Nicolas Dorier
531f28817e
Refactor transaction matching, fix elements (#489)
Co-authored-by: Kukks <evilkukka@gmail.com>
2024-11-28 13:30:08 +09:00
nicolas.dorier
654b103128
Improve doc 2024-11-28 13:14:09 +09:00
José Manuel Nieto
21122b4a1f
Refine endpoints (#488) 2024-11-28 12:29:27 +09:00
José Manuel Nieto
e547b99a47
Add API documentation for /cryptos/{cryptoCode}/rpc (#486)
* Add documentation for /cryptos/{cryptoCode}/rpc

* Fix issues during review

* Improve wording
2024-11-28 12:18:07 +09:00
Nicolas Dorier
88f7d8248a
Refactor matching (#487) 2024-11-26 22:05:48 +09:00
nicolas.dorier
a201004b42
Fix: RPC proxy wasn't supporting requests with string as version 2024-11-26 13:25:57 +09:00
nicolas.dorier
0bf9492e1d
Replace from azure to github pages 2024-11-25 17:34:30 +09:00
nicolas.dorier
8275c6effb
Add index.html 2024-11-25 17:27:34 +09:00
nicolas.dorier
3d746aeff9
Fix some links 2024-11-25 16:24:57 +09:00
nicolas.dorier
8f7371dc47
Make API openapi 3.0 compliant 2024-11-25 16:06:40 +09:00
nicolas.dorier
048eab8625
Document basic auth 2024-11-25 15:55:59 +09:00
nicolas.dorier
d4068e7dec
Add doc for generate hot wallet 2024-11-25 13:09:55 +09:00
nicolas.dorier
1352c8dcdf
Add missing Create PSBT 2024-11-25 12:47:16 +09:00
nicolas.dorier
e2767a85c5
Adjustement docs 2024-11-25 12:13:38 +09:00
nicolas.dorier
09d56b22be
Cleanup repetitions 2024-11-25 12:09:53 +09:00
nicolas.dorier
1ee089836d
Add missing routes to groups 2024-11-25 12:04:00 +09:00
nicolas.dorier
4c9db11e6c
Fix broken link 2024-11-25 11:48:11 +09:00
nicolas.dorier
dddd825571
Fix doc of /status 2024-11-25 11:31:25 +09:00
nicolas.dorier
42c9dda280
Rewrite doc of /rescan 2024-11-25 11:17:19 +09:00
nicolas.dorier
9af5962b1e
Improve tags 2024-11-25 11:03:50 +09:00
nicolas.dorier
d5b56d3206
Add /v1 to all paths, reuse parameters definition 2024-11-25 10:41:42 +09:00
Andrew Camilleri
ddde4ae301
Is tracked api (#482) 2024-11-25 10:12:59 +09:00
José Manuel Nieto
a76965d12c
Enrich API documentation (#485)
* Enrich API documentation

* Add missing parameter "includeTransaction" in /cryptos/{cryptoCode}/transactions/{txId}

* Add missing "includeTransaction" parameter in /cryptos/{cryptoCode}/derivations/{derivationScheme}/transactions
2024-11-25 09:31:21 +09:00
Nicolas Dorier
7fb40b5a81
Add browseable doc (#484) 2024-11-18 17:55:58 +09:00
José Manuel Nieto
2e75bdf65e
Add Redoc (#483)
* Add Redoc

* Add CORS Service

* Make sure index.html is served correctly
2024-11-18 17:13:03 +09:00
nicolas.dorier
ac6e63c20f
Bump NBitcoin 2024-11-14 22:11:59 +09:00
nicolas.dorier
5519caf601
Bump NBitcoin 2024-11-14 21:40:51 +09:00
nicolas.dorier
1814138225
Bump NBitcoin 2024-11-13 15:34:05 +09:00
nicolas.dorier
d612420059
bump 2024-11-13 14:47:03 +09:00
nicolas.dorier
f3aeea447d
Fix: Transaction dates are wrong after server / BTC node downtime 2024-11-13 14:46:48 +09:00
nicolas.dorier
261a1e73a1
bump 2024-10-29 10:49:20 +09:00
gruve-p
7c870bdeaa
Bump GRS to 28.0 (#480) 2024-10-29 10:10:30 +09:00
Andrew Camilleri
15a07a8b47
Support mutiny net (#474)
* Support mutiny net

* Small fixup

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2024-10-29 10:09:52 +09:00
nicolas.dorier
52258aecd1
bump 2024-10-26 10:22:36 +09:00
nicolas.dorier
b672a28c1c
Fix DASH block parsing 2024-10-26 10:21:56 +09:00
nicolas.dorier
a33de3bec7
Make sure index progress has at least one block 2024-10-25 23:57:59 +09:00
nicolas.dorier
f98fa1addb
Fix WatchDog being stuck 2024-09-17 15:46:44 +09:00
nicolas.dorier
b5b90871ae
Remove IRPCClient 2024-09-17 15:39:21 +09:00
nicolas.dorier
e937448b6b
Rewrite for readability 2024-09-17 15:21:20 +09:00
nicolas.dorier
c84c0fce97
Small adaptation for 28.0 2024-08-30 22:32:24 +09:00
nicolas.dorier
4456dd04d0
bump 2024-08-30 16:31:40 +09:00
Nicolas Dorier
6ba0315de4
Merge pull request #475 from NicolasDorier/improve-taproot
Fix taproot PSBT generation
2024-08-30 16:31:21 +09:00
nicolas.dorier
2c6418c058
Fix taproot PSBT generation 2024-08-30 16:15:22 +09:00
Nicolas Dorier
f978e88b85
Merge pull request #469 from NicolasDorier/inputsinfo
Add MatchedInputs to events
2024-06-04 11:52:55 +09:00
nicolas.dorier
22b8e0b17a
Add matchedInputs to events 2024-06-04 11:45:51 +09:00
nicolas.dorier
2bd46f7820
Small refactoring 2024-06-04 09:30:40 +09:00
nicolas.dorier
d03b511db6
Remove useless code 2024-06-03 18:27:02 +09:00
nicolas.dorier
0c9a3569e6
Bump images 2024-05-21 11:03:03 +09:00
Nicolas Dorier
f200ea4930
Merge pull request #466 from NicolasDorier/fixtests
Fix tests
2024-05-21 09:28:00 +09:00
nicolas.dorier
dfef4e8c7e
Fix tests 2024-05-21 00:28:03 +09:00
nicolas.dorier
4bf8e25902
bump packages 2024-05-20 21:23:26 +09:00
Nicolas Dorier
019f8c420a
Merge pull request #463 from NicolasDorier/removedbtrie2
Remove DBTrie
2024-04-08 16:58:16 +09:00
nicolas.dorier
a2dc680338
Remove dead code 2024-04-08 16:43:55 +09:00
nicolas.dorier
72a102a8fc
Split actions into several controllers 2024-04-08 16:31:56 +09:00
nicolas.dorier
e7c701f3f7
Renaming namespace 2024-04-08 15:53:06 +09:00
nicolas.dorier
7c710e34b7
Remove abstractions (Interfaces) 2024-04-08 15:49:53 +09:00
nicolas.dorier
efc9ae6444
Remove abstractions 2024-04-08 14:32:43 +09:00
nicolas.dorier
b20d1567e0
Remove DBTrie 2024-04-08 14:07:00 +09:00
nicolas.dorier
21f5f04f64
Remove debug logs 2024-03-06 19:27:33 +09:00
nicolas.dorier
825d5bc78d
Fix: NBX would sometimes stays stuck in case of reorg (Fix #461 #409) 2024-03-06 16:21:21 +09:00
nicolas.dorier
e83cfdb720
bump npgsql 2024-02-26 09:33:04 +09:00
nicolas.dorier
96633a3b7d
Pass down cancellationToken to RPC commands 2024-02-26 09:11:32 +09:00
nicolas.dorier
900f99545d
Avoid crashing PostgresIndexers.StartAsync 2024-02-05 10:24:39 +09:00
nicolas.dorier
082373501e
bump 2024-01-18 15:43:52 +09:00
nicolas.dorier
279521c507
Bump NBitcoin 2024-01-17 22:07:49 +09:00
nicolas.dorier
2000affe7b
Bump libs 2024-01-17 12:44:56 +09:00
Nicolas Dorier
7d70e72d91
Merge pull request #457 from NicolasDorier/groupapi
Add Group API
2024-01-12 09:10:00 +09:00
nicolas.dorier
9b6358221a
Add Group API 2024-01-10 21:15:04 +09:00
nicolas.dorier
5d4d028b0e
Rename WalletTrackedSource to GroupTrackedSource 2024-01-09 13:09:29 +09:00
nicolas.dorier
07b1193237
bump 2023-12-31 12:32:09 +09:00
nicolas.dorier
56d2293c30
Fix possible nbxplorer sync hanging 2023-12-31 12:31:56 +09:00
nicolas.dorier
84305c84d8
Remove unused code, Use IEnumerable.Chunk instead of custom Batch method 2023-12-30 23:45:30 +09:00
nicolas.dorier
fffa6f0bca
bump 2023-12-25 14:20:38 +09:00
Nicolas Dorier
15eee7ae47
Merge pull request #456 from NicolasDorier/foiqntq
Remove useless db roundtrip when indexing transactions
2023-12-25 14:20:21 +09:00
nicolas.dorier
a490c24351
Remove useless db roundtrip when indexing transactions 2023-12-25 14:15:24 +09:00
nicolas.dorier
6eb53ed8d2
Update sln 2023-12-18 13:33:15 +09:00
Nicolas Dorier
6ca2560a1a
Merge pull request #454 from gruve-p/patch-4
Bump Groestlcoin Core to 26.0
2023-12-18 13:31:41 +09:00
Nicolas Dorier
f69581f69e
Merge pull request #455 from dgarage/rewriteci
Use buildx
2023-12-18 13:31:25 +09:00
nicolas.dorier
190eabbc5e
Use buildx 2023-12-18 13:30:26 +09:00
gruve-p
826dc2d4ac
Bump Groestlcoin Core to 26.0 2023-12-13 17:45:39 +01:00
nicolas.dorier
95f28ac578
Properly delete chain-slim.dat 2023-12-12 19:11:05 +09:00
nicolas.dorier
8d309e4de5
bump NBX 2023-12-12 19:06:14 +09:00
nicolas.dorier
9bcf963a4a
Fix weird error during migration 2023-12-12 18:59:00 +09:00
nicolas.dorier
85f508756a
Bump deps 2023-12-12 13:05:16 +09:00
nicolas.dorier
5ddba1d816
bump 2023-12-11 16:17:56 +09:00
Nicolas Dorier
eca963def6
Merge pull request #453 from NicolasDorier/txts3
Add WalletTrackedSource to backend
2023-12-11 16:02:43 +09:00
nicolas.dorier
cfedff079d
Add WalletTrackedSource to backend 2023-12-11 15:57:23 +09:00
Nicolas Dorier
ceef9651f3
Merge pull request #452 from dgarage/watchdog
Fix spurious NBXplorer stuck synching
2023-12-11 13:23:10 +09:00
nicolas.dorier
1d89e331ed
Fix spurious NBXplorer stuck synching 2023-12-11 10:39:42 +09:00
Nicolas Dorier
7860ebbda4
Merge pull request #451 from NicolasDorier/refactorgetblocks
Refactor getting block headers from RPC efficiently
2023-12-06 17:49:53 +09:00
nicolas.dorier
2f855b5cc3
Refactor getting block headers from RPC efficiently 2023-12-06 17:43:39 +09:00
Nicolas Dorier
8ec608f2bb
Merge pull request #449 from NicolasDorier/trackctx
Introduce TrackedSourceContext
2023-12-05 13:06:05 +09:00
Kukks
4260cb8e04
Introduce TrackedSourceContext 2023-12-05 12:51:49 +09:00
nicolas.dorier
9b6c0010fb
Fix spurious deadlocks 2023-12-04 08:53:20 +09:00
nicolas.dorier
a01893d09d
Remove chain-slim.dat after migration to postgres 2023-11-30 08:14:26 +09:00
nicolas.dorier
57048b0d05
bump 2023-11-27 12:10:35 +09:00
Nicolas Dorier
9a64c246b8
Merge pull request #448 from NicolasDorier/qoinq
Replaced[] wasn't set for transactions mined into a block
2023-11-27 08:53:09 +09:00
nicolas.dorier
fa26cd4fcf
Replaced[] wasn't set for transactions mined into a block 2023-11-24 18:16:20 +09:00
nicolas.dorier
3b192971f0
fixup 2023-11-24 16:41:18 +09:00
Nicolas Dorier
02e4714bce
Merge pull request #447 from NicolasDorier/fwpoeqz
Fix double spend detection (Fix #421)
2023-11-24 09:44:06 +09:00
nicolas.dorier
342a16ad07
Fix double spend detection (Fix #421) 2023-11-24 09:29:25 +09:00
nicolas.dorier
31a1ab4c89
bump NBX 2023-11-21 12:49:00 +09:00
nicolas.dorier
5de2a002a0
bump npgsql 2023-11-21 11:31:01 +09:00
Nicolas Dorier
9faab8f97d
Merge pull request #446 from NicolasDorier/fixuri
Make sure NBXClient's uris are well escaped
2023-11-17 15:41:51 +09:00
nicolas.dorier
dbef90b927
Do not use connection pooling for the refresh wallet history 2023-11-17 15:32:43 +09:00
nicolas.dorier
30c1536323
Make sure NBXClient's uris are well encoded 2023-11-17 15:29:48 +09:00
nicolas.dorier
ef1f78b9e9
Improve performance of get_wallet_recents 2023-11-16 10:09:15 +09:00
nicolas.dorier
d7a9af1d7a
Do not hardcode public schema in migration scripts 2023-11-16 07:42:15 +09:00
Nicolas Dorier
1ac249db79
Merge pull request #442 from NicolasDorier/bumplibs
Bump dependencies
2023-11-15 21:56:48 +09:00
nicolas.dorier
c547f20d20
Bump dependencies 2023-11-15 20:00:18 +09:00
nicolas.dorier
a9a8f56d73
Bump commandtimeout for migration 2023-11-15 18:13:25 +09:00
nicolas.dorier
42dbfdb59d
bump client lib 2023-11-15 15:42:29 +09:00
nicolas.dorier
f8707aca8b
Update README.md 2023-11-15 13:08:42 +09:00
nicolas.dorier
f76543311d
Update README 2023-11-15 13:07:31 +09:00
Nicolas Dorier
8a051016f4
Merge pull request #441 from NicolasDorier/net8
Bump to .NET 8.0
2023-11-15 13:05:13 +09:00
nicolas.dorier
506388937e
Bump to .NET 8.0 2023-11-15 12:57:47 +09:00
Nicolas Dorier
600a642ebc
Merge pull request #440 from dgarage/qointee
Stop supporting DBTrie backend
2023-11-15 11:33:01 +09:00
nicolas.dorier
3cbb864713
Stop supporting DBTrie backend 2023-11-15 11:22:30 +09:00
Nicolas Dorier
25a3f579d8
Merge pull request #437 from dgarage/wionqr
Allow creation of PSBTs conflicting with mempool with includeOnlyOutpoints
2023-11-15 11:14:55 +09:00
nicolas.dorier
a858d56552
Allow creation of PSBTs conflicting with mempool with includeOnlyOutpoints 2023-11-15 11:05:24 +09:00
Nicolas Dorier
06ad54141e
Merge pull request #377 from Kukks/add-input-index-to-output
Add InputIndex to matched inputs
2023-11-15 11:03:25 +09:00
Kukks
ef84e74bbb
Add InputIndex to matched inputs 2023-11-15 10:40:36 +09:00
nicolas.dorier
3579fcd226
Fix: Confirmation status of unrelated double spend txs was not updated 2023-11-15 09:29:46 +09:00
Nicolas Dorier
fbf8787ad0
Merge pull request #438 from dgarage/cleanup
Code cleanup
2023-11-14 19:15:57 +09:00
nicolas.dorier
f6e1a5c4df
Remove unused code 2023-11-14 19:10:50 +09:00
nicolas.dorier
e03ef32ab9
Remove unused usings 2023-11-14 18:11:52 +09:00
nicolas.dorier
26f23c0e75
Remove #if for .net core 2.1 2023-11-14 17:39:54 +09:00
Nicolas Dorier
299032a210
Merge pull request #436 from dgarage/qointqqlc
Properly detect double spending even if not directly related to a wallet
2023-11-14 09:04:22 +09:00
nicolas.dorier
f2b91e3513
Properly detect double spending even if not directly related to a wallet 2023-11-13 21:08:42 +09:00
nicolas.dorier
c7b5a73ea1
bump 2023-11-09 10:33:25 +09:00
Nicolas Dorier
4385f75d39
Merge pull request #435 from NicolasDorier/qptnnq
Refactor Broadcaster
2023-11-09 10:13:57 +09:00
nicolas.dorier
a9059c1fba
Fix: Transactions not properly marked replaced (Fix #421) 2023-11-09 10:13:23 +09:00
nicolas.dorier
50532c4a92
Refactor Broadcaster 2023-11-08 08:39:04 +09:00
nicolas.dorier
73e6100a19
Fix rejecting replacement RPC message detection 2023-11-07 09:35:16 +09:00
nicolas.dorier
ebec46553c
Better detect replaced transactions 2023-11-06 09:18:10 +09:00
Nicolas Dorier
7cd88f88da
Merge pull request #432 from bitcoinbrisbane/patch-1
Update API.md
2023-10-19 16:57:29 +09:00
Lucas Cullen
0534c50898
Update API.md
Update grammar
2023-10-15 10:52:23 +10:00
nicolas.dorier
70d608bc09
Fix ElementsTests 2023-10-11 23:11:34 +09:00
nicolas.dorier
d0ad70f6cb
Fix occasional fetch_matches if temp tables are partially created 2023-09-27 16:04:53 +09:00
Nicolas Dorier
f35be21e74
Merge pull request #425 from farukterzioglu/output-below-dust
Output below dust
2023-09-26 17:14:18 +09:00
nicolas.dorier
faf7621545
If sweep funds without coins should send not-enough-funds error 2023-09-26 16:45:41 +09:00
Nicolas Dorier
092018cb6f
Merge pull request #427 from farukterzioglu/merge-outputs
mergeOutputs documentation
2023-09-26 10:53:23 +09:00
Faruk Terzioglu
c2c4cb7709 Add Reason to NBXplorerError 2023-09-14 00:28:42 +03:00
Faruk Terzioglu
994dbfe55b mergeOutputs 2023-09-13 09:21:23 +03:00
Faruk Terzioglu
b1a14c1bd3 Merge branch 'master' of https://github.com/dgarage/NBXplorer into output-below-dust 2023-09-13 09:00:43 +03:00
Nicolas Dorier
256f62385b
Merge pull request #426 from farukterzioglu/merge-outputs
Add MergeOutputs parameter to CreatePSBT
2023-09-13 08:47:29 +09:00
Faruk Terzioglu
bc92de9db4 Add MergeOutputs parameter to CreatePSBT 2023-09-12 19:29:31 +03:00
Faruk Terzioglu
8ada11e7cb
throws output-below-dust before not-enough-funds 2023-09-12 14:56:41 +03:00
Faruk Terzioglu
f9a5cdc999 Handle outputs below dust 2023-09-07 18:40:45 +02:00
Nicolas Dorier
898258d87f
Merge pull request #424 from dgarage/qoitbq
bump NBitcoin
2023-09-01 15:55:58 +09:00
nicolas.dorier
f07649fe1c
bump NBitcoin 2023-09-01 15:32:14 +09:00
Nicolas Dorier
9dfd5f6adf
Merge pull request #417 from gruve-p/patch-3
Bump GRS test to 25.0
2023-08-30 13:46:23 +09:00
nicolas.dorier
cfb9aa9506
Do not remove a tx from mempool if fee too low for mempool 2023-08-23 15:25:33 +09:00
Nicolas Dorier
d7c2da3f11
Merge pull request #419 from NicolasDorier/feiowtnq
Allow SOCKSv5 configuration for P2P connection
2023-06-26 11:26:59 +09:00
nicolas.dorier
9998c66492
Fix CI 2023-06-26 11:22:58 +09:00
nicolas.dorier
5437af5975
Allow SOCKSv5 configuration for P2P connection 2023-06-23 23:04:19 +09:00
nicolas.dorier
a8c5bdc615
Fix tests 2023-06-14 19:57:16 +09:00
nicolas.dorier
67edae79d0
Do not crash if loadwallet is sending RPC_WALLET_ALREADY_LOADED error 2023-06-14 19:27:58 +09:00
Nicolas Dorier
32b048f0b6
Can build NBXplorer without DBTrie support (#418) 2023-06-14 14:26:55 +09:00
gruve-p
ab8c365ae5
Bump GRS test to 25.0 2023-06-09 20:08:54 +02:00
nicolas.dorier
919a1f68d7
bump 2023-06-01 19:06:15 +09:00
nicolas.dorier
d42378ff91
Do not use ReadOnlyDictionary for GenerateWalletRequest 2023-06-01 19:06:03 +09:00
nicolas.dorier
b287ea5521
bump nbitcoin 2023-06-01 18:21:56 +09:00
nicolas.dorier
91e76440d0
Fix crash during migration if not using public schema 2023-05-30 20:54:22 +09:00
nicolas.dorier
64376a41e1
bump 2023-05-30 10:49:36 +09:00
Nicolas Dorier
369d3579e9
Improve fetch_matches performances (#416) 2023-05-30 10:49:10 +09:00
nicolas.dorier
795af96df2
bump lib 2023-05-09 22:03:54 +09:00
nicolas.dorier
28db6d419f
bump 2023-05-09 22:03:33 +09:00
Nicolas Dorier
f3d4508e69
Fix: Timeout was possible when trimming event table at startup (#415) 2023-05-09 22:03:17 +09:00
Nicolas Dorier
b64b7abc04
Fix: If too much mempool activity, NBX would become stuck processing transactions (#414) 2023-05-09 22:02:43 +09:00
Nicolas Dorier
64cfb2fc90
Add replaced tx ids in transaction events (#413) 2023-04-29 22:21:51 +09:00
nicolas.dorier
06aa2b5dcc
Improve grammar 2023-04-10 12:50:49 +09:00
nicolas.dorier
9eeb5c6d82
The Rebroadcasted HostedService shouldn't start with postgres backend 2023-04-10 12:40:10 +09:00
nicolas.dorier
8768d129b6
Run up to 3 periodic tasks concurrently 2023-04-10 12:36:09 +09:00
Nicolas Dorier
a064c60932
Add replacedBy to cryptos/{cryptoCode}/transactions/{txId} (#412) 2023-03-25 10:42:16 +09:00
nicolas.dorier
f2e9615f9b
bump NBX 2023-03-08 16:58:51 +09:00
nicolas.dorier
7dd960dfee
Add log if txindex is supported 2023-03-08 16:57:50 +09:00
nicolas.dorier
578426da15
Reboot connection if node start sending block announcement with invs 2023-03-08 16:54:51 +09:00
nicolas.dorier
8d09c57c7d
Automatically detect txindex availability when possible 2023-02-15 10:00:18 +09:00
nicolas.dorier
db3142b92f
Remove SupportCookieAuthentication 2023-02-15 09:40:38 +09:00
nicolas.dorier
4a0e16861b
Fix possible error 500 in CreatePSBT 2023-02-13 21:54:02 +09:00
nicolas.dorier
b73cb2e110
bump 2023-02-11 19:46:04 +09:00
Nicolas Dorier
ad90a241a6
Add eventId to real time notifications (Close #393) (#405) 2023-02-11 19:45:33 +09:00
nicolas.dorier
7045800e48
Fix possible error 500 if no more address in the change address pool (Fix #403) 2023-02-11 19:35:13 +09:00
nicolas.dorier
b5592e0106
Do not send mempool message on p2p on dbtrie backend 2023-02-07 22:26:22 +09:00
nicolas.dorier
4874373783
bump 2023-02-07 15:18:44 +09:00
Nicolas Dorier
194f16c7a5
Fix: Wallets migrated from DBTrie might reuse change address (#402) 2023-02-07 15:18:03 +09:00
nicolas.dorier
c7b6885eed
Fix: Make it impossible to cancel reservation on an address already seen on-chain 2023-02-07 12:59:21 +09:00
nicolas.dorier
b0b7e98cb9
Fix tests 2023-02-06 14:25:23 +09:00
nicolas.dorier
1c535e62d4
Bump NBX 2023-02-02 08:54:36 +09:00
Andrew Camilleri
bdd32e1147
add getmempoolinfo to whitelist (#397) 2023-02-02 08:52:54 +09:00
nicolas.dorier
45f9a4adc9
bump 2023-01-18 21:22:15 +09:00
Nicolas Dorier
f28bb22c7f
Allow some RPC methods to be called even if RPC not explicitely exposed by NBX (#396) 2023-01-18 21:21:43 +09:00
nicolas.dorier
000376564e
bump client 2023-01-16 10:07:21 +09:00
nicolas.dorier
416a0e7bd2
bump 2023-01-16 09:57:23 +09:00
nicolas.dorier
1f18ac5bd2
Bump NBitcoin (Fix PSBT parsing for some altcoins) 2023-01-16 09:56:38 +09:00
nicolas.dorier
8332e05023
bump 2023-01-13 11:16:25 +09:00
Nicolas Dorier
639ad9ba45
tracked_txs shouldn't return transaction not in mempool and not confirmed (#392) 2023-01-13 11:15:41 +09:00
gruve-p
8e069c5b9b
Bump GRS to v24.0.1 on ServerTester.Environment (#390) 2022-12-16 12:28:58 +09:00
nicolas.dorier
33bfb8b9e7
Fix flaky test 2022-12-12 20:57:13 +09:00
nicolas.dorier
15f6c42245
bump 2022-12-12 20:55:00 +09:00
Nicolas Dorier
23a8e335e4
Save birthdate of wallet on GenerateWallet (#391) 2022-12-12 20:55:00 +09:00
nicolas.dorier
0801eca1eb
Do not import in RPC if not asked 2022-12-12 20:55:00 +09:00
nicolas.dorier
c6ae43adb2
bump 2022-12-12 16:11:22 +09:00
Nicolas Dorier
0a09a4ba1e
NBXplorer create default RPC wallet when possible (#389) 2022-12-12 16:10:21 +09:00
nicolas.dorier
ebac7ce987
Recalculate addresses rather than joining a table, return blinded addresses in getUtxos 2022-12-09 12:26:00 +09:00
nicolas.dorier
a4456088e8
Bump client 2022-12-08 17:35:35 +09:00
nicolas.dorier
a906570f08
bump 2022-12-08 17:35:14 +09:00
nicolas.dorier
6becdca483
Include Address in the Get UTXOs route 2022-12-08 17:35:04 +09:00
nicolas.dorier
086fc3b42c
Add Address to txevent.outputs 2022-12-08 16:58:34 +09:00
nicolas.dorier
b9fd115c6c
Add confirmations number to block event 2022-12-08 10:32:44 +09:00
nicolas.dorier
844af377e2
bump 2022-12-08 10:19:14 +09:00
nicolas.dorier
d17f60913a
Fix: Confirmation number of tx event were incorrect (Fix #388) 2022-12-08 10:18:39 +09:00
nicolas.dorier
19c57353e6
If blocks isn't downloaded after querying, connection should be restarted 2022-11-29 21:16:19 +09:00
Nicolas Dorier
a993e45266
bump NBitcoin.TestFramwork on 24.0 (#386) 2022-11-26 00:32:41 +09:00
nicolas.dorier
4e13f90605
Can disable block mining on regtest via NBXPLORER_NOWARMUP=1 2022-11-23 11:21:42 +09:00
nicolas.dorier
8a6f6dee9c
Fix error if too low amount sent on an output with substractfee 2022-11-16 08:07:38 +09:00
nicolas.dorier
00df1e03be
Fix error if too low amount sent on an output with substractfee 2022-11-15 23:26:28 +09:00
nicolas.dorier
f063979667
Stop asking mempool connection after connecting to a node 2022-11-15 23:06:11 +09:00
nicolas.dorier
9f691db055
bump 2022-11-14 19:09:17 +09:00
nicolas.dorier
07323f16ea
Fix potential invalid evts_ids after migration 2022-11-14 19:04:11 +09:00
nicolas.dorier
1ddf28ee2a
Update doc 2022-11-10 21:24:34 +09:00
nicolas.dorier
fd61110a1e
Throw output-too-small if SubstractFee is called on an output that doesn't have enough funds 2022-11-10 21:15:13 +09:00
nicolas.dorier
754278f473
bump NBitcoin 2022-11-02 13:26:00 +09:00
nicolas.dorier
af2ee4c09c
Fix spurious problem when importing descriptors 2022-10-25 09:05:10 +09:00
nicolas.dorier
381c90fc0d
Fix error on RPC import descriptors 2022-10-24 23:49:39 +09:00
nicolas.dorier
030084cdfa
Do not timeout on wallets_history_refresh 2022-10-20 18:21:39 +09:00
nicolas.dorier
38ddc7d211
Fix tests 2022-10-18 21:46:43 +09:00
nicolas.dorier
a1205d8f97
Support saving keys in descriptors wallets 2022-10-18 21:25:45 +09:00
nicolas.dorier
f381e00023
Fix SQL query in postgres when inserting event 2022-10-18 13:37:47 +09:00
nicolas.dorier
4447eea417
Bump Bitcoin Core in tests 2022-10-18 12:59:15 +09:00
nicolas.dorier
2fb8e2630f
Fix Liquid regression where blinded transaction couldn't get decoded 2022-10-11 23:29:58 +09:00
nicolas.dorier
f7649f490a
bump packages 2022-10-07 14:50:55 +09:00
nicolas.dorier
916329e4ff
Fix performance regression on get_wallet_recent 2022-09-27 21:52:25 +09:00
nicolas.dorier
788e800b44
Bump NBitcoin, fixing qtum and dash 2022-09-22 15:15:31 +09:00
nicolas.dorier
51a300b70d
GetMetadata shouldn't crash if RPC not available 2022-09-22 14:30:09 +09:00
nicolas.dorier
848c6c2665
bump 2022-09-22 13:05:09 +09:00
nicolas.dorier
9d601ec868
bump NBitcoin.Altcoins, fixing dash issue 2022-09-22 13:04:44 +09:00
nicolas.dorier
2a493b911a
Do not crash if p2p node != from RPC node 2022-09-20 22:20:52 +09:00
d11n
da8b7b7653
Remove reference to design doc (#374)
See [this comment](https://github.com/btcpayserver/btcpayserver-doc/pull/1160#issuecomment-1248970919).
2022-09-16 17:40:28 +09:00
d11n
5cc4389cda
Docs: References and formatting (#371)
Some minor updates for better display on the docs website. See btcpayserver/btcpayserver-doc#1160.
2022-09-16 15:30:38 +09:00
nicolas.dorier
b3fcda0cf6
bump 2022-09-10 16:26:17 +09:00
nicolas.dorier
7cf5f33443
Fix get_wallets_recents not returning correct set with offset!=0 2022-09-10 16:25:28 +09:00
nicolas.dorier
b8a4a38184
Increase timeout when querying blockchaininfo 2022-07-29 14:51:36 +09:00
nicolas.dorier
dda0bac750
Fix timeout 2022-07-28 17:41:30 +09:00
nicolas.dorier
713ff5e256
bump 2022-07-28 17:31:27 +09:00
nicolas.dorier
4d32024b90
Increase timeout when running migration scripts 2022-07-28 17:30:58 +09:00
nicolas.dorier
bc72700847
bump various deps 2022-07-23 23:09:22 +09:00
nicolas.dorier
969b73a49e
Bump NBitcoin 2022-07-20 22:42:57 +09:00
nicolas.dorier
cd1f5df749
Remove outdated banlist (Fix #367) 2022-07-15 13:13:28 +09:00
d11n
2075f2200f
Update Postgres-Migration.md (#366)
See https://github.com/btcpayserver/btcpayserver/pull/3876#issuecomment-1157549182
2022-06-17 19:01:39 +09:00
nicolas.dorier
05235a6de0
Bump deps of example code 2022-06-16 21:01:18 +09:00
nicolas.dorier
5238f1f2c7
bump deps 2022-06-16 20:54:46 +09:00
nicolas.dorier
c8e9d2f135
Remove docker experimental flag in CI 2022-06-15 19:29:15 +09:00
nicolas.dorier
72830a49c4
Crash test if scanutxoset is stalling 2022-06-15 19:17:22 +09:00
nicolas.dorier
4128f8b068
Bump NBitcoin 2022-06-15 18:46:35 +09:00
nicolas.dorier
0939c00d13
Fix CI 2022-06-06 13:49:07 +09:00
nicolas.dorier
8e060f545d
Add full schema 2022-06-06 13:05:23 +09:00
nicolas.dorier
bbcb4e53e8
Fix: High postgres memory consumption for long rescan 2022-06-06 11:30:09 +09:00
nicolas.dorier
af9d7ce7a5
Make nbxv1_evts.id BIGINT 2022-06-01 12:53:28 +09:00
nicolas.dorier
0595a87f22
Rescan API should save blocks of imported tx in the repo 2022-05-31 12:54:23 +09:00
nicolas.dorier
a1dead87c3
Fix flaky tests 2022-05-30 16:49:10 +09:00
nicolas.dorier
e4700d21db
Fix query 2022-05-24 19:22:47 +09:00
nicolas.dorier
52585b3348
Small perf improvement 2022-05-24 14:45:05 +09:00
Marcelo Ceccon
4aa2cce05c
Update README.md (#363)
Just updating to make appropriate mention of the new Postgres support.
2022-05-24 14:30:52 +09:00
nicolas.dorier
37c49b356f
Fix: descriptors_scripts_unused was causing a full seq scan 2022-05-23 13:14:35 +09:00
nicolas.dorier
ce7819bdc3
Note about backends in README 2022-05-17 12:04:15 +09:00
Nicolas Dorier
d0bb7cf9ac
Do not lose timestamp information when scanutxo (#361)
* Do not lose timestamp information when scanutxo

* bump
2022-05-16 18:47:07 +09:00
nicolas.dorier
05e443fa38
Scanning unknown blocks with scan utxo shouldn't result in unconf utxos 2022-05-11 23:07:28 +09:00
nicolas.dorier
3692c797fc
CreatePSBT shouldn't select utxos with too many ancestors 2022-05-05 13:38:10 +09:00
nicolas.dorier
ea99e3cc9e
bump 2022-05-03 16:01:15 +09:00
nicolas.dorier
0b659c82e2
Fix migration for one user with corrupt DBTrie 2022-05-03 16:01:03 +09:00
nicolas.dorier
f8b4c650ba
bump 2022-04-28 22:14:09 +09:00
nicolas.dorier
ba274bfeb1
Missing clause in NewBlockCommit 2022-04-27 12:09:27 +09:00
nicolas.dorier
afb16b5890
Update doc (Fix #360) 2022-04-23 21:01:36 +09:00
nicolas.dorier
9091abff44
Throw an error if two backend are selected 2022-04-20 19:00:17 +09:00
nicolas.dorier
1fff142f6f
Improve performance on get unused of big wallets 2022-04-19 14:45:06 +09:00
nicolas.dorier
9a08aef0fb
Improve perf for CreatePSBT on SegwitP2SH with postgres 2022-04-14 16:02:18 +09:00
nicolas.dorier
229bf48bee
Fix migration when using CustomKeyPathTemplate 2022-04-13 13:13:34 +09:00
nicolas.dorier
55fbdcb9cd
Fix: Crash if wrong case on some routes 2022-04-11 22:12:15 +09:00
nicolas.dorier
53eb9d08b8
Bump timeout for vacuum/analyze after migration 2022-04-11 21:52:22 +09:00
nicolas.dorier
9d235e28dd
Fix flaky test 2022-04-11 21:51:21 +09:00
Nicolas Dorier
9fe95038a4
Make save_matches faster (#358) 2022-04-08 15:18:49 +09:00
nicolas.dorier
893ea4eb94
On regtest, still consider as synched if 200 blocks late 2022-04-08 12:59:03 +09:00
nicolas.dorier
fea82a5f57
Fix: NBXplorer not showing sync progress (for real) 2022-04-07 17:15:46 +09:00
nicolas.dorier
f7aaa1ae91
Fix: NBXplorer not showing sync progress 2022-04-06 19:04:13 +09:00
nicolas.dorier
bdd741e753
Add GetWalletsRecents filtered by code 2022-04-06 16:27:10 +09:00
nicolas.dorier
79fbbbf523
Fix to_btc helper function 2022-04-06 14:05:08 +09:00
nicolas.dorier
86d00dd152
Fix: Block could be skipped if crash during processing 2022-04-06 10:30:08 +09:00
nicolas.dorier
e15c7c3969
Fix timeout on vacuum 2022-04-05 23:41:57 +09:00
nicolas.dorier
273fffa950
Fix get_wallets_histogram 2022-04-05 20:47:37 +09:00
nicolas.dorier
ee8f65b251
bump 2022-04-05 13:32:26 +09:00
nicolas.dorier
8707ac15a6
Fix case where DBTrie doesn't create change descriptor (#357) 2022-04-05 13:23:27 +09:00
nicolas.dorier
cb5877d959
Generate addresses if get unused return null (Fix #357) 2022-04-04 23:43:17 +09:00
nicolas.dorier
813a49ad99
Bump 2022-04-04 14:23:24 +09:00
Andrew Camilleri
acf2db03e2
Liquid: Support Slip77 derivation for blinding keys (#353) 2022-04-04 14:16:02 +09:00
nicolas.dorier
ccac0d6463
Fix flaky test 2022-04-04 14:13:35 +09:00
nicolas.dorier
a03a897a0b
Fix test 2022-04-04 14:04:18 +09:00
nicolas.dorier
4f9a0688b6
Remove string concat for query building 2022-04-04 11:58:01 +09:00
nicolas.dorier
3944aca2c0
Fix exception thrown in GetTransactions, run full vacuum after migration 2022-04-04 11:15:02 +09:00
nicolas.dorier
4a35012713
GC collect after migration 2022-04-04 00:26:51 +09:00
nicolas.dorier
88a437fc60
Fix error 500 on invalid network 2022-04-03 19:45:01 +09:00
nicolas.dorier
6c708a4c07
Fix warmup regtest 2022-04-03 19:27:13 +09:00
nicolas.dorier
79c9196f22
Fix schema link 2022-04-03 19:13:15 +09:00
Nicolas Dorier
7d44d4cb74
Support postgres backend (#356) 2022-04-03 19:07:29 +09:00
nicolas.dorier
89ada4a172
Fix potential NRE 2022-01-21 21:39:48 +09:00
nicolas.dorier
b96763eca4
Fix dockerfile 2022-01-19 12:08:01 +09:00
nicolas.dorier
6454ffcc80
Delete the event table if corrupted 2022-01-19 12:03:36 +09:00
gruve-p
b42e5dc031
Fix warning from CLI parser (#351)
`-p` is deprecated as an abbreviation for `--project`, and using `-p` generates a warning.
2022-01-03 17:37:04 +09:00
nicolas.dorier
de6d25edf7
Update README 2021-12-28 17:41:26 +09:00
nicolas.dorier
edf3fab86a
Remove outdated coments 2021-12-27 14:50:43 +09:00
nicolas.dorier
3726a56374
Remove app veyor 2021-12-27 14:49:37 +09:00
Nicolas Dorier
2a610db2d3
bump nbx to 6.0 (#350) 2021-12-27 14:45:34 +09:00
Reza Ahmadi
8d993ff85f
Update README.md (#348) 2021-12-14 19:14:24 +09:00
262 changed files with 19242 additions and 14000 deletions

View File

@ -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
View File

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

View File

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

17
.gitattributes vendored Normal file
View 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

View File

@ -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"]

View File

@ -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"]

View File

@ -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"]

View File

@ -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>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

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

View 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);
}
}
}

View File

@ -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
}

View File

@ -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(@"-\[([^ \]\-]+)\]");
}
}

View File

@ -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()

View File

@ -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);
}
}
}

View File

@ -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()

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View 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

View File

@ -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;

View File

@ -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}"
};
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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());
}
}
}

View File

@ -2,8 +2,6 @@
using System.Linq;
using System.Reflection;
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin;
using NBitcoin.JsonConverters;

View File

@ -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());
}
}
}
}

View File

@ -4,7 +4,6 @@ using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.JsonConverters
{

View File

@ -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;

View File

@ -4,7 +4,6 @@ using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.JsonConverters
{

View File

@ -4,7 +4,6 @@ using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.JsonConverters
{

View File

@ -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>()

View File

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

View File

@ -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>

View File

@ -1,8 +1,5 @@
using NBitcoin;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{

View File

@ -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; }
}
}

View File

@ -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()
{

View File

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

View File

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

View File

@ -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; }
}
}

View 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 };
}
}

View File

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

View File

@ -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>();
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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()
{

View File

@ -22,6 +22,8 @@ namespace NBXplorer.Models
get; set;
}
public long Confirmations { get; set; }
[JsonIgnore]
public override string EventType => "newblock";

View File

@ -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;
}
}
}

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
namespace NBXplorer.Models
{
public class PruneRequest
{

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
namespace NBXplorer.Models
{
public class PruneResponse
{

View File

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

View File

@ -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
{

View File

@ -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;

View File

@ -1,8 +1,5 @@
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{

View File

@ -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
}
}

View 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>();
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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
{

View File

@ -1,9 +1,7 @@
using NBitcoin;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text;
namespace NBXplorer.Models
{

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'")
});
}

View File

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

View File

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

View File

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

View File

@ -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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View 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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,7 @@ namespace NBXplorer
InitBitcore(networkType);
InitLitecoin(networkType);
InitDogecoin(networkType);
InitPepecoin(networkType);
InitBCash(networkType);
InitGroestlcoin(networkType);
InitBGold(networkType);

View 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);
}

View File

@ -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
View 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"

View File

@ -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());
}

View File

@ -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
{

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
{
"Servers": {
"1": {
"Name": "docker_postgres",
"Group": "Servers",
"Host": "postgres",
"Port": 5432,
"MaintenanceDB": "postgres",
"Username": "postgres",
"SSLMode": "disable"
}
}
}

View 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