Compare commits

..
22 Commits
Author SHA1 Message Date
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
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
19 changed files with 1381 additions and 174 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
+2 -2
View File
@@ -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)
+178 -153
View File
@@ -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
View File
@@ -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",
+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
+341 -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,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())
+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 {
+71 -1
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.
@@ -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
+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
+496 -2
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 {
+63 -1
View File
@@ -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')
})
})
}) })
+76 -1
View File
@@ -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
View File
@@ -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')
})
})
}) })
/* /*