Compare commits

..
56 Commits
Author SHA1 Message Date
Chris Troutner 77ba06d68c Merge pull request #30 from Permissionless-Software-Foundation/ct-unstable
fix(internal calls): Applying paid rate limits to internal calls
2020-09-28 08:48:00 -07:00
Chris Troutner 40f6b17d3b fix(internal calls): Applying paid rate limits to internal calls 2020-09-28 08:37:34 -07:00
Chris Troutner 4d40a7cb01 Merge pull request #29 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo()
2020-09-27 17:30:20 -07:00
Chris Troutner 0a14ca08a4 fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo() 2020-09-27 17:26:34 -07:00
Chris Troutner 198141b75a Merge pull request #28 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxos): Porting hydrateUtxos convenience utility function
2020-09-27 00:36:09 -07:00
Chris Troutner 6f25ec4b68 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:31:38 -07:00
Chris Troutner cb416d26c0 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:26:57 -07:00
Chris Troutner 376094c6c2 feat(hydrateUtxos): Porting hydrateUtxos convenience utility function 2020-09-27 00:21:36 -07:00
Chris Troutner f566d44252 Merge pull request #27 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Fixes GET tx details
2020-09-10 10:54:25 -07:00
Chris Troutner 0e7b45214f fix(elecrtrumx): Fixes GET tx details 2020-09-10 10:51:22 -07:00
Chris Troutner 58366b5442 Merge pull request #26 from Permissionless-Software-Foundation/25-fix-txdetails-get
Change url param name
2020-09-10 10:48:32 -07:00
Rosco Kalis f97e7016c5 Change url param name 2020-09-10 19:39:35 +02:00
Chris Troutner f82c85712c Merge pull request #24 from Permissionless-Software-Foundation/ct-unstable
fix(electrum-cash): Upgrading to electrum-cash v2.0.2
2020-08-29 13:28:22 -07:00
Chris Troutner 5f18bc578b fix(electrum-cash): Upgrading to electrum-cash v2.0.2 2020-08-29 13:24:22 -07:00
Chris Troutner e72a6b50e4 Merge pull request #22 from Permissionless-Software-Foundation/7-electrum-block-header
Add /block/headers GET/POST endpoints + tests and mock data
2020-08-27 17:12:02 -07:00
Chris Troutner 66c99ae442 Adding temp.sh to the gitignore 2020-08-27 16:54:59 -07:00
Rosco Kalis 5e04125bc3 Update electrum-cash version 2020-08-26 12:46:53 +02:00
Rosco Kalis fd3fd43800 Add /block/headers GET/POST endpoints + tests and mock data 2020-08-26 12:32:32 +02:00
Chris Troutner 5184da7c26 Merge pull request #23 from Permissionless-Software-Foundation/ct-unstable
fix(webhooks): testing webhooks deployment
2020-08-21 17:28:51 -07:00
Chris Troutner 595caffb51 fix(webhooks): testing webhooks deployment 2020-08-21 17:26:33 -07:00
Chris Troutner 6b00f0fd5b Merge pull request #20 from Permissionless-Software-Foundation/5-electrum-tx-data
Add get transaction details from Electrum
2020-08-17 10:33:50 -07:00
Chris Troutner e4a427788f Merge branch 'master' into 5-electrum-tx-data 2020-08-17 10:31:08 -07:00
Chris Troutner 27a2825a43 fix(Electrumx): Adding get tx details endpoints 2020-08-17 10:31:05 -07:00
Chris Troutner 3d453c335f Merge pull request #17 from rkalis/electrumx-p2sh
Enable P2SH addresses to be used with the ElectrumX endpoints
2020-08-17 08:17:50 -07:00
Chris Troutner 9a85a2a4ef Merge pull request #19 from Permissionless-Software-Foundation/ct-unstable
fix(util): Fixed failing integration test
2020-08-17 08:09:30 -07:00
Chris Troutner 443fb233ae fix(util): Fixed failing integration test 2020-08-17 08:08:54 -07:00
Rosco Kalis 63b8168c45 Add get transaction details from Electrum
- Add functions for getting a single transaction details or bulk
- Add GET and POST routes for these functions
- Add tests +mock data for these functions

