Compare commits

...
126 Commits
Author SHA1 Message Date
Chris Troutner 84b44b299a Merge pull request #214 from Permissionless-Software-Foundation/ct-unstable
fix(UTXO.get()): Added detection of null-tagged UTXOs
2022-01-31 21:13:50 -08:00
Chris Troutner b6456df1f4 Fixing unit tests 2022-01-31 21:11:11 -08:00
Chris Troutner b1826c04cb fix(UTXO.get()): Added detection of null-tagged UTXOs 2022-01-31 20:57:36 -08:00
Chris Troutner 71755964c8 Merge pull request #213 from Permissionless-Software-Foundation/ct-unstable
Adding Util.chunk100()
2022-01-27 17:39:47 -08:00
Chris Troutner bd1c3b622d feat(chunk100): Adding chunk100 which works just like chunk20 2022-01-27 17:37:20 -08:00
Chris Troutner bdced13230 fix(Electrumx): Added commented code for debugging sorting 2022-01-27 08:52:12 -08:00
Chris Troutner c925bfce46 Merge pull request #212 from Permissionless-Software-Foundation/ct-unstable
Integration Test Maintenance
2022-01-23 07:11:32 -08:00
Chris Troutner 056a47f6b2 fix(integration tests): Got BCHN and ABC integration tests passing 2022-01-23 07:08:41 -08:00
Chris Troutner 51038681d3 fix(transaction.js): Added integrationt test 2022-01-23 06:46:06 -08:00
Chris Troutner 1a2f144baf Merge pull request #211 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Properly handling addresses with no SLP utxos
2022-01-19 14:48:29 -08:00
Chris Troutner 6d70568082 fix(Utxo.get()): Properly handling addresses with no SLP utxos 2022-01-19 14:46:00 -08:00
Chris Troutner 0b1cf57852 fix(integration): Updating integration tests 2022-01-19 12:20:08 -08:00
Chris Troutner 48f7f75c9c Merge pull request #210 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Hydrating SLP UTXOs with additional token data
2022-01-19 10:37:45 -08:00
Chris Troutner 0bc96fded5 fix(Utxo.get()): Hydrating SLP UTXOs with additional token data 2022-01-19 10:33:36 -08:00
Chris Troutner 9869211572 Merge pull request #209 from Permissionless-Software-Foundation/ct-unstable
Big changes
2022-01-18 11:53:27 -08:00
Chris Troutner 1056c3724c feat(Utxo.get()): Moving old function to getOld. New get() uses psf-slp-indexer 2022-01-18 11:41:52 -08:00
Chris Troutner 6b838ef9cd feat(Ninsight): Removing Ninsight libraries and tests 2022-01-18 08:45:20 -08:00
Chris Troutner f09573554d fix(PsfSlpIndexer.tx()): Added detection of blacklisted SLP Tokens 2022-01-18 08:32:45 -08:00
Chris Troutner 4a62ae64de Merge pull request #208 from Permissionless-Software-Foundation/ct-unstable
Replacing Transaction.get() with psf-slp-indexer
2022-01-17 19:21:49 -08:00
Chris Troutner 78de9e390e Merge branch 'master' into ct-unstable 2022-01-17 19:16:21 -08:00
Chris Troutner 7d8d0c2d69 Bumping major version to 5.0.0 2022-01-17 19:12:57 -08:00
Chris Troutner 656b0f6809 feat(Transaction.get()): Replacing call with proxy to psf-slp-indexer 2022-01-17 19:12:12 -08:00
Chris Troutner 48808aec14 Merge pull request #207 from Permissionless-Software-Foundation/ct-unstable
fix(slpdb): Removing slpdb from default integration tests
2022-01-17 15:10:26 -08:00
Chris Troutner d50f4a0d50 fix(slpdb): Removing slpdb from default integration tests 2022-01-17 15:07:06 -08:00
Chris Troutner f09f96d365 Merge pull request #206 from Permissionless-Software-Foundation/dh-slp-indexer
feat(indexer): Added PSF SLP Indexer to bch-js
2022-01-17 14:43:23 -08:00
Chris Troutner 7f4745f83c fix(psf-slp-indexer): Added integration tests 2022-01-17 14:39:52 -08:00
Chris Troutner dd5de1424c fix(PsfSlpIndexer.tx): Renaming txId() to tx() 2022-01-17 14:33:49 -08:00
Daniel Gonzalez 30604591bd feat(indexer): Added PSF SLP Indexer to bch-js 2022-01-17 13:37:29 -04:00
Chris Troutner aeb1efe23e Merge pull request #205 from Permissionless-Software-Foundation/ct-unstable
Adding eCash class library
2022-01-10 19:54:31 -08:00
Chris Troutner 6e43f795d7 feat(eCash): Adding eCash class library 2022-01-10 19:51:12 -08:00
Chris Troutner e07a5b9da0 Adding getBlockchainInfo() integration test 2022-01-10 14:48:43 -08:00
Chris Troutner 5ada76eace Merge branch 'master' into ct-unstable 2021-12-20 12:38:18 -08:00
Chris Troutner e605f6f386 Merge pull request #203 from Permissionless-Software-Foundation/temp
Temp
2021-12-15 05:47:56 -08:00
Chris Troutner eb9d0bea2b Merge pull request #202 from mr-zwets/documentation-toHash160
added documentation to function toHash160()
2021-12-15 05:45:17 -08:00
Chris Troutner f185f5de16 Updating integrat test scripts 2021-12-15 05:44:27 -08:00
Chris Troutner d795174916 Merge branch 'master' into documentation-toHash160 2021-12-15 05:39:47 -08:00
mathi 1294bc2149 added documentation toHash160() 2021-12-15 12:08:58 +01:00
Chris Troutner 8117448fd3 Merge pull request #201 from Permissionless-Software-Foundation/ct-unstable
fix(utxo.get()): Throwing error if address===undefined
2021-12-01 14:48:58 -08:00
Chris Troutner c7b51a38ff fix(utxo.get()): Throwing error if address===undefined 2021-12-01 14:45:39 -08:00
Chris Troutner 07d490134a Merge pull request #198 from Permissionless-Software-Foundation/salemkode
Salemkode
2021-11-30 06:38:15 -08:00
Chris Troutner a8a4eb68a6 fix(bchjs.Crypto.sha256): Updating docs 2021-11-30 06:35:19 -08:00
Chris Troutner c05e2f14e2 Merge pull request #197 from salemkode/master
edit docs of bchjs.Crypto.sha256
2021-11-30 06:34:09 -08:00
Salem Mohammed Shamakh 3540d98910 Update docs of bchjs.Crypto.sha256 2021-11-30 08:15:12 +03:00
Chris Troutner d0129c01f3 Merge pull request #196 from Permissionless-Software-Foundation/temp-documentation
Temp documentation
2021-11-13 06:46:59 -08:00
Chris Troutner ad40607e5b Merge pull request #186 from mr-zwets/documentation-fix
small fixes documentation
2021-11-13 06:43:55 -08:00
Chris Troutner a4f0ee6e42 Merge branch 'master' into documentation-fix 2021-11-13 06:42:57 -08:00
Chris Troutner 0b70ea108b Merge pull request #195 from Permissionless-Software-Foundation/temp-documentation
Temp documentation
2021-11-13 06:42:05 -08:00
Chris Troutner cef9a3cf52 Merge pull request #194 from mr-zwets/documentation
clarified some of the documentation
2021-11-13 06:38:04 -08:00
mathi c216f6c075 clarified some of the documentation 2021-11-13 11:37:56 +01:00
Chris Troutner b53f6bcfc6 Merge pull request #193 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Moving util.js integration tests to bchn specific folder
2021-11-10 18:16:45 -08:00
Chris Troutner cbc834cc1a fix(tests): Moving util.js integration tests to bchn specific folder 2021-11-10 17:32:17 -08:00
Chris Troutner 1cb5fe069b Merge pull request #192 from Permissionless-Software-Foundation/ct-unstable
Created Transaction.get3() to help with SLP indexer
2021-11-03 10:21:21 -07:00
Chris Troutner 425e2cbbc1 fix(Transaction.get3()): Created Transaction.get3() 2021-11-03 10:16:38 -07:00
Chris Troutner 9e92f77523 Making progress on Transaction.get3() 2021-11-03 07:53:56 -07:00
Chris Troutner dc85bb4cfe Making progress on Transaction.get3() 2021-11-03 07:53:42 -07:00
Chris Troutner 92960b6957 Merge branch 'master' into ct-unstable 2021-11-02 20:37:07 -07:00
Chris Troutner 6f34ea286a Merge pull request #191 from Permissionless-Software-Foundation/fix-bip32
fix(bip32-util): Moving to @chris.troutner/bip32-util
2021-11-01 06:21:49 -07:00
Chris Troutner 26f39023c4 fix(bip32-util): Moving to @chris.troutner/bip32-util 2021-11-01 06:18:55 -07:00
Chris Troutner 4a3baca72d Messing around with different variations of transaction.get2() 2021-11-01 06:12:14 -07:00
Chris Troutner fe37c3ca43 Merge pull request #190 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get2()): Improvement to vin token inputs
2021-10-31 18:56:09 -07:00
Chris Troutner ff4ffab670 fix(Transaction.get2()): Improvement to vin token inputs 2021-10-31 18:50:47 -07:00
Chris Troutner 400e7b31a8 Merge pull request #189 from Permissionless-Software-Foundation/ct-unstable
Adding Transaction.get2()
2021-10-31 17:02:14 -07:00
Chris Troutner 360fca0475 fix(Transaction.get2()): Added block height to tx data 2021-10-31 16:58:40 -07:00
Chris Troutner ef7056ee7a fix(Transaction.get2()): Added non-validating tx get 2021-10-31 16:29:50 -07:00
Chris Troutner d221077786 Merge pull request #188 from Permissionless-Software-Foundation/ct-unstable
fix(Jenkins): Attempting to fix CI/CD
2021-10-31 14:52:22 -07:00
Chris Troutner 046ab78c6a fix(Jenkins): Attempting to fix CI/CD 2021-10-31 14:44:07 -07:00
Chris Troutner 29e90915e7 Merge pull request #187 from Permissionless-Software-Foundation/ct-unstable
fix(bip-32): Updated bip-32 library to improve vulns
2021-10-31 14:31:52 -07:00
Chris Troutner 3d85dbfb22 Updating depds 2021-10-31 14:26:43 -07:00
Chris Troutner 2c0fa9dfbd Switched to exact dependency versions 2021-10-31 14:22:36 -07:00
Chris Troutner 6ca1d729dc fix(bip-32): Updated bip-32 library to improve vulns 2021-10-31 14:14:13 -07:00
Chris Troutner c095af8a29 Merge branch 'master' into documentation-fix 2021-10-30 09:07:46 -07:00
mathi 89e4ea1635 small fixes documentation 2021-10-30 17:06:46 +02:00
Chris Troutner 85a34a8305 Merge pull request #185 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Adding token ID to tx token inputs
2021-10-28 16:58:56 -07:00
Chris Troutner ef6cfe4409 fix(Transaction.get()): Adding token ID to tx token inputs 2021-10-28 16:56:35 -07:00
Chris Troutner a29d6ed26a Merge pull request #184 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Validating SLP inputs
2021-10-21 21:07:15 -07:00
Chris Troutner fa58a0764c fix(Transaction.get()): Validating SLP inputs 2021-10-21 21:04:11 -07:00
Chris Troutner 2c2fbeaa46 Merge pull request #183 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Fixing another bug in MINT input handling
2021-10-21 20:47:03 -07:00
Chris Troutner 1e4cd5f99e fix(Transaction.get()): Fixing another bug in MINT input handling 2021-10-21 20:44:47 -07:00
Chris Troutner 52c1ebdafe Merge pull request #182 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Fixing bug in previous PR
2021-10-21 19:45:36 -07:00
Chris Troutner 2e56d66f48 fix(Transaction.get()): Fixing bug in previous PR 2021-10-21 19:42:12 -07:00
Chris Troutner 9cf6c7e7a7 Merge pull request #181 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Adding support for MINT corner-case
2021-10-21 19:15:08 -07:00
Chris Troutner 43043672c6 linting 2021-10-21 19:12:10 -07:00
Chris Troutner 4f4b2c864d fix(Transaction.get()): Adding support for MINT corner-case 2021-10-21 19:11:55 -07:00
Chris Troutner 951fea8f9b Merge pull request #180 from Permissionless-Software-Foundation/ct-unstable
fix(npm): Ran npm audit fix, to fix vulns
2021-10-16 09:49:50 -07:00
Chris Troutner 6d75dceffb fix(npm): Ran npm audit fix, to fix vulns 2021-10-16 09:46:55 -07:00
Chris Troutner 3a491c7aba Merge pull request #178 from Permissionless-Software-Foundation/ct-unstable
fix(generateGenesisOpReturn): Fixing bug when 0 decimal
2021-10-14 13:08:31 -07:00
Chris Troutner f03c020e4f fix(generateGenesisOpReturn): Fixing bug when 0 decimal 2021-10-14 13:06:15 -07:00
Chris Troutner 4aeb1148e5 Merge pull request #177 from Permissionless-Software-Foundation/review01
added toEcashAddress() & ecashtoCashAddress() for ecash format support
2021-10-04 07:55:41 -07:00
Chris Troutner f03ee13a1e Merge pull request #175 from mr-zwets/master
conversion between bitcoincash: and ecash:
2021-10-04 06:55:48 -07:00
mr-zwets cb98773d6a Merge branch 'Permissionless-Software-Foundation:master' into master 2021-09-23 07:43:07 +02:00
Chris Troutner c593d68ee5 Merge pull request #176 from Permissionless-Software-Foundation/ct-unstable
Improvements to Transaction.get()
2021-09-22 17:36:30 -07:00
Chris Troutner dc7d3a9339 fix(Transaction.get): Using BigNumber for better tokenqty calc 2021-09-22 17:33:27 -07:00
Chris Troutner 6f4cc0f439 fix(Transaction.get): Fixed bug when Genesis TX was input to queried TX 2021-09-22 16:58:14 -07:00
Chris Troutner 7aa0fa865e fix(decodeOpReturn): Fixed api docs 2021-09-22 15:40:23 -07:00
mathi e8b6d7fb36 added toEcashAddress() & ecashtoCashAddress() for ecash format support 2021-09-22 14:38:54 +02:00
Chris Troutner b13ef94ea3 Merge pull request #174 from Permissionless-Software-Foundation/ct-unstable
fix(npm): Ran npm audit fix, 0 vulns
2021-09-20 16:07:18 -07:00
Chris Troutner 912f899f4e fix(npm): Ran npm audit fix, 0 vulns 2021-09-20 16:04:14 -07:00
Chris Troutner 9063d8da0b Merge pull request #173 from Permissionless-Software-Foundation/feat/ecash-price
Feat/ecash price
2021-09-20 16:00:32 -07:00
Chris Troutner f4e2bd96f0 fix(XEC price): Fixing BCHA XEC price feeds 2021-09-20 15:56:42 -07:00
Chris Troutner 1c898a4ad3 Removing ninsight from integration tests 2021-09-20 15:54:54 -07:00
Chris Troutner c4db813f76 Merge pull request #172 from mr-zwets/master
changed getBchaUsd which returns the price for the BCHA denomination, added getXecUsd for the XEC denomination
2021-09-20 15:33:30 -07:00
mathi 38b96b441f changed getBchaUsd so it returns the BCHA denomination, added getXecUsd 2021-09-20 22:13:24 +02:00
Chris Troutner c94f78f510 Merge pull request #170 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Fixed code that was breaking minimal-slp-wal…
2021-08-12 09:16:52 -07:00
Chris Troutner 6de2b712ec fix(minimal-slp-wallet): Fixed code that was breaking minimal-slp-wallet build 2021-08-12 09:14:29 -07:00
Chris Troutner 6f681fd7b0 Merge pull request #169 from Permissionless-Software-Foundation/ct-unstable
fix(npm): Ran npm audit fix. 0 vulns
2021-08-09 17:59:43 -07:00
Chris Troutner 9a82effb2b fix(npm): Ran npm audit fix. 0 vulns 2021-08-09 17:41:38 -07:00
Chris Troutner 955a5e428e Merge pull request #168 from Permissionless-Software-Foundation/ct-unstable
fix(getRawTransaction): Fixing integration test
2021-08-09 14:56:52 -07:00
Chris Troutner 44c3bf44f9 fix(getRawTransaction): Fixing integration test 2021-08-09 14:54:49 -07:00
Chris Troutner 31ce84abb0 Merge pull request #167 from Permissionless-Software-Foundation/ct-unstable
fix(waterfallValidateTxid): Skipping validateTxid3() if it throws an error
2021-08-09 10:39:18 -07:00
Chris Troutner 40fa4b6bc1 linting 2021-08-09 10:36:07 -07:00
Chris Troutner 8c5e111d60 fix(waterfallValidateTxid): Skipping validateTxid3() if it throws an error 2021-08-09 10:35:51 -07:00
Chris Troutner 8965dfc57f Merge pull request #166 from Permissionless-Software-Foundation/ct-unstable
fix(apiToken): Changed _this.axiosOptions to this.axiosOptions
2021-08-08 20:06:05 -07:00
Chris Troutner 93904a632f fix(apiToken): Changed _this.axiosOptions to this.axiosOptions 2021-08-08 20:01:38 -07:00
Chris Troutner 851d4c404c fix(rate limits): Fixing more bugs with rate limit handling axios.options 2021-08-08 19:51:01 -07:00
Chris Troutner 44bac45c32 Updating README 2021-08-08 18:42:04 -07:00
Chris Troutner 95ee8054be Merge pull request #165 from Permissionless-Software-Foundation/ct-unstable
fix(Transactions.get()): Adding extra error handling for rate limits
2021-08-08 18:40:43 -07:00
Chris Troutner 547b28cac8 fix(Transactions.get()): Adding extra error handling for rate limits 2021-08-08 18:38:46 -07:00
Chris Troutner 40ba334a41 Merge pull request #163 from Permissionless-Software-Foundation/ct-unstable
fix(validateTxid3): Throwing an error when rate limit encountered.
2021-08-08 18:06:52 -07:00
Chris Troutner ffd8ccaf2b fix(validateTxid3): Throwing an error when rate limit encountered. 2021-08-08 18:04:39 -07:00
Chris Troutner 9a16c0c6d6 Merge pull request #162 from Permissionless-Software-Foundation/ct-unstable
fix(raw-transaction): Fixing JWT token bug
2021-08-07 12:48:00 -07:00
Chris Troutner 52a891fd78 fix(raw-transaction): Fixing JWT token bug 2021-08-07 12:41:24 -07:00
Chris Troutner c09df08429 fix(npm): Ran npm audit fix 2021-07-16 15:56:36 -07:00
Chris Troutner 4dc88c0af3 Merge pull request #160 from Permissionless-Software-Foundation/ct-unstable
fix(transactions): Adding better error messages
2021-07-16 15:20:12 -07:00
Chris Troutner 85caa9556b fix(transactions): Adding better error messages 2021-07-16 15:18:35 -07:00
Chris Troutner bbdc417eb2 Merge pull request #159 from Permissionless-Software-Foundation/ct-unstable
Updating README
2021-07-12 19:57:47 -07:00
Chris Troutner cc3bc002e7 Updating README 2021-07-12 19:52:29 -07:00
62 changed files with 21512 additions and 8403 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
Copyright 2021 Chris Troutner
Copyright 2021 Permissionless Software Foundation contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+5 -7
View File
@@ -33,17 +33,13 @@ Here are two YouTube walk-through videos to help you get started:
```
const BCHJS = require("@psf/bch-js")
let bchjs = new BCHJS() // Defaults to BCHN network.
// testnet
bchjs = new BCHJS({ restURL: 'https://testnet3.fullstack.cash/v4/' })
```
This library is intended to be paired with
the [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API, and the infrastructure provided by [FullStack.cash](https://fullstack.cash). The `restURL` property can be changed to work with different Bitcoin Cash networks:
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v5/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v5/
- Check server status: https://metrics.fullstack.cash
### API Key
@@ -57,7 +53,7 @@ rate limits when interacting with the back end server. See [this article](https:
```
const BCHJS = require("@psf/bch-js")
let bchjs = new BCHJS({
restURL: 'https://bchn.fullstack.cash/v3/',
restURL: 'https://bchn.fullstack.cash/v5/',
apiToken: 'eyJhbGciO...' // Your JWT token here.
})
```
@@ -122,3 +118,5 @@ Copies of this repository are also published on [IPFS](https://ipfs.io).
## License
[MIT](LICENSE.md)
test
+16284 -5817
View File
File diff suppressed because it is too large Load Diff
+46 -46
View File
@@ -1,6 +1,6 @@
{
"name": "@psf/bch-js",
"version": "4.0.0",
"version": "5.0.0",
"description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
"author": "Chris Troutner <chris.troutner@gmail.com>",
"contributors": [
@@ -14,11 +14,12 @@
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
"test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/",
"test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
"test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
"test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
@@ -36,53 +37,52 @@
"url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
},
"dependencies": {
"@psf/bip21": "^2.0.1",
"@psf/bip32-utils": "^1.0.0",
"@psf/bitcoincash-ops": "^2.0.0",
"@psf/bitcoincashjs-lib": "^4.0.2",
"@psf/coininfo": "^4.0.0",
"@uppy/core": "^1.10.4",
"@uppy/tus": "^1.5.12",
"axios": "^0.21.1",
"bc-bip68": "^1.0.5",
"bchaddrjs-slp": "^0.2.5",
"bigi": "^1.4.2",
"bignumber.js": "^9.0.0",
"bip-schnorr": "^0.3.0",
"bip38": "^2.0.2",
"bip39": "^3.0.2",
"bip66": "^1.1.5",
"bitcoinjs-message": "^2.0.0",
"bs58": "^4.0.1",
"cashaddrjs": "^0.3.3",
"ini": "^1.3.8",
"randombytes": "^2.0.6",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"@psf/bip21": "2.0.1",
"@chris.troutner/bip32-utils": "1.0.5",
"@psf/bitcoincash-ops": "2.0.0",
"@psf/bitcoincashjs-lib": "4.0.2",
"@psf/coininfo": "4.0.0",
"@uppy/core": "1.10.4",
"@uppy/tus": "1.5.12",
"axios": "^0.21.4",
"bc-bip68": "1.0.5",
"bchaddrjs-slp": "0.2.5",
"bigi": "1.4.2",
"bignumber.js": "9.0.0",
"bip-schnorr": "0.3.0",
"bip38": "2.0.2",
"bip39": "3.0.2",
"bip66": "1.1.5",
"bitcoinjs-message": "2.0.0",
"bs58": "4.0.1",
"ecashaddrjs": "1.0.7",
"ini": "1.3.8",
"randombytes": "2.0.6",
"safe-buffer": "5.1.2",
"satoshi-bitcoin": "1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"wif": "^2.0.6"
"wif": "2.0.6"
},
"devDependencies": {
"apidoc": "^0.17.7",
"assert": "^2.0.0",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
"eslint-config-prettier": "^6.0.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-standard": "^4.0.0",
"husky": "^4.3.6",
"lodash.clonedeep": "^4.5.0",
"mocha": "^6.1.4",
"node-mocks-http": "^1.7.0",
"nyc": "^14.1.0",
"prettier": "^1.18.2",
"semantic-release": "^17.3.9",
"sinon": "^7.3.2",
"standard": "^16.0.3"
"apidoc": "0.17.7",
"assert": "2.0.0",
"chai": "4.1.2",
"coveralls": "3.0.2",
"eslint": "7.17.0",
"eslint-config-prettier": "7.1.0",
"eslint-config-standard": "16.0.3",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "^4.3.8",
"lodash.clonedeep": "4.5.0",
"mocha": "9.1.3",
"node-mocks-http": "1.7.0",
"nyc": "15.1.0",
"semantic-release": "18.0.0",
"sinon": "9.2.2",
"standard": "^16.0.4"
},
"apidoc": {
"title": "bch-js",
+92 -4
View File
@@ -1,6 +1,6 @@
// const axios = require("axios")
const Bitcoin = require('@psf/bitcoincashjs-lib')
const cashaddr = require('cashaddrjs')
const cashaddr = require('ecashaddrjs')
const coininfo = require('@psf/coininfo')
class Address {
@@ -129,6 +129,78 @@ class Address {
return cashAddress.split(':')[1]
}
/**
* @api Address.toEcashAddress() toEcashAddress()
* @apiName toEcashAddress
* @apiGroup Address
* @apiDescription Convert legacy to cashAddress format
*
* @apiExample Example usage:
* // mainnet
* bchjs.Address.toEcashAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2')
* // ecash:qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
*
* // mainnet no prefix
* bchjs.Address.toEcashAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2', false)
* // qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
*
*/
toEcashAddress (address, prefix = true) {
const decoded = this._decode(address)
const ecashAddress = cashaddr.encode(
'ecash',
decoded.type,
decoded.hash
)
if (prefix) return ecashAddress
return ecashAddress.split(':')[1]
}
/**
* @api Address.ecashtoCashAddress() ecashtoCashAddress()
* @apiName ecashtoCashAddress
* @apiGroup Address
* @apiDescription Convert legacy to cashAddress format
*
* @apiExample Example usage:
* // mainnet
* bchjs.Address.ecashtoCashAddress('ecash:qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da')
* // bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2
*
* // mainnet no prefix
* bchjs.Address.ecashtoCashAddress('ecash:qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da', false)
* // qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2
*/
ecashtoCashAddress (address, prefix = true) {
const decoded = this._decodeEcashAddress(address)
const cashAddress = cashaddr.encode(
'bitcoincash',
decoded.type,
decoded.hash
)
if (prefix) return cashAddress
return cashAddress.split(':')[1]
}
/**
* @api Address.toHash160() toHash160()
* @apiName toHash160
* @apiGroup Address
* @apiDescription Converts any address format to hash160
*
* @apiExample Example usage:
* // cash address mainnet p2pkh
* bchjs.Address.toHash160("bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh")
* // 573d93b475be4f1925f3b74ed951201b0147eac1
*
* // cash address mainnet p2sh
* bchjs.Address.toHash160("bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug")
* // 7dc85da64d1d93ef01ef62e0221c02f512e3942f
*/
// Converts any address format to hash160
toHash160 (address) {
const legacyAddress = this.toLegacyAddress(address)
@@ -166,7 +238,7 @@ class Address {
* @api Address.hash160ToCash() hash160ToCash()
* @apiName hash160ToCash
* @apiGroup Address
* @apiDescription Convert hash160 to cash address.
* @apiDescription Convert hash160 to cash address. Accepts either hexadecimal or buffer.
*
* @apiExample Example usage:
* bchjs.Address.hash160ToCash("573d93b475be4f1925f3b74ed951201b0147eac1")
@@ -271,6 +343,22 @@ class Address {
throw new Error(`Invalid format : ${address}`)
}
_decodeEcashAddress (address) {
if (address.indexOf(':') !== -1) {
const decoded = cashaddr.decode(address)
decoded.format = 'cashaddr'
return decoded
}
try {
const decoded = cashaddr.decode(`ecash:${address}`)
decoded.format = 'cashaddr'
return decoded
} catch (error) {}
throw new Error(`Invalid format : ${address}`)
}
/**
* @api Address.isLegacyAddress() isLegacyAddress()
* @apiName isLegacyAddress
@@ -721,13 +809,13 @@ class Address {
* @apiDescription Detect an addess from an OutputScript..
*
* @apiExample Example usage:
* const script = bchjs.Script.encode([
* const scriptBuffer = bchjs.Script.encode([
* Buffer.from("BOX", "ascii"),
* bchjs.Script.opcodes.OP_CAT,
* Buffer.from("BITBOX", "ascii"),
* bchjs.Script.opcodes.OP_EQUAL
* ]);
* const p2sh_hash160 = bchjs.Crypto.hash160(script);
* const p2sh_hash160 = bchjs.Crypto.hash160(scriptBuffer);
* const scriptPubKey = bchjs.Script.scriptHash.output.encode(p2sh_hash160);
*
* // mainnet address from output script
+5 -4
View File
@@ -33,10 +33,11 @@ const Encryption = require('./encryption')
const Utxo = require('./utxo')
const Transaction = require('./transaction')
const DSProof = require('./dsproof')
const Ecash = require('./ecash')
// Indexers
const Ninsight = require('./ninsight')
const Electrumx = require('./electrumx')
const PsfSlpIndexer = require('./psf-slp-indexer')
class BCHJS {
constructor (config) {
@@ -81,9 +82,6 @@ class BCHJS {
// console.log(`apiToken: ${this.apiToken}`)
// Bitcoin.com Ninsight indexer
this.Ninsight = new Ninsight(config)
// ElectrumX indexer
this.Electrumx = new Electrumx(libConfig)
@@ -118,6 +116,9 @@ class BCHJS {
this.Transaction = new Transaction(libConfig)
this.DSProof = new DSProof(libConfig)
this.eCash = new Ecash()
this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
}
}
+26 -26
View File
@@ -5,7 +5,7 @@
const axios = require('axios')
let _this
// let _this
class Blockchain {
constructor (config) {
@@ -29,7 +29,7 @@ class Blockchain {
}
}
_this = this
// _this = this
}
/**
@@ -54,7 +54,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getBestBlockHash`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -112,7 +112,7 @@ class Blockchain {
blockhash,
verbosity
},
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -162,7 +162,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getBlockchainInfo`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -193,7 +193,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getBlockCount`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -226,7 +226,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getBlockHash/${height}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -273,7 +273,7 @@ class Blockchain {
if (typeof hash === 'string') {
const response = await axios.get(
`${this.restURL}blockchain/getBlockHeader/${hash}?verbose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -287,7 +287,7 @@ class Blockchain {
hashes: hash,
verbose: verbose
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -334,7 +334,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getChainTips`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -366,7 +366,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getDifficulty`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -382,7 +382,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getMempoolAncestors/${txid}?verbose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -397,7 +397,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getMempoolDescendants/${txid}?verbose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -494,7 +494,7 @@ class Blockchain {
if (typeof txid === 'string') {
const response = await axios.get(
`${this.restURL}blockchain/getMempoolEntry/${txid}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -505,7 +505,7 @@ class Blockchain {
{
txids: txid
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -545,7 +545,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getMempoolInfo`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -592,7 +592,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/getRawMempool?vebose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -636,7 +636,7 @@ class Blockchain {
// Send the request to the REST API.
// const response = await axios.get(
// `${this.restURL}blockchain/getTxOut/${txid}/${n}?includeMempool=${includeMempool}`,
// _this.axiosOptions
// this.axiosOptions
// )
const response = await axios.post(
`${this.restURL}blockchain/getTxOut`,
@@ -645,7 +645,7 @@ class Blockchain {
vout: n,
mempool: includeMempool
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -698,7 +698,7 @@ class Blockchain {
const path = `${this.restURL}blockchain/getTxOutProof/${txids}`
// if (blockhash) path = `${path}?blockhash=${blockhash}`
const response = await axios.get(path, _this.axiosOptions)
const response = await axios.get(path, this.axiosOptions)
return response.data
// Array of txids.
@@ -709,7 +709,7 @@ class Blockchain {
{
txids: txids
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -726,7 +726,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/preciousBlock/${blockhash}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -739,7 +739,7 @@ class Blockchain {
try {
const response = await axios.post(
`${this.restURL}blockchain/pruneBlockchain/${height}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -752,7 +752,7 @@ class Blockchain {
try {
const response = await axios.get(
`${this.restURL}blockchain/verifyChain?checklevel=${checklevel}&nblocks=${nblocks}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -804,7 +804,7 @@ class Blockchain {
if (typeof proof === 'string') {
const response = await axios.get(
`${this.restURL}blockchain/verifyTxOutProof/${proof}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -816,7 +816,7 @@ class Blockchain {
{
proofs: proof
},
_this.axiosOptions
this.axiosOptions
)
return response.data
+4 -4
View File
@@ -4,7 +4,7 @@
const axios = require('axios')
let _this // Global reference to the instance of this class.
// let _this // Global reference to the instance of this class.
class Control {
constructor (config) {
@@ -28,7 +28,7 @@ class Control {
}
}
_this = this
// _this = this
}
/**
@@ -81,7 +81,7 @@ class Control {
try {
const response = await axios.get(
`${this.restURL}control/getNetworkInfo`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -94,7 +94,7 @@ class Control {
try {
const response = await axios.get(
`${this.restURL}control/getMemoryInfo`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
+3 -3
View File
@@ -22,7 +22,7 @@ class Crypto {
* // buffer from hex
* let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex')
* bchjs.Crypto.sha256(buffer)
* // <Buffer 26 b0 78 0a 68 3a 1e 09 8e 9c b8 cf a1 b0 92 42 28 25 00 97>
* // <Buffer 97 8c 09 dd 46 09 1d 19 22 fa 01 e9 f4 a9 75 b9 1a 37 1f 26 ba 83 99 de 27 d5 38 01 15 21 21 de>
*
* */
// Translate address from any address format into a specific format.
@@ -60,7 +60,7 @@ class Crypto {
* @api Crypto.hash256() hash256()
* @apiName hash256
* @apiGroup Crypto
* @apiDescription Utility for creating double sha256 hash digests of data.
* @apiDescription Utility for creating double sha256 hash digests of buffer encoded data.
*
* @apiExample Example usage:
* // buffer from hex
@@ -86,7 +86,7 @@ class Crypto {
* @api Crypto.hash160() hash160()
* @apiName hash160
* @apiGroup Crypto
* @apiDescription Utility for creating ripemd160(sha256()) hash digests of data.
* @apiDescription Utility for creating ripemd160(sha256()) hash digests of buffer encoded data.
*
* @apiExample Example usage:
* // buffer from hex
+1 -1
View File
@@ -68,7 +68,7 @@ class DSProof {
}
const response = await _this.axios.get(
`${this.restURL}dsproof/getdsproof/${txid}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
+47
View File
@@ -0,0 +1,47 @@
/*
Utility library for converting units with eCash.
*/
class eCash {
/**
* @api eCash.toSatoshi() toSatoshi()
* @apiName toSatoshi
* @apiGroup eCash
* @apiDescription
* Convert XEC units into satoshi units
*
* @apiExample Example usage:
* // convert 10,704.35 XEC to satoshis:
* bchjs.eCash.toSatoshi(10704.35)
* // 1070435
*/
toSatoshi (xec) {
if (typeof xec !== 'number') {
throw new Error('input must be a floating number representing XEC')
}
return Math.floor(xec * 100)
}
/**
* @api eCash.toXec() toXec()
* @apiName toXec
* @apiGroup eCash
* @apiDescription
* Convert satoshi units to XEC units
*
* @apiExample Example usage:
* // convert 1,070,435 satoshis to XEC:
* bchjs.eCash.toSatoshi(1070435)
* // 10704.35
*/
toXec (sats) {
if (typeof sats !== 'number') {
throw new Error('input must be a floating number representing satoshis')
}
return sats / 100
}
}
module.exports = eCash
+31 -18
View File
@@ -8,7 +8,7 @@ const axios = require('axios')
// Local libraries.
const Blockchain = require('./blockchain')
let _this
// let _this
class ElectrumX {
constructor (config) {
@@ -34,7 +34,7 @@ class ElectrumX {
this.blockchain = new Blockchain(config)
_this = this
// _this = this
}
/**
@@ -108,7 +108,7 @@ class ElectrumX {
if (typeof address === 'string') {
const response = await axios.get(
`${this.restURL}electrumx/utxos/${address}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -119,7 +119,7 @@ class ElectrumX {
{
addresses: address
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -142,7 +142,7 @@ class ElectrumX {
* (async () => {
* try {
* let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf');
* console.log(utxo);
* console.log(balance);
* } catch(error) {
* console.error(error)
* }
@@ -159,7 +159,7 @@ class ElectrumX {
* (async () => {
* try {
* let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']);
* console.log(utxo);
* console.log(balance);
* } catch(error) {
* console.error(error)
* }
@@ -192,7 +192,7 @@ class ElectrumX {
if (typeof address === 'string') {
const response = await axios.get(
`${this.restURL}electrumx/balance/${address}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -203,7 +203,7 @@ class ElectrumX {
{
addresses: address
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -290,7 +290,7 @@ class ElectrumX {
if (typeof address === 'string') {
const response = await axios.get(
`${this.restURL}electrumx/transactions/${address}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -302,7 +302,7 @@ class ElectrumX {
addresses: address,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -383,7 +383,7 @@ class ElectrumX {
if (typeof address === 'string') {
const response = await axios.get(
`${this.restURL}electrumx/unconfirmed/${address}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -394,7 +394,7 @@ class ElectrumX {
{
addresses: address
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -452,7 +452,7 @@ class ElectrumX {
try {
const response = await axios.get(
`${this.restURL}electrumx/block/headers/${height}?count=${count}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -543,7 +543,7 @@ class ElectrumX {
if (typeof txid === 'string') {
const response = await axios.get(
`${this.restURL}electrumx/tx/data/${txid}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} else if (Array.isArray(txid)) {
@@ -552,7 +552,7 @@ class ElectrumX {
{
txids: txid
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -592,7 +592,7 @@ class ElectrumX {
const response = await axios.post(
`${this.restURL}electrumx/tx/broadcast`,
{ txHex },
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -638,13 +638,24 @@ class ElectrumX {
// Sort confirmed Transactions by the block height
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
try {
// console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`)
// Filter out unconfirmed transactions, with a height of 0 or less.
txs = txs.filter(elem => elem.height > 0)
if (sortingOrder === 'DESCENDING') {
return txs.sort((a, b) => b.height - a.height)
// console.log('Sorting in descending order')
return txs.sort((a, b) => {
// console.log(`descending b.height: ${b.height}, a.height: ${a.height}`)
return b.height - a.height
})
}
return txs.sort((a, b) => a.height - b.height)
// console.log('Sorting in ascending order')
return txs.sort((a, b) => {
// console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`)
return a.height - b.height
})
} catch (err) {
console.log('Error in util.js/sortConfTxs()')
throw err
@@ -685,6 +696,8 @@ class ElectrumX {
// Substitute zero-conf txs with the current block-height + 1
async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
try {
// console.log(`sortingOrder: ${sortingOrder}`)
// Calculate the height of the next block
const nextBlock = (await this.blockchain.getBlockCount()) + 1
+4 -2
View File
@@ -35,11 +35,13 @@ class Encryption {
// Search the blockchain for a public key associated with a BCH address.
async getPubKey (addr) {
try {
if (!addr || typeof addr !== 'string') { throw new Error('Input must be a valid Bitcoin Cash address.') }
if (!addr || typeof addr !== 'string') {
throw new Error('Input must be a valid Bitcoin Cash address.')
}
const response = await _this.axios.get(
`${this.restURL}encryption/publickey/${addr}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
+3 -3
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
let _this
// let _this
class Generating {
constructor (config) {
@@ -24,14 +24,14 @@ class Generating {
}
}
_this = this
// _this = this
}
async generateToAddress (blocks, address, maxtries = 1000000) {
try {
const response = await axios.post(
`${this.restURL}generating/generateToAddress/${blocks}/${address}?maxtries=${maxtries}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
+4 -2
View File
@@ -1,6 +1,6 @@
const Bitcoin = require('@psf/bitcoincashjs-lib')
const coininfo = require('@psf/coininfo')
const bip32utils = require('@psf/bip32-utils')
const bip32utils = require('@chris.troutner/bip32-utils')
const bchaddrjs = require('bchaddrjs-slp')
class HDNode {
@@ -34,7 +34,9 @@ class HDNode {
*/
fromSeed (rootSeedBuffer, network = 'mainnet') {
let bitcoincash
if (network === 'bitcoincash' || network === 'mainnet') { bitcoincash = coininfo.bitcoincash.main } else bitcoincash = coininfo.bitcoincash.test
if (network === 'bitcoincash' || network === 'mainnet') {
bitcoincash = coininfo.bitcoincash.main
} else bitcoincash = coininfo.bitcoincash.test
const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS()
return Bitcoin.HDNode.fromSeedBuffer(
+6 -6
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
let _this
// let _this
class Mining {
constructor (config) {
@@ -24,14 +24,14 @@ class Mining {
}
}
_this = this
// _this = this
}
async getBlockTemplate (templateRequest) {
try {
const response = await axios.get(
`${this.restURL}mining/getBlockTemplate/${templateRequest}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -44,7 +44,7 @@ class Mining {
try {
const response = await axios.get(
`${this.restURL}mining/getMiningInfo`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -57,7 +57,7 @@ class Mining {
try {
const response = await axios.get(
`${this.restURL}mining/getNetworkHashps?nblocks=${nblocks}&height=${height}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
@@ -71,7 +71,7 @@ class Mining {
if (parameters) path = `${path}?parameters=${parameters}`
try {
const response = await axios.post(path, _this.axiosOptions)
const response = await axios.post(path, this.axiosOptions)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
-319
View File
@@ -1,319 +0,0 @@
/*
This library interacts with the ninsight REST API endpoints operated by
Bitcoin.com
*/
const axios = require('axios')
let _this
class Ninsight {
constructor (config) {
// this.restURL = config.restURL
// this.apiToken = config.apiToken
// this.ninsightURL = `https://bch-explorer.api.bitcoin.com/v1/`
if (config) {
this.ninsightURL = config.ninsightURL
? config.ninsightURL
: 'https://rest.bitcoin.com/v2'
}
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`,
timeout: 15000
}
}
_this = this
}
/**
* @api Ninsight.utxo() utxo()
* @apiName Ninsight Utxo
* @apiGroup Ninsight
* @apiDescription Return list of uxto for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.Ninsight.utxo('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
* // "vout": 1,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "height": 585570,
* // "confirmations": 10392
* // },
* // {
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
* // "vout": 0,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "height": 577125,
* // "confirmations": 18837
* // }
* // ]
*
*/
async utxo (address) {
try {
// Handle single address.
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/utxo`,
{
addresses: [address]
},
_this.axiosOptions
)
return response.data
// Handle array of addresses.
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/utxo`,
{
addresses: address
},
_this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.unconfirmed() unconfirmed()
* @apiName Ninsight Unconfirmed Utxo
* @apiGroup Ninsight
* @apiDescription Returns a list of unconfirmed UTXOs for an address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.Ninsight.unconfirmed('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
* // "vout": 1,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "confirmations": 0
* // "ts": 1559670801
* // },
* // {
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
* // "vout": 0,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "confirmations": 0
* // "ts": 1559670902
* // }
* // ]
*
*/
async unconfirmed (address) {
try {
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/unconfirmed`,
{
addresses: [address]
},
_this.axiosOptions
)
return response.data
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/unconfirmed`,
{
addresses: address
},
_this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.transactions() transactions()
* @apiName Ninsight TX History
* @apiGroup Ninsight
* @apiDescription Return transactions history for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txs = await bchjs.Ninsight.transactions('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(txs);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "pagesTotal": 1,
* // "txs": [
* // {
* // "txid": "ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b",
* // "version": 2,
* // "locktime": 0,
* // "vin": [
* // {
* // "txid": "4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0",
* // "vout": 0,
* // "sequence": 4294967295,
* // "n": 0,
* // ...
* // }
* // ...
* // ]
* // ...
* // }
* // ],
* // "legacyAddress": "1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP",
* // "cashAddress": "bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n",
* // "currentPage": 0
* // }
* // ]
* //
*
*/
async transactions (address) {
try {
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/transactions`,
{
addresses: [address]
},
_this.axiosOptions
)
return response.data
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/transactions`,
{
addresses: address
},
_this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.txDetails() txDetails()
* @apiName Ninsight TX Details
* @apiGroup Ninsight
* @apiDescription Return transactions details for address(es).
*
* @apiExample Example usage:
* (async () => {
* try {
* let result = await bchjs.Ninsight.txDetails('fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33');
* console.log(result);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33",
* // "version": 1,
* // "locktime": 0,
* // "vin": [
* // {
* // "sequence": 4294967295,
* // "n": 0
* // }
* // ],
* // "vout": [
* // ...
* // ],
* // "blockhash": "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09",
* // "blockheight": 1000,
* // "confirmations": 659909,
* // "time": 1232346882,
* // "blocktime": 1232346882,
* // "firstSeenTime": null,
* // "valueOut": 50,
* // "size": 135
* // }
* // ]
*
*/
async txDetails (txid) {
try {
if (typeof txid === 'string') {
const response = await axios.post(
`${this.ninsightURL}/transaction/details`,
{
txids: [txid]
},
_this.axiosOptions
)
return response.data
} else if (Array.isArray(txid)) {
const response = await axios.post(
`${this.ninsightURL}/transaction/details`,
{
txids: txid
},
_this.axiosOptions
)
return response.data
}
throw new Error('Transaction ID must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = Ninsight
+52 -13
View File
@@ -1,10 +1,10 @@
const axios = require('axios')
let _this
// let _this
class Price {
constructor (config) {
_this = this
// _this = this
this.restURL = config.restURL
this.apiToken = config.apiToken
@@ -58,7 +58,7 @@ class Price {
* let current = await bchjs.Price.getUsd();
* console.log(current);
* } catch(err) {
* console.err(err)
* console.error(err)
* }
*})()
*
@@ -68,7 +68,7 @@ class Price {
try {
const response = await this.axios.get(
`${this.restURL}price/usd`,
_this.axiosOptions
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
@@ -94,7 +94,7 @@ class Price {
* let current = await bchjs.Price.rates();
* console.log(current);
* } catch(err) {
* console.err(err)
* console.error(err)
* }
*})()
*
@@ -111,7 +111,7 @@ class Price {
try {
const response = await this.axios.get(
`${this.restURL}price/rates`,
_this.axiosOptions
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
@@ -127,8 +127,9 @@ class Price {
* @apiName Price getBchaUsd()
* @apiGroup Price
* @apiDescription Return current price of BCHA in USD.
* This endpoint gets the USD price of BCHA from the Coinex API. The price
* comes from bch-api, so it has a better chance of working in Tor.
* This endpoint gets the USD price of XEC from the Coinex API. The price
* denominated in BCHA comes from bch-api, so it has a better chance of
* working in Tor.
*
* @apiExample Example usage:
*(async () => {
@@ -136,17 +137,55 @@ class Price {
* let current = await bchjs.Price.getBchaUsd();
* console.log(current);
* } catch(err) {
* console.err(err)
* console.error(err)
* }
*})()
*
* // 18.81
* // 212.34
*/
async getBchaUsd () {
try {
const response = await this.axios.get(
`${this.restURL}price/bchausd`,
_this.axiosOptions
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
const bchaPrice = response.data.usd * 1000000
// Convert XEC denomination to BCHA denomination
return bchaPrice
} catch (err) {
if (err.response && err.response.data) throw err.response.data
else throw err
}
}
/**
* @api price.getXecUsd() getXecUsd()
* @apiName Price getXecUsd()
* @apiGroup Price
* @apiDescription Return current price of XEC in USD.
* This endpoint gets the USD price of XEC from the Coinex API. The price
* comes from bch-api, so it has a better chance of working in Tor.
*
* @apiExample Example usage:
*(async () => {
* try {
* let current = await bchjs.Price.getXecUsd();
* console.log(current);
* } catch(err) {
* console.error(err)
* }
*})()
*
* // 0.00021234
*/
async getXecUsd () {
try {
const response = await this.axios.get(
`${this.restURL}price/bchausd`,
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
@@ -171,7 +210,7 @@ class Price {
* let current = await bchjs.Price.getBchUsd();
* console.log(current);
* } catch(err) {
* console.err(err)
* console.error(err)
* }
*})()
*
@@ -181,7 +220,7 @@ class Price {
try {
const response = await this.axios.get(
`${this.restURL}price/bchusd`,
_this.axiosOptions
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
+371
View File
@@ -0,0 +1,371 @@
/*
This library interacts with the PSF slp indexer REST API endpoints operated
by FullStack.cash
TODO:
- detect TXs from tokens in the blacklist.
*/
// Public npm libraries
const axios = require('axios')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
// let _this
class PsfSlpIndexer {
constructor (config = {}) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.authToken = config.authToken
if (this.authToken) {
// Add Basic Authentication token to the authorization header.
this.axiosOptions = {
headers: {
authorization: this.authToken
}
}
} else {
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`
}
}
}
// Encapsulate dependencies
this.rawTransaction = new RawTransaction(config)
this.slpUtils = new SlpUtils(config)
// _this = this
}
/**
* @api PsfSlpIndexer.status() status()
* @apiName Status
* @apiGroup PSF SLP
* @apiDescription Return status from psf slp indexer.
*
* @apiExample Example usage:
* (async () => {
* try {
* let status = await bchjs.PsfSlpIndexer.status();
* console.log(status);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "status": {
* "startBlockHeight": 543376,
* "syncedBlockHeight": 723249,
* "chainBlockHeight": 722679
* }
* }
*
*/
async status () {
try {
const response = await axios.get(
`${this.restURL}psf/slp/status`,
this.axiosOptions
)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.balance() balance()
* @apiName SLP Balance
* @apiGroup PSF SLP
* @apiDescription Return slp balance for a single address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
* console.log(balance);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* balance: {
* utxos: [
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* vout: 1,
* type: 'token',
* qty: '1800',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
* }
* ],
* txs: [
* {
* txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
* height: 717796
* },
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* height: 717832
* }
* ],
* balances: [
* {
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* qty: '1800'
* }
* ]
* }
* }
*
*/
async balance (address) {
try {
// console.log('balance() address: ', address)
// Handle single address.
if (typeof address === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/address`,
{ address },
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tokenStats() tokenStats()
* @apiName Token Stats
* @apiGroup PSF SLP
* @apiDescription Return list stats for a single slp token.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
* console.log(tokenStats);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* tokenData: {
* type: 1,
* ticker: 'TROUT',
* name: "Trout's test token",
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* documentUri: 'troutsblog.com',
* documentHash: '',
* decimals: 2,
* mintBatonIsActive: true,
* tokensInCirculationBN: '100098953386',
* tokensInCirculationStr: '100098953386',
* blockCreated: 622414,
* totalBurned: '1046614',
* totalMinted: '100100000000'
* txs: [
* {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* height: 622414,
* type: 'GENESIS',
* qty: '100000000000'
* }
* ]
* }
* }
*
*/
async tokenStats (tokenId) {
try {
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/token`,
{ tokenId },
this.axiosOptions
)
return response.data
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tx() tx()
* @apiName SLP Transaction Data
* @apiGroup PSF SLP
* @apiDescription Return slp transaction data.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* txData: {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* version: 2,
* size: 339,
* locktime: 0,
* vin: [
* {
* txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
* vout: 0,
* scriptSig: {
* asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
* hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
* },
* sequence: 4294967295,
* address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
* value: 0.00051303,
* tokenQty: 0,
* tokenQtyStr: '0',
* tokenId: null
* }
* ],
* vout: [
* {
* value: 0,
* n: 0,
* scriptPubKey: {
* asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
* hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
* type: 'nulldata'
* },
* tokenQtyStr: '0',
* tokenQty: 0
* }
* ],
* hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
* blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
* confirmations: 97398,
* time: 1581773131,
* blocktime: 1581773131,
* blockheight: 622414,
* isSlpTx: true,
* tokenTxType: 'GENESIS',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* tokenType: 1,
* tokenTicker: 'TROUT',
* tokenName: "Trout's test token",
* tokenDecimals: 2,
* tokenUri: 'troutsblog.com',
* tokenDocHash: '',
* isValidSlp: true
* }
* }
*
*/
async tx (txid) {
try {
// Handle single address.
if (typeof txid === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/txid`,
{ txid },
this.axiosOptions
)
return response.data
}
throw new Error('Input txid must be a string.')
} catch (error) {
// console.log('error: ', error)
// Case: txid is not stored in the psf-slp-indexer tx database.
// Response: If it's not in the database, then it can be assumed the TX
// is not a token TX?
if (
error.response &&
error.response.data &&
error.response.data.error &&
error.response.data.error.includes('Key not found in database')
) {
// console.log(
// 'TX not found in psf-slp-indexer. Retrieving from full node.'
// )
// Check if this txid belongs to a blacklisted token.
const isInBlacklist = await this.checkBlacklist(txid)
// Get the TX Details from the full node.
const txDetails = await this.rawTransaction.getTxData(txid)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
if (isInBlacklist) {
txDetails.isValidSlp = null
} else {
txDetails.isValidSlp = false
}
const outObj = {
txData: txDetails
}
return outObj
} else throw error
}
}
// Check if the txid has an OP_RETURN containing a tokenID that is in the
// blacklist. In that case, the isValidSlp property should be marked as
// null, and not false.
async checkBlacklist (txid) {
try {
// TODO: Add endpoint to psf-slp-indexer to retrieve current blacklist.
// This should be done once at startup, and not each time this function
// is called.
const blacklist = [
'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9'
]
const outTokenData = await this.slpUtils.decodeOpReturn(txid)
// console.log('outTokenData: ', outTokenData)
// Loop through each token in the blacklist.
for (let i = 0; i < blacklist.length; i++) {
// If a match is found, return true.
if (outTokenData.tokenId === blacklist[i]) {
return true
}
}
// By default, return false.
return false
} catch (err) {
// console.log(err)
// Exit quietly.
return false
}
}
}
module.exports = PsfSlpIndexer
+42 -18
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
let _this
// let _this
class RawTransactions {
constructor (config) {
@@ -27,7 +27,7 @@ class RawTransactions {
// Encapsulate dependencies
this.axios = axios
_this = this
// this = this
}
/**
@@ -98,7 +98,7 @@ class RawTransactions {
if (typeof hex === 'string') {
const response = await axios.get(
`${this.restURL}rawtransactions/decodeRawTransaction/${hex}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -111,7 +111,7 @@ class RawTransactions {
data: {
hexes: hex
},
headers: _this.axiosOptions.headers
headers: this.axiosOptions.headers
}
const response = await axios(options)
@@ -166,7 +166,7 @@ class RawTransactions {
if (typeof script === 'string') {
const response = await axios.get(
`${this.restURL}rawtransactions/decodeScript/${script}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -177,7 +177,7 @@ class RawTransactions {
data: {
hexes: script
},
headers: _this.axiosOptions.headers
headers: this.axiosOptions.headers
}
const response = await axios(options)
@@ -259,9 +259,13 @@ class RawTransactions {
async getRawTransaction (txid, verbose = false, usrObj = null) {
try {
if (typeof txid === 'string') {
// console.log(
// 'getRawTransaction() this.axiosOptions: ',
// this.axiosOptions
// )
const response = await axios.get(
`${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -274,7 +278,7 @@ class RawTransactions {
verbose: verbose,
usrObj // pass user data when making an internal call.
},
headers: _this.axiosOptions.headers
headers: this.axiosOptions.headers
}
const response = await axios(options)
@@ -284,8 +288,13 @@ class RawTransactions {
throw new Error('Input must be a string or array of strings.')
} catch (error) {
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
// This case handles rate limit errors.
if (error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error)
} else if (error.response && error.response.data) {
throw error.response.data
} else throw error
}
}
@@ -310,6 +319,9 @@ class RawTransactions {
const inputTxid = vin.txid
const inputVout = vin.vout
// TODO: Coinbase TXs have no input transaction. Figure out how to
// handle this corner case.
// Get the TX details for the input, in order to retrieve the address of
// the sender.
const txDetailsParent = await this.getRawTransaction(inputTxid, true)
@@ -330,8 +342,13 @@ class RawTransactions {
return retArray
} catch (error) {
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
// This case handles rate limit errors.
if (error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error)
} else if (error.response && error.response.data) {
throw error.response.data
} else throw error
}
}
@@ -365,7 +382,9 @@ class RawTransactions {
async getTxData (txid) {
try {
if (typeof txid !== 'string') {
throw new Error('Input must be a string or array of strings.')
throw new Error(
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
)
}
// Get the TX details for the transaction under consideration.
@@ -384,8 +403,13 @@ class RawTransactions {
return txDetails
} catch (error) {
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
// This case handles rate limit errors.
if (error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error)
} else if (error.response && error.response.data) {
throw error.response.data
} else throw error
}
}
@@ -427,7 +451,7 @@ class RawTransactions {
if (typeof hex === 'string') {
const response = await this.axios.get(
`${this.restURL}rawtransactions/sendRawTransaction/${hex}`,
_this.axiosOptions
this.axiosOptions
)
if (response.data === '66: insufficient priority') {
@@ -448,9 +472,9 @@ class RawTransactions {
data: {
hexes: hex
},
headers: _this.axiosOptions.headers
headers: this.axiosOptions.headers
}
const response = await _this.axios(options)
const response = await this.axios(options)
return response.data
}
+1 -1
View File
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
* bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
* // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
*
* // tesnet legacy
* // testnet legacy
* bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
* // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
*
+4 -4
View File
@@ -17,7 +17,7 @@ const Address = require('./address')
// const BigNumber = require('bignumber.js')
const slpMdm = require('slp-mdm')
let _this
// let _this
// const addy = new Address()
let addy
const TransactionBuilder = require('../transaction-builder')
@@ -31,7 +31,7 @@ class Nft1 {
// Instantiate the transaction builder.
TransactionBuilder.setAddress(addy)
_this = this
// _this = this
}
/**
@@ -446,7 +446,7 @@ class Nft1 {
if (typeof groupId === 'string') {
const response = await axios.get(
`${this.restURL}slp/nftChildren/${groupId}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
}
@@ -498,7 +498,7 @@ class Nft1 {
if (typeof tokenId === 'string') {
const response = await axios.get(
`${this.restURL}slp/nftGroup/${tokenId}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
}
+18 -12
View File
@@ -125,13 +125,13 @@ class TokenType1 {
// console.log('baseChange: ', baseChange)
// Check for potential burns
const outputQty = new BigNumber(baseChange).plus(
new BigNumber(baseQty)
)
const outputQty = new BigNumber(baseChange).plus(new BigNumber(baseQty))
const inputQty = new BigNumber(totalTokens)
const tokenOutputDelta = outputQty.minus(inputQty).toString() !== '0'
if (tokenOutputDelta) {
throw new Error('Token transaction inputs do not match outputs, cannot send transaction')
throw new Error(
'Token transaction inputs do not match outputs, cannot send transaction'
)
}
// Generate the OP_RETURN as a Buffer.
@@ -152,7 +152,9 @@ class TokenType1 {
const tokenSingleOutputError =
noChangeOutputQty.minus(noChangeInputQty).toString() !== '0'
if (tokenSingleOutputError) {
throw new Error('Token transaction inputs do not match outputs, cannot send transaction')
throw new Error(
'Token transaction inputs do not match outputs, cannot send transaction'
)
}
// Generate the OP_RETURN as a Buffer.
@@ -285,9 +287,15 @@ class TokenType1 {
try {
// TODO: Add input validation.
let baseQty = new BigNumber(configObj.initialQty).times(
10 ** configObj.decimals
)
let baseQty
if (configObj.decimals !== 0) {
baseQty = new BigNumber(configObj.initialQty).times(
10 ** configObj.decimals
)
} else {
baseQty = new BigNumber(configObj.initialQty)
}
baseQty = baseQty.absoluteValue()
baseQty = Math.floor(baseQty)
baseQty = baseQty.toString()
@@ -378,9 +386,7 @@ class TokenType1 {
// Throw an error if the minting baton could not be found.
if (!mintBatonUtxo) {
throw new Error(
'Minting baton could not be found in tokenUtxos array.'
)
throw new Error('Minting baton could not be found in tokenUtxos array.')
}
const tokenId = mintBatonUtxo.tokenId
@@ -454,7 +460,7 @@ class TokenType1 {
const result = await _this.axios.post(
`${this.restURL}slp/generatesendopreturn`,
data,
_this.axiosOptions
this.axiosOptions
)
const slpSendObj = result.data
+39 -20
View File
@@ -11,7 +11,7 @@ const Util = require('../util')
let _this
class Utils {
constructor (config) {
constructor (config = {}) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.slpParser = slpParser
@@ -217,14 +217,14 @@ class Utils {
try {
let response
if (method === 'get') {
response = await _this.axios.get(path, _this.axiosOptions)
response = await _this.axios.get(path, this.axiosOptions)
} else {
response = await _this.axios.post(
path,
{
tokenIds: id
},
_this.axiosOptions
this.axiosOptions
)
}
return response.data
@@ -315,7 +315,7 @@ class Utils {
if (typeof address === 'string') {
const path = `${this.restURL}slp/balancesForAddress/${address}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
// Array of addresses.
@@ -328,7 +328,7 @@ class Utils {
{
addresses: address
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -380,7 +380,7 @@ class Utils {
try {
const path = `${this.restURL}slp/balancesForToken/${tokenId}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
@@ -450,13 +450,14 @@ class Utils {
else txids = txid
try {
// console.log('validateTxid() this.axiosOptions: ', this.axiosOptions)
const response = await _this.axios.post(
path,
{
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
this.axiosOptions
)
// console.log(
// `validateTxid response.data: ${JSON.stringify(response.data, null, 2)}`
@@ -520,7 +521,8 @@ class Utils {
const path = `${this.restURL}slp/validateTxid2/${txid}`
const response = await _this.axios.get(path, _this.axiosOptions)
// console.log('validateTxid2() this.axiosOptions: ', this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
@@ -589,7 +591,7 @@ class Utils {
// Retrieve the whitelist from the REST API if we haven't gotten it yet.
if (this.whitelist.length === 0) {
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
this.whitelist = response.data
@@ -666,13 +668,14 @@ class Utils {
else txids = txid
try {
// console.log('validateTxid3() this.axiosOptions: ', this.axiosOptions)
const response = await _this.axios.post(
path,
{
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
@@ -680,6 +683,14 @@ class Utils {
return validatedTxids
} catch (error) {
// console.log('validateTxid3 error: ', error)
// This case handles rate limit errors.
if (error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error)
}
// Not sure if this can be safely deprecated?
if (error.response && error.response.data) throw error.response.data
throw error
}
@@ -724,7 +735,7 @@ class Utils {
try {
const path = `${this.restURL}slp/tokenStats/${tokenId}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
@@ -795,7 +806,7 @@ class Utils {
try {
const path = `${this.restURL}slp/transactions/${tokenId}/${address}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
@@ -835,7 +846,7 @@ class Utils {
try {
const path = `${this.restURL}slp/burnTotal/${transactionId}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
@@ -879,7 +890,7 @@ class Utils {
// console.log(`this.restURL: ${this.restURL}`)
const path = `${this.restURL}slp/txDetails/${txid}`
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
@@ -910,7 +921,7 @@ class Utils {
* const txid =
* "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1"
*
* const data = await slp.Utils.decodeOpReturn2(txid)
* const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
*
* console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`)
* } catch (error) {
@@ -959,14 +970,15 @@ class Utils {
// CT: 2/24/21 Deprected GET in favor of POST, to pass IP address.
// Retrieve the transaction object from the full node.
const path = `${this.restURL}rawtransactions/getRawTransaction`
const response = await _this.axios.post(
// console.log('decodeOpReturn() this.axiosOptions: ', this.axiosOptions)
const response = await this.axios.post(
path,
{
verbose: true,
txids: [txid],
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
this.axiosOptions
)
const txDetails = response.data[0]
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
@@ -1639,6 +1651,11 @@ class Utils {
// could not be validated.
return isValid
} catch (error) {
// This case handles rate limit errors.
if (error.response && error.response.data && error.response.data.error) {
throw new Error(error.response.data.error)
}
// console.log('Error in waterfallValidateTxid()')
if (error.response && error.response.data) throw error.response.data
throw error
@@ -1836,11 +1853,13 @@ class Utils {
utxos: utxos,
usrObj
},
_this.axiosOptions
this.axiosOptions
)
return response.data
} catch (error) {
console.log('Error in hydrateUtxos(): ', error)
if (error.response && error.response.data) {
throw new Error(JSON.stringify(error.response.data, null, 2))
}
@@ -1874,7 +1893,7 @@ class Utils {
{
utxos: utxos
},
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -1914,7 +1933,7 @@ class Utils {
const path = `${this.restURL}slp/status`
try {
const response = await _this.axios.get(path, _this.axiosOptions)
const response = await _this.axios.get(path, this.axiosOptions)
// console.log(
// `getStatus response.data: ${JSON.stringify(response.data, null, 2)}`
// )
+404 -14
View File
@@ -2,14 +2,27 @@
High-level functions for working with Transactions
*/
// Global npm libraries
const BigNumber = require('bignumber.js')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
const Blockchain = require('./blockchain')
const PsfSlpIndexer = require('./psf-slp-indexer')
class Transaction {
constructor (config) {
constructor (config = {}) {
// Encapsulate dependencies
this.slpUtils = new SlpUtils(config)
this.rawTransaction = new RawTransaction(config)
this.blockchain = new Blockchain(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
}
// Proxy the call to the psf-slp-indexer.
async get (txid) {
return await this.psfSlpIndexer.tx(txid)
}
/**
@@ -34,10 +47,16 @@ class Transaction {
* }
* })()
*/
async get (txid) {
// CT 10/31/21: TODO: this function should be refactored to use get2(), but
// add waterfall validation of the TX and its inputs.
async getOld (txid) {
try {
if (typeof txid !== 'string') {
throw new Error('Input must be a string or array of strings.')
throw new Error(
'Input to Transaction.get() must be a string containing a TXID.'
)
}
const txDetails = await this.rawTransaction.getTxData(txid)
@@ -73,9 +92,18 @@ class Transaction {
// Add the token quantity to each output.
for (let i = 0; i < outTokenData.amounts.length; i++) {
const rawQty = outTokenData.amounts[i]
const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals)
// const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals)
txDetails.vout[i + 1].tokenQty = realQty
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(rawQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
txDetails.vout[i + 1].tokenQtyStr = realQty
txDetails.vout[i + 1].tokenQty = parseFloat(realQty)
}
// Add tokenQty = null to any outputs that don't have a value.
@@ -90,6 +118,7 @@ class Transaction {
// Loop through each input and retrieve the token data.
for (let i = 0; i < txDetails.vin.length; i++) {
const thisVin = txDetails.vin[i]
// console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
try {
// If decodeOpReturn() throws an error, then this input is not
@@ -99,18 +128,66 @@ class Transaction {
// `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}`
// )
// Get the appropriate vout token amount. This may throw an error,
// which means this Vin is not actually a token UTXO, it was just
// associated with a previous token TX.
const tokenQty = inTokenData.amounts[thisVin.vout - 1]
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
let tokenQty = 0
// Validate the input. Mark qty as null if not valid.
const vinIsValid = await this.slpUtils.waterfallValidateTxid(
thisVin.txid
)
if (!vinIsValid) {
// If the input is not a valid, then set qty as null.
tokenQty = null
} else if (inTokenData.txType === 'SEND') {
// Get the appropriate vout token amount. This may throw an error,
// which means this Vin is not actually a token UTXO, it was just
// associated with a previous token TX.
tokenQty = inTokenData.amounts[thisVin.vout - 1]
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
//
} else if (inTokenData.txType === 'GENESIS') {
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = inTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
}
} else if (inTokenData.txType === 'MINT') {
// vout=1 (second output) recieves the newly minted tokens.
if (thisVin.vout === 1) {
tokenQty = inTokenData.qty
} else {
tokenQty = null
}
//
} else {
console.log(
'Unexpected code path in Transaction.get(). What is the txType?'
)
console.log(inTokenData)
throw new Error('Unexpected code path')
}
if (tokenQty) {
const realQty =
Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
// const realQty =
// Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
thisVin.tokenQty = realQty
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
// txDetails.vin[i].tokenQty = tokenQty
// Add token ID to input
thisVin.tokenId = inTokenData.tokenId
} else {
thisVin.tokenQty = null
}
@@ -126,9 +203,17 @@ class Transaction {
// Finally, validate the SLP TX.
// this.slpUtils.waterfallValidateTxid(txid, usrObj)
txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid)
// TODO: Convert the block hash to a block height. Add block height
// value to the transaction.
} catch (err) {
// console.log('Error: ', err)
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
// If decoding the op_return fails, then it's not an SLP transaction,
// and the non-hyrated TX details can be returned.
return txDetails
@@ -136,12 +221,317 @@ class Transaction {
return txDetails
} catch (err) {
console.error('Error in transactions.js/get()')
// console.error('Error in transactions.js/get(): ', err)
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
if (err.error) throw new Error(err.error)
throw err
}
}
/**
* @api Transaction.get3() get3()
* @apiName get3
* @apiGroup Transaction
* @apiDescription
* Returns an object of transaction data, including addresses for input UTXOs.
* If it is a SLP token transaction, the token information for inputs and
* outputs will also be included.
*
* This is an API heavy call. This function will only work with a single txid.
* It does not yet support an array of TXIDs.
*
* This is the same as get(), except it omits DAG validation of the TXID.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.Transaction.get2("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*/
async get3 (txid) {
try {
if (typeof txid !== 'string') {
throw new Error(
'Input to Transaction.get() must be a string containing a TXID.'
)
}
// Get TX data
const txDetails = await this.rawTransaction.getTxData(txid)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// Get the block height the transaction was mined in.
const blockHeader = await this.blockchain.getBlockHeader(
txDetails.blockhash
)
txDetails.blockheight = blockHeader.height
// console.log(`blockHeader: ${JSON.stringify(blockHeader, null, 2)}`)
// Set default as not an SLP tx
txDetails.isSlpTx = false
// Get Token Data
const txTokenData = await this.getTokenInfo(txid)
// console.log(`txTokenData: ${JSON.stringify(txTokenData, null, 2)}`)
// If not a token, return the tx data. Processing is complete.
if (!txTokenData) return txDetails
// Mark TX as an SLP tx. This does not mean it's valid, it just means
// the OP_RETURN passes a basic check.
txDetails.isSlpTx = true
// Get Genesis data
const genesisData = await this.getTokenInfo(txTokenData.tokenId)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
// Add token information to the tx details object.
txDetails.tokenTxType = txTokenData.txType
txDetails.tokenId = txTokenData.tokenId
txDetails.tokenTicker = genesisData.ticker
txDetails.tokenName = genesisData.name
txDetails.tokenDecimals = genesisData.decimals
txDetails.tokenUri = genesisData.documentUri
txDetails.tokenDocHash = genesisData.documentHash
// console.log(`txDetails before processing input and outputs: ${JSON.stringify(txDetails, null, 2)}`)
// Process TX Outputs
// Add the token quantity to each output.
// 'i' starts at 1, because vout[0] is the OP_RETURN
for (let i = 0; i < txDetails.vout.length; i++) {
const thisVout = txDetails.vout[i]
if (txTokenData.txType === 'SEND') {
// console.log(
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
// )
// First output is OP_RETURN, so tokenQty is null.
if (i === 0) {
thisVout.tokenQty = null
thisVout.tokenQtyStr = null
continue
}
// Non SLP outputs.
if (i > txTokenData.amounts.length) {
thisVout.tokenQty = null
thisVout.tokenQtyStr = null
continue
}
const rawQty = txTokenData.amounts[i - 1]
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(rawQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
txDetails.vout[i].tokenQtyStr = realQty
txDetails.vout[i].tokenQty = parseFloat(realQty)
// console.log(
// `thisVout ${i}: ${JSON.stringify(txDetails.vout[i], null, 2)}`
// )
} else if (
txTokenData.txType === 'GENESIS' ||
txTokenData.txType === 'MINT'
) {
// console.log(
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis or Mint transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (i === 1) {
tokenQty = txTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVout.tokenQtyStr = realQty
thisVout.tokenQty = parseFloat(realQty)
// console.log(`thisVout[${i}]: ${JSON.stringify(thisVout, null, 2)}`)
} else if (i === txTokenData.mintBatonVout) {
// Optional Mint baton
thisVout.tokenQtyStr = '0'
thisVout.tokenQty = 0
thisVout.isMintBaton = true
} else {
thisVout.tokenQtyStr = '0'
thisVout.tokenQty = 0
}
} else {
throw new Error('Unknown SLP TX type for TX')
}
}
// Process TX inputs
for (let i = 0; i < txDetails.vin.length; i++) {
const thisVin = txDetails.vin[i]
// console.log(`thisVin[${i}]: ${JSON.stringify(thisVin, null, 2)}`)
const vinTokenData = await this.getTokenInfo(thisVin.txid)
// console.log(
// `vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
// Corner case: Ensure the token ID is the same.
const vinTokenIdIsTheSame = vinTokenData.tokenId === txDetails.tokenId
// If the input is not a token input, or if the tokenID is not the same,
// then mark the token output as null.
if (!vinTokenData || !vinTokenIdIsTheSame) {
thisVin.tokenQty = 0
thisVin.tokenQtyStr = '0'
thisVin.tokenId = null
continue
}
if (vinTokenData.txType === 'SEND') {
// console.log(
// `SEND vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
const tokenQty = vinTokenData.amounts[thisVin.vout - 1]
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (vinTokenData.txType === 'MINT') {
// console.log(
// `MINT vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = vinTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
// Optional Mint baton
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = vinTokenData.tokenId
thisVin.isMintBaton = true
} else {
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = null
}
} else if (vinTokenData.txType === 'GENESIS') {
// console.log(
// `GENESIS vinTokenData ${i}: ${JSON.stringify(
// vinTokenData,
// null,
// 2
// )}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = vinTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
// Optional Mint baton
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = vinTokenData.tokenId
thisVin.isMintBaton = true
} else {
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = null
}
} else {
console.log(
`Unknown vinTokenData: ${JSON.stringify(vinTokenData, null, 2)}`
)
throw new Error('Unknown token type in input')
}
}
return txDetails
} catch (err) {
console.error('Error in get3()')
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
if (err.error) throw new Error(err.error)
throw err
}
}
// A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx.
// Returns the token data if the txid is an SLP tx.
async getTokenInfo (txid) {
try {
const tokenData = await this.slpUtils.decodeOpReturn(txid)
return tokenData
} catch (err) {
return false
}
}
}
module.exports = Transaction
+53 -3
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
let _this
// let _this
class Util {
constructor (config) {
@@ -24,7 +24,7 @@ class Util {
}
}
_this = this
// _this = this
}
/**
@@ -150,6 +150,56 @@ class Util {
}
}
/**
* @api Util.chunk100() chunk100()
* @apiName chunk100
* @apiGroup Util
* @apiDescription chunk up an array into multiple arrays of 100 elements each.
* Input: arrayToSlice - a one-dimensional array of elements.
* Returns a two-dimensional array. An array of 100-element arrays.
*
* @apiExample Example usage:
* (async () => {
* try {
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,...,148, 149, 150]
*
* const chunked = bchjs.Util.chunk20(bigArray)
* console.log(chunked)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* [
* [0,1,2,3,4,5,6,7,8,9,10,11,...,98,99],
* [100,101,102,...,148,149,150]
* ]
*/
chunk100 (arrayToSlice) {
try {
// Validate inputs
if (!Array.isArray(arrayToSlice)) {
throw new Error('input must be an array')
}
let offset = 0
const result = []
// Loop over the array and slice off chunks of 100 elements.
while (offset < arrayToSlice.length) {
const chunk = arrayToSlice.slice(offset, offset + 100)
result.push(chunk)
offset = offset + 100
}
return result
} catch (err) {
console.error('Error in chunk100()')
throw err
}
}
/**
* @api Util.sleep() sleep()
* @apiName sleep
@@ -225,7 +275,7 @@ class Util {
if (typeof address === 'string') {
const response = await axios.get(
`${this.restURL}util/validateAddress/${address}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
+233 -6
View File
@@ -8,19 +8,23 @@
// Local libraries
const Electrumx = require('./electrumx')
const Slp = require('./slp/slp')
const PsfSlpIndexer = require('./psf-slp-indexer')
const BigNumber = require('bignumber.js')
class UTXO {
constructor (config) {
constructor (config = {}) {
// Encapsulate dependencies for easier mocking.
this.electrumx = new Electrumx(config)
this.slp = new Slp(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
this.BigNumber = BigNumber
}
/**
* @api Utxo.get() get()
* @apiName get
* @api Utxo.getOld() getOld()
* @apiName getOld
* @apiGroup UTXO
* @apiDescription Get UTXOs for an address
* @apiDescription Get UTXOs for an address (from SLPDB)
*
* Given an address, this function will return an object with thre following
* properties:
@@ -45,7 +49,7 @@ class UTXO {
* @apiExample Example usage:
* (async () => {
* try {
* let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
* let utxos = await bchjs.Utxo.getOld('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
* console.log(utxos);
* } catch(error) {
* console.error(error)
@@ -181,8 +185,12 @@ class UTXO {
*
*
*/
async get (address, useWhitelist = false) {
async getOld (address, useWhitelist = false) {
try {
if (!address) {
throw new Error('Address must be an array or a string')
}
// Convert address to an array if it is a string.
if (typeof address === 'string') address = [address]
@@ -282,6 +290,225 @@ class UTXO {
}
}
/**
* @api Utxo.get() get()
* @apiName get
* @apiGroup UTXO
* @apiDescription Get UTXOs for an address (from psf-slp-indexer)
*
* Given an address, this function will return an object with thre following
* properties:
* - address: "" - the address these UTXOs are associated with
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
* not spent, to be safe.
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
* - type1: {}
* - tokens: [] - SLP token Type 1 tokens.
* - mintBatons: [] - SLP token Type 1 mint batons.
* - nft: {}
* - tokens: [] - NFT tokens
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
* console.log(utxos);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* [
* {
* "address": "bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv",
* "bchUtxos": [
* {
* "height": 674513,
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "tx_pos": 3,
* "value": 38134,
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "vout": 3,
* "isValid": false
* }
* ],
*/
// This version of get() uses the psf-slp-indexer. It will replace the older
// get() function that uses SLPDB.
// TODO: NFT UTXOs are identified as non-token UTXOs, which will cause a wallet
// to burn them. The psf-slp-indexer needs to be updated to mark these UTXOs.
async get (address) {
try {
// Convert address to an array if it is a string.
if (typeof address !== 'string') {
throw new Error('address input must be a string')
}
// Ensure the address is a BCH address.
const addr = this.slp.Address.toCashAddress(address)
// Get the UTXOs associated with the address.
const utxoData = await this.electrumx.utxo(addr)
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
const utxos = utxoData.utxos
let slpUtxos = []
// Get SLP UTXOs from the psf-slp-indexer
try {
const slpUtxoData = await this.psfSlpIndexer.balance(addr)
// console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
slpUtxos = slpUtxoData.balance.utxos
} catch (err) {
// console.log('err: ', err)
// Exit quietly if address has no SLP UTXOs. Otherwise, throw the error.
if (err.error && !err.error.includes('Key not found in database')) {
throw err
}
}
// Loop through the Fulcrum UTXOs.
for (let i = 0; i < utxos.length; i++) {
const thisUtxo = utxos[i]
// Loop through the UTXOs from psf-slp-indexer.
for (let j = 0; j < slpUtxos.length; j++) {
const thisSlpUtxo = slpUtxos[j]
// If the non-hydrated UTXO matches the SLP UTXO, then combine the data
// and mark the UTXO as an SLP token.
if (
thisUtxo.tx_hash === thisSlpUtxo.txid &&
thisUtxo.tx_pos === thisSlpUtxo.vout
) {
thisUtxo.txid = thisUtxo.tx_hash
thisUtxo.vout = thisUtxo.tx_pos
thisUtxo.isSlp = true
thisUtxo.type = thisSlpUtxo.type
thisUtxo.qty = thisSlpUtxo.qty
thisUtxo.tokenId = thisSlpUtxo.tokenId
thisUtxo.address = thisSlpUtxo.address
break
}
}
// If there was no match, then this is a normal BCH UTXO. Mark it as such.
if (!thisUtxo.isSlp) {
thisUtxo.txid = thisUtxo.tx_hash
thisUtxo.vout = thisUtxo.tx_pos
thisUtxo.address = addr
// Check the transaction to see if its a 'null' token, ignored by
// the indexer.
const txData = await this.psfSlpIndexer.tx(thisUtxo.tx_hash)
// console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
if (txData.txData.isValidSlp === null) {
thisUtxo.isSlp = null
} else {
thisUtxo.isSlp = false
}
// console.log(`thisUtxo.isSlp: ${thisUtxo.isSlp}`)
}
}
// Get token UTXOs
let type1TokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token'
)
// Hydrate the UTXOs with additional token data.
type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
const bchUtxos = utxos.filter(x => x.isSlp === false)
const type1BatonUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'baton'
)
const nullUtxos = utxos.filter(x => x.isSlp === null)
const outObj = {
address: addr,
bchUtxos,
slpUtxos: {
type1: {
tokens: type1TokenUtxos,
mintBatons: type1BatonUtxos
},
nft: {} // Allocated for future support of NFT spec.
},
nullUtxos
}
return outObj
} catch (err) {
console.error('Error in bchjs.Utxo.get(): ', err)
if (err.error) throw new Error(err.error)
throw err
}
}
// Hydrate an array of token UTXOs with token information.
// Returns an array of token UTXOs with additional data.
async hydrateTokenData (utxoAry) {
try {
// console.log('utxoAry: ', utxoAry)
// Create a list of token IDs without duplicates.
let tokenIds = utxoAry.map(x => x.tokenId)
// Remove duplicates. https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array
tokenIds = [...new Set(tokenIds)]
// console.log('tokenIds: ', tokenIds)
// Get Genesis data for each tokenId
const genesisData = []
for (let i = 0; i < tokenIds.length; i++) {
const thisTokenId = tokenIds[i]
const thisTokenData = await this.psfSlpIndexer.tokenStats(thisTokenId)
// console.log('thisTokenData: ', thisTokenData)
genesisData.push(thisTokenData)
}
// console.log('genesisData: ', genesisData)
// Hydrate each token UTXO with data from the genesis transaction.
for (let i = 0; i < utxoAry.length; i++) {
const thisUtxo = utxoAry[i]
// Get the genesis data for this token.
const genData = genesisData.filter(
x => x.tokenData.tokenId === thisUtxo.tokenId
)
// console.log('genData: ', genData)
thisUtxo.ticker = genData[0].tokenData.ticker
thisUtxo.name = genData[0].tokenData.name
thisUtxo.documentUri = genData[0].tokenData.documentUri
thisUtxo.documentHash = genData[0].tokenData.documentHash
thisUtxo.decimals = genData[0].tokenData.decimals
// Calculate the real token quantity
const qty = new BigNumber(thisUtxo.qty).dividedBy(
10 ** parseInt(thisUtxo.decimals)
)
thisUtxo.qtyStr = qty.toString()
}
return utxoAry
} catch (err) {
console.log('Error in hydrateTokenData()')
throw err
}
}
/**
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
* @apiName findBiggestUtxo
+9 -1
View File
@@ -22,7 +22,7 @@ util.inspect.defaultOptions = {
describe('#blockchain', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#getBestBlockHash', () => {
@@ -313,6 +313,14 @@ describe('#blockchain', () => {
assert.isString(result)
})
})
describe('#getBlockchainInfo', () => {
it('should get info about the blockchain', async () => {
const result = await bchjs.Blockchain.getBlockchainInfo()
console.log(`blockchain info: ${JSON.stringify(result, null, 2)}`)
})
})
})
function sleep (ms) {
@@ -20,7 +20,7 @@ util.inspect.defaultOptions = {
describe('#rawtransaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
/*
+1 -1
View File
@@ -25,7 +25,7 @@ describe('#SLP', () => {
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
bchjs = new BCHJS({ restURL: process.env.RESTURL })
})
@@ -2,18 +2,19 @@
Integration tests for the utxo.js library.
*/
const assert = require('chai').assert
// const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS()
describe('#UTXO', () => {
beforeEach(async () => {
// sandbox = sinon.createSandbox()
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
/*
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
@@ -31,6 +32,7 @@ describe('#UTXO', () => {
assert.isArray(result[0].nullUtxos)
})
})
*/
})
function sleep (ms) {
@@ -0,0 +1,121 @@
/*
Integration tests for the psf-slp-indexer.js library
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
let bchjs
describe('#psf-slp-indexer', () => {
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
bchjs = new BCHJS()
})
describe('#status', () => {
it('should return the status of the indexer.', async () => {
const result = await bchjs.PsfSlpIndexer.status()
// console.log('result: ', result)
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
})
describe('#balance', () => {
it('should get balance data for an address.', async () => {
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log('result: ', result)
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
})
})
describe('#tokenStats', () => {
it('should get stats on a token', async () => {
const tokenId =
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log('result: ', result)
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'txs')
assert.property(result.tokenData, 'totalBurned')
})
})
describe('#tx', () => {
it('should get hydrated tx data for an SLP transaction', async () => {
const txid =
'947ccb2a0d62ca287bc4b0993874ab0f9f6afd454193e631e2bf84dca66731fc'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, true)
})
it('should mark non-SLP transaction as false', async () => {
const txid =
'03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, false)
})
// FlexUSD transactions
// Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means
// the wallet will burn them. There is a TODO in the code. This test will
// need to be changed when it is done.
it('should mark blacklisted token as null', async () => {
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, null)
})
it('should throw error for non-existent txid', async () => {
try {
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85e'
await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(err.message, 'No such mempool or blockchain transaction')
}
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+241 -237
View File
@@ -30,265 +30,269 @@ describe('#SLP', () => {
bchjs = new BCHJS()
})
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
if (process.env.TESTSLP) {
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
})
})
})
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
// Malformed SLP tx
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
// Normal TX (non-SLP)
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
})
})
// Valid PSF SLP tx
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
// Valid SLP token not in whitelist
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
assert.isArray(result)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId =
'68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId =
'45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
}
})
// Promise-based sleep function
@@ -0,0 +1,28 @@
/*
Integration tests for the transaction.js library.
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
describe('#Transaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#get', () => {
it('should get a tx details for a non-SLP TX with an OP_RETURN', async () => {
const txid =
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
const result = await bchjs.Transaction.get(txid)
// console.log('result: ', result)
assert.property(result.txData, 'txid')
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.equal(result.txData.isValidSlp, false)
})
})
})
@@ -5,7 +5,7 @@
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../src/bch-js')
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// Inspect utility used for debugging.
@@ -18,7 +18,7 @@ util.inspect.defaultOptions = {
describe('#util', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#validateAddress', () => {
@@ -81,7 +81,8 @@ describe('#util', () => {
it('should throw error on array size rate limit', async () => {
try {
const dataMock = 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z'
const dataMock =
'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z'
const data = []
for (let i = 0; i < 25; i++) data.push(dataMock)
+191 -80
View File
@@ -5,7 +5,8 @@
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// const bchjs = new BCHJS()
const bchjs = new BCHJS({ restURL: 'http://192.168.2.129:3000/v5/' })
describe('#UTXO', () => {
beforeEach(async () => {
@@ -14,108 +15,218 @@ describe('#UTXO', () => {
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
})
if (process.env.TESTSLP) {
describe('#getOld', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
it('should handle an array of addresses', async () => {
const addr = [
'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9',
'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
]
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
it('should handle NFTs and minting batons', async () => {
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
assert.isArray(result[0].slpUtxos.type1.mintBatons)
assert.isArray(result[0].slpUtxos.type1.tokens)
assert.isArray(result[0].slpUtxos.nft.groupMintBatons)
assert.isArray(result[0].slpUtxos.nft.groupTokens)
assert.isArray(result[0].slpUtxos.nft.tokens)
})
it('should use the whitelist when flag is set', async () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const useWhitelist = true
const result = await bchjs.Utxo.getOld(addr, useWhitelist)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
// Most token UTXOs should end up in the nullUtxos array.
assert.isAbove(result[0].bchUtxos.length, 0)
assert.isAbove(result[0].nullUtxos.length, 1)
})
})
describe('#findBiggestUtxo', () => {
it('should sort UTXOs from Electrumx', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
// console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
})
it('should sort UTXOs from Utxos.get()', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
const utxos = await bchjs.Utxo.getOld(addr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
})
})
}
describe('#hydrateTokenData', () => {
it('should hydrate token UTXOs', async () => {
const utxos = [
{
txid:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
type: 'token',
qty: '10000000',
tokenId:
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid:
'4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
vout: 1,
type: 'token',
qty: '100000000',
tokenId:
'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid:
'42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
vout: 5,
type: 'token',
qty: '4764',
tokenId:
'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid:
'06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
vout: 5,
type: 'token',
qty: '238',
tokenId:
'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
const result = await bchjs.Utxo.hydrateTokenData(utxos)
// console.log('result: ', result)
assert.property(result[0], 'ticker')
assert.property(result[0], 'name')
assert.property(result[0], 'qtyStr')
assert.property(result[0], 'documentUri')
assert.property(result[0], 'documentHash')
})
})
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
it('should hydrate address with BCH and SLP UTXOs', async () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
// Assert expected properties exist.
assert.property(result, 'address')
assert.property(result, 'bchUtxos')
assert.property(result, 'slpUtxos')
assert.property(result.slpUtxos, 'type1')
assert.property(result.slpUtxos, 'nft')
assert.property(result, 'nullUtxos')
assert.isAbove(result.bchUtxos.length, 0)
assert.isAbove(result.slpUtxos.type1.tokens.length, 0)
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
})
it('should handle an array of addresses', async () => {
const addr = [
'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9',
'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
]
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
it('should handle NFTs and minting batons', async () => {
// TODO: NFTs are currently not identified as different than normal BCH UTXOs.
// The psf-slp-indexer needs to be updated to fix this issue.
it('should handle minting batons', async () => {
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
assert.isArray(result[0].slpUtxos.type1.mintBatons)
assert.isArray(result[0].slpUtxos.type1.tokens)
assert.isArray(result[0].slpUtxos.nft.groupMintBatons)
assert.isArray(result[0].slpUtxos.nft.groupTokens)
assert.isArray(result[0].slpUtxos.nft.tokens)
// Assert that minting batons are correctly identified.
assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0)
})
it('should use the whitelist when flag is set', async () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const useWhitelist = true
it('should return UTXOs for address with no SLP tokens', async () => {
const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
const result = await bchjs.Utxo.get(addr, useWhitelist)
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
// Most token UTXOs should end up in the nullUtxos array.
assert.isAbove(result[0].bchUtxos.length, 0)
assert.isAbove(result[0].nullUtxos.length, 1)
})
})
describe('#findBiggestUtxo', () => {
it('should sort UTXOs from Electrumx', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
// console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
assert.isAbove(result.bchUtxos.length, 0)
assert.equal(result.slpUtxos.type1.tokens.length, 0)
})
it('should sort UTXOs from Utxos.get()', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
it('should handle Group NFTs', async () => {
const addr = 'bitcoincash:qrnghwrfgccf3s5e9wnglzxegcnhje9rkcwv2eka33'
const utxos = await bchjs.Utxo.get(addr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
assert.isAbove(result.nullUtxos.length, 0)
})
})
})
@@ -29,7 +29,7 @@ util.inspect.defaultOptions = {
describe('#blockchain', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#getBestBlockHash', () => {
+1 -1
View File
@@ -14,7 +14,7 @@ const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
describe('#control', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#getNetworkInfo', () => {
+1 -1
View File
@@ -16,7 +16,7 @@ describe('#ElectrumX', () => {
beforeEach(async () => {
sandbox = sinon.createSandbox()
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
afterEach(() => sandbox.restore())
-149
View File
@@ -1,149 +0,0 @@
// const chai = require('chai')
// const assert = chai.assert
const sinon = require('sinon')
// const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ ninsightURL: 'https://trest.bitcoin.com/v2' })
describe('#Ninsight', () => {
let sandbox
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
// describe('#utxo', () => {
// it('should GET utxos for a single address', async () => {
// const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
//
// const result = await bchjs.Ninsight.utxo(addr)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.property(result[0], 'utxos')
// assert.property(result[0], 'legacyAddress')
// assert.property(result[0], 'cashAddress')
// assert.property(result[0], 'slpAddress')
// assert.property(result[0], 'scriptPubKey')
// assert.property(result[0], 'asm')
//
// assert.isArray(result[0].utxos)
//
// assert.property(result[0].utxos[0], 'txid')
// assert.property(result[0].utxos[0], 'vout')
// assert.property(result[0].utxos[0], 'amount')
// assert.property(result[0].utxos[0], 'satoshis')
// assert.property(result[0].utxos[0], 'height')
// assert.property(result[0].utxos[0], 'confirmations')
// })
//
// it('should POST utxo details for an array of addresses', async () => {
// const addr = [
// 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
// 'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
// ]
//
// const result = await bchjs.Ninsight.utxo(addr)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isArray(result)
// assert.isArray(result[0].utxos)
//
// assert.property(result[0], 'utxos')
// assert.property(result[0], 'legacyAddress')
// assert.property(result[0], 'cashAddress')
// assert.property(result[0], 'slpAddress')
// assert.property(result[0], 'scriptPubKey')
// assert.property(result[0], 'asm')
//
// // assert.hasAnyKeys(result[0][0], [
// // "txid",
// // "vout",
// // "value",
// // "height",
// // "confirmations"
// // ])
// })
// })
//
// describe('#transactions', () => {
// it('should POST transactions history for a single address', async () => {
// const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
//
// const result = await bchjs.Ninsight.transactions(addr)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isArray(result)
// assert.property(result[0], 'cashAddress')
// assert.property(result[0], 'legacyAddress')
// assert.property(result[0], 'txs')
// assert.isArray(result[0].txs)
// assert.property(result[0].txs[0], 'txid')
// assert.property(result[0].txs[0], 'vin')
// assert.property(result[0].txs[0], 'vout')
// })
// it('should POST transactions history for an array of addresses', async () => {
// const addr = [
// 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
// 'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
// ]
//
// const result = await bchjs.Ninsight.transactions(addr)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isArray(result)
// assert.property(result[0], 'cashAddress')
// assert.property(result[0], 'legacyAddress')
// assert.property(result[0], 'txs')
// assert.isArray(result[0].txs)
// assert.property(result[0].txs[0], 'txid')
// assert.property(result[0].txs[0], 'vin')
// assert.property(result[0].txs[0], 'vout')
// })
// })
//
// describe('#txDetails', () => {
// it('should POST transactions details for a single address', async () => {
// const txid =
// '76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
//
// const result = await bchjs.Ninsight.txDetails(txid)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isArray(result)
// assert.property(result[0], 'txid')
// assert.property(result[0], 'version')
// assert.property(result[0], 'locktime')
// assert.property(result[0], 'vin')
// assert.property(result[0], 'vout')
// assert.property(result[0], 'blockhash')
// assert.property(result[0], 'blockheight')
// assert.property(result[0], 'confirmations')
// assert.property(result[0], 'time')
// assert.property(result[0], 'blocktime')
// assert.property(result[0], 'valueOut')
// assert.property(result[0], 'size')
// })
// it('should POST transactions details for an array of addresses', async () => {
// const txid = [
// 'f191d1062789d7071da6f2168ba306869a829294f6b1c09d03492db4ca3a8e77',
// '76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
// ]
//
// const result = await bchjs.Ninsight.txDetails(txid)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.isArray(result)
// assert.property(result[0], 'txid')
// assert.property(result[0], 'vin')
// assert.property(result[0], 'vout')
// })
// })
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@@ -27,7 +27,7 @@ util.inspect.defaultOptions = {
describe('#rawtransaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#decodeRawTransaction', () => {
+1 -1
View File
@@ -25,7 +25,7 @@ util.inspect.defaultOptions = {
describe('#SLP', () => {
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#util', () => {
+1 -1
View File
@@ -25,7 +25,7 @@ util.inspect.defaultOptions = {
describe('#util', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#validateAddress', () => {
+1 -1
View File
@@ -9,7 +9,7 @@ const bchjs = new BCHJS()
describe('#control', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#getNetworkInfo', () => {
+1 -1
View File
@@ -11,7 +11,7 @@ describe('#ElectrumX', () => {
beforeEach(async () => {
sandbox = sinon.createSandbox()
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
afterEach(() => sandbox.restore())
+1 -1
View File
@@ -5,7 +5,7 @@ const bchjs = new BCHJS()
describe('#Encryption', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#getPubKey', () => {
-138
View File
@@ -1,138 +0,0 @@
const chai = require('chai')
const assert = chai.assert
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS({ ninsightURL: 'https://rest.bitcoin.com/v2' })
describe('#Ninsight', () => {
let sandbox
beforeEach(async () => {
sandbox = sinon.createSandbox()
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
})
afterEach(() => sandbox.restore())
describe('#utxo', () => {
it('should GET utxos for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
assert.property(result[0], 'asm')
assert.isArray(result[0].utxos)
assert.property(result[0].utxos[0], 'txid')
assert.property(result[0].utxos[0], 'vout')
assert.property(result[0].utxos[0], 'amount')
assert.property(result[0].utxos[0], 'satoshis')
assert.property(result[0].utxos[0], 'height')
assert.property(result[0].utxos[0], 'confirmations')
})
it('should POST utxo details for an array of addresses', async () => {
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.isArray(result[0].utxos)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
assert.property(result[0], 'asm')
})
})
describe('#transactions', () => {
it('should POST transactions history for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
assert.property(result[0].txs[0], 'vin')
assert.property(result[0].txs[0], 'vout')
})
it('should POST transactions history for an array of addresses', async () => {
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
assert.property(result[0].txs[0], 'vin')
assert.property(result[0].txs[0], 'vout')
})
})
describe('#txDetails', () => {
it('should POST transactions details for a single address', async () => {
const txid = 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'version')
assert.property(result[0], 'locktime')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
assert.property(result[0], 'blockhash')
assert.property(result[0], 'blockheight')
assert.property(result[0], 'confirmations')
assert.property(result[0], 'time')
assert.property(result[0], 'blocktime')
assert.property(result[0], 'valueOut')
assert.property(result[0], 'size')
})
it('should POST transactions details for an array of addresses', async () => {
const txid = [
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
]
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+1 -1
View File
@@ -4,7 +4,7 @@ const bchjs = new BCHJS()
describe('#price', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#current', () => {
+2 -3
View File
@@ -19,7 +19,7 @@ util.inspect.defaultOptions = {
describe('#rawtransaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#decodeRawTransaction', () => {
@@ -191,8 +191,7 @@ describe('#rawtransaction', () => {
console.log(`result: ${util.inspect(result)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.hasAnyKeys(err, ['error'])
assert.include(err.error, 'Array too large')
assert.include(err.message, 'Array too large')
}
})
})
+921 -915
View File
File diff suppressed because it is too large Load Diff
+66 -42
View File
@@ -11,50 +11,74 @@ describe('#Transaction', () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#get', () => {
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
if (process.env.TESTSLP) {
describe('#getOld', () => {
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, false)
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, false)
})
it('should get details about a SLP transaction', async () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
})
// it('should get problematic transaction', async () => {
// const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
//
// const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// })
// TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
// an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
// that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
// but for separate tokens.
it('should get problematic transaction', async () => {
const txid =
'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// The token ID should equal the txid for this Vin.
assert.equal(result.vin[2].txid, result.vin[2].tokenId)
})
})
it('should get details about a SLP transaction', async () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
})
})
}
})
+56
View File
@@ -0,0 +1,56 @@
/*
Unit tests for eCash library.
*/
// Global npm libraries
const assert = require('chai').assert
const Ecash = require('../../src/ecash')
const uut = new Ecash()
describe('#eCash', () => {
describe('#toSatoshi', () => {
it('should convert XEC to satoshis', () => {
const xec = 10704.35
const result = uut.toSatoshi(xec)
assert.equal(result, 1070435)
})
it('should throw an error if input is not a number', () => {
try {
uut.toSatoshi('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing XEC'
)
}
})
})
describe('#toXec', () => {
it('should convert satoshis to XEC', () => {
const sats = 1070435
const result = uut.toXec(sats)
assert.equal(result, 10704.35)
})
it('should throw an error if input is not a number', () => {
try {
uut.toXec('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing satoshis'
)
}
})
})
})
-170
View File
@@ -1,170 +0,0 @@
/*
Mocking data for unit tests for the Ninsight library.
*/
const utxo = {
utxos: [
{
txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
vout: 0,
amount: 0.00001,
satoshis: 1000,
height: 602405,
confirmations: 36459
}
],
legacyAddress: '15NCRBJsHaJy8As5bX1oh2YauRejnZ1MKF',
cashAddress: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9',
slpAddress: 'simpleledger:qqh793x9au6ehvh7r2zflzguanlme760wuwzunhjfm',
scriptPubKey: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac',
asm:
'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG'
}
const unconfirmed = {
utxos: [
{
txid: '3904ffe6f8fba4ceda5e887130f60fcb18bdc7dcee10392a57f89475c5c108f1',
vout: 0,
amount: 0.03608203,
satoshis: 3608203,
confirmations: 0,
ts: 1559670801
}
],
legacyAddress: '1AyWs8U4HUnTLmxxFiGoJbsXauRsvBrcKW',
cashAddress: 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
slpAddress: 'simpleledger:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rkuk8mdxlf2',
scriptPubKey: '76a9146d695af951760b837c8e58b045b098f9c17e23b788ac'
}
const utxoPost = [utxo, utxo]
const unconfirmedPost = [unconfirmed, unconfirmed]
const transactions = {
pagesTotal: 1,
txs: [
{
txid: 'ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b',
version: 2,
locktime: 0,
vin: [
{
txid:
'4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0',
vout: 0,
sequence: 4294967295,
n: 0,
scriptSig: {
hex:
'483045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
asm:
'3045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
},
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
valueSat: 1111,
value: 0.00001111,
doubleSpentTxID: null
},
{
txid:
'126d62c299e7e14c66fe0b485d13082c23641f003690462046bc24ad2d1180c1',
vout: 0,
sequence: 4294967295,
n: 1,
scriptSig: {
hex:
'47304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
asm:
'304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
},
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
valueSat: 1000,
value: 0.00001,
doubleSpentTxID: null
}
],
vout: [
{
value: '0.00001736',
n: 0,
scriptPubKey: {
hex: '76a914d96ac75ca8df9729d278da50ccd7355c5785444e88ac',
asm:
'OP_DUP OP_HASH160 d96ac75ca8df9729d278da50ccd7355c5785444e OP_EQUALVERIFY OP_CHECKSIG',
addresses: ['1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP'],
type: 'pubkeyhash'
},
spentTxId: null,
spentIndex: null,
spentHeight: null
}
],
blockheight: -1,
confirmations: 0,
time: 1559673337,
valueOut: 0.00001736,
size: 339,
valueIn: 0.00002111,
fees: 0.00000375
}
],
legacyAddress: '1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP',
cashAddress: 'bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n',
currentPage: 0
}
const transactionsPost = [transactions, transactions]
const details = {
txid: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
version: 1,
locktime: 0,
vin: [
{
coinbase: '04ffff001d02fd04',
sequence: 4294967295,
n: 0
}
],
vout: [
{
value: '50.00000000',
n: 0,
scriptPubKey: {
hex:
'4104f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446aac',
asm:
'04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a OP_CHECKSIG',
addresses: ['1BW18n7MfpU35q4MTBSk8pse3XzQF8XvzT'],
type: 'pubkeyhash',
cashAddrs: ['bitcoincash:qpej6mkrwca4tvy2snq4crhrf88v4ljspysx0ueetk']
},
spentTxId: null,
spentIndex: null,
spentHeight: null
}
],
blockhash:
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
blockheight: 1000,
confirmations: 585610,
time: 1232346882,
blocktime: 1232346882,
isCoinBase: true,
valueOut: 50,
size: 135
}
const detailsPost = [details, details]
module.exports = {
utxo,
utxoPost,
unconfirmed,
unconfirmedPost,
transactions,
transactionsPost,
details,
detailsPost
}
+153
View File
@@ -0,0 +1,153 @@
/*
Mocking data for unit tests for the PSF SLP INDEXER library.
*/
'use strict'
const tokenStats = {
tokenData: {
type: 1,
ticker: 'TP03',
name: 'Test Plugin 03',
tokenId: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
documentUri: 'fullstack.cash',
documentHash: '',
decimals: 0,
mintBatonIsActive: false,
tokensInCirculationBN: '1',
tokensInCirculationStr: '1',
blockCreated: 722420,
totalBurned: '0',
totalMinted: '1',
txs: [
{
txid:
'13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
height: 722420,
type: 'GENESIS',
qty: '1'
}
]
}
}
const txData = {
txData: {
txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
version: 2,
size: 339,
locktime: 0,
vin: [
{
txid:
'8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
vout: 0,
scriptSig: {
asm:
'304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
hex:
'47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
},
sequence: 4294967295,
address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
value: 0.00051303,
tokenQty: 0,
tokenQtyStr: '0',
tokenId: null
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
hex:
'6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
type: 'nulldata'
},
tokenQtyStr: '0',
tokenQty: 0
}
],
hex:
'0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
blockhash:
'0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
confirmations: 97398,
time: 1581773131,
blocktime: 1581773131,
blockheight: 622414,
isSlpTx: true,
tokenTxType: 'GENESIS',
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
tokenType: 1,
tokenTicker: 'TROUT',
tokenName: "Trout's test token",
tokenDecimals: 2,
tokenUri: 'troutsblog.com',
tokenDocHash: '',
isValidSlp: true
}
}
const balance = {
balance: {
utxos: [
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
vout: 1,
type: 'token',
qty: '1800',
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
}
],
txs: [
{
txid:
'078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
height: 717796
},
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
height: 717832
}
],
balances: [
{
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: '1800'
}
]
}
}
const status = {
status: {
startBlockHeight: 543376,
syncedBlockHeight: 722860,
chainBlockHeight: 722679
}
}
const tokenData01 = {
tokenType: 1,
txType: 'MINT',
tokenId: 'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9',
mintBatonVout: 2,
qty: {}
}
module.exports = {
tokenStats,
txData,
balance,
status,
tokenData01
}
+612 -1
View File
@@ -175,10 +175,621 @@ const mockOpReturnData03 = {
amounts: ['1000000000', '99883400000000']
}
const genesisTestInputTx = {
txid: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e',
hash: '874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e',
version: 1,
size: 480,
locktime: 543408,
vin: [
{
txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35',
vout: 1,
scriptSig: {
asm:
'30440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f55[ALL|FORKID] 028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11',
hex:
'4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11'
},
sequence: 4294967294,
address: 'bitcoincash:qp2jesd06k8ycj4wvkpl9lcwaemtr04f5yphjsa07v',
value: 0.00000546
},
{
txid: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35',
vout: 3,
scriptSig: {
asm:
'3045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519[ALL|FORKID] 02cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc',
hex:
'483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bc'
},
sequence: 4294967294,
address: 'bitcoincash:qppj3euc36x5u6twr5cxrrea2rca53vsfu3dxwr86j',
value: 0.00172192
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 1145980243 323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35 00000000004c4b40 00000000004c4b40',
hex:
'6a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b40',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 0a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e0 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qq98fnuup7eldhtzc067ang76mss29pguqh7qv9eac']
}
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn']
}
},
{
value: 0.00171165,
n: 3,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 d4548261e1be0de7e50b7511597799ec4af2b173 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914d4548261e1be0de7e50b7511597799ec4af2b17388ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qr29fqnpuxlqmel9pd63zkthn8ky4u43wv0v7pg5mn']
}
}
],
hex:
'0100000002359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32010000006a4730440220268dacee1117975d904dd0d45ef8de42b86030d825a9522bae196a38bbf6b271022001ae1ce2536ab300040e597bcfaa8ef9fb2beaf702d0842f3161aae8e9867f554121028ff9e32b0dbc82c1d5e0fc945b2537b00420513b10684726f312f1b717c0ae11feffffff359dab0d9c8ddc788b14c0b51493d195c9fbd9f2203d091663350bae351e3a32030000006b483045022100fa241bb2de46f68688451bfcae3f165b724e3ccf13b219e7bf2d8d2df7712ad60220353017d6e581a06efce478adfcd2047cea2f92531e283845f3d0a345ef101519412102cc48ad10516f97e914b8836ff25448d07ad96ebb4704c6a828339880280831bcfeffffff040000000000000000406a04534c500001010453454e4420323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d350800000000004c4b400800000000004c4b4022020000000000001976a9140a74cf9c0fb3f6dd62c3f5eecd1ed6e1051428e088ac22020000000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388ac9d9c0200000000001976a914d4548261e1be0de7e50b7511597799ec4af2b17388acb04a0800',
blockhash: '000000000000000000292a9c6150fce48e2edd8df346948494fe6249e6e7f63b',
confirmations: 163095,
time: 1534271330,
blocktime: 1534271330
}
const mintTestInputTx = {
txid: '4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa',
hash: '4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa',
version: 1,
size: 585,
locktime: 543613,
vin: [
{
txid: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
vout: 1,
scriptSig: {
asm:
'3045022100e3d5f9a48f9aa1cf7e9ed1992e0281e7300b734ba0e4bd5bb9265b2be60bd71002200ccde53ce7ea9da9df3e834f234f3bea65c2e58c96f8436f93333fd8946a1db2[ALL|FORKID] 02056220984cc2cf5261a27d4f66d31c9ef601a688ca1a5ab81e55b6f0d311be74',
hex:
'483045022100e3d5f9a48f9aa1cf7e9ed1992e0281e7300b734ba0e4bd5bb9265b2be60bd71002200ccde53ce7ea9da9df3e834f234f3bea65c2e58c96f8436f93333fd8946a1db2412102056220984cc2cf5261a27d4f66d31c9ef601a688ca1a5ab81e55b6f0d311be74'
},
sequence: 4294967294,
address: 'bitcoincash:qpaf9wltgmpjlg2vxwwu7zdw5y4z7m277ckxn8cufl',
value: 0.00000546,
tokenQtyStr: '43545.34534',
tokenQty: 43545.34534
},
{
txid: 'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34',
vout: 1,
scriptSig: {
asm:
'3045022100bb90d52be9b568f643fdb1a302e9f063be27a9f914e2a6b192cbabf2cbdeaf9302203c275fc8d1b82390bc1726390d361c5266056bddd54dec23efa207c79eacea4a[ALL|FORKID] 024146cfcd0c02e99d6451dc48ad0f97114aeda18fe55c52a10bfc0490f314e6a2',
hex:
'483045022100bb90d52be9b568f643fdb1a302e9f063be27a9f914e2a6b192cbabf2cbdeaf9302203c275fc8d1b82390bc1726390d361c5266056bddd54dec23efa207c79eacea4a4121024146cfcd0c02e99d6451dc48ad0f97114aeda18fe55c52a10bfc0490f314e6a2'
},
sequence: 4294967294,
address: 'bitcoincash:qqf89zzwd0fqc3npkks997r5l7dpfjf9kgx2rqu500',
value: 0.00000546,
tokenQtyStr: '2.34123',
tokenQty: 2.34123
},
{
txid: 'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34',
vout: 3,
scriptSig: {
asm:
'3044022029ab770c249f467b40bb90410429f6d657f9be299baa19a6838d04d972436b450220143660297e8e836bc16d317649fad8c310bdadb4fb61d4e9f6ba11203abe5dae[ALL|FORKID] 02eed4ac9dda3405d9b1ccbbf09f8056f3e7c615924274bd5643238b543d0a1d56',
hex:
'473044022029ab770c249f467b40bb90410429f6d657f9be299baa19a6838d04d972436b450220143660297e8e836bc16d317649fad8c310bdadb4fb61d4e9f6ba11203abe5dae412102eed4ac9dda3405d9b1ccbbf09f8056f3e7c615924274bd5643238b543d0a1d56'
},
sequence: 4294967294,
address: 'bitcoincash:qqt30r33k0jx3sxe34tmupaujpaljnglmvqgrrfp2x',
value: 0.00054848,
tokenQtyStr: '2.34123',
tokenQty: 2.34123
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 1145980243 938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8 0000000103907f11',
hex:
'6a04534c500001010453454e4420938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8080000000103907f11',
type: 'nulldata'
},
tokenQty: null
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 059775ff94f65c04e8a6847e74eb9809d8cd779b OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914059775ff94f65c04e8a6847e74eb9809d8cd779b88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqzewa0ljnm9cp8g56z8ua8tnqya3nthnvhv5hpu8y']
},
tokenQtyStr: '43547.68657',
tokenQty: 43547.68657
},
{
value: 0.00054808,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 0e330dc9009e1e07831f5a22d4ade8977ab674c8 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9140e330dc9009e1e07831f5a22d4ade8977ab674c888ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qq8rxrwfqz0pupurradz949dazth4dn5eqs3mhrucv']
},
tokenQty: null
}
],
hex:
'0100000003f8cda53b43b0ea49991a9bc6c74eb901d2a8b964bc7b8987d76789618ec18c93010000006b483045022100e3d5f9a48f9aa1cf7e9ed1992e0281e7300b734ba0e4bd5bb9265b2be60bd71002200ccde53ce7ea9da9df3e834f234f3bea65c2e58c96f8436f93333fd8946a1db2412102056220984cc2cf5261a27d4f66d31c9ef601a688ca1a5ab81e55b6f0d311be74feffffff34dbf62a45295ae11c2a45f44368218ec64498ac3f7e1434c1993515f53c9dee010000006b483045022100bb90d52be9b568f643fdb1a302e9f063be27a9f914e2a6b192cbabf2cbdeaf9302203c275fc8d1b82390bc1726390d361c5266056bddd54dec23efa207c79eacea4a4121024146cfcd0c02e99d6451dc48ad0f97114aeda18fe55c52a10bfc0490f314e6a2feffffff34dbf62a45295ae11c2a45f44368218ec64498ac3f7e1434c1993515f53c9dee030000006a473044022029ab770c249f467b40bb90410429f6d657f9be299baa19a6838d04d972436b450220143660297e8e836bc16d317649fad8c310bdadb4fb61d4e9f6ba11203abe5dae412102eed4ac9dda3405d9b1ccbbf09f8056f3e7c615924274bd5643238b543d0a1d56feffffff030000000000000000376a04534c500001010453454e4420938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8080000000103907f1122020000000000001976a914059775ff94f65c04e8a6847e74eb9809d8cd779b88ac18d60000000000001976a9140e330dc9009e1e07831f5a22d4ade8977ab674c888ac7d4b0800',
blockhash: '00000000000000000140f0d813052da59c811a936494a8d8b2d60b215d19e3dc',
confirmations: 167007,
time: 1534391953,
blocktime: 1534391953,
isValidSLPTx: true,
tokenTxType: 'SEND',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
tokenTicker: 'Bubb2',
tokenName: 'the new bubbles!',
tokenDecimals: 5,
tokenUri: '',
tokenDocHash: ''
}
const genesisTestOpReturnData01 = {
tokenType: 1,
txType: 'SEND',
tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35',
amounts: ['5000000', '5000000']
}
const genesisTestOpReturnData02 = {
tokenType: 1,
txType: 'GENESIS',
ticker: '',
name: '',
tokenId: '323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35',
documentUri: '',
documentHash: '',
decimals: 0,
mintBatonVout: 2,
qty: '10000000'
}
const mintTestOpReturnData01 = {
tokenType: 1,
txType: 'SEND',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
amounts: ['4354768657']
}
const mintTestOpReturnData02 = {
tokenType: 1,
txType: 'GENESIS',
ticker: 'Bubb2',
name: 'the new bubbles!',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
documentUri: '',
documentHash: '',
decimals: 5,
mintBatonVout: 2,
qty: '4354534534'
}
const mintTestOpReturnData03 = {
tokenType: 1,
txType: 'MINT',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
mintBatonVout: 2,
qty: '234123'
}
const sendTestInputTx01 = {
txid: '6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a',
hash: '6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a',
version: 1,
size: 627,
locktime: 543956,
vin: [
{
txid: 'b36b0c7485ad569b98cc9b9614dc68a5208495f22ec3b00effcf963b135a5215',
vout: 1,
scriptSig: {
asm:
'3045022100934ca9732bf6c1b146f09e6198142a79a80788b576c6b94e2052df5bcdc86ad902205ef6205192200ea93f6c97f966b838ee785529b694eb61e576e3452470613847[ALL|FORKID] 02b23484341cf36ec00184e045d54e6ffca97da7a8f8a6fd719cead192132306b1',
hex:
'483045022100934ca9732bf6c1b146f09e6198142a79a80788b576c6b94e2052df5bcdc86ad902205ef6205192200ea93f6c97f966b838ee785529b694eb61e576e3452470613847412102b23484341cf36ec00184e045d54e6ffca97da7a8f8a6fd719cead192132306b1'
},
sequence: 4294967294,
address: 'bitcoincash:qqxj7p2jatt8h3tcpadyxw8a2mr7myqk2qm57u4rdf',
value: 0.00000546
},
{
txid: 'b36b0c7485ad569b98cc9b9614dc68a5208495f22ec3b00effcf963b135a5215',
vout: 3,
scriptSig: {
asm:
'3044022004d4a95658ef140561ae3a936fb01511778a5376a499bcd6f47b64074a6ca4430220273a4bf553ca17c7e10f393582f980e17a6a98ada867633faad9dbcc595dfd23[ALL|FORKID] 0324fe3b895cfa09523e8ec529eb15c681e3a20f96df85175c54a952b68ac018df',
hex:
'473044022004d4a95658ef140561ae3a936fb01511778a5376a499bcd6f47b64074a6ca4430220273a4bf553ca17c7e10f393582f980e17a6a98ada867633faad9dbcc595dfd2341210324fe3b895cfa09523e8ec529eb15c681e3a20f96df85175c54a952b68ac018df'
},
sequence: 4294967294,
address: 'bitcoincash:qrcdncgpnv3s8lekcamv4yekp8sghnk37qtsxdyzla',
value: 0.00153976
},
{
txid: '95d460512143b636bbc5780d8b27b04fca3bfd2f22003ab48da594e2bab9cfc1',
vout: 2,
scriptSig: {
asm:
'3044022079f239216f8d806a084221e841227e55d23a1b90aaf5b5ab54d6f865fbeacf8b022053568f10afc00459af083e4c77d0b85229eb8973a1de406aaa699557f05c7fed[ALL|FORKID] 02035f98ff79d6f3a8ec5a2738c103bbb7796e94fcaffdfaae8f5b7a7fae5d93e3',
hex:
'473044022079f239216f8d806a084221e841227e55d23a1b90aaf5b5ab54d6f865fbeacf8b022053568f10afc00459af083e4c77d0b85229eb8973a1de406aaa699557f05c7fed412102035f98ff79d6f3a8ec5a2738c103bbb7796e94fcaffdfaae8f5b7a7fae5d93e3'
},
sequence: 4294967294,
address: 'bitcoincash:qrcukahsm53lsywcdarehh73vqazpdu8zv547nym9g',
value: 0.00000546
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 1145980243 550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35 00005af3107a4000 004657febe8d8000',
hex:
'6a04534c500001010453454e4420550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b350800005af3107a400008004657febe8d8000',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 268dadd7c7becc11782ad3d956342442d8eae92a OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914268dadd7c7becc11782ad3d956342442d8eae92a88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqngmtwhc7lvcytc9tfaj435y3pd36hf9gtdq8nsuu']
}
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 9fb45b370f9ff38a775ee95cabcad7615817924e OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9149fb45b370f9ff38a775ee95cabcad7615817924e88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qz0mgkehp70l8znhtm54e2726as4s9ujfc9s2szctr']
}
},
{
value: 0.00153347,
n: 3,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 9fb45b370f9ff38a775ee95cabcad7615817924e OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9149fb45b370f9ff38a775ee95cabcad7615817924e88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qz0mgkehp70l8znhtm54e2726as4s9ujfc9s2szctr']
}
}
],
hex:
'010000000315525a133b96cfff0eb0c32ef2958420a568dc14969bcc989b56ad85740c6bb3010000006b483045022100934ca9732bf6c1b146f09e6198142a79a80788b576c6b94e2052df5bcdc86ad902205ef6205192200ea93f6c97f966b838ee785529b694eb61e576e3452470613847412102b23484341cf36ec00184e045d54e6ffca97da7a8f8a6fd719cead192132306b1feffffff15525a133b96cfff0eb0c32ef2958420a568dc14969bcc989b56ad85740c6bb3030000006a473044022004d4a95658ef140561ae3a936fb01511778a5376a499bcd6f47b64074a6ca4430220273a4bf553ca17c7e10f393582f980e17a6a98ada867633faad9dbcc595dfd2341210324fe3b895cfa09523e8ec529eb15c681e3a20f96df85175c54a952b68ac018dffeffffffc1cfb9bae294a58db43a00222ffd3bca4fb0278b0d78c5bb36b643215160d495020000006a473044022079f239216f8d806a084221e841227e55d23a1b90aaf5b5ab54d6f865fbeacf8b022053568f10afc00459af083e4c77d0b85229eb8973a1de406aaa699557f05c7fed412102035f98ff79d6f3a8ec5a2738c103bbb7796e94fcaffdfaae8f5b7a7fae5d93e3feffffff040000000000000000406a04534c500001010453454e4420550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b350800005af3107a400008004657febe8d800022020000000000001976a914268dadd7c7becc11782ad3d956342442d8eae92a88ac22020000000000001976a9149fb45b370f9ff38a775ee95cabcad7615817924e88ac03570200000000001976a9149fb45b370f9ff38a775ee95cabcad7615817924e88acd44c0800',
blockhash: '000000000000000001882548bbd9a41bc53a039634f2afa4a12d653434347d04',
confirmations: 166670,
time: 1534593905,
blocktime: 1534593905
}
const sendTestOpReturnData01 = {
tokenType: 1,
txType: 'SEND',
tokenId: '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35',
amounts: ['100000000000000', '19800000000000000']
}
const sendTestOpReturnData02 = {
tokenType: 1,
txType: 'GENESIS',
ticker: 'USDT',
name: 'Tether Ltd. US dollar backed tokens',
tokenId: '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35',
documentUri:
'https://tether.to/wp-content/uploads/2016/06/TetherWhitePaper.pdf',
documentHash:
'DQ\u001e3\u0006ppM\u0001\u0017pW(;\u0003,wy19\u0016',
decimals: 8,
mintBatonVout: 2,
qty: '10000000000000000'
}
const sendTestOpReturnData03 = {
tokenType: 1,
txType: 'MINT',
tokenId: '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35',
mintBatonVout: 2,
qty: '10000000000000000'
}
const sendTestOpReturnData04 = {
tokenType: 1,
txType: 'SEND',
tokenId: '550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35',
amounts: ['100000000000000', '9900000000000000']
}
const mintTestInputTx02 = {
txid: 'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34',
hash: 'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34',
version: 1,
size: 473,
locktime: 543613,
vin: [
{
txid: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
vout: 3,
scriptSig: {
asm:
'3045022100ac43a871e0f84787001db745e4801b789fd9cd7efa247727ebb5c0c7b66d597302205316328b6501759e9a15ec65202219a433172d153f9f1b68f65f3a63494ba6f0[ALL|FORKID] 0345a5f5dd11c987b346c18c245f0df6d305369984b9b3c7ba465c29875466e374',
hex:
'483045022100ac43a871e0f84787001db745e4801b789fd9cd7efa247727ebb5c0c7b66d597302205316328b6501759e9a15ec65202219a433172d153f9f1b68f65f3a63494ba6f041210345a5f5dd11c987b346c18c245f0df6d305369984b9b3c7ba465c29875466e374'
},
sequence: 4294967294,
address: 'bitcoincash:qqj0durd4qzdddvl2u6sen8n8h6mljtywugqctt9km',
value: 0.00056266
},
{
txid: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
vout: 2,
scriptSig: {
asm:
'304402202ce2d9d63bc6ab7765d267540fd3429bf7a8d1b50e975391fff3a4506289461a02205daf3f761a9b02d141f22136a6fe019062182c9da1b1d3a5245ff745f1236e45[ALL|FORKID] 02c00b05bc633a48e074bc54dd195b19a470ad6252ac8d306c82a2bd729c1c4c69',
hex:
'47304402202ce2d9d63bc6ab7765d267540fd3429bf7a8d1b50e975391fff3a4506289461a02205daf3f761a9b02d141f22136a6fe019062182c9da1b1d3a5245ff745f1236e45412102c00b05bc633a48e074bc54dd195b19a470ad6252ac8d306c82a2bd729c1c4c69'
},
sequence: 4294967294,
address: 'bitcoincash:qzf72qccdgjrpjpewq8dazlze6jyxzku8q3cauna4t',
value: 0.00000546
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 1414416717 938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8 2 000000000003928b',
hex:
'6a04534c50000101044d494e5420938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8010208000000000003928b',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 1272884e6bd20c4661b5a052f874ff9a14c925b2 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9141272884e6bd20c4661b5a052f874ff9a14c925b288ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqf89zzwd0fqc3npkks997r5l7dpfjf9kgx2rqu500']
}
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 d1eaebf8d1face5bd866e36bd94f43e2ffceef71 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914d1eaebf8d1face5bd866e36bd94f43e2ffceef7188ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qrg746lc68avuk7cvm3khk20g030lnh0wy5h2k2fqr']
}
},
{
value: 0.00054848,
n: 3,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 17178e31b3e468c0d98d57be07bc907bf94d1fdb OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91417178e31b3e468c0d98d57be07bc907bf94d1fdb88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqt30r33k0jx3sxe34tmupaujpaljnglmvqgrrfp2x']
}
}
],
hex:
'0100000002f8cda53b43b0ea49991a9bc6c74eb901d2a8b964bc7b8987d76789618ec18c93030000006b483045022100ac43a871e0f84787001db745e4801b789fd9cd7efa247727ebb5c0c7b66d597302205316328b6501759e9a15ec65202219a433172d153f9f1b68f65f3a63494ba6f041210345a5f5dd11c987b346c18c245f0df6d305369984b9b3c7ba465c29875466e374fefffffff8cda53b43b0ea49991a9bc6c74eb901d2a8b964bc7b8987d76789618ec18c93020000006a47304402202ce2d9d63bc6ab7765d267540fd3429bf7a8d1b50e975391fff3a4506289461a02205daf3f761a9b02d141f22136a6fe019062182c9da1b1d3a5245ff745f1236e45412102c00b05bc633a48e074bc54dd195b19a470ad6252ac8d306c82a2bd729c1c4c69feffffff040000000000000000396a04534c50000101044d494e5420938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8010208000000000003928b22020000000000001976a9141272884e6bd20c4661b5a052f874ff9a14c925b288ac22020000000000001976a914d1eaebf8d1face5bd866e36bd94f43e2ffceef7188ac40d60000000000001976a91417178e31b3e468c0d98d57be07bc907bf94d1fdb88ac7d4b0800',
blockhash: '00000000000000000140f0d813052da59c811a936494a8d8b2d60b215d19e3dc',
confirmations: 168436,
time: 1534391953,
blocktime: 1534391953
}
const mintTestOpReturnData04 = {
tokenType: 1,
txType: 'MINT',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
mintBatonVout: 2,
qty: '234123'
}
const mintTestOpReturnData05 = {
tokenType: 1,
txType: 'GENESIS',
ticker: 'Bubb2',
name: 'the new bubbles!',
tokenId: '938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8',
documentUri: '',
documentHash: '',
decimals: 5,
mintBatonVout: 2,
qty: '4354534534'
}
const genesisTestInputTx02 = {
txid: '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf',
hash: '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf',
version: 1,
size: 444,
locktime: 571211,
vin: [
{
txid: 'f3e056874846b6f491a1ccac3da81a3411c61d8466835446a52efa68c0d11804',
vout: 0,
scriptSig: {
asm:
'3044022022fb67bd9b35810db3fa04da4014cc93063c5ed15e8febeca09d32c03397068502206fbb86498919ca8a9be6940c904c944cd923691391a4a7d92e0c841605d46b2e[ALL|FORKID] 03fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bf',
hex:
'473044022022fb67bd9b35810db3fa04da4014cc93063c5ed15e8febeca09d32c03397068502206fbb86498919ca8a9be6940c904c944cd923691391a4a7d92e0c841605d46b2e412103fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bf'
},
sequence: 4294967294,
address: 'bitcoincash:qz6rcxjw34yuy5fngu7fngnyy55t0v802cmsmjvsp2',
value: 0.00001111
},
{
txid: '87bf0efc1630f5179b73496f9f885b141cb4c8366432a91ba678298432b197ea',
vout: 0,
scriptSig: {
asm:
'3045022100d5fbab461c19c7b56245287eb15510c86872ceb405ede7322f5266efa6cc32ea022074b557bcef9a129fb31cb2e7c2ada9fe51d5cf924ae55d1d2a29368ca1f15ef2[ALL|FORKID] 03fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bf',
hex:
'483045022100d5fbab461c19c7b56245287eb15510c86872ceb405ede7322f5266efa6cc32ea022074b557bcef9a129fb31cb2e7c2ada9fe51d5cf924ae55d1d2a29368ca1f15ef2412103fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bf'
},
sequence: 4294967294,
address: 'bitcoincash:qz6rcxjw34yuy5fngu7fngnyy55t0v802cmsmjvsp2',
value: 0.007
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 47454e45534953 5350494345 5370696365 7370696365736c7040676d61696c2e636f6d 0 8 0 016345785d8a0000',
hex:
'6a04534c500001010747454e45534953055350494345055370696365127370696365736c7040676d61696c2e636f6d4c0001084c0008016345785d8a0000',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 70e5c384ae563b4fd6cfc5f7f8df43ea427d9e10 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91470e5c384ae563b4fd6cfc5f7f8df43ea427d9e1088ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qpcwtsuy4etrkn7kelzl07xlg04yylv7zqru2cqwvm']
}
},
{
value: 0.0070012,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 df305a87d98cd64e1bee825d2167a16445ecdfdd OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914df305a87d98cd64e1bee825d2167a16445ecdfdd88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qr0nqk58mxxdvnsma6p96gt859jytmxlm5t0v7de4q']
}
}
],
hex:
'01000000020418d1c068fa2ea546548366841dc611341aa83daccca191f4b646488756e0f3000000006a473044022022fb67bd9b35810db3fa04da4014cc93063c5ed15e8febeca09d32c03397068502206fbb86498919ca8a9be6940c904c944cd923691391a4a7d92e0c841605d46b2e412103fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bffeffffffea97b132842978a61ba9326436c8b41c145b889f6f49739b17f53016fc0ebf87000000006b483045022100d5fbab461c19c7b56245287eb15510c86872ceb405ede7322f5266efa6cc32ea022074b557bcef9a129fb31cb2e7c2ada9fe51d5cf924ae55d1d2a29368ca1f15ef2412103fbe2c56094dc336f6244eac2e45d3d25a8e97059f2cc7de31cbf179e73dcc9bffeffffff0300000000000000003e6a04534c500001010747454e45534953055350494345055370696365127370696365736c7040676d61696c2e636f6d4c0001084c0008016345785d8a000022020000000000001976a91470e5c384ae563b4fd6cfc5f7f8df43ea427d9e1088acd8ae0a00000000001976a914df305a87d98cd64e1bee825d2167a16445ecdfdd88ac4bb70800',
blockhash: '000000000000000002278c188c52b536d4ed84a1970d8ff59043e082cef7748a',
confirmations: 141226,
time: 1551042875,
blocktime: 1551042875
}
const genesisTestOpReturn03 = {
tokenType: 1,
txType: 'GENESIS',
ticker: 'SPICE',
name: 'Spice',
tokenId: '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf',
documentUri: 'spiceslp@gmail.com',
documentHash: '',
decimals: 8,
mintBatonVout: 0,
qty: '100000000000000000'
}
module.exports = {
nonSlpTxDetails,
slpTxDetails,
mockOpReturnData01,
mockOpReturnData02,
mockOpReturnData03
mockOpReturnData03,
genesisTestInputTx,
mintTestInputTx,
genesisTestOpReturnData01,
genesisTestOpReturnData02,
mintTestOpReturnData01,
mintTestOpReturnData02,
mintTestOpReturnData03,
sendTestInputTx01,
sendTestOpReturnData01,
sendTestOpReturnData02,
sendTestOpReturnData03,
sendTestOpReturnData04,
mintTestInputTx02,
mintTestOpReturnData04,
mintTestOpReturnData05,
genesisTestInputTx02,
genesisTestOpReturn03
}
+205 -1
View File
@@ -280,10 +280,214 @@ const electrumxUtxos = {
]
}
const fulcrumUtxos01 = {
success: true,
utxos: [
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 1,
value: 546
},
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 2,
value: 546
},
{
height: 674512,
tx_hash:
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
tx_pos: 1,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 1,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 2,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 3,
value: 38134
}
]
}
const fulcrumUtxos02 = {
success: true,
utxos: [
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 3,
value: 38134
}
]
}
const psfSlpIndexerUtxos01 = {
balance: {
utxos: [
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 1,
type: 'token',
qty: '10000000000',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
},
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 2,
type: 'baton',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
}
],
txs: [
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
height: 674512
}
],
balances: [
{
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
qty: '10000000000'
}
]
}
}
const tokenUtxos01 = [
{
txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
type: 'token',
qty: '10000000',
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
vout: 1,
type: 'token',
qty: '100000000',
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
vout: 5,
type: 'token',
qty: '4764',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
vout: 5,
type: 'token',
qty: '238',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
const genesisData01 = {
tokenData: {
type: 1,
ticker: 'sleven',
name: 'sleven',
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
documentUri: 'sleven',
documentHash: '',
decimals: 7,
mintBatonIsActive: false,
tokensInCirculationBN: '770059999999',
tokensInCirculationStr: '770059999999',
blockCreated: 555483,
totalBurned: '7711234568',
totalMinted: '777771234567'
}
}
const genesisData02 = {
tokenData: {
type: 1,
ticker: 'NAKAMOTO',
name: 'NAKAMOTO',
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
documentUri: '',
documentHash: '',
decimals: 8,
mintBatonIsActive: false,
tokensInCirculationBN: '2099260968799900',
tokensInCirculationStr: '2099260968799900',
blockCreated: 555671,
totalBurned: '739031200100',
totalMinted: '2100000000000000'
}
}
const genesisData03 = {
tokenData: {
type: 1,
ticker: 'AUDC',
name: 'AUD Coin',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
documentUri: 'audcoino@gmail.com',
documentHash: '',
decimals: 6,
mintBatonIsActive: false,
tokensInCirculationBN: '974791786216512742',
tokensInCirculationStr: '974791786216512742',
blockCreated: 603311,
totalBurned: '1025208213783487258',
totalMinted: '2000000000000000000'
}
}
const noUtxoErr = {
success: false,
error:
'Key not found in database [bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7]'
}
module.exports = {
mockUtxoData,
mockHydratedUtxos,
mockTwoHydratedAddrs,
mockEveryUtxoType,
electrumxUtxos
electrumxUtxos,
fulcrumUtxos01,
fulcrumUtxos02,
psfSlpIndexerUtxos01,
tokenUtxos01,
genesisData01,
genesisData02,
genesisData03,
noUtxoErr
}
-255
View File
@@ -1,255 +0,0 @@
const chai = require('chai')
const assert = chai.assert
const axios = require('axios')
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/ninsight-mock')
describe('#Ninsight', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#utxo', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.utxo(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should GET utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.utxo })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'utxos')
assert.property(result, 'legacyAddress')
assert.property(result, 'cashAddress')
assert.property(result, 'slpAddress')
assert.property(result, 'scriptPubKey')
assert.property(result, 'asm')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'txid')
assert.property(result.utxos[0], 'vout')
assert.property(result.utxos[0], 'amount')
assert.property(result.utxos[0], 'satoshis')
assert.property(result.utxos[0], 'height')
assert.property(result.utxos[0], 'confirmations')
})
it('should POST utxo details for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.utxoPost })
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.isArray(result[0].utxos)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
assert.property(result[0], 'asm')
})
})
describe('#unconfirmed', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.unconfirmed(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should POST utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmed })
const addr = 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5'
const result = await bchjs.Ninsight.unconfirmed(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'utxos')
assert.property(result, 'legacyAddress')
assert.property(result, 'cashAddress')
assert.property(result, 'slpAddress')
assert.property(result, 'scriptPubKey')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'txid')
assert.property(result.utxos[0], 'vout')
assert.property(result.utxos[0], 'amount')
assert.property(result.utxos[0], 'satoshis')
assert.property(result.utxos[0], 'confirmations')
assert.property(result.utxos[0], 'ts')
})
it('should POST utxo details for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmedPost })
const addr = [
'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.unconfirmed(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.isArray(result[0].utxos)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
})
})
describe('#transactions', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.transactions(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should POST transaction history for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
assert.property(result[0].txs[0], 'vin')
assert.property(result[0].txs[0], 'vout')
})
it('should POST transaction history for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
})
})
describe('#txDetails', () => {
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.Ninsight.txDetails(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Transaction ID must be a string or array of strings.'
)
}
})
it('should POST transaction details for a single TxID', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
const txid = 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'version')
assert.property(result[0], 'locktime')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
assert.property(result[0], 'blockhash')
assert.property(result[0], 'blockheight')
assert.property(result[0], 'confirmations')
assert.property(result[0], 'time')
assert.property(result[0], 'blocktime')
assert.property(result[0], 'isCoinBase')
assert.property(result[0], 'valueOut')
assert.property(result[0], 'size')
})
it('should POST transaction details for an array of TxIDs', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
const txid = [
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
]
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
})
})
})
+339
View File
@@ -0,0 +1,339 @@
const chai = require('chai')
const assert = chai.assert
const axios = require('axios')
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/psf-slp-indexer-mock')
describe('#PsfSlpIndexer', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#status', () => {
it('should GET status', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.status })
const result = await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'status')
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'get').throws(new Error('test error'))
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'get').throws(testErr)
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#balance', () => {
it('should GET balance', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.balance })
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'balance')
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
assert.isArray(result.balance.utxos)
assert.isArray(result.balance.txs)
assert.isArray(result.balance.balances)
})
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.PsfSlpIndexer.balance(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input address must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tokenStats', () => {
it('should GET token stats', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenStats })
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'tokenData')
assert.property(result.tokenData, 'type')
assert.property(result.tokenData, 'ticker')
assert.property(result.tokenData, 'name')
assert.property(result.tokenData, 'tokenId')
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'documentHash')
assert.property(result.tokenData, 'decimals')
assert.property(result.tokenData, 'mintBatonIsActive')
assert.property(result.tokenData, 'tokensInCirculationBN')
assert.property(result.tokenData, 'tokensInCirculationStr')
assert.property(result.tokenData, 'blockCreated')
assert.property(result.tokenData, 'totalBurned')
assert.property(result.tokenData, 'totalMinted')
assert.property(result.tokenData, 'txs')
})
it('should throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input tokenId must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tx', () => {
it('should GET transaction data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.txData })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'txData')
assert.property(result.txData, 'txid')
assert.property(result.txData, 'hash')
assert.property(result.txData, 'version')
assert.property(result.txData, 'size')
assert.property(result.txData, 'locktime')
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'hex')
assert.property(result.txData, 'blockhash')
assert.property(result.txData, 'confirmations')
assert.property(result.txData, 'time')
assert.property(result.txData, 'blocktime')
assert.property(result.txData, 'blockheight')
assert.property(result.txData, 'isSlpTx')
assert.property(result.txData, 'tokenTxType')
assert.property(result.txData, 'tokenId')
assert.property(result.txData, 'tokenType')
assert.property(result.txData, 'tokenTicker')
assert.property(result.txData, 'tokenName')
assert.property(result.txData, 'tokenDecimals')
assert.property(result.txData, 'tokenUri')
assert.property(result.txData, 'tokenDocHash')
assert.property(result.txData, 'isValidSlp')
})
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.PsfSlpIndexer.tx(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input txid must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should get tx from full node if not available from slp indexer', async () => {
// Stub the call to the SLP indexer.
const testErr = {
response: { data: { error: 'Key not found in database' } }
}
sandbox.stub(axios, 'post').rejects(testErr)
// Stub the call to the full node
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(false)
sandbox
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
.resolves({ txid: 'fakeTxid' })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txData.txid, 'fakeTxid')
assert.equal(result.txData.isValidSlp, false)
})
it('should return isValidSlp=null for blacklisted token', async () => {
// Stub the call to the SLP indexer.
const testErr = {
response: { data: { error: 'Key not found in database' } }
}
sandbox.stub(axios, 'post').rejects(testErr)
// Stub the call to the full node
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(true)
sandbox
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
.resolves({ txid: 'fakeTxid' })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txData.txid, 'fakeTxid')
assert.equal(result.txData.isValidSlp, null)
})
})
describe('#checkBlacklist', () => {
it('should return true if txid contains token in blacklist', async () => {
// Mock dependencies
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.resolves(mockData.tokenData01)
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
// console.log('result: ', result)
assert.equal(result, true)
})
it('should return false if there is an error', async () => {
// Force an error
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.rejects(new Error('test error'))
const result = await bchjs.PsfSlpIndexer.checkBlacklist()
assert.equal(result, false)
})
it('should return false if there is no tokenId match', async () => {
// Mock dependencies
mockData.tokenData01.tokenId = 'abc123'
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.resolves(mockData.tokenData01)
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
assert.equal(result, false)
})
})
})
+14 -5
View File
@@ -171,10 +171,15 @@ describe('#RawTransactions', () => {
describe('#getTxData', () => {
it('should return tx data with input addresses', async () => {
// Mock dependencies
sandbox.stub(bchjs.RawTransactions, 'getRawTransaction').resolves(mockData.mockTx)
sandbox.stub(bchjs.RawTransactions, '_getInputAddrs').resolves(mockData.mockGetInputAddrsOutput)
sandbox
.stub(bchjs.RawTransactions, 'getRawTransaction')
.resolves(mockData.mockTx)
sandbox
.stub(bchjs.RawTransactions, '_getInputAddrs')
.resolves(mockData.mockGetInputAddrsOutput)
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const result = await bchjs.RawTransactions.getTxData(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
@@ -190,7 +195,10 @@ describe('#RawTransactions', () => {
} catch (err) {
// console.log(err)
assert2.include(err.message, 'Input must be a string or array of strings')
assert2.include(
err.message,
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
)
}
})
@@ -201,7 +209,8 @@ describe('#RawTransactions', () => {
.stub(bchjs.RawTransactions, 'getRawTransaction')
.rejects(new Error('test error'))
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
await bchjs.RawTransactions.getTxData(txid)
+19 -17
View File
@@ -2281,23 +2281,25 @@ describe('#SLP Utils', () => {
assert.equal(result, true)
})
it('catches and throws an error', async () => {
try {
const txid = 'fakeTxid'
// Force an error
sandbox
.stub(uut.Utils, 'validateTxid3')
.rejects(new Error('fake error'))
await await uut.Utils.waterfallValidateTxid(txid)
assert.fail('Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'fake error')
}
})
// CT 08-09-21 Changed behavior. validateTxid3() is now wrapped in a
// try/catch statement that exits quietly if there is an error.
// it('catches and throws an error', async () => {
// try {
// const txid = 'fakeTxid'
//
// // Force an error
// sandbox
// .stub(uut.Utils, 'validateTxid3')
// .rejects(new Error('fake error'))
//
// await await uut.Utils.waterfallValidateTxid(txid)
//
// assert.fail('Unexpected result')
// } catch (err) {
// // console.log(`err.message: ${err.message}`)
// assert.include(err.message, 'fake error')
// }
// })
})
describe('#getWhitelist', () => {
+609 -9
View File
@@ -5,27 +5,33 @@
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const cloneDeep = require('lodash.clonedeep')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/transaction-mock.js')
const mockDataLib = require('./fixtures/transaction-mock.js')
describe('#TransactionLib', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
let sandbox, mockData
beforeEach(() => {
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
})
afterEach(() => sandbox.restore())
describe('#get', () => {
describe('#getOld', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get()
await bchjs.Transaction.getOld()
assert.fail('Unexpected code path!')
} catch (err) {
assert.include(
err.message,
'Input must be a string or array of strings.'
'Input to Transaction.get() must be a string containing a TXID.'
)
}
})
@@ -39,7 +45,7 @@ describe('#TransactionLib', () => {
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.nonSlpTxDetails)
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -78,7 +84,7 @@ describe('#TransactionLib', () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -108,12 +114,606 @@ describe('#TransactionLib', () => {
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.rejects(new Error('test error'))
await bchjs.Transaction.get(txid)
await bchjs.Transaction.getOld(txid)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
// This test case was created in response to a bug. When the input TX
// was a Genesis SLP transaction, the inputs of the transaction were not
// being hydrated properly.
it('should get input details when input is a genesis tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.genesisTestOpReturnData01)
.onCall(1)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(2)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(3)
.resolves(mockData.genesisTestOpReturnData02)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
// Assert inputs values unique to a Genesis input have the proper values.
assert.equal(result.vin[0].tokenQty, 10000000)
assert.equal(result.vin[1].tokenQty, null)
})
it('should get input details when input is a mint tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData01)
.onCall(1)
.resolves(mockData.mintTestOpReturnData02)
.onCall(2)
.resolves(mockData.mintTestOpReturnData02)
.onCall(3)
.resolves(mockData.mintTestOpReturnData03)
.onCall(4)
.resolves(mockData.mintTestOpReturnData03)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
// Assert inputs values unique to a Mint input have the proper values.
assert.equal(result.vin[0].tokenQty, 43545.34534)
assert.equal(result.vin[1].tokenQty, 2.34123)
assert.equal(result.vin[2].tokenQty, null)
})
// This test case was generated from the problematic transaction that
// used inputs in a 'non-standard' way.
it('should correctly assign quantities to mixed mint inputs', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.sendTestInputTx01)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.sendTestOpReturnData01)
.onCall(1)
.resolves(mockData.sendTestOpReturnData02)
.onCall(2)
.resolves(mockData.sendTestOpReturnData03)
.onCall(3)
.resolves(mockData.sendTestOpReturnData03)
.onCall(4)
.resolves(mockData.sendTestOpReturnData04)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
// Assert inputs values unique to a Mint input have the proper values.
assert.equal(result.vin[0].tokenQty, 100000000)
assert.equal(result.vin[1].tokenQty, null)
assert.equal(result.vin[2].tokenQty, 99000000)
})
})
describe('#get3', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get3()
assert.fail('Unexpected code path!')
} catch (err) {
assert.include(
err.message,
'Input to Transaction.get() must be a string containing a TXID.'
)
}
})
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.nonSlpTxDetails)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 602405 })
sandbox.stub(bchjs.Transaction, 'getTokenInfo').resolves(false)
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
// Assert blockheight is added
assert.equal(result.blockheight, 602405)
assert.equal(result.isSlpTx, false)
})
it('should get details about a SLP SEND tx with SEND input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.slpTxDetails)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 603424 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mockOpReturnData01)
.onCall(1)
.resolves(mockData.mockOpReturnData02)
.onCall(2)
.resolves(mockData.mockOpReturnData03)
.onCall(3)
.rejects(new Error('No OP_RETURN'))
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[0].tokenQtyStr, null)
assert.equal(result.vout[1].tokenQty, 1)
assert.equal(result.vout[1].tokenQtyStr, '1')
assert.equal(result.vout[2].tokenQty, 998833)
assert.equal(result.vout[2].tokenQtyStr, '998833')
assert.equal(result.vout[3].tokenQty, null)
assert.equal(result.vout[3].tokenQtyStr, null)
// Assert that inputs have expected properties
assert.equal(result.vin[0].tokenQtyStr, '998834')
assert.equal(result.vin[0].tokenQty, 998834)
assert.equal(
result.vin[0].tokenId,
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 603424)
assert.equal(result.isSlpTx, true)
})
it('should catch and throw error on network error', async () => {
try {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
// Force an error
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.rejects(new Error('test error'))
await bchjs.Transaction.get3(txid)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
// This test case was created in response to a bug. When the input TX
// was a Genesis SLP transaction, the inputs of the transaction were not
// being hydrated properly.
it('should get details about a SLP SEND tx with GENSIS input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543409 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.genesisTestOpReturnData01)
.onCall(1)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(2)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(3)
.resolves(mockData.genesisTestOpReturnData02)
const txid =
'874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties and values
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[0].tokenQtyStr, null)
assert.equal(result.vout[1].tokenQty, 5000000)
assert.equal(result.vout[1].tokenQtyStr, '5000000')
assert.equal(result.vout[2].tokenQty, 5000000)
assert.equal(result.vout[2].tokenQtyStr, '5000000')
assert.equal(result.vout[3].tokenQty, null)
assert.equal(result.vout[3].tokenQtyStr, null)
// Assert inputs have expected properties and values
assert.equal(result.vin[0].tokenQty, 10000000)
assert.equal(result.vin[0].tokenQtyStr, '10000000')
assert.equal(
result.vin[0].tokenId,
'323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35'
)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 543409)
assert.equal(result.isSlpTx, true)
})
it('should get details about a SLP SEND tx with MINT (and GENESIS) input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543614 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData01)
.onCall(1)
.resolves(mockData.mintTestOpReturnData02)
.onCall(2)
.resolves(mockData.mintTestOpReturnData02)
.onCall(3)
.resolves(mockData.mintTestOpReturnData03)
.onCall(4)
.resolves(mockData.mintTestOpReturnData03)
const txid =
'4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert expected output properties and values exist.
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[1].tokenQty, 43547.68657)
assert.equal(result.vout[1].tokenQtyStr, '43547.68657')
assert.equal(result.vout[2].tokenQty, null)
// Assert expected input properties and values exist.
assert.equal(result.vin[0].tokenQty, 43545.34534)
assert.equal(result.vin[0].tokenQtyStr, '43545.34534')
assert.equal(
result.vin[0].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[1].tokenQty, 2.34123)
assert.equal(result.vin[1].tokenQtyStr, '2.34123')
assert.equal(
result.vin[1].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[2].tokenQty, 0)
assert.equal(result.vin[2].tokenQtyStr, '0')
assert.equal(result.vin[2].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 543614)
assert.equal(result.isSlpTx, true)
})
// This test case was generated from the problematic transaction that
// used inputs in a 'non-standard' way.
it('should get details about a SLP SEND tx with MINT (and SEND) input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.sendTestInputTx01)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543957 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.sendTestOpReturnData01)
.onCall(1)
.resolves(mockData.sendTestOpReturnData02)
.onCall(2)
.resolves(mockData.sendTestOpReturnData03)
.onCall(3)
.resolves(mockData.sendTestOpReturnData03)
.onCall(4)
.resolves(mockData.sendTestOpReturnData04)
const txid =
'6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert the outputs have expected properties and values.
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[1].tokenQty, 1000000)
assert.equal(result.vout[1].tokenQtyStr, '1000000')
assert.equal(result.vout[2].tokenQty, 198000000)
assert.equal(result.vout[2].tokenQtyStr, '198000000')
assert.equal(result.vout[3].tokenQty, null)
// Assert the inputs have expected properties and values.
assert.equal(result.vin[0].tokenQty, 100000000)
assert.equal(result.vin[0].tokenQtyStr, '100000000')
assert.equal(
result.vin[0].tokenId,
'550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35'
)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, 0)
assert.equal(result.vin[1].tokenId, null)
assert.equal(result.vin[2].tokenQty, 99000000)
assert.equal(result.vin[2].tokenQtyStr, '99000000')
assert.equal(
result.vin[2].tokenId,
'550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35'
)
// Assert blockheight is added
assert.equal(result.blockheight, 543957)
assert.equal(result.isSlpTx, true)
})
// This was a problematic TX
it('should process MINT TX with GENESIS input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx02)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543614 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData04)
.onCall(1)
.resolves(mockData.mintTestOpReturnData05)
.onCall(2)
.resolves(mockData.mintTestOpReturnData05)
.onCall(3)
.resolves(mockData.mintTestOpReturnData05)
.onCall(4)
.resolves(mockData.mintTestOpReturnData05)
.onCall(5)
.resolves(mockData.mintTestOpReturnData05)
const txid =
'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties and values
assert.equal(result.vout[0].tokenQty, 0)
assert.equal(result.vout[0].tokenQty, '0')
assert.equal(result.vout[1].tokenQty, 2.34123)
assert.equal(result.vout[1].tokenQty, '2.34123')
assert.equal(result.vout[2].tokenQty, 0)
assert.equal(result.vout[2].tokenQty, '0')
assert.equal(result.vout[2].isMintBaton, true)
assert.equal(result.vout[3].tokenQty, 0)
assert.equal(result.vout[3].tokenQty, '0')
// Assert inputs have expected properties and values
assert.equal(result.vin[0].tokenQty, 0)
assert.equal(result.vin[0].tokenQtyStr, '0')
assert.equal(result.vin[0].tokenId, null)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(
result.vin[1].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[1].isMintBaton, true)
// Assert added TX data exists.
assert.equal(result.blockheight, 543614)
assert.equal(result.isSlpTx, true)
})
// It should process a GENESIS tx
it('should process a GENESIS tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx02)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 571212 })
sandbox
.stub(bchjs.Transaction, 'getTokenInfo')
.onCall(0)
.resolves(mockData.genesisTestOpReturn03)
.onCall(1)
.resolves(mockData.genesisTestOpReturn03)
.onCall(2)
.resolves(false)
.onCall(3)
.resolves(false)
.onCall(4)
.resolves(false)
const txid =
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert output have expected properties and values
assert.equal(result.vout[0].tokenQty, 0)
assert.equal(result.vout[0].tokenQtyStr, '0')
assert.equal(result.vout[0].isMintBaton, true)
assert.equal(result.vout[1].tokenQty, 1000000000)
assert.equal(result.vout[1].tokenQtyStr, '1000000000')
assert.equal(result.vout[2].tokenQty, 0)
assert.equal(result.vout[2].tokenQtyStr, '0')
// Assert input have expected properties and values
assert.equal(result.vin[0].tokenQty, 0)
assert.equal(result.vin[0].tokenQtyStr, '0')
assert.equal(result.vin[0].tokenId, null)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenId, null)
// Assert added TX data exists.
assert.equal(result.blockheight, 571212)
assert.equal(result.isSlpTx, true)
})
})
describe('#get', () => {
it('should proxy psf-slp-indexer', async () => {
// console.log('bchjs.Transaction: ', bchjs.Transaction)
sandbox.stub(bchjs.Transaction.psfSlpIndexer, 'tx').resolves('test data')
const result = await bchjs.Transaction.get()
assert.equal(result, 'test data')
})
})
})
+124 -7
View File
@@ -15,7 +15,7 @@ describe('#utxo', () => {
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#get', () => {
describe('#getOld', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// Mock dependencies.
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
@@ -25,7 +25,7 @@ describe('#utxo', () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const result = await bchjs.Utxo.get(addr)
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
@@ -46,7 +46,7 @@ describe('#utxo', () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
await bchjs.Utxo.get(addr)
await bchjs.Utxo.getOld(addr)
assert.fail('Unexpected code path')
} catch (err) {
@@ -66,7 +66,7 @@ describe('#utxo', () => {
'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
]
const result = await bchjs.Utxo.get(addr)
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
@@ -88,7 +88,7 @@ describe('#utxo', () => {
addrs.push(addr)
}
await bchjs.Utxo.get(addrs)
await bchjs.Utxo.getOld(addrs)
assert.fail('Unexpected code path')
} catch (err) {
@@ -105,7 +105,7 @@ describe('#utxo', () => {
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
const result = await bchjs.Utxo.getOld(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
@@ -133,7 +133,7 @@ describe('#utxo', () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const useWhitelist = true
const result = await bchjs.Utxo.get(addr, useWhitelist)
const result = await bchjs.Utxo.getOld(addr, useWhitelist)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
@@ -188,4 +188,121 @@ describe('#utxo', () => {
assert.equal(result.satoshis, 800)
})
})
describe('#hydrateTokenData', () => {
it('should hydrate token UTXOs', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
.onCall(0)
.resolves(mockData.genesisData01)
.onCall(1)
.resolves(mockData.genesisData02)
.onCall(2)
.resolves(mockData.genesisData03)
const result = await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
// console.log('result: ', result)
assert.equal(result.length, 4)
assert.property(result[0], 'qtyStr')
assert.property(result[0], 'ticker')
assert.property(result[0], 'name')
assert.property(result[0], 'documentUri')
assert.property(result[0], 'documentHash')
})
it('should should catch and throw errors', async () => {
try {
// Force error
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
.rejects(new Error('test error'))
await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(err.message, 'test error')
}
})
})
describe('#get', () => {
it('should throw an error if input is not a string', async () => {
try {
const addr = 123
await bchjs.Utxo.get(addr)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'address input must be a string')
}
})
it('should return UTXO information', async () => {
// mock dependencies
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.resolves(mockData.fulcrumUtxos01)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
.resolves(mockData.psfSlpIndexerUtxos01)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
.resolves({ txData: { isValidSlp: false } })
// Mock function to return the same input. Good enough for this test.
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x)
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert expected properties exist
assert.property(result, 'address')
assert.property(result, 'bchUtxos')
assert.property(result, 'slpUtxos')
assert.property(result.slpUtxos, 'type1')
assert.property(result.slpUtxos.type1, 'tokens')
assert.property(result.slpUtxos.type1, 'mintBatons')
assert.property(result.slpUtxos, 'nft')
assert.property(result, 'nullUtxos')
assert.equal(result.bchUtxos.length, 4)
assert.equal(result.slpUtxos.type1.tokens.length, 1)
assert.equal(result.slpUtxos.type1.mintBatons.length, 1)
assert.equal(result.nullUtxos.length, 0)
})
it('should handle an address with no SLP UTXOs', async () => {
// mock dependencies
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.resolves(mockData.fulcrumUtxos02)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
.resolves({ txData: { isValidSlp: false } })
// Force psf-slp-indexer to return no UTXOs
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
.rejects(mockData.noUtxoErr)
// Mock function to return the same input. Good enough for this test.
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => [])
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.bchUtxos.length, 1)
assert.equal(result.slpUtxos.type1.tokens.length, 0)
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
assert.equal(result.nullUtxos.length, 0)
})
})
})