mirror of
https://github.com/fullstack-cash/bch-api.git
synced 2026-09-22 09:12:05 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
198141b75a | ||
|
|
6f25ec4b68 | ||
|
|
cb416d26c0 | ||
|
|
376094c6c2 | ||
|
|
f566d44252 | ||
|
|
0e7b45214f | ||
|
|
58366b5442 | ||
|
|
f97e7016c5 | ||
|
|
f82c85712c | ||
|
|
5f18bc578b | ||
|
|
e72a6b50e4 | ||
|
|
66c99ae442 | ||
|
|
5e04125bc3 | ||
|
|
fd3fd43800 | ||
|
|
5184da7c26 | ||
|
|
595caffb51 | ||
|
|
6b00f0fd5b | ||
|
|
e4a427788f | ||
|
|
27a2825a43 | ||
|
|
3d453c335f | ||
|
|
63b8168c45 | ||
|
|
1e5dae56af |
@@ -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
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ Both bch-api and bch-js are part of the
|
|||||||
- [API Documentation](https://fullstack.cash/documentation)
|
- [API Documentation](https://fullstack.cash/documentation)
|
||||||
- [Example Code](https://fullstack.cash/examples)
|
- [Example Code](https://fullstack.cash/examples)
|
||||||
|
|
||||||
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
|
||||||
@@ -94,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)
|
||||||
|
|||||||
Generated
+178
-153
@@ -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,42 +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": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.20",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2381,9 +2390,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"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"
|
||||||
@@ -4424,11 +4433,22 @@
|
|||||||
"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": {
|
||||||
@@ -7551,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",
|
||||||
@@ -9042,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": {
|
||||||
|
|||||||
+2
-2
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+341
-3
@@ -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,6 +50,10 @@ 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)
|
||||||
@@ -324,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.
|
||||||
@@ -911,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())
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
+71
-1
@@ -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.
|
||||||
@@ -79,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.
|
||||||
@@ -1519,6 +1520,75 @@ 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'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Token Utxo Details
|
||||||
|
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||||
|
// console.log('details : ', details)
|
||||||
|
|
||||||
|
res.status(200)
|
||||||
|
return res.json({ slpUtxos: details })
|
||||||
|
} 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
|
||||||
|
|||||||
@@ -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/'
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+496
-2
@@ -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 {
|
||||||
|
|||||||
@@ -102,7 +102,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 +121,66 @@ describe('#slp', () => {
|
|||||||
assert.isNumber(result.outputs)
|
assert.isNumber(result.outputs)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#hydrateUtxos', () => {
|
||||||
|
it('should return utxo details', async () => {
|
||||||
|
const 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, 2)
|
||||||
|
|
||||||
|
// Test the non-slp UTXO.
|
||||||
|
assert.property(result.slpUtxos[0], 'txid')
|
||||||
|
assert.property(result.slpUtxos[0], 'vout')
|
||||||
|
assert.property(result.slpUtxos[0], 'value')
|
||||||
|
assert.property(result.slpUtxos[0], 'height')
|
||||||
|
assert.property(result.slpUtxos[0], 'confirmations')
|
||||||
|
assert.property(result.slpUtxos[0], 'satoshis')
|
||||||
|
assert.property(result.slpUtxos[0], 'isValid')
|
||||||
|
assert.equal(result.slpUtxos[0].isValid, false)
|
||||||
|
|
||||||
|
// Test the slp UTXO.
|
||||||
|
assert.property(result.slpUtxos[1], 'txid')
|
||||||
|
assert.property(result.slpUtxos[1], 'vout')
|
||||||
|
assert.property(result.slpUtxos[1], 'value')
|
||||||
|
assert.property(result.slpUtxos[1], 'height')
|
||||||
|
assert.property(result.slpUtxos[1], 'confirmations')
|
||||||
|
assert.property(result.slpUtxos[1], 'satoshis')
|
||||||
|
assert.property(result.slpUtxos[1], 'isValid')
|
||||||
|
assert.equal(result.slpUtxos[1].isValid, true)
|
||||||
|
assert.property(result.slpUtxos[1], 'transactionType')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenId')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenTicker')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenName')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenDocumentUrl')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenDocumentHash')
|
||||||
|
assert.property(result.slpUtxos[1], 'decimals')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenType')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenQty')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -39,9 +39,84 @@ const mempool = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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
|
mempool,
|
||||||
|
txDetails,
|
||||||
|
blockHeaders
|
||||||
}
|
}
|
||||||
|
|||||||
+142
@@ -888,6 +888,148 @@ 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 = [
|
||||||
|
{
|
||||||
|
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, 2)
|
||||||
|
|
||||||
|
// Test the non-slp UTXO.
|
||||||
|
assert.property(result.slpUtxos[0], 'txid')
|
||||||
|
assert.property(result.slpUtxos[0], 'vout')
|
||||||
|
assert.property(result.slpUtxos[0], 'value')
|
||||||
|
assert.property(result.slpUtxos[0], 'height')
|
||||||
|
assert.property(result.slpUtxos[0], 'confirmations')
|
||||||
|
assert.property(result.slpUtxos[0], 'satoshis')
|
||||||
|
assert.property(result.slpUtxos[0], 'isValid')
|
||||||
|
assert.equal(result.slpUtxos[0].isValid, false)
|
||||||
|
|
||||||
|
// Test the slp UTXO.
|
||||||
|
assert.property(result.slpUtxos[1], 'txid')
|
||||||
|
assert.property(result.slpUtxos[1], 'vout')
|
||||||
|
assert.property(result.slpUtxos[1], 'value')
|
||||||
|
assert.property(result.slpUtxos[1], 'height')
|
||||||
|
assert.property(result.slpUtxos[1], 'confirmations')
|
||||||
|
assert.property(result.slpUtxos[1], 'satoshis')
|
||||||
|
assert.property(result.slpUtxos[1], 'isValid')
|
||||||
|
assert.equal(result.slpUtxos[1].isValid, true)
|
||||||
|
assert.property(result.slpUtxos[1], 'transactionType')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenId')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenTicker')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenName')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenDocumentUrl')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenDocumentHash')
|
||||||
|
assert.property(result.slpUtxos[1], 'decimals')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenType')
|
||||||
|
assert.property(result.slpUtxos[1], 'tokenQty')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
Reference in New Issue
Block a user