- Add test helper function for stubbing a method for unit tests
- Add test helper function for asserting an API error
2020-08-16 19:43:12 +02:00
Rosco Kalis 1e5dae56af Enable P2SH addresses to be used with the ElectrumX endpoints 2020-08-14 12:15:26 +02:00
Chris Troutner 553d6fd4b0 Merge pull request #16 from Permissionless-Software-Foundation/ct-unstable
fix(slp-api): Adding increased rate limits for slp-api
2020-08-08 10:54:05 -07:00
Chris Troutner ab98bf05da fix(slp-api): Adding increased rate limits for slp-api 2020-08-08 10:48:12 -07:00
Chris Troutner 6806b4a197 Doing silent npm install 2020-08-08 10:27:34 -07:00
Chris Troutner 4ae74aed59 Merge pull request #15 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Updating dockerfile to PSF repo
2020-08-08 09:35:29 -07:00
Chris Troutner 98e1a5b40f fix(Docker): Updating dockerfile to PSF repo 2020-08-08 09:32:36 -07:00
Chris Troutner 279c9b7bea Merge pull request #14 from Permissionless-Software-Foundation/ct-unstable
Tracking origin of requests in logs
2020-08-08 08:27:52 -07:00
Chris Troutner 56be614980 Merge branch 'master' into ct-unstable 2020-08-08 08:22:33 -07:00
Chris Troutner 584037ebdd fix(origin): Tracking origin of requests in logs 2020-08-08 08:22:23 -07:00
Chris Troutner 44230b73f7 Merge pull request #13 from Permissionless-Software-Foundation/ct-unstable
fix(deploy): Testing deploy again
2020-08-07 17:18:08 -07:00
Chris Troutner b7f1eba6bd fix(deploy): Testing deploy again 2020-08-07 17:15:00 -07:00
Chris Troutner 35bc3025d6 Merge pull request #12 from Permissionless-Software-Foundation/ct-unstable
fix(deploy): Updating deploy script
2020-08-07 17:06:59 -07:00
Chris Troutner 6f6531428a fix(deploy): Updating deploy script 2020-08-07 17:00:57 -07:00
Chris Troutner cb5e962167 Merge pull request #11 from Permissionless-Software-Foundation/ct-unstable
feat(SLP balance): Removing redundent, non-functional SLP balance end…
2020-08-06 14:25:01 -07:00
Chris Troutner 33da626f78 Fixing linting issues 2020-08-06 14:21:39 -07:00
Chris Troutner e9766630ed Updating index.html 2020-08-06 14:14:10 -07:00
Chris Troutner 9b6adbf4ec Updates to readme 2020-08-06 14:06:45 -07:00
Chris Troutner 5f09447da4 feat(SLP balance): Removing redundent, non-functional SLP balance endpoint 2020-08-06 14:02:22 -07:00
Chris Troutner 3c1a140cad Merge pull request #8 from Permissionless-Software-Foundation/ct-unstable
ElectrumX Unconfirmed UTXO
2020-07-31 08:50:55 -07:00
Chris Troutner cd3444fb27 Merge branch 'master' of https://github.com/christroutner/bch-api into ct-unstable 2020-07-31 08:47:39 -07:00
Chris Troutner b383312ec0 fix(unconfirmed tests): Not easy to test, so skipping for integration test 2020-07-31 08:47:11 -07:00
Chris Troutner 34b4c423d6 Merge pull request #152 from zh/uncorrect-documentations-urls
Correct urls in the api-doc: mempool -> unconfirmed
2020-07-31 08:45:40 -07:00
Stoyan Zhekov b84ca11a6b Correct urls in the api-doc: mempool -> unconfirmed 2020-07-30 19:20:47 +09:00
Chris Troutner 5cc7b6f1cd Merge pull request #4 from Permissionless-Software-Foundation/ct-unstable
ElectrumX GET and POST endpoints for unconfirmed utxos
2020-07-29 10:22:45 -07:00
Chris Troutner 96f948f410 Merge branch 'master' of https://github.com/christroutner/bch-api into ct-unstable 2020-07-29 10:19:31 -07:00
Chris Troutner b2f32b2ff8 Merge pull request #151 from zh/unconfirmed-utxos-per-address
Unconfirmed utxos per address
2020-07-29 10:18:42 -07:00
Stoyan Zhekov 14ac642bdb Bulk mempool response format fixed 2020-07-29 00:10:26 +09:00
Stoyan Zhekov 74112a1ed8 Routes added 2020-07-28 23:46:02 +09:00
Stoyan Zhekov 1e53a42efd GET and POST endpoints for unconfirmed utxos 2020-07-28 23:39:16 +09:00
25 changed files with 2055 additions and 464 deletions
+1
View File
@@ -19,6 +19,7 @@ start-fullstack-test.sh
start-free-main.sh start-free-main.sh
start-local-mainnet.sh.save start-local-mainnet.sh.save
start-local-testnet.sh.save start-local-testnet.sh.save
temp.sh
coverage coverage
start-my-infra start-my-infra
+4 -6
View File
@@ -1,6 +1,5 @@
# bch-api # bch-api
[![Greenkeeper badge](https://badges.greenkeeper.io/christroutner/bch-api.svg)](https://greenkeeper.io/)
[![Build Status](https://travis-ci.org/christroutner/bch-api.svg?branch=master)](https://travis-ci.org/christroutner/bch-api) [![Build Status](https://travis-ci.org/christroutner/bch-api.svg?branch=master)](https://travis-ci.org/christroutner/bch-api)
@@ -16,11 +15,10 @@ of [BITBOX SDK](https://github.com/Bitcoin-com/bitbox-sdk).
Both bch-api and bch-js are part of the Both bch-api and bch-js are part of the
[full stack of BCH software](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer). [full stack of BCH software](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer).
- [API Documentation](https://api.bchjs.cash/docs/) - [API Documentation](https://fullstack.cash/documentation)
- [BCH Example Applications](https://github.com/Permissionless-Software-Foundation/bch-js-examples) - [Example Code](https://fullstack.cash/examples)
- [slp-cli-wallet](https://github.com/christroutner/slp-cli-wallet): a hacker-friendly, command-line BCH and SLP token wallet using this REST API.
Have questions? Need help? Join our community support Have questions? Need help? Join our community support:
[Telegram channel](https://t.me/bch_js_toolkit) [Telegram channel](https://t.me/bch_js_toolkit)
## Features ## Features
@@ -96,4 +94,4 @@ Have questions? Need help? Join our community support
[Telegram channel](https://t.me/bch_js_toolkit) [Telegram channel](https://t.me/bch_js_toolkit)
## License ## License
[MIT](LICENSE.md) [MIT](./LICENSE.md)
+2
View File
@@ -11,5 +11,7 @@ export DATA="{\"ref\":\"$DEPLOY_SECRET\"}"
curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA curl -X POST http://fullstack.cash:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://fullstack.cash:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA curl -X POST http://fullstack.cash:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-testnet -H "Content-Type: application/json" -d $DATA
curl -X POST http://$FREE_SERVER:9000/hooks/bch-api-mainnet -H "Content-Type: application/json" -d $DATA
echo "...Finished deploying to production." echo "...Finished deploying to production."
+2 -2
View File
@@ -16,9 +16,9 @@ USER safeuser
# Clone the repository # Clone the repository
WORKDIR /home/safeuser WORKDIR /home/safeuser
RUN git clone https://github.com/christroutner/bch-api RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api
WORKDIR /home/safeuser/bch-api WORKDIR /home/safeuser/bch-api
RUN npm install RUN npm install --silent
# Generate documentation # Generate documentation
RUN npm run docs RUN npm run docs
+2 -2
View File
@@ -13,9 +13,9 @@ USER safeuser
# Clone the repository # Clone the repository
WORKDIR /home/safeuser WORKDIR /home/safeuser
RUN git clone https://github.com/christroutner/bch-api RUN git clone https://github.com/Permissionless-Software-Foundation/bch-api
WORKDIR /home/safeuser/bch-api WORKDIR /home/safeuser/bch-api
RUN npm install RUN npm install --silent
# Generate documentation # Generate documentation
RUN npm run docs RUN npm run docs
+226 -189
View File
@@ -1,6 +1,6 @@
{ {
"name": "bch-api", "name": "bch-api",
"version": "1.0.0", "version": "1.16.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@@ -147,106 +147,6 @@
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
} }
}, },
"@chris.troutner/bch-js": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@chris.troutner/bch-js/-/bch-js-3.0.1.tgz",
"integrity": "sha512-XTOCFeMCBThXlGxdWsmW6OmfZBqa3XtsvFGE3hIapWfvoCUd6fSyegn9G1mXod236D3qKTW/Vk/NBwGlc7Y1YQ==",
"requires": {
"@uppy/core": "^1.10.4",
"@uppy/tus": "^1.5.12",
"apidoc": "^0.17.7",
"assert": "^2.0.0",
"axios": "^0.19.0",
"bc-bip68": "^1.0.5",
"bch-wallet-bridge.js": "github:web3bch/bch-wallet-bridge.js#master",
"bchaddrjs-slp": "^0.2.5",
"bigi": "^1.4.2",
"bignumber.js": "^9.0.0",
"bip-schnorr": "^0.3.0",
"bip21": "github:Bitcoin-com/bip21",
"bip32-utils": "github:Bitcoin-com/bip32-utils#0.13.1",
"bip38": "^2.0.2",
"bip39": "^3.0.2",
"bip66": "^1.1.5",
"bitcoincash-ops": "github:christroutner/bitcoincash-ops",
"bitcoincashjs-lib": "github:christroutner/bitcoincashjs-lib",
"bitcoinjs-message": "^2.0.0",
"bs58": "^4.0.1",
"buffer": "^5.1.0",
"cashaddrjs": "^0.3.3",
"chalk": "^2.3.0",
"clear": "0.1.0",
"coininfo": "github:Bitcoin-com/coininfo",
"commander": "^3.0.0",
"cp-file": "^7.0.0",
"ecurve": "^1.0.6",
"figlet": "^1.2.0",
"git-clone": "^0.1.0",
"ini": "^1.3.5",
"mkdirp": "^0.5.1",
"node-cmd": "^3.0.0",
"node-emoji": "^1.8.1",
"qrcode": "^1.4.1",
"randombytes": "^2.0.6",
"repl.history": "^0.1.4",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1",
"touch": "^3.1.0",
"wif": "^2.0.6"
},
"dependencies": {
"apidoc": {
"version": "0.17.7",
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.17.7.tgz",
"integrity": "sha512-9Wf4bRPwCuWOIOxR42dDnsXnFw+rhJg5VrMQK+KmNxJwyIh30UqX6gvjjXSG6YO74MqE87F18bbQXUENK9dPGg==",
"requires": {
"apidoc-core": "~0.8.2",
"commander": "^2.19.0",
"fs-extra": "^7.0.0",
"lodash": "^4.17.10",
"markdown-it": "^8.3.1",
"winston": "^3.0.0"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"@istanbuljs/load-nyc-config": { "@istanbuljs/load-nyc-config": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz",
@@ -465,6 +365,127 @@
"@types/node": ">= 8" "@types/node": ">= 8"
} }
}, },
"@psf/bch-js": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-3.5.5.tgz",
"integrity": "sha512-bZs1p6VNDBYJPJuKuYD4h2Wsp+gt38181XlVX+2Yq8JGFOvf5ZRSrkqcd0LYvfiP8f9IA46aaAtTaLQT1KQsJw==",
"requires": {
"@uppy/core": "^1.10.4",
"@uppy/tus": "^1.5.12",
"apidoc": "^0.17.7",
"assert": "^2.0.0",
"axios": "^0.19.0",
"bc-bip68": "^1.0.5",
"bch-wallet-bridge.js": "github:web3bch/bch-wallet-bridge.js#master",
"bchaddrjs-slp": "^0.2.5",
"bigi": "^1.4.2",
"bignumber.js": "^9.0.0",
"bip-schnorr": "^0.3.0",
"bip21": "github:Bitcoin-com/bip21",
"bip32-utils": "github:Bitcoin-com/bip32-utils#0.13.1",
"bip38": "^2.0.2",
"bip39": "^3.0.2",
"bip66": "^1.1.5",
"bitcoincash-ops": "github:christroutner/bitcoincash-ops",
"bitcoincashjs-lib": "github:christroutner/bitcoincashjs-lib#master",
"bitcoinjs-message": "^2.0.0",
"bs58": "^4.0.1",
"buffer": "^5.1.0",
"cashaddrjs": "^0.3.3",
"chalk": "^2.3.0",
"clear": "0.1.0",
"coininfo": "github:Bitcoin-com/coininfo",
"commander": "^3.0.0",
"cp-file": "^7.0.0",
"ecurve": "^1.0.6",
"figlet": "^1.2.0",
"git-clone": "^0.1.0",
"ini": "^1.3.5",
"mkdirp": "^0.5.1",
"node-cmd": "^3.0.0",
"node-emoji": "^1.8.1",
"qrcode": "^1.4.1",
"randombytes": "^2.0.6",
"repl.history": "^0.1.4",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1",
"touch": "^3.1.0",
"wif": "^2.0.6"
},
"dependencies": {
"apidoc": {
"version": "0.17.7",
"resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.17.7.tgz",
"integrity": "sha512-9Wf4bRPwCuWOIOxR42dDnsXnFw+rhJg5VrMQK+KmNxJwyIh30UqX6gvjjXSG6YO74MqE87F18bbQXUENK9dPGg==",
"requires": {
"apidoc-core": "~0.8.2",
"commander": "^2.19.0",
"fs-extra": "^7.0.0",
"lodash": "^4.17.10",
"markdown-it": "^8.3.1",
"winston": "^3.0.0"
},
"dependencies": {
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
}
}
},
"bitcoincashjs-lib": {
"version": "github:christroutner/bitcoincashjs-lib#715823b5d6eb3e2961e715605ad1464f3223da0b",
"from": "github:christroutner/bitcoincashjs-lib#master",
"requires": {
"bech32": "^1.1.2",
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoincash-ops": "github:christroutner/bitcoincash-ops",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"ecurve": "^1.0.0",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "github:Bitcoin-com/pushdata-bitcoin#1.2.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.0.1",
"typeforce": "^1.18.0",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
}
},
"fs-extra": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
}
},
"jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
"requires": {
"graceful-fs": "^4.1.6"
}
},
"mkdirp": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
}
}
},
"@semantic-release/commit-analyzer": { "@semantic-release/commit-analyzer": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-7.0.0.tgz", "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-7.0.0.tgz",
@@ -872,6 +893,14 @@
"resolved": "https://registry.npmjs.org/@types/tus-js-client/-/tus-js-client-1.8.0.tgz", "resolved": "https://registry.npmjs.org/@types/tus-js-client/-/tus-js-client-1.8.0.tgz",
"integrity": "sha512-lWxu5+6qfyfwsW99GzUeJ9y9JeOSSLduKxgYMvaYM7sGTDKZsrIIHTUbHI2P016xhXtu9NxmUM3GrB4i14ie4A==" "integrity": "sha512-lWxu5+6qfyfwsW99GzUeJ9y9JeOSSLduKxgYMvaYM7sGTDKZsrIIHTUbHI2P016xhXtu9NxmUM3GrB4i14ie4A=="
}, },
"@types/ws": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.6.tgz",
"integrity": "sha512-Q07IrQUSNpr+cXU4E4LtkSIBPie5GLZyyMC1QtQYRLWz701+XcoVygGUZgvLqElq1nU4ICldMYPnexlBsg3dqQ==",
"requires": {
"@types/node": "*"
}
},
"@uppy/companion-client": { "@uppy/companion-client": {
"version": "1.4.5", "version": "1.4.5",
"resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-1.4.5.tgz", "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-1.4.5.tgz",
@@ -1402,6 +1431,21 @@
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
}, },
"async-mutex": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.4.tgz",
"integrity": "sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg==",
"requires": {
"tslib": "^2.0.0"
},
"dependencies": {
"tslib": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz",
"integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ=="
}
}
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2266,27 +2310,6 @@
"resolved": "https://registry.npmjs.org/bitcoincashjs/-/bitcoincashjs-0.1.14.tgz", "resolved": "https://registry.npmjs.org/bitcoincashjs/-/bitcoincashjs-0.1.14.tgz",
"integrity": "sha512-x0wMVSCZ56ZQnSQ+Xim7XfS0IYiOyOwo0pdMCmR+cDNB7zRhICFuH5i3lNdLSgUw1e7Pc3FmUlC9wruQNlYMnA==" "integrity": "sha512-x0wMVSCZ56ZQnSQ+Xim7XfS0IYiOyOwo0pdMCmR+cDNB7zRhICFuH5i3lNdLSgUw1e7Pc3FmUlC9wruQNlYMnA=="
}, },
"bitcoincashjs-lib": {
"version": "github:christroutner/bitcoincashjs-lib#b03ca974d4fd548cb42c7a522863e5d9be6051ef",
"from": "github:christroutner/bitcoincashjs-lib",
"requires": {
"bech32": "^1.1.2",
"bigi": "^1.4.0",
"bip66": "^1.1.0",
"bitcoincash-ops": "github:christroutner/bitcoincash-ops",
"bs58check": "^2.0.0",
"create-hash": "^1.1.0",
"create-hmac": "^1.1.3",
"ecurve": "^1.0.0",
"merkle-lib": "^2.0.10",
"pushdata-bitcoin": "github:Bitcoin-com/pushdata-bitcoin#1.2.1",
"randombytes": "^2.0.1",
"safe-buffer": "^5.0.1",
"typeforce": "^1.18.0",
"varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1"
}
},
"bitcoinjs-message": { "bitcoinjs-message": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.1.0.tgz", "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.1.0.tgz",
@@ -2301,37 +2324,28 @@
} }
}, },
"bitcore-lib": { "bitcore-lib": {
"version": "8.21.0", "version": "8.22.2",
"resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.21.0.tgz", "resolved": "https://registry.npmjs.org/bitcore-lib/-/bitcore-lib-8.22.2.tgz",
"integrity": "sha512-Z1lDqSReD/Ah/n7qEsR564AlNImwYeB1MQ0fnOD6e2piNsTXQ3cSjgH93MxVARpX4LRkEev2XBnYCJ4NcYTQdQ==", "integrity": "sha512-atl/RN7x2R/JQMaIgUzX0EQZ+I/d7fTaOoS2/5k+H5POGc2vygWZ9MWpGJi3T9Yb5jM/cT5aFsrDq8s8l6lqgQ==",
"requires": { "requires": {
"bech32": "=1.1.3", "bech32": "=1.1.3",
"bn.js": "=4.11.8", "bn.js": "=4.11.8",
"bs58": "^4.0.1", "bs58": "^4.0.1",
"buffer-compare": "=1.1.1", "buffer-compare": "=1.1.1",
"elliptic": "=6.4.0", "elliptic": "^6.5.3",
"inherits": "=2.0.1", "inherits": "=2.0.1",
"lodash": "=4.17.15" "lodash": "^4.17.20"
}, },
"dependencies": { "dependencies": {
"elliptic": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz",
"integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=",
"requires": {
"bn.js": "^4.4.0",
"brorand": "^1.0.1",
"hash.js": "^1.0.0",
"hmac-drbg": "^1.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"minimalistic-crypto-utils": "^1.0.0"
}
},
"inherits": { "inherits": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
} }
} }
}, },
@@ -2367,13 +2381,18 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
} }
} }
}, },
"bl": { "bl": {
"version": "1.2.2", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
"requires": { "requires": {
"readable-stream": "^2.3.5", "readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
@@ -3248,24 +3267,13 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
}, },
"compare-func": { "compare-func": {
"version": "1.3.2", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
"integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
"dev": true, "dev": true,
"requires": { "requires": {
"array-ify": "^1.0.0", "array-ify": "^1.0.0",
"dot-prop": "^3.0.0" "dot-prop": "^5.1.0"
},
"dependencies": {
"dot-prop": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz",
"integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=",
"dev": true,
"requires": {
"is-obj": "^1.0.0"
}
}
} }
}, },
"component-bind": { "component-bind": {
@@ -3387,22 +3395,34 @@
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
}, },
"conventional-changelog-angular": { "conventional-changelog-angular": {
"version": "5.0.6", "version": "5.0.11",
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.6.tgz", "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.11.tgz",
"integrity": "sha512-QDEmLa+7qdhVIv8sFZfVxU1VSyVvnXPsxq8Vam49mKUcO1Z8VTLEJk9uI21uiJUsnmm0I4Hrsdc9TgkOQo9WSA==", "integrity": "sha512-nSLypht/1yEflhuTogC03i7DX7sOrXGsRn14g131Potqi6cbGbGEE9PSDEHKldabB6N76HiSyw9Ph+kLmC04Qw==",
"dev": true, "dev": true,
"requires": { "requires": {
"compare-func": "^1.3.1", "compare-func": "^2.0.0",
"q": "^1.5.1" "q": "^1.5.1"
},
"dependencies": {
"compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
"integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
"dev": true,
"requires": {
"array-ify": "^1.0.0",
"dot-prop": "^5.1.0"
}
}
} }
}, },
"conventional-changelog-writer": { "conventional-changelog-writer": {
"version": "4.0.16", "version": "4.0.17",
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.17.tgz",
"integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", "integrity": "sha512-IKQuK3bib/n032KWaSb8YlBFds+aLmzENtnKtxJy3+HqDq5kohu3g/UdNbIHeJWygfnEbZjnCKFxAW0y7ArZAw==",
"dev": true, "dev": true,
"requires": { "requires": {
"compare-func": "^1.3.1", "compare-func": "^2.0.0",
"conventional-commits-filter": "^2.0.6", "conventional-commits-filter": "^2.0.6",
"dateformat": "^3.0.0", "dateformat": "^3.0.0",
"handlebars": "^4.7.6", "handlebars": "^4.7.6",
@@ -3431,11 +3451,12 @@
"dev": true "dev": true
}, },
"through2": { "through2": {
"version": "3.0.1", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz",
"integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"inherits": "^2.0.4",
"readable-stream": "2 || 3" "readable-stream": "2 || 3"
} }
} }
@@ -4412,17 +4433,28 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
}, },
"electrum-cash": { "electrum-cash": {
"version": "1.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/electrum-cash/-/electrum-cash-1.0.1.tgz", "resolved": "https://registry.npmjs.org/electrum-cash/-/electrum-cash-2.0.2.tgz",
"integrity": "sha512-snMgRt6JzsHdCdf+Un1rLj4RXjje1WuFVTx3Xx+6mitEibYalmD+x0ts146VF4Ki3+42DQKXtQXl0bzCAY7Jtw==", "integrity": "sha512-b/mLctjwnmfp4cuvJbl9wgCxf4GWWiKPYZXEtekoyGPviZwOe2bnMC7LGOxAMpOW0YyU4W12kK6rbqskQc6EkA==",
"requires": { "requires": {
"debug": "^4.1.1" "@types/ws": "^7.2.6",
"async-mutex": "^0.2.4",
"debug": "^4.1.1",
"isomorphic-ws": "^4.0.1",
"ws": "^7.3.1"
},
"dependencies": {
"ws": {
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz",
"integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA=="
}
} }
}, },
"elliptic": { "elliptic": {
"version": "6.5.2", "version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"requires": { "requires": {
"bn.js": "^4.4.0", "bn.js": "^4.4.0",
"brorand": "^1.0.1", "brorand": "^1.0.1",
@@ -5056,6 +5088,12 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true "dev": true
}, },
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"dev": true
},
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
@@ -7315,12 +7353,6 @@
"resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz",
"integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw=="
}, },
"is-obj": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz",
"integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=",
"dev": true
},
"is-object": { "is-object": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz",
@@ -7539,6 +7571,11 @@
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
"dev": true "dev": true
}, },
"isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w=="
},
"isstream": { "isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -8040,9 +8077,9 @@
} }
}, },
"lodash": { "lodash": {
"version": "4.17.15", "version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
}, },
"lodash._baseiteratee": { "lodash._baseiteratee": {
"version": "4.7.0", "version": "4.7.0",
@@ -9030,9 +9067,9 @@
} }
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.0", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
"dev": true "dev": true
}, },
"node-gyp-build": { "node-gyp-build": {
+3 -3
View File
@@ -17,7 +17,7 @@
"test": "npm run lint && npm run test-v3", "test": "npm run lint && npm run test-v3",
"lint": "standard --env mocha --fix", "lint": "standard --env mocha --fix",
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/", "test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
"test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/integration/slp.js", "test:temp": "export NETWORK=mainnet && mocha --timeout 25000 test/v3/util.js",
"test:integration": "mocha test/v3/integration", "test:integration": "mocha test/v3/integration",
"test:integration:slpdb": "mocha --timeout 25000 test/v3/integration/slp*.js", "test:integration:slpdb": "mocha --timeout 25000 test/v3/integration/slp*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls", "coverage": "nyc report --reporter=text-lcov | coveralls",
@@ -28,7 +28,7 @@
"node": ">=10.15.1" "node": ">=10.15.1"
}, },
"dependencies": { "dependencies": {
"@chris.troutner/bch-js": "^3.0.1", "@psf/bch-js": "^3.5.5",
"apidoc": "^0.23.0", "apidoc": "^0.23.0",
"axios": "^0.19.0", "axios": "^0.19.0",
"bitcore-lib-cash": "^8.20.3", "bitcore-lib-cash": "^8.20.3",
@@ -37,7 +37,7 @@
"cors": "^2.8.3", "cors": "^2.8.3",
"debug": "~4.1.1", "debug": "~4.1.1",
"dotenv": "^8.0.0", "dotenv": "^8.0.0",
"electrum-cash": "^1.0.1", "electrum-cash": "^2.0.2",
"express": "^4.15.5", "express": "^4.15.5",
"express-basic-auth": "^1.1.3", "express-basic-auth": "^1.1.3",
"express-rate-limit": "^5.0.0", "express-rate-limit": "^5.0.0",
+10 -2
View File
@@ -116,13 +116,21 @@ class RateLimits {
let pointsToConsume = _this.calcPoints(decoded) let pointsToConsume = _this.calcPoints(decoded)
res.locals.pointsToConsume = pointsToConsume // Feedback for tests. res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
// Retrieve the origin.
let origin = req.get('origin')
if (origin === undefined && key.indexOf('10.0.0.5') > -1) {
origin = 'slp-api'
}
wlogger.info(`origin: ${origin}`)
// If the request originates from one of the approved wallet apps, then // If the request originates from one of the approved wallet apps, then
// apply paid-access rate limits. // apply paid-access rate limits.
const origin = req.get('origin')
if ( if (
origin && origin &&
(origin.toString().indexOf('wallet.fullstack.cash') > -1 || (origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
origin.toString().indexOf('sandbox.fullstack.cash') > -1) origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
origin.toString().indexOf('172.17.') > -1 ||
origin === 'slp-api')
) { ) {
pointsToConsume = 1 pointsToConsume = 1
res.locals.pointsToConsume = pointsToConsume // Feedback for tests. res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
+14 -14
View File
@@ -23,24 +23,24 @@
target="_blank" target="_blank"
>REST API</a >REST API</a
>, allowing modern applications to interact with the Bitcoin Cash (BCH) >, allowing modern applications to interact with the Bitcoin Cash (BCH)
blockchain. New to the BCH blockchain? Start by reading more about blockchain. New to the BCH blockchain? Check out these
<a <a
href="https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer" href="https://fullstack.cash/documentation"
target="_blank" target="_blank"
>building full stack applications on BCH</a >educational resources</a
>. >.
</p><br /> </p><br />
<p> <p>
A REST API can work with any programming language, but bch-api is intended A REST API can work with any programming language, but bch-api is intended
to be paired with the to be paired with the
<a <a
href="https://www.npmjs.com/package/@chris.troutner/bch-js" href="https://www.npmjs.com/package/@psf/bch-js"
target="_blank" target="_blank"
>npm bch-js</a >npm bch-js</a
> >
JavaScript library. JavaScript library.
<a <a
href="https://github.com/Permissionless-Software-Foundation/bch-js-examples" href="https://fullstack.cash/examples"
target="_blank" target="_blank"
>Here is example code</a >Here is example code</a
> >
@@ -53,7 +53,7 @@
</p><br /> </p><br />
<p> <p>
While you'll be able to run through the examples without one, you should While you'll be able to run through the examples without one, you should
<a href="https://account.bchjs.cash/" target="_blank" <a href="https://fullstack.cash/login" target="_blank"
>sign-up for a free API key</a >sign-up for a free API key</a
> >
in order to unlock higher rate limits. in order to unlock higher rate limits.
@@ -66,7 +66,7 @@
<h3>bch-api</h3> <h3>bch-api</h3>
<ul> <ul>
<li> <li>
<a href="https://account.bchjs.cash" target="_blank"> <a href="https://fullstack.cash/login" target="_blank">
Get an API key to increase rate limits Get an API key to increase rate limits
</a> </a>
</li> </li>
@@ -80,14 +80,14 @@
<br /> <br />
<li> <li>
<a href="https://tapi.bchjs.cash"> <a href="https://tapi.fullstack.cash">
testnet REST API testnet REST API
</a> </a>
</li> </li>
<br /> <br />
<li> <li>
<a href="https://github.com/christroutner/bch-api" target="_blank"> <a href="https://github.com/Permissionless-Software-Foundation/bch-api" target="_blank">
bch-api GitHub repository bch-api GitHub repository
</a> </a>
</li> </li>
@@ -114,14 +114,14 @@
</li> </li>
<br /> <br />
<li> <li>
<a href="https://bchjs.cash/" target="_blank"> <a href="https://fullstack.cash/cashstrap" target="_blank">
Run your own infrastructure Run your own infrastructure
</a> </a>
</li> </li>
<br /> <br />
<li> <li>
<a href="https://github.com/bitcoinbook/bitcoinbook#chapters" target="_blank"> <a href="https://fullstack.cash/documentation" target="_blank">
Mastering Bitcoin (great free reference) Mastering Bitcoin (great free reference)
</a> </a>
</li> </li>
@@ -135,7 +135,7 @@
<br /> <br />
<li> <li>
<a href="https://developer.bitcoin.com/faucets/bch/" target="_blank"> <a href="https://fullstack.cash/faucet/" target="_blank">
Testnet Faucet Testnet Faucet
</a> </a>
</li> </li>
@@ -148,7 +148,7 @@
<h3>bch-js</h3> <h3>bch-js</h3>
<ul> <ul>
<li> <li>
<a href="https://www.npmjs.com/package/@chris.troutner/bch-js" <a href="https://www.npmjs.com/package/@psf/bch-js"
target="_blank"> target="_blank">
npm library npm library
</a> </a>
@@ -163,7 +163,7 @@
<br /> <br />
<li> <li>
<a href="https://github.com/christroutner/bch-js" target="_blank"> <a href="https://github.com/Permissionless-Software-Foundation/bch-js" target="_blank">
GitHub code repository GitHub code repository
</a> </a>
</li> </li>
+1 -1
View File
@@ -23,7 +23,7 @@ const router = express.Router()
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
let _this let _this
+536 -3
View File
@@ -10,7 +10,7 @@ const axios = require('axios')
const util = require('util') const util = require('util')
const bitcore = require('bitcore-lib-cash') const bitcore = require('bitcore-lib-cash')
const ElectrumCash = require('electrum-cash').Client const ElectrumCash = require('electrum-cash').ElectrumClient
// const ElectrumCash = require('/home/trout/work/personal/electrum-cash/electrum.js').Client // eslint-disable-line // const ElectrumCash = require('/home/trout/work/personal/electrum-cash/electrum.js').Client // eslint-disable-line
const wlogger = require('../../util/winston-logging') const wlogger = require('../../util/winston-logging')
@@ -19,7 +19,7 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils') const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils() const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
let _this let _this
@@ -50,10 +50,16 @@ class Electrum {
_this.router.get('/', _this.root) _this.router.get('/', _this.root)
_this.router.get('/utxos/:address', _this.getUtxos) _this.router.get('/utxos/:address', _this.getUtxos)
_this.router.post('/utxos', _this.utxosBulk) _this.router.post('/utxos', _this.utxosBulk)
_this.router.get('/tx/data/:txid', _this.getTransactionDetails)
_this.router.post('/tx/data', _this.transactionDetailsBulk)
_this.router.get('/block/headers/:height', _this.getBlockHeaders)
_this.router.post('/block/headers', _this.blockHeadersBulk)
_this.router.get('/balance/:address', _this.getBalance) _this.router.get('/balance/:address', _this.getBalance)
_this.router.post('/balance', _this.balanceBulk) _this.router.post('/balance', _this.balanceBulk)
_this.router.get('/transactions/:address', _this.getTransactions) _this.router.get('/transactions/:address', _this.getTransactions)
_this.router.post('/transactions', _this.transactionsBulk) _this.router.post('/transactions', _this.transactionsBulk)
_this.router.get('/unconfirmed/:address', _this.getMempool)
_this.router.post('/unconfirmed', _this.mempoolBulk)
} }
// Initializes a connection to electrum servers. // Initializes a connection to electrum servers.
@@ -322,6 +328,338 @@ class Electrum {
} }
} }
// Returns a promise that resolves to transaction details data for a txid.
// Expects input to be a txid string, and input validation to have already
// been done by parent, calling function.
async _transactionDetailsFromElectrum (txid, verbose = true) {
try {
if (!_this.isReady) {
throw new Error(
'ElectrumX server connection is not ready. Call await connectToServer() first.'
)
}
// Query the utxos from the ElectrumX server.
const electrumResponse = await _this.electrumx.request(
'blockchain.transaction.get',
txid,
verbose
)
// console.log(
// `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}`
// )
return electrumResponse
} catch (err) {
// console.log('err: ', err)
// Write out error to error log.
wlogger.error(
'Error in elecrumx.js/_transactionDetailsFromElectrum(): ',
err
)
throw err
}
}
/**
* @api {get} /electrumx/tx/data/{txid} Get transaction details for a TXID
* @apiName transaction details for a TXID
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an object with transaction details of the TXID
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json"
*
*/
// GET handler for single transaction
async getTransactionDetails (req, res, next) {
try {
const txid = req.params.txid
const verbose = req.query.verbose
// Reject if txid is anything other than a string
if (typeof txid !== 'string') {
res.status(400)
return res.json({
success: false,
error: 'txid must be a string'
})
}
wlogger.debug(
'Executing electrumx/getTransactionDetails with this txid: ',
txid
)
// Get data from ElectrumX server.
const electrumResponse = await _this._transactionDetailsFromElectrum(
txid,
verbose
)
// console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`)
// Pass the error message if ElectrumX reports an error.
if (electrumResponse instanceof Error) {
res.status(400)
return res.json({
success: false,
error: electrumResponse.message
})
}
res.status(200)
return res.json({
success: true,
details: electrumResponse
})
} catch (err) {
// Write out error to error log.
wlogger.error('Error in elecrumx.js/getTransactionDetails().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /electrumx/tx/data Get transaction details for an array of TXIDs
* @apiName Transaction details for an array of TXIDs
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array of objects with transaction details of an array of TXIDs.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}'
*
*
*/
// POST handler for bulk queries on transaction details
async transactionDetailsBulk (req, res, next) {
try {
const txids = req.body.txids
const verbose = req.body.verbose || true
// Reject if txids is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({
success: false,
error: 'txids needs to be an array. Use GET for single txid.'
})
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
success: false,
error: 'Array too large.'
})
}
wlogger.debug(
'Executing electrumx.js/transactionDetailsBulk with these txids: ',
txids
)
// Loops through each address and creates an array of Promises, querying
// the Electrum server in parallel.
const transactions = txids.map(async (txid, index) => {
// console.log(`address: ${address}`)
const details = await _this._transactionDetailsFromElectrum(
txid,
verbose
)
return { details, txid }
})
// Wait for all parallel Electrum requests to return.
const result = await Promise.all(transactions)
// Return the array of retrieved transaction details.
res.status(200)
return res.json({
success: true,
transactions: result
})
} catch (err) {
wlogger.error('Error in electrumx.js/transactionDetailsBulk().', err)
return _this.errorHandler(err, res)
}
}
// Returns a promise that resolves to block header data for a block height.
// Expects input to be a height number, and input validation to have already
// been done by parent, calling function.
async _blockHeadersFromElectrum (height, count = 1) {
try {
if (!_this.isReady) {
throw new Error(
'ElectrumX server connection is not ready. Call await connectToServer() first.'
)
}
// Query the block header from the ElectrumX server.
const electrumResponse = await _this.electrumx.request('blockchain.block.headers', height, count)
// console.log(
// `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}`
// )
const HEADER_SIZE = 80 * 2
if (!(electrumResponse instanceof Error)) {
const headers = electrumResponse.hex.match(new RegExp(`.{1,${HEADER_SIZE}}`, 'g'))
return headers
}
return electrumResponse
} catch (err) {
// console.log('err: ', err)
// Write out error to error log.
wlogger.error(
'Error in elecrumx.js/_blockHeaderFromElectrum(): ',
err
)
throw err
}
}
/**
* @api {get} /electrumx/block/headers/{height} Get `count` block headers starting at a height
* @apiName Block header data for a `count` blocks starting at a block height
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array with block headers starting at the block height
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/block/header/42?count=2" -H "accept: application/json"
*
*/
// GET handler for single block headers
async getBlockHeaders (req, res, next) {
try {
const height = Number(req.params.height)
const count = req.query.count === undefined ? 1 : Number(req.query.count)
// Reject if height is not a number
if (Number.isNaN(height) || height < 0) {
res.status(400)
return res.json({
success: false,
error: 'height must be a positive number'
})
}
// Reject if height is not a number
if (Number.isNaN(count) || count < 0) {
res.status(400)
return res.json({
success: false,
error: 'count must be a positive number'
})
}
wlogger.debug(
'Executing electrumx/getBlockHeaders with this height: ',
height
)
// Get data from ElectrumX server.
const electrumResponse = await _this._blockHeadersFromElectrum(height, count)
// console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`)
// Pass the error message if ElectrumX reports an error.
if (electrumResponse instanceof Error) {
res.status(400)
return res.json({
success: false,
error: electrumResponse.message
})
}
res.status(200)
return res.json({
success: true,
headers: electrumResponse
})
} catch (err) {
// Write out error to error log.
wlogger.error('Error in elecrumx.js/getBlockHeader().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /electrumx/block/headers Get block headers for an array of height + count pairs
* @apiName Block headers for an array of height + count pairs
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array of objects with blockheaders of an array of TXIDs.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}'
*
*/
// POST handler for bulk queries on block headers
async blockHeadersBulk (req, res, next) {
try {
const heights = req.body.heights
// Reject if heights is not an array.
if (!Array.isArray(heights)) {
res.status(400)
return res.json({
success: false,
error: 'heights needs to be an array. Use GET for single height.'
})
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, heights)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
success: false,
error: 'Array too large.'
})
}
wlogger.debug(
'Executing electrumx.js/blockHeadersBulk with these txids: ',
heights
)
// Loops through each address and creates an array of Promises, querying
// the Electrum server in parallel.
const transactions = heights.map(async (obj) => {
const headers = await _this._blockHeadersFromElectrum(
obj.height,
obj.count
)
return { headers }
})
// Wait for all parallel Electrum requests to return.
const result = await Promise.all(transactions)
// Return the array of retrieved transaction details.
res.status(200)
return res.json({
success: true,
headers: result
})
} catch (err) {
wlogger.error('Error in electrumx.js/blockHeadersBulk().', err)
return _this.errorHandler(err, res)
}
}
// Returns a promise that resolves to a balance for an address. Expects input // Returns a promise that resolves to a balance for an address. Expects input
// to be a cash address, and input validation to have already been done by // to be a cash address, and input validation to have already been done by
// parent, calling function. // parent, calling function.
@@ -708,6 +1046,199 @@ class Electrum {
} }
} }
// Returns a promise that resolves to unconfirmed UTXO data (mempool) for an address.
// Expects input to be a cash address, and input validation to have
// already been done by parent, calling function.
async _mempoolFromElectrumx (address) {
try {
// Convert the address to a scripthash.
const scripthash = _this.addressToScripthash(address)
if (!_this.isReady) {
throw new Error(
'ElectrumX server connection is not ready. Call await connectToServer() first.'
)
}
// Query the unconfirmed utxos from the ElectrumX server.
const electrumResponse = await _this.electrumx.request(
'blockchain.scripthash.get_mempool',
scripthash
)
// console.log(
// `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}`
// )
return electrumResponse
} catch (err) {
// console.log('err: ', err)
// Write out error to error log.
wlogger.error('Error in elecrumx.js/_mempoolFromElectrumx(): ', err)
throw err
}
}
/**
* @api {get} /electrumx/unconfirmed/{addr} Get unconfirmed utxos for a single address.
* @apiName Unconfirmed UTXOs for a single address
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an object with unconfirmed UTXOs associated with an address.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance
async getMempool (req, res, next) {
try {
const address = req.params.address
// Reject if address is an array.
if (Array.isArray(address)) {
res.status(400)
return res.json({
success: false,
error: 'address can not be an array. Use POST for bulk upload.'
})
}
// Ensure the address is in cash address format.
const cashAddr = _this.bchjs.Address.toCashAddress(address)
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = _this.routeUtils.validateNetwork(cashAddr)
if (!networkIsValid) {
res.status(400)
return res.json({
success: false,
error:
'Invalid network. Trying to use a testnet address on mainnet, or vice versa.'
})
}
wlogger.debug(
'Executing electrumx/getMempool with this address: ',
cashAddr
)
// Get data from ElectrumX server.
const electrumResponse = await _this._mempoolFromElectrumx(cashAddr)
// console.log(`_mempoolFromElectrumx(): ${JSON.stringify(electrumResponse, null, 2)}`)
// Pass the error message if ElectrumX reports an error.
if (Object.prototype.hasOwnProperty.call(electrumResponse, 'code')) {
res.status(400)
return res.json({
success: false,
message: electrumResponse.message
})
}
res.status(200)
return res.json({
success: true,
utxos: electrumResponse
})
} catch (err) {
// Write out error to error log.
wlogger.error('Error in elecrumx.js/getMempool().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /electrumx/unconfirmed Get unconfirmed utxos for an array of addresses.
* @apiName Unconfirmed UTXOs for an array of addresses
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array of objects with unconfirmed UTXOs associated with an address.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
// POST handler for bulk queries on address details
async mempoolBulk (req, res, next) {
try {
let addresses = req.body.addresses
// Reject if addresses is not an array.
if (!Array.isArray(addresses)) {
res.status(400)
return res.json({
error: 'addresses needs to be an array. Use GET for single address.'
})
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, addresses)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: 'Array too large.'
})
}
wlogger.debug(
'Executing electrumx.js/mempoolBulk with these addresses: ',
addresses
)
// Validate each element in the address array.
for (let i = 0; i < addresses.length; i++) {
const thisAddress = addresses[i]
// Ensure the input is a valid BCH address.
try {
_this.bchjs.Address.toLegacyAddress(thisAddress)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${thisAddress}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const networkIsValid = _this.routeUtils.validateNetwork(thisAddress)
if (!networkIsValid) {
res.status(400)
return res.json({
error: `Invalid network for address ${thisAddress}. Trying to use a testnet address on mainnet, or vice versa.`
})
}
}
// Loops through each address and creates an array of Promises, querying
// Insight API in parallel.
addresses = addresses.map(async (address, index) => {
// console.log(`address: ${address}`)
const utxos = await _this._mempoolFromElectrumx(address)
return {
utxos,
address
}
})
// Wait for all parallel Insight requests to return.
const result = await Promise.all(addresses)
// Return the array of retrieved address information.
res.status(200)
return res.json({
success: true,
utxos: result
})
} catch (err) {
wlogger.error('Error in electrumx.js/mempoolBulk().', err)
return _this.errorHandler(err, res)
}
}
// Convert a 'bitcoincash:...' address to a script hash used by ElectrumX. // Convert a 'bitcoincash:...' address to a script hash used by ElectrumX.
addressToScripthash (addrStr) { addressToScripthash (addrStr) {
try { try {
@@ -716,7 +1247,9 @@ class Electrum {
const address = _this.bitcore.Address.fromString(addrStr) const address = _this.bitcore.Address.fromString(addrStr)
// console.log(`address: ${address}`) // console.log(`address: ${address}`)
const script = _this.bitcore.Script.buildPublicKeyHashOut(address) const script = address.isPayToPublicKeyHash()
? _this.bitcore.Script.buildPublicKeyHashOut(address)
: _this.bitcore.Script.buildScriptHashOut(address)
// console.log(`script: ${script}`) // console.log(`script: ${script}`)
const scripthash = _this.bitcore.crypto.Hash.sha256(script.toBuffer()) const scripthash = _this.bitcore.crypto.Hash.sha256(script.toBuffer())
+1 -1
View File
@@ -15,7 +15,7 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils') const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils() const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// Use Blockbook for getting indexer data. // Use Blockbook for getting indexer data.
+1 -1
View File
@@ -17,7 +17,7 @@ const routeUtils = new RouteUtils()
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
let _this let _this
+1 -1
View File
@@ -11,7 +11,7 @@ const express = require('express')
const router = express.Router() const router = express.Router()
// const BCHJS = require('@chris.troutner/bch-js') // const BCHJS = require('@psf/bch-js')
// const bchjs = new BCHJS() // const bchjs = new BCHJS()
// let _this // let _this
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('../../util/winston-logging')
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
module.exports = { module.exports = {
+1 -1
View File
@@ -1,6 +1,6 @@
const axios = require('axios') const axios = require('axios')
const SLPSDK = require('@chris.troutner/bch-js') const SLPSDK = require('@psf/bch-js')
const SLP = new SLPSDK() const SLP = new SLPSDK()
class Slpdb { class Slpdb {
+79 -169
View File
@@ -13,7 +13,7 @@ const Slpdb = require('./services/slpdb')
// const strftime = require('strftime') // const strftime = require('strftime')
const wlogger = require('../../util/winston-logging') const wlogger = require('../../util/winston-logging')
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// Used to convert error messages to strings, to safely pass to users. // Used to convert error messages to strings, to safely pass to users.
@@ -64,10 +64,6 @@ class Slp {
_this.router.get('/balancesForAddress/:address', _this.balancesForAddress) _this.router.get('/balancesForAddress/:address', _this.balancesForAddress)
_this.router.post('/balancesForAddress', _this.balancesForAddressBulk) _this.router.post('/balancesForAddress', _this.balancesForAddressBulk)
_this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle) _this.router.get('/balancesForToken/:tokenId', _this.balancesForTokenSingle)
_this.router.get(
'/balance/:address/:tokenId',
_this.balancesForAddressByTokenID
)
_this.router.get('/convert/:address', _this.convertAddressSingle) _this.router.get('/convert/:address', _this.convertAddressSingle)
_this.router.post('/convert', _this.convertAddressBulk) _this.router.post('/convert', _this.convertAddressBulk)
_this.router.post('/validateTxid', _this.validateBulk) _this.router.post('/validateTxid', _this.validateBulk)
@@ -83,6 +79,7 @@ class Slp {
_this.txsByAddressSingle _this.txsByAddressSingle
) )
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn) _this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos)
} }
// DRY error handler. // DRY error handler.
@@ -830,170 +827,6 @@ class Slp {
} }
} }
/**
* @api {get} /slp/balance/{address}/{TokenId} List single slp token balance for address.
* @apiName List single slp token balance for address.
* @apiGroup SLP
* @apiDescription Returns List single slp token balance for address.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/balance/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m/1cda254d0a995c713b7955298ed246822bee487458cd9747a91d9e81d9d28125" -H "accept:application/json"
*
*
*/
// Retrieve token balances for a single token class, for a single address.
async balancesForAddressByTokenID (req, res, next) {
try {
// Validate input data.
const address = req.params.address
if (!address || address === '') {
res.status(400)
return res.json({ error: 'address can not be empty' })
}
const tokenId = req.params.tokenId
if (!tokenId || tokenId === '') {
res.status(400)
return res.json({ error: 'tokenId can not be empty' })
}
// Ensure the input is a valid BCH address.
try {
_this.bchjs.SLP.Address.toCashAddress(address)
} catch (err) {
res.status(400)
return res.json({
error: `Invalid BCH address. Double check your address is valid: ${address}`
})
}
// Prevent a common user error. Ensure they are using the correct network address.
const cashAddr = _this.bchjs.SLP.Address.toCashAddress(address)
const networkIsValid = _this.routeUtils.validateNetwork(cashAddr)
if (!networkIsValid) {
res.status(400)
return res.json({
error:
'Invalid network. Trying to use a testnet address on mainnet, or vice versa.'
})
}
// Convert input to an simpleledger: address.
const slpAddr = _this.bchjs.SLP.Address.toSlpAddress(req.params.address)
const query = {
v: 3,
q: {
db: ['g'],
aggregate: [
{
$match: {
'graphTxn.outputs': {
$elemMatch: {
address: slpAddr,
status: 'UNSPENT',
slpAmount: { $gte: 0 }
}
}
}
},
{
$unwind: '$graphTxn.outputs'
},
{
$match: {
'graphTxn.outputs.address': slpAddr,
'graphTxn.outputs.status': 'UNSPENT',
'graphTxn.outputs.slpAmount': { $gte: 0 }
}
},
{
$project: {
amount: '$graphTxn.outputs.slpAmount',
address: '$graphTxn.outputs.address',
txid: '$graphTxn.txid',
vout: '$graphTxn.outputs.vout',
tokenId: '$tokenDetails.tokenIdHex'
}
},
{
$group: {
_id: '$tokenId',
balanceString: {
$sum: '$amount'
},
slpAddress: {
$first: '$address'
}
}
}
],
limit: 10000
}
}
const options = _this.generateCredentials()
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
// Get data from SLPDB.
const opt = {
method: 'get',
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
const tokenRes = await _this.axios.request(opt)
console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
let resVal = {
cashAddress: _this.bchjs.SLP.Address.toCashAddress(slpAddr),
legacyAddress: _this.bchjs.SLP.Address.toLegacyAddress(slpAddr),
slpAddress: slpAddr,
tokenId: tokenId,
balance: 0,
balanceString: '0'
}
if (tokenRes.data.g.length > 0) {
tokenRes.data.g.forEach(async (token) => {
if (token._id === tokenId) {
resVal = {
cashAddress: _this.bchjs.SLP.Address.toCashAddress(slpAddr),
legacyAddress: _this.bchjs.SLP.Address.toLegacyAddress(slpAddr),
slpAddress: slpAddr,
tokenId: token._id,
balance: parseFloat(token.balanceString),
balanceString: token.balanceString
}
}
})
} else {
resVal = {
cashAddress: _this.bchjs.SLP.Address.toCashAddress(slpAddr),
legacyAddress: _this.bchjs.SLP.Address.toLegacyAddress(slpAddr),
slpAddress: slpAddr,
tokenId: tokenId,
balance: 0,
balanceString: '0'
}
}
res.status(200)
return res.json(resVal)
} catch (err) {
wlogger.error('Error in slp.ts/balancesForAddressByTokenID().', err)
return _this.errorHandler(err, res)
// return res.json({
// error: `Error in /balance/:address/:tokenId: ${err.message}`
// })
}
}
/** /**
* @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy. * @api {get} /slp/convert/{address} Convert address to slpAddr, cashAddr and legacy.
* @apiName Convert address to slpAddr, cashAddr and legacy. * @apiName Convert address to slpAddr, cashAddr and legacy.
@@ -1687,6 +1520,83 @@ class Slp {
}) })
} }
} }
/**
* @api {post} /slp/hydrateUtxos/ hydrateUtxos
* @apiName SLP hydrateUtxos
* @apiGroup SLP
* @apiDescription Hydrate UTXO data with SLP information.
*
* Expects an array of UTXO objects as input. Returns an array of equal size.
* Returns UTXO data hydrated with token information. If the UTXO does not
* belong to a SLP transaction, it will return an isValid property set to
* false. If the UTXO is part of an SLP transaction, it will return the UTXO
* object with additional SLP information attached. An isValid property will
* be included. If its value is true, the UTXO is a valid SLP UTXO. If the
* value is null, then SLPDB has not yet processed that txid and validity has
* not been confirmed.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}'
*
*
*/
async hydrateUtxos (req, res, next) {
try {
const utxos = req.body.utxos
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
return res.json({
error: 'Input must be an array.'
})
}
if (!utxos.length) {
res.status(422)
return res.json({
error: 'Array should not be empty'
})
}
if (utxos.length > 20) {
res.status(422)
return res.json({
error: 'Array too long, max length is 20'
})
}
// Loop through each address and query the UTXOs for that element.
for (let i = 0; i < utxos.length; i++) {
const theseUtxos = utxos[i].utxos
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details : ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
}
res.status(200)
return res.json({ slpUtxos: utxos })
} catch (err) {
wlogger.error('Error in slp.js/hydrateUtxos().', err)
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxos()'
})
}
}
} }
module.exports = Slp module.exports = Slp
+1 -1
View File
@@ -12,7 +12,7 @@ const blockbook = new Blockbook()
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/' // const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/'
+1 -1
View File
@@ -16,7 +16,7 @@ const router = express.Router()
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// Connect the route endpoints to their handler functions. // Connect the route endpoints to their handler functions.
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('./winston-logging')
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js') const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS() const bchjs = new BCHJS()
// let _this // let _this
+811 -5
View File
@@ -2,7 +2,7 @@
TESTS FOR THE ELECTRUMX.JS LIBRARY TESTS FOR THE ELECTRUMX.JS LIBRARY
Named with a01 prefix so that these tests are run first. Something about running Named with a01 prefix so that these tests are run first. Something about running
the Blcokbook and Blockchain tests screws up these tests. Spend a couple hours the Blcokbook and Blockchain tests screws up these tests. Spent a couple hours
debugging and couldn't isolate the source of the issue, but renaming the file debugging and couldn't isolate the source of the issue, but renaming the file
was an easy fix. was an easy fix.
@@ -30,6 +30,18 @@ const mockData = require('./mocks/electrumx-mock')
const util = require('util') const util = require('util')
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
// A wrapper for asserting that the correct response is returned when an error
// is expected.
function expectRouteError (res, result, expectedError, code = 400) {
assert.equal(res.statusCode, code, `HTTP status code ${code} expected.`)
assert.property(result, 'error')
assert.include(result.error, expectedError)
assert.property(result, 'success')
assert.equal(result.success, false)
}
describe('#ElectrumX Router', () => { describe('#ElectrumX Router', () => {
let req, res let req, res
let sandbox let sandbox
@@ -82,6 +94,17 @@ describe('#ElectrumX Router', () => {
// //
}) })
// A wrapper for stubbing with the Sinon sandbox.
function stubMethodForUnitTests (obj, method, value) {
if (process.env.TEST !== 'unit') return false
electrumxRoute.isReady = true // Force flag.
sandbox.stub(obj, method).resolves(value)
return true
}
describe('#root', () => { describe('#root', () => {
// root route handler. // root route handler.
const root = electrumxRoute.root const root = electrumxRoute.root
@@ -94,7 +117,7 @@ describe('#ElectrumX Router', () => {
}) })
describe('#addressToScripthash', () => { describe('#addressToScripthash', () => {
it('should accurately return a scripthash', () => { it('should accurately return a scripthash for a P2PKH address', () => {
const addr = 'bitcoincash:qpr270a5sxphltdmggtj07v4nskn9gmg9yx4m5h7s4' const addr = 'bitcoincash:qpr270a5sxphltdmggtj07v4nskn9gmg9yx4m5h7s4'
const scripthash = electrumxRoute.addressToScripthash(addr) const scripthash = electrumxRoute.addressToScripthash(addr)
@@ -104,6 +127,17 @@ describe('#ElectrumX Router', () => {
assert.equal(scripthash, expectedOutput) assert.equal(scripthash, expectedOutput)
}) })
it('should accurately return a scripthash for a P2SH address', () => {
const addr = 'bitcoincash:pz0z7u9p96h2p6hfychxdrmwgdlzpk5luc5yks2wxq'
const scripthash = electrumxRoute.addressToScripthash(addr)
const expectedOutput =
'8bc2235c8e7d5634d9ec429fc0171f2c58e728d4f1e2fb7e440e313133cfa4f0'
assert.equal(scripthash, expectedOutput)
})
}) })
describe('#_utxosFromElectrumx', () => { describe('#_utxosFromElectrumx', () => {
@@ -412,6 +446,466 @@ describe('#ElectrumX Router', () => {
}) })
}) })
describe('#_transactionDetailsFromElectrum', () => {
it('should return error object for invalid txid', async () => {
const txid =
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25'
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid tx hash')
)
const result = await electrumxRoute._transactionDetailsFromElectrum(txid)
assert.instanceOf(result, Error)
assert.include(result.message, 'Invalid tx hash')
})
it('should get details for a single txid', async () => {
const txid =
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails
)
const result = await electrumxRoute._transactionDetailsFromElectrum(txid)
assert.isObject(result)
assert.property(result, 'blockhash')
assert.property(result, 'hash')
assert.property(result, 'hex')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.equal(result.hash, txid)
})
})
describe('#getTransactionDetails', () => {
it('should throw 400 if txid is not a string', async () => {
req.params.txid = 5
const result = await electrumxRoute.getTransactionDetails(req, res)
expectRouteError(res, result, 'txid must be a string')
})
it('should throw 400 on array input', async () => {
req.params.txid = [
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
]
const result = await electrumxRoute.getTransactionDetails(req, res)
expectRouteError(res, result, 'txid must be a string')
})
it('should return error object for invalid txid', async () => {
req.params.txid =
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25'
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid tx hash')
)
// Call the details API.
const result = await electrumxRoute.getTransactionDetails(req, res)
expectRouteError(res, result, 'Invalid tx hash')
})
it('should get details for a single txid', async () => {
req.params.txid =
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails
)
// Call the details API.
const result = await electrumxRoute.getTransactionDetails(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'details')
assert.isObject(result.details)
assert.property(result.details, 'blockhash')
assert.property(result.details, 'hash')
assert.property(result.details, 'hex')
assert.property(result.details, 'vin')
assert.property(result.details, 'vout')
assert.equal(result.details.hash, req.params.txid)
})
})
describe('#transactionDetailsBulk', () => {
it('should throw an error for an empty body', async () => {
req.body = {}
const result = await electrumxRoute.transactionDetailsBulk(req, res)
expectRouteError(res, result, 'txids needs to be an array')
})
it('should error on non-array single txid', async () => {
req.body = {
txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
}
const result = await electrumxRoute.transactionDetailsBulk(req, res)
expectRouteError(res, result, 'txids needs to be an array')
})
it('should NOT throw 400 error for an invalid txid', async () => {
req.body = {
txids: [
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb25'
]
}
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails
)
const result = await electrumxRoute.transactionDetailsBulk(req, res)
// This should probably throw a 400 error, but to be consistent with the other
// bulk endpoints it doesn't throw. This will change in the future
// expectRouteError(res, result, 'Invalid tx hash')
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'transactions')
assert.isArray(result.transactions)
})
it('should throw 429 error if txid array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
req.body.txids = testArray
const result = await electrumxRoute.transactionDetailsBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
expectRouteError(res, result, 'Array too large', 429)
})
it('should get details for a single txid', async () => {
req.body = {
txids: [
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
]
}
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails
)
// Call the details API.
const result = await electrumxRoute.transactionDetailsBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'transactions')
assert.isArray(result.transactions)
assert.property(result.transactions[0], 'txid')
assert.property(result.transactions[0], 'details')
assert.property(result.transactions[0].details, 'blockhash')
assert.property(result.transactions[0].details, 'hash')
assert.property(result.transactions[0].details, 'hex')
assert.property(result.transactions[0].details, 'vin')
assert.property(result.transactions[0].details, 'vout')
})
it('should get details for multiple txids', async () => {
req.body = {
txids: [
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
]
}
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockData.txDetails
)
// Call the details API.
const result = await electrumxRoute.transactionDetailsBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)'
assert.property(result, 'success')
assert.equal(result.success, true)
assert.isArray(result.transactions)
assert.isObject(result.transactions[0].details)
assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs')
})
})
describe('#_blockHeadersFromElectrum', () => {
it('should return error object for invalid block height', async () => {
const height = -10
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid height')
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, 2)
assert.instanceOf(result, Error)
assert.include(result.message, 'Invalid height')
})
it('should return error object for invalid count', async () => {
const height = 42
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid count')
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, -1)
assert.instanceOf(result, Error)
assert.include(result.message, 'Invalid count')
})
it('should get block header for a single block height', async () => {
const height = 42
const mockedResponse = { count: 2, hex: mockData.blockHeaders.join(''), max: 2016 }
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockedResponse
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, 2)
assert.isArray(result)
assert.deepEqual(result, mockData.blockHeaders)
})
})
describe('#getBlockheaders', () => {
it('should throw 400 if height is not a number', async () => {
req.params.height = 'Hello'
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should throw 400 if height is negative', async () => {
req.params.height = -42
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should throw 400 if count is not a number', async () => {
req.params.height = 42
req.query.count = 'Hello'
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'count must be a positive number')
})
it('should throw 400 if count is negative', async () => {
req.params.height = 42
req.query.count = -10
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'count must be a positive number')
})
it('should throw 400 on array input', async () => {
req.params.height = [42, 42]
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should return error object for invalid height', async () => {
req.params.height = 1000000000
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid height')
)
// Call the details API.
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'Invalid height')
})
it('should get headers for a single block height with count 2', async () => {
req.params.height = 42
req.query.count = 2
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.getBlockHeaders(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
assert.deepEqual(result.headers, mockData.blockHeaders)
})
})
describe('#blockHeadersBulk', () => {
it('should throw an error for an empty body', async () => {
req.body = {}
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'heights needs to be an array')
})
it('should error on non-array single height', async () => {
req.body = {
heights: 42
}
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'heights needs to be an array')
})
it('should NOT throw 400 error for an invalid height', async () => {
req.body = {
heights: [
{ height: -10, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
const result = await electrumxRoute.blockHeadersBulk(req, res)
// This should probably throw a 400 error, but to be consistent with the other
// bulk endpoints it doesn't throw. This will change in the future
// expectRouteError(res, result, 'Invalid tx hash')
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
})
it('should throw 429 error if heights array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
req.body.heights = testArray
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'Array too large', 429)
})
it('should get details for a single height', async () => {
req.body = {
heights: [
{ height: 42, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.blockHeadersBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
assert.property(result.headers[0], 'headers')
assert.isArray(result.headers[0].headers)
})
it('should get details for multiple txids', async () => {
req.body = {
heights: [
{ height: 42, count: 2 },
{ height: 42, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.blockHeadersBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)'
assert.property(result, 'success')
assert.equal(result.success, true)
assert.isArray(result.headers)
assert.isArray(result.headers[0].headers)
assert.equal(result.headers.length, 2, '2 outputs for 2 inputs')
})
})
describe('#_balanceFromElectrumx', () => { describe('#_balanceFromElectrumx', () => {
it('should throw error for invalid address', async () => { it('should throw error for invalid address', async () => {
try { try {
@@ -753,9 +1247,7 @@ describe('#ElectrumX Router', () => {
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag. electrumxRoute.isReady = true // Force flag.
sandbox sandbox.stub(electrumxRoute.electrumx, 'request').resolves([])
.stub(electrumxRoute.electrumx, 'request')
.resolves([])
} }
// Call the details API. // Call the details API.
@@ -999,4 +1491,318 @@ describe('#ElectrumX Router', () => {
assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs') assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs')
}) })
}) })
describe('#_mempoolFromElectrumx', () => {
it('should throw error for invalid address', async () => {
try {
// Address has invalid checksum.
const address = 'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2'
// Call the details API.
await electrumxRoute._mempoolFromElectrumx(address)
assert.equal(true, false, 'Unexpected code path')
} catch (err) {
assert.include(err.message, 'Invalid checksum')
}
})
it('should return empty array for address with no unconfirmed utxos', async () => {
// Address has invalid checksum.
const address = 'bchtest:qqtmlpspjakqlvywae226esrcdrj9auynuwadh55uf'
// Mock unit tests to prevent live network calls.
if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag.
sandbox.stub(electrumxRoute.electrumx, 'request').resolves([])
}
// Call the details API.
const result = await electrumxRoute._mempoolFromElectrumx(address)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result.length, 0)
})
it('should get mempool for a single address', async () => {
const address = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
// Mock unit tests to prevent live network calls.
if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag.
sandbox
.stub(electrumxRoute.electrumx, 'request')
.resolves(mockData.mempool)
} else {
// Skip this test for integrations. Unconfirmed UTXOs are transient and
// not easy to test in real-time.
assert.equal(true, true)
return
}
// Call the details API.
const result = await electrumxRoute._mempoolFromElectrumx(address)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'height')
assert.property(result[0], 'tx_hash')
assert.property(result[0], 'fee')
})
})
describe('#getMempool', () => {
it('should throw 400 if address is empty', async () => {
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
assert.property(result, 'success')
assert.equal(result.success, false)
})
it('should throw 400 on array input', async () => {
req.params.address = ['qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c']
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error')
assert.include(result.error, 'address can not be an array')
assert.property(result, 'success')
assert.equal(result.success, false)
})
it('should throw an error for an invalid address', async () => {
req.params.address = '02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
assert.property(result, 'success')
assert.equal(result.success, false)
})
it('should detect a network mismatch', async () => {
req.params.address = 'bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4'
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.property(result, 'error')
assert.include(result.error, 'Invalid network', 'Proper error message')
assert.property(result, 'success')
assert.equal(result.success, false)
})
it('should pass errors from electrum-cash to user', async () => {
// Address has invalid checksum.
req.params.address =
'bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur2'
// Call the details API.
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, false)
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
})
it('should get mempool for a single address', async () => {
req.params.address =
'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
// Mock unit tests to prevent live network calls.
if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag.
sandbox
.stub(electrumxRoute, '_mempoolFromElectrumx')
.resolves(mockData.mempool)
} else {
// Skip this test for integrations. Unconfirmed UTXOs are transient and
// not easy to test in real-time.
assert.equal(true, true)
return
}
// Call the details API.
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'utxos')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'height')
assert.property(result.utxos[0], 'tx_hash')
assert.property(result.utxos[0], 'fee')
})
})
describe('#mempoolBulk', () => {
it('should throw an error for an empty body', async () => {
req.body = {}
const result = await electrumxRoute.mempoolBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.include(
result.error,
'addresses needs to be an array',
'Proper error message'
)
})
it('should error on non-array single address', async () => {
req.body = {
address: 'qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'
}
const result = await electrumxRoute.mempoolBulk(req, res)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.include(
result.error,
'addresses needs to be an array',
'Proper error message'
)
})
it('should throw 400 error if addresses array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
req.body.addresses = testArray
const result = await electrumxRoute.mempoolBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too large')
})
it('should throw an error for an invalid address', async () => {
req.body = {
addresses: ['02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c']
}
const result = await electrumxRoute.mempoolBulk(req, res)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.include(
result.error,
'Invalid BCH address',
'Proper error message'
)
})
it('should detect a network mismatch', async () => {
req.body = {
addresses: ['bchtest:qq89kjkeqz9mngp8kl3dpmu43y2wztdjqu500gn4c4']
}
const result = await electrumxRoute.mempoolBulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'HTTP status code 400 expected.')
assert.include(result.error, 'Invalid network', 'Proper error message')
})
it('should get mempool details for a single address', async () => {
req.body = {
addresses: ['bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7']
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag.
sandbox
.stub(electrumxRoute, '_mempoolFromElectrumx')
.resolves(mockData.mempool)
} else {
// Skip this test for integrations. Unconfirmed UTXOs are transient and
// not easy to test in real-time.
assert.equal(true, true)
return
}
// Call the details API.
const result = await electrumxRoute.mempoolBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'utxos')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'address')
assert.property(result.utxos[0], 'utxos')
assert.isArray(result.utxos[0].utxos)
assert.property(result.utxos[0].utxos[0], 'height')
assert.property(result.utxos[0].utxos[0], 'tx_hash')
assert.property(result.utxos[0].utxos[0], 'fee')
})
it('should get mempool for multiple addresses', async () => {
req.body = {
addresses: [
'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf',
'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
]
}
// Mock the Insight URL for unit tests.
if (process.env.TEST === 'unit') {
electrumxRoute.isReady = true // Force flag.
sandbox
.stub(electrumxRoute, '_mempoolFromElectrumx')
.resolves(mockData.mempool)
} else {
// Skip this test for integrations. Unconfirmed UTXOs are transient and
// not easy to test in real-time.
assert.equal(true, true)
return
}
// Call the details API.
const result = await electrumxRoute.mempoolBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.isArray(result.utxos)
assert.isArray(result.utxos[0].utxos)
assert.equal(result.utxos.length, 2, '2 outputs for 2 inputs')
})
})
}) })
+93 -1
View File
@@ -22,6 +22,9 @@ if (!process.env.SLPDB_URL) {
const SLP = require('../../../src/routes/v3/slp') const SLP = require('../../../src/routes/v3/slp')
const slp = new SLP() const slp = new SLP()
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
const { mockReq, mockRes } = require('../mocks/express-mocks') const { mockReq, mockRes } = require('../mocks/express-mocks')
describe('#slp', () => { describe('#slp', () => {
@@ -102,7 +105,7 @@ describe('#slp', () => {
}) })
}) })
describe('generateSendOpReturn', () => { describe('#generateSendOpReturn', () => {
it('should return OP_RETURN script', async () => { it('should return OP_RETURN script', async () => {
req.body.tokenUtxos = [ req.body.tokenUtxos = [
{ {
@@ -121,4 +124,93 @@ describe('#slp', () => {
assert.isNumber(result.outputs) assert.isNumber(result.outputs)
}) })
}) })
describe('#hydrateUtxos', () => {
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
req.body.utxos = utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should process data directly from Electrumx', async () => {
const addrs = [
'bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg',
'bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89',
'bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk'
]
const utxos = await bchjs.Electrumx.utxo(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
req.body.utxos = utxos.utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 3)
assert.equal(result.slpUtxos[0].utxos.length, 1)
assert.equal(result.slpUtxos[1].utxos.length, 1)
assert.equal(result.slpUtxos[2].utxos.length, 2)
})
})
}) })
+85 -1
View File
@@ -31,8 +31,92 @@ const txHistory = [
} }
] ]
const mempool = [
{
tx_hash: '45381031132c57b2ff1cbe8d8d3920cf9ed25efd9a0beb764bdb2f24c7d1c7e3',
height: 0,
fee: 24310
}
]
const txDetails = {
blockhash: '0000000000000000002aaf94953da3b487317508ebd1003a1d75d6d6ec2e75cc',
blocktime: 1578327094,
confirmations: 31861,
hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
locktime: 0,
size: 392,
time: 1578327094,
txid: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
version: 2,
vin: [
{
scriptSig: {
asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
vout: 1
},
{
scriptSig: {
asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
vout: 0
}
],
vout: [
{
n: 0,
scriptPubKey: {
addresses: ['bitcoincash: pqvfecpwxvj53ayqfwkxtjaxsgpvnklcyg8xewk9hl'],
asm: 'OP_HASH160 189ce02e332548f4804bac65cba68202c9dbf822 OP_EQUAL',
hex: 'a914189ce02e332548f4804bac65cba68202c9dbf82287',
reqSigs: 1,
type: 'scripthash'
},
value: 0.0562057
},
{
n: 1,
scriptPubKey: {
addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'],
asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac',
reqSigs: 1,
type: 'pubkeyhash'
},
value: 0.00589197
},
{
n: 2,
scriptPubKey: {
addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'],
asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac',
reqSigs: 1,
type: 'pubkeyhash'
},
value: 0.03272697
}
]
}
const blockHeaders = [
'010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6',
'01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c'
]
module.exports = { module.exports = {
utxos, utxos,
balance, balance,
txHistory txHistory,
mempool,
txDetails,
blockHeaders
} }
+147
View File
@@ -888,6 +888,153 @@ describe('#SLP', () => {
assert.isNumber(result.outputs) assert.isNumber(result.outputs)
}) })
}) })
describe('#hydrateUtxos', () => {
it('should throw error if input is not an array.', async () => {
req.body.utxos = 'test'
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: `, result)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Input must be an array')
})
it('should throw error if Array is empty', async () => {
req.body.utxos = []
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array should not be empty')
})
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
}
const utxos = []
// Populate array with 21 utxos
for (let i = 0; i < 21; i++) {
utxos.push(utxo)
}
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too long, max length is 20')
})
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816,
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546,
utxoType: 'token',
transactionType: 'send',
tokenId:
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
tokenTicker: 'TAP',
tokenName: 'Thoughts and Prayers',
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 0,
tokenType: 1,
tokenQty: 5,
isValid: true
}
])
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
})
}) })
/* /*
+31 -58
View File
@@ -135,11 +135,12 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
nock(`${process.env.RPC_BASEURL}`) nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes('/')) .post((uri) => uri.includes('/'))
.reply(200, { result: mockData.mockAddress }) .reply(200, { result: mockData.mockAddress })
} }
req.params.address = 'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd' req.params.address =
'bitcoincash:qpujxqra3jmdlzzapwmmt7uspr7q0c9ff5hzljcrnd'
const result = await validateAddress(req, res) const result = await validateAddress(req, res)
// console.log(`result: ${util.inspect(result)}`) // console.log(`result: ${util.inspect(result)}`)
@@ -255,7 +256,7 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
nock(`${process.env.RPC_BASEURL}`) nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes('/')) .post((uri) => uri.includes('/'))
.reply(200, { result: mockData.mockAddress }) .reply(200, { result: mockData.mockAddress })
} }
@@ -281,7 +282,7 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
nock(`${process.env.RPC_BASEURL}`) nock(`${process.env.RPC_BASEURL}`)
.post(uri => uri.includes('/')) .post((uri) => uri.includes('/'))
.times(2) .times(2)
.reply(200, { result: mockData.mockAddress }) .reply(200, { result: mockData.mockAddress })
} }
@@ -356,17 +357,11 @@ describe('#Util', () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
utilRouteInst.blockbook,
'balanceFromBlockbook'
)
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
utilRouteInst.blockbook,
'utxosFromBlockbook'
)
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
sandbox sandbox
@@ -389,51 +384,41 @@ describe('#Util', () => {
assert.equal(result, 'test-txid') assert.equal(result, 'test-txid')
}) })
}
it('should return balance if balance-only is true', async () => { it('should return balance if balance-only is true', async () => {
// Mock the RPC call for unit tests. // Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
utilRouteInst.blockbook,
'balanceFromBlockbook'
)
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
}
// Mock sendRawTransaction() so that the hex does not actually get broadcast // Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network. // to the network.
sandbox sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction') .stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid') .resolves('test-txid')
req.body = { req.body = {
wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt', wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt',
balanceOnly: true balanceOnly: true
} }
const result = await utilRouteInst.sweepWif(req, res) const result = await utilRouteInst.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`) // console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isNumber(result) assert.isNumber(result)
}) })
}
// Unit tests only // Unit tests only
if (process.env.TEST === 'unit') { if (process.env.TEST === 'unit') {
it('should generate transaction for valid BCH-only sweep', async () => { it('should generate transaction for valid BCH-only sweep', async () => {
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
utilRouteInst.blockbook,
'balanceFromBlockbook'
)
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
utilRouteInst.blockbook,
'utxosFromBlockbook'
)
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.
@@ -460,17 +445,11 @@ describe('#Util', () => {
it('should throw 422 error if no non-token UTXOs', async () => { it('should throw 422 error if no non-token UTXOs', async () => {
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
utilRouteInst.blockbook,
'balanceFromBlockbook'
)
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
utilRouteInst.blockbook,
'utxosFromBlockbook'
)
.resolves(mockData.mockUtxos) .resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.
@@ -502,17 +481,11 @@ describe('#Util', () => {
it('should detect and throw error for multiple token classes', async () => { it('should detect and throw error for multiple token classes', async () => {
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
utilRouteInst.blockbook,
'balanceFromBlockbook'
)
.resolves(mockData.mockBalance) .resolves(mockData.mockBalance)
sandbox sandbox
.stub( .stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
utilRouteInst.blockbook,
'utxosFromBlockbook'
)
.resolves(mockData.mockThreeUtxos) .resolves(mockData.mockThreeUtxos)
// Force token utxo to appear as regular BCH utxo. // Force token utxo to appear as regular BCH utxo.