Compare commits

..
193 Commits
Author SHA1 Message Date
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
Chris Troutner a75e1ab633 Merge pull request #158 from Permissionless-Software-Foundation/ct-unstable
fix(dsproof): Adding documentation
2021-06-22 15:49:17 -07:00
Chris Troutner 8c5f1e2274 fix(dsproof): Adding documentation 2021-06-22 15:47:22 -07:00
Chris Troutner c30e76c942 Merge pull request #157 from Permissionless-Software-Foundation/dh-dsproof
feat(DSProof): Added DSProof to bch-js
2021-06-22 12:28:40 -07:00
Chris Troutner a575621c50 Fixing ABC integration tests 2021-06-22 12:24:29 -07:00
Daniel Gonzalez d3a0c5948a feat(DSProof): Added DSProof to bch-js 2021-06-19 22:40:16 -04:00
Chris Troutner 61bb509195 Merge pull request #156 from Permissionless-Software-Foundation/ct-unstable
fix(raw-transactions): Adding better error handling for rate-limits
2021-06-19 10:43:24 -07:00
Chris Troutner a18e549694 fix(raw-transactions): Adding better error handling for rate-limits 2021-06-19 10:41:49 -07:00
Chris Troutner 7d53f47d8a Merge pull request #155 from Permissionless-Software-Foundation/ct-unstable
fix(error): Adding additional rate limit error handling
2021-06-19 10:09:51 -07:00
Chris Troutner e8e615795d fix(error): Adding additional rate limit error handling 2021-06-19 10:08:28 -07:00
Chris Troutner 5759b096d5 Merge pull request #153 from Permissionless-Software-Foundation/ct-unstable
fix(errors): Throwing rate limit errors as Error objects
2021-06-19 09:44:11 -07:00
Chris Troutner 5b300f68b2 fix(errors): Throwing rate limit errors as Error objects 2021-06-19 09:42:36 -07:00
Chris Troutner 3cce6f9754 Merge pull request #152 from Permissionless-Software-Foundation/ct-unstable
fix(testnet): Fixing testnet integration test
2021-06-18 09:43:06 -07:00
Chris Troutner 3da04d9327 fix(testnet): Fixing testnet integration test 2021-06-18 09:32:07 -07:00
Chris Troutner 79091815a2 Merge pull request #151 from Permissionless-Software-Foundation/ct-unstable
fix(Electrumx): Fixing integration test
2021-06-17 07:19:55 -07:00
Chris Troutner 258144d26d fix(Electrumx): Fixing integration test 2021-06-17 07:16:48 -07:00
Chris Troutner 4c4a45841b Merge pull request #150 from Permissionless-Software-Foundation/ct-unstable
fix(package.json): Using /v5 in npm scripts
2021-06-16 19:17:14 -07:00
Chris Troutner 010afd10a3 Merge branch 'master' into ct-unstable 2021-06-16 19:13:03 -07:00
Chris Troutner 82b4d2ab9e fix(package.json): Using /v5 in npm scripts 2021-06-16 19:12:48 -07:00
Chris Troutner 8eb6a21dc2 Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
feat(/v5 REST API): Using the new v5 route in bch-api
2021-06-16 19:03:45 -07:00
Chris Troutner 5f2a762d4f feat(/v5 REST API): Using the new v5 route in bch-api 2021-06-16 17:00:08 -07:00
Chris Troutner 24e2c78d03 Merge pull request #148 from Permissionless-Software-Foundation/ct-unstable
fix(testnet): Removing testnet ninsight tests as they rarely pass
2021-05-22 07:58:39 -07:00
Chris Troutner eab02fdbc9 fix(testnet): Removing testnet ninsight tests as they rarely pass 2021-05-22 07:55:02 -07:00
Chris Troutner f7bd9c75db Merge pull request #147 from Permissionless-Software-Foundation/ct-unstable
fix(nft docs): Fixing a typo in docs
2021-05-21 15:54:07 -07:00
Chris Troutner 2a96f3a4f5 fix(nft docs): Fixing a typo in docs 2021-05-21 15:51:43 -07:00
Chris Troutner 3dc4a8ba6f Merge pull request #146 from Permissionless-Software-Foundation/ct-unstable
fix(dependencies): Updating dependencies. 0 npm vulns
2021-05-21 15:39:46 -07:00
Chris Troutner 65916f2c5c fix(dependencies): Updating dependencies. 0 npm vulns 2021-05-21 15:38:02 -07:00
Chris Troutner 18188bfdce Merge pull request #145 from Permissionless-Software-Foundation/nft-group-get-call
Return parent NFT group token information
2021-05-18 12:40:16 -07:00
Stoyan Zhekov 45acc2b0b7 Return parent NFT group token information 2021-05-16 14:22:33 +09:00
Chris Troutner 31913c3fb7 Merge pull request #144 from Permissionless-Software-Foundation/nft-children-get-call
NFT children get call
2021-05-15 08:54:58 -07:00
Stoyan Zhekov 2311643398 Example fixed 2021-05-14 21:58:52 +09:00
Stoyan Zhekov 7501aa0238 Lint fixes 2021-05-14 21:18:08 +09:00
Stoyan Zhekov 92a89a543d List NFT children per group 2021-05-14 21:17:07 +09:00
Chris Troutner cdf8171994 Merge pull request #143 from Permissionless-Software-Foundation/ct-unstable
fix(integration tests): Adding integration tests for bchn with no whi…
2021-05-03 19:18:34 -07:00
Chris Troutner 385baf0afd fix(integration tests): Adding integration tests for bchn with no whitelist SLPDB 2021-05-03 19:15:41 -07:00
Chris Troutner 9a9aababd2 Merge pull request #142 from Permissionless-Software-Foundation/ct-unstable
fix(integration tests): Adding tests for bchn with no whitelist SLPDB
2021-05-03 19:06:14 -07:00
Chris Troutner c228acd28a fix(integration tests): Adding tests for bchn with no whitelist SLPDB 2021-05-03 18:53:41 -07:00
Chris Troutner c1c140bdce Merge pull request #139 from Permissionless-Software-Foundation/dh-getblock
feat(getBlock): Added getBlock endpoint to bch-js
2021-04-07 13:07:05 -07:00
Daniel Gonzalez 54d9b80f26 feat(getBlock): Added getBlock endpoint to bch-js 2021-04-06 21:33:33 -04:00
Chris Troutner 961a190309 Merge pull request #138 from Permissionless-Software-Foundation/ct-unstable
fix(debugging): Removing all the debugging statements
2021-03-26 18:05:04 -08:00
Chris Troutner 2d2a91dfde fix(debugging): Removing all the debugging statements 2021-03-26 18:57:08 -07:00
Chris Troutner fbac18d456 Merge pull request #137 from Permissionless-Software-Foundation/ct-unstable
More connection reset debugging
2021-03-26 17:32:23 -08:00
Chris Troutner 62ae67ceab Logging axios options in decodeOpReturn 2021-03-26 18:29:48 -07:00
Chris Troutner 8e64d854cf fix(hydrateUtxos): Adding debugging info for axiosOptions 2021-03-26 18:26:18 -07:00
Chris Troutner 5fe8e515ff Merge pull request #136 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Adding debugging information
2021-03-26 16:30:50 -08:00
Chris Troutner f11297339b fix(decodeOpReturn): Adding debugging information 2021-03-26 17:27:57 -07:00
Chris Troutner 61d52c8ebe Merge pull request #135 from Permissionless-Software-Foundation/ct-unstable
fix(debugging): Adding additional debugging info to SLP utils
2021-03-26 12:53:35 -08:00
Chris Troutner d89bbe0bf6 fix(debugging): Adding additional debugging info to SLP utils 2021-03-26 13:51:29 -07:00
Chris Troutner 2a1c2500e6 Merge pull request #134 from Permissionless-Software-Foundation/ct-unstable
fix(slp): Adding more verbose error messages for SLP utility functions
2021-03-26 12:16:55 -08:00
Chris Troutner 8879a5170a fix(slp): Adding more verbose error messages for SLP utility functions 2021-03-26 13:11:40 -07:00
Chris Troutner b29cb7c12b Merge pull request #133 from Permissionless-Software-Foundation/ct-unstable
fix(integration tests): slowing down integration tests for the sake o…
2021-03-26 08:28:46 -07:00
Chris Troutner 69fa10d3a6 fix(integration tests): slowing down integration tests for the sake of rate limits 2021-03-26 08:25:39 -07:00
Chris Troutner d16431d165 Merge pull request #132 from Permissionless-Software-Foundation/ct-unstable
Adding Transaction.get()
2021-03-24 07:30:48 -07:00
Chris Troutner faa2972460 fix(Transaction.get()): Added documentation 2021-03-24 07:28:38 -07:00
Chris Troutner 5d3ea2b559 fix(Transaction.get()): Added unit tests 2021-03-24 07:24:08 -07:00
Chris Troutner 40fa6440f4 feat(Transactions.get()): Added tx hydration method 2021-03-23 18:49:27 -07:00
Chris Troutner ce951afddb fix(RawTransactions.getTxData): Adding BCH value to tx inputs 2021-03-23 17:37:00 -07:00
Chris Troutner d2e847d3fb feat(transactions): Created transaction library 2021-03-23 17:11:37 -07:00
Chris Troutner fe697455c0 Merge branch 'master' into ct-unstable 2021-03-09 08:09:01 -08:00
Chris Troutner 25832d52fd fix(raw-transactions): Adding proper axios header for Basic Auth 2021-03-09 08:04:14 -08:00
Chris Troutner 9d0840c2fe Merge pull request #131 from Permissionless-Software-Foundation/ct-unstable
fix(tokenUtxoDetailsWL): Adding usrObj input variable
2021-03-08 17:37:31 -08:00
Chris Troutner 74dc3e7e1d Merge branch 'master' into ct-unstable 2021-03-08 17:33:25 -08:00
Chris Troutner 171786a470 fix(tokenUtxoDetailsWL): Adding usrObj input variable 2021-03-08 17:32:33 -08:00
Chris Troutner 5cbd685060 Merge pull request #130 from Permissionless-Software-Foundation/dh-bch-price
feat(price): Created getBchUsd() for bch-js
2021-03-05 07:51:32 -08:00
Daniel Gonzalez ef05aff7a3 feat(price): Created getBchUsd() for bch-js 2021-03-04 15:14:46 -04:00
Chris Troutner 3f01fdba7c Merge pull request #129 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Stringifying bch-api object when passing error
2021-03-02 07:49:14 -08:00
Chris Troutner 232747280e Merge branch 'master' into ct-unstable 2021-03-02 07:47:03 -08:00
Chris Troutner e96da1615b fix(hydrateUtxos): Stringifying bch-api object when passing error 2021-03-02 07:46:51 -08:00
Chris Troutner 9538501f01 Merge pull request #128 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adding better error handler
2021-03-02 07:31:43 -08:00
Chris Troutner 0a4fee5716 fix(hydrateUtxos): Adding better error handler 2021-03-02 07:29:41 -08:00
Chris Troutner cec79be4fa Merge pull request #127 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adding usrObj input for delaying UTXO processing
2021-03-01 08:21:23 -08:00
Chris Troutner c839f0b891 fix(integration tests): Fixing broken ABC integration test 2021-03-01 08:19:16 -08:00
Chris Troutner 24d629ebe1 fix(hydrateUtxos): Adding usrObj input for delaying UTXO processing 2021-03-01 07:48:08 -08:00
Chris Troutner f729918385 fix(_hydrateUtxo): Adding user-configurable artifical delay 2021-03-01 06:55:35 -08:00
Chris Troutner b7ab02c0ed Merge pull request #126 from Permissionless-Software-Foundation/ct-unstable
fix(docs): Updating docs and commenting out debug logs
2021-02-27 15:40:58 -08:00
Chris Troutner 1c291880e0 fix(docs): Updating docs and commenting out debug logs 2021-02-27 15:38:24 -08:00
Chris Troutner 09b79c4d3e Merge pull request #125 from Permissionless-Software-Foundation/ct-unstable
Adding unit tests for 429 and 503 errors
2021-02-27 14:56:14 -08:00
Chris Troutner 828f6a1b1c fix(_hydrateUtxo): Added unit tests 2021-02-27 14:53:54 -08:00
Chris Troutner 1b8727da55 fix(decodeOpReturn): Adding test cases for 429 and 503 errors 2021-02-27 13:28:53 -08:00
Chris Troutner f84862d875 Merge pull request #124 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Added NFT child test case
2021-02-27 13:12:30 -08:00
Chris Troutner 179d924899 Adding respond.data in error 2021-02-27 13:10:33 -08:00
Chris Troutner 676fb85abf fix(decodeOpReturn): Added NFT child test case 2021-02-27 12:58:40 -08:00
Chris Troutner d59c1fe445 Merge pull request #123 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Trying different error configuration
2021-02-27 10:03:58 -08:00
Chris Troutner 0a34611557 fix(decodeOpReturn): Trying different error configuration 2021-02-27 10:02:12 -08:00
Chris Troutner c13b029e99 Merge pull request #122 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Replacing error message with better one
2021-02-27 09:47:20 -08:00
Chris Troutner b038e9a7fd fix(decodeOpReturn): Replacing error message with better one 2021-02-27 09:45:24 -08:00
Chris Troutner 7b728fb45d Merge pull request #121 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Adding additional debugging for errors
2021-02-27 09:33:00 -08:00
Chris Troutner d4dbce8df0 fix(decodeOpReturn): Adding additional debugging for errors 2021-02-27 09:31:16 -08:00
Chris Troutner 2419aeceaa Merge pull request #120 from Permissionless-Software-Foundation/ct-unstable
fix(_hydrateUtxo): Adding more debugging
2021-02-27 09:09:58 -08:00
Chris Troutner 158b009069 fix(_hydrateUtxo): Adding more debugging 2021-02-27 09:07:50 -08:00
Chris Troutner f3c3250a69 Merge pull request #119 from Permissionless-Software-Foundation/ct-unstable
fix(_hydrateUtxo): Debugging error handling
2021-02-27 09:00:20 -08:00
Chris Troutner 5ae4db88c3 Merge branch 'master' into ct-unstable 2021-02-27 08:58:23 -08:00
Chris Troutner 33e161df26 fix(_hydrateUtxo): Debugging error handling 2021-02-27 08:58:12 -08:00
Chris Troutner 81c910361c Merge pull request #118 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Debugging error handling by decodeOpReturn()
2021-02-27 08:41:57 -08:00
Chris Troutner 9751a39538 Merge branch 'master' into ct-unstable 2021-02-27 08:38:38 -08:00
Chris Troutner 7058f77ee9 fix(decodeOpReturn): Debugging error handling by decodeOpReturn() 2021-02-27 08:37:29 -08:00
Chris Troutner ad580d67a4 Adding decatur test script 2021-02-27 08:23:07 -08:00
Chris Troutner acc0300a44 Merge pull request #117 from Permissionless-Software-Foundation/ct-unstable
fix(bip32-utils): Bumping to v1.0.0 with updated keccak
2021-02-24 18:56:39 -08:00
Chris Troutner 4e1b806230 fix(bip32-utils): Bumping to v1.0.0 with updated keccak 2021-02-24 18:53:20 -08:00
Chris Troutner 357e350176 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Adding usrObj to Electrumx.Transactions()
2021-02-24 17:59:35 -08:00
Chris Troutner f6140acc76 fix(electrumx): Adding usrObj to Electrumx.Transactions() 2021-02-24 17:53:53 -08:00
Chris Troutner df65b12aab Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): replacing ip with usrObj
2021-02-24 16:44:58 -08:00
Chris Troutner 754b4cd807 fix(decodeOpReturn): replacing ip with usrObj 2021-02-24 16:42:54 -08:00
Chris Troutner 3c6079e54c Merge pull request #114 from Permissionless-Software-Foundation/ct-unstable
fix(usrObj): Fixing typo of usbObj to usrObj
2021-02-24 16:37:09 -08:00
Chris Troutner 18621cd76c fix(usrObj): Fixing typo of usbObj to usrObj 2021-02-24 16:35:09 -08:00
Chris Troutner 456159a4cc Merge pull request #113 from Permissionless-Software-Foundation/ct-unstable
fix(usbObj): Passing a usrObj when bch-api calls bch-js
2021-02-24 16:29:54 -08:00
Chris Troutner 33e723befe fix(usbObj): Passing a usrObj when bch-api calls bch-js 2021-02-24 16:27:49 -08:00
Chris Troutner 30fdee1920 Merge pull request #112 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Switching to POST from GET to getRawTx call
2021-02-24 15:14:51 -08:00
Chris Troutner feb336c331 fix(decodeOpReturn): Switching to POST from GET to getRawTx call 2021-02-24 15:11:58 -08:00
Chris Troutner 8bacc0a991 Merge pull request #111 from Permissionless-Software-Foundation/ct-unstable
fix(ip): Renaming origin to ip
2021-02-24 14:10:07 -08:00
Chris Troutner d22f1ec8a6 fix(ip): Renaming origin to ip 2021-02-24 14:05:25 -08:00
Chris Troutner 7eb3a796b8 Merge pull request #110 from Permissionless-Software-Foundation/ct-unstable
fix(origin): Testing idea around passing origin when bch-api calls bc…
2021-02-24 13:29:05 -08:00
Chris Troutner 9919f811b0 fix(origin): Testing idea around passing origin when bch-api calls bch-js 2021-02-24 13:26:08 -08:00
Chris Troutner e7854481a0 Merge pull request #109 from Permissionless-Software-Foundation/ct-unstable
fix(dependencies): Removing several unused dependencies
2021-02-23 10:29:22 -08:00
Chris Troutner 0b406c467e fix(dependencies): Removing more unnecessary dependencies 2021-02-23 10:26:12 -08:00
Chris Troutner e41217b710 fix(dependencies): Removing several unused dependencies 2021-02-23 09:04:49 -08:00
Chris Troutner c22ce8d57d fix(dependencies): Ran npm audit with 0 vulns 2021-02-23 08:46:27 -08:00
Chris Troutner bf3612583e Merge pull request #108 from Permissionless-Software-Foundation/ct-unstable
Sleep: Adding a Promise-based wait function to Util lib
2021-02-15 15:28:26 -08:00
Chris Troutner ef56452ec9 feat(sleep): Adding sleep function to Util lib 2021-02-15 15:16:30 -08:00
Chris Troutner b3be470c0e fix(utxo.get()): Fixing ABC integration test 2021-02-15 15:12:39 -08:00
Chris Troutner 7274475898 Merge pull request #107 from Permissionless-Software-Foundation/ct-unstable
fix(package-lock.json): Updating package-lock.json
2021-02-13 17:46:19 -08:00
Chris Troutner 7bb347a833 fix(package-lock.json): Updating package-lock.json 2021-02-13 17:44:14 -08:00
Chris Troutner fadceedd00 Merge pull request #106 from Permissionless-Software-Foundation/ct-unstable
fix(debug): Removing debugging lines
2021-02-13 17:33:43 -08:00
Chris Troutner 7f4ef60f8e fix(debug): Removing debugging lines 2021-02-13 17:30:55 -08:00
Chris Troutner 81e54e0590 Merge pull request #105 from Permissionless-Software-Foundation/ct-unstable
Porting bch-util into bch-js
2021-02-13 15:07:44 -08:00
Chris Troutner 1d5e3da976 feat(chunk20): bchjs.Util.chunk20() - chunk array into 20-element arrays 2021-02-13 15:04:39 -08:00
Chris Troutner 2cff305f46 feat(floor2): bchjs.Util.floor2(num) - round down to 2 decimal places 2021-02-13 14:59:33 -08:00
Chris Troutner 9495381cf4 feat(floor8): bchjs.Util.floor8(num) - Round down to 8 decimal places 2021-02-13 14:55:12 -08:00
Chris Troutner 32f89346c3 linting 2021-02-13 14:38:37 -08:00
Chris Troutner e8582fd0ab feat(findBiggestUtxo): Ported from bch-util. Finds biggest UTXO in an array 2021-02-13 14:38:01 -08:00
Chris Troutner 7049b96df1 Merge pull request #104 from Permissionless-Software-Foundation/ct-unstable
utxo.get(): Refined and finalized.
2021-02-12 19:48:13 -08:00
Chris Troutner 9cc6824591 feat(utxo.get whitelist): Added flag to validate against the whitelist filtered SLPDB 2021-02-12 19:44:24 -08:00
Chris Troutner bb82d520ed feat(utxo.get()): Output changed for different types of utxos 2021-02-12 18:30:25 -08:00
Chris Troutner fef05124f1 feat(utxo.get()): Changes to accept arrays of up to 20 addresses 2021-02-12 15:58:16 -08:00
Chris Troutner 978db07526 Merge pull request #103 from Permissionless-Software-Foundation/ct-unstable
feat(utxo): Added Utxo.get() for easy getting of utxos
2021-02-11 19:25:42 -08:00
Chris Troutner 68bab5dd26 feat(utxo): Added Utxo.get() for easy getting of utxos 2021-02-11 19:22:25 -08:00
Chris Troutner 20e685206d Removing the integration/implementations folder 2021-02-11 14:14:15 -08:00
Chris Troutner e90afac701 Updating local testnet integration test script 2021-02-10 13:31:21 -08:00
Chris Troutner 96f96f837e Merge pull request #102 from Permissionless-Software-Foundation/ct-unstable
fix(abc): Fixing testing scripts for ABC chain
2021-02-09 15:12:47 -08:00
Chris Troutner 03be1d5838 fix(abc): Fixing testing scripts for ABC chain 2021-02-09 15:08:14 -08:00
Chris Troutner 6b6bfa2ae2 Merge pull request #101 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxosWL): hydrateUtxo but uses SLPDB with whitelist filter
2021-02-09 12:37:15 -08:00
Chris Troutner da3af3656a feat(hydrateUtxosWL): hydrateUtxo but uses SLPDB with whitelist filter 2021-02-09 12:34:25 -08:00
Chris Troutner 26fdb5c5d5 Merge pull request #100 from Permissionless-Software-Foundation/ct-unstable
fix(package.json): Slight reconfiguration by npm
2021-02-09 09:12:41 -08:00
Chris Troutner 86d71d0f49 fix(package.json): Slight reconfiguration by npm 2021-02-09 09:10:11 -08:00
Chris Troutner 86351aaf4f Merge pull request #99 from Permissionless-Software-Foundation/ct-unstable
tokenUtxoDetailsWL() and waterfallValidateTxid()
2021-02-09 07:25:13 -08:00
Chris Troutner 36432af7d5 feat(tokenUtxoDetailsWL): tokenUtxoDetails that use whitelist SLPDB 2021-02-09 07:22:01 -08:00
Chris Troutner 95c88b5f29 fix(_hydrateUtxo): Moving utxo hydration into a private function 2021-02-08 20:33:27 -08:00
Chris Troutner 28f563b58c Updating unit tests to reflect use of waterfallValidateTxid() 2021-02-08 19:17:50 -08:00
Chris Troutner 79ea4b77b9 fix(tokenUtxoDetails): Replacing validation with waterfallValidateTxid() 2021-02-08 17:25:08 -08:00
Chris Troutner d37a2f8139 Merge pull request #98 from Permissionless-Software-Foundation/ct-unstable
feat(waterfallValidateTxid): SLP.Utils.waterfallValidateTxid(txid)
2021-02-08 17:16:35 -08:00
Chris Troutner 3b873b6064 feat(waterfallValidateTxid): SLP.Utils.waterfallValidateTxid(txid) 2021-02-08 17:10:32 -08:00
Chris Troutner 687b5bca02 feat(waterfallValidateTxid): Adding a new waterfall SLP validation method 2021-02-08 10:45:24 -08:00
Chris Troutner b80697500c Adding integration test script to gitignore 2021-02-08 09:43:25 -08:00
Chris Troutner 626d27033e Merge pull request #96 from Permissionless-Software-Foundation/ct-unstable
fix(typo): Fixing typo in IPFS class
2021-02-03 17:41:29 -08:00
Chris Troutner ce2ee3fc1b fix(typo): Fixing typo in IPFS class 2021-02-03 17:38:43 -08:00
Chris Troutner 4768dcadc6 Merge pull request #95 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Updating the server that handles bch-ipfs file uploads
2021-02-03 15:42:47 -08:00
Chris Troutner 5fef09edbf fix(ipfs): Updating the server that handles bch-ipfs file uploads 2021-02-03 15:40:38 -08:00
Chris Troutner 37109cd7ef Merge pull request #94 from Permissionless-Software-Foundation/ct-unstable
fix(getTxDataSlp): Adding prototype function
2021-01-30 14:43:13 -08:00
Chris Troutner 83d523260d fix(getTxDataSlp): Adding prototype function 2021-01-30 14:40:46 -08:00
Chris Troutner 8768a810c7 Merge pull request #93 from Permissionless-Software-Foundation/ct-unstable
RawTransactions.getTxData() returns tx info with vin addresses
2021-01-29 12:49:50 -08:00
Chris Troutner 8832bfc8df fix(decodeOpReturn): Fixing documentation 2021-01-29 12:47:22 -08:00
Chris Troutner a5208110b9 feat(getTxData): RawTransactions.getTxData() returns tx info with vin addresses 2021-01-29 12:43:02 -08:00
Chris Troutner 99ad7cd87f fix(_getInputAddrs): Added private function with tests get tx input addrs 2021-01-29 12:11:23 -08:00
Chris Troutner 68ae084fb0 Merge pull request #92 from Permissionless-Software-Foundation/ct-unstable
Merging documentation changes from @uncletammy
2021-01-29 07:20:32 -08:00
Chris Troutner b0b0e17abc Merge pull request #91 from uncletammy/master
Corrects example usages for HDNode.Mnemonic.toSeed method
2021-01-29 07:11:57 -08:00
Nicholas Crumrine a54306e382 corrects example usages for HDNode.toSeed method 2021-01-28 15:24:03 -06:00
Chris Troutner 1860232fa6 Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
fix(sortAllTxs): Renaming Electrumx.sort0ConfTxs to sortAllTxs
2021-01-27 07:28:53 -08:00
Chris Troutner d8437cf3fb fix(sortAllTxs): Renaming Electrumx.sort0ConfTxs to sortAllTxs 2021-01-27 07:24:28 -08:00
Chris Troutner 338788c46d Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
fix(sort0ConfTxs): Switching to Descending by default
2021-01-27 06:57:15 -08:00
Chris Troutner cad2c6378e fix(sort0ConfTxs): Switching to Descending by default 2021-01-27 06:51:36 -08:00
Chris Troutner 2e6938e21f Merge pull request #88 from Permissionless-Software-Foundation/ct-unstable
feat(tx sorting): added Electrumx.sort0ConfTxs()
2021-01-26 20:28:40 -08:00
Chris Troutner 1e13c71715 Added integration test 2021-01-26 20:25:03 -08:00
Chris Troutner d4b0807d16 feat(tx sorting): added Electrumx.sort0ConfTxs() 2021-01-26 20:20:49 -08:00
Chris Troutner 611c7977ef Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
fix(license): Updating license
2021-01-26 08:06:20 -08:00
Chris Troutner 39ac634675 fix(license): Updating license 2021-01-26 08:00:07 -08:00
Chris Troutner 278a54bd9b Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2021-01-19 14:08:03 -08:00
Chris Troutner 30b1f71d81 feat(OpenBazaar): Removing support for OpenBazaar API since it's been taken down 2021-01-19 13:43:07 -08:00
Chris Troutner e7d7fc1019 Merge branch 'master' into ct-unstable 2021-01-19 13:11:59 -08:00
Chris Troutner 524da532e2 Merge pull request #85 from Permissionless-Software-Foundation/fix/generateBurnOpReturn-tokenQty
fix(generateBurnOpReturn): Added handler when burning amount is higher than token amount
2021-01-16 11:14:42 -08:00
Gary Nadir aef56c5c14 fix(generateBurnOpReturn): Added handler when burning amount is higher than token amount 2021-01-16 14:22:02 -04:00
Chris Troutner e2ba265878 Updating README 2021-01-11 16:45:47 -08:00
Chris Troutner 2472dc3606 Updating bch-api paths to v4 in readme 2021-01-09 15:48:02 -08:00
Chris Troutner 2df36124b4 Merge pull request #84 from Permissionless-Software-Foundation/ct-unstable
fix(tokenUtxoDetails): Removing requirement to provide satoshis or va…
2021-01-09 15:45:43 -08:00
Chris Troutner c422d8db34 fix(tokenUtxoDetails): Removing requirement to provide satoshis or value in input 2021-01-09 15:43:29 -08:00
Chris Troutner ec2d8ea80a Merge pull request #83 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Updated unit and integration tests
2021-01-07 17:52:02 -08:00
Chris Troutner fc527834ea Merge branch 'master' into ct-unstable 2021-01-07 17:44:56 -08:00
Chris Troutner 3ceebc1f12 fix(tests): Updated unit and integration tests 2021-01-07 17:44:04 -08:00
73 changed files with 23052 additions and 4781 deletions
+1
View File
@@ -3,6 +3,7 @@ node_modules/*
wallet-info.txt
wallet.json
integration-test-sweet.sh
integration-test-ss.sh
docs/
coverage/
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2020 Chris Troutner
Copyright 2021 Chris Troutner
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:
+7 -7
View File
@@ -1,9 +1,9 @@
# bch-js
[![Build Status](https://travis-ci.org/Permissionless-Software-Foundation/bch-js.svg?branch=master)](https://travis-ci.org/Permissionless-Software-Foundation/bch-js)
[![Version](https://img.shields.io/npm/v/@psf/bch-js)](https://www.npmjs.com/package/@psf/bch-js)
[![Downloads/week](https://img.shields.io/npm/dw/@psf/bch-js)](https://npmjs.org/package/@psf/bch-js)
[![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard)
[bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) blockchains. It can be used for free, but requires an account on [FullStack.cash](https://fullstack.cash) for increased rate limits. Learn more from [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) about Full Stack Bitcoin Cash development.
@@ -35,15 +35,15 @@ const BCHJS = require("@psf/bch-js")
let bchjs = new BCHJS() // Defaults to BCHN network.
// testnet
bchjs = new BCHJS({ restURL: 'https://testnet3.fullstack.cash/v3/' })
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/v3/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v3/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v3/
- 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/
- Check server status: https://metrics.fullstack.cash
### API Key
@@ -57,7 +57,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.
})
```
@@ -117,7 +117,7 @@ Have questions? Need help? Join our community support
Copies of this repository are also published on [IPFS](https://ipfs.io).
- v3.8.10: QmauhggzKDo9A9je5y2K6WRAp32JmGRFCw2ZFTiSnW8yot
- v4.5.4: QmWv3pxJy3MH8vU5nLUVyzqFxfNKXLQQEnz1rgStNuQijd
## License
+1 -1
View File
@@ -1,3 +1,3 @@
Examples have been moved to this repository:
Examples have been moved to this repository
https://github.com/Permissionless-Software-Foundation/bch-js-examples
+18269 -3473
View File
File diff suppressed because it is too large Load Diff
+22 -32
View File
@@ -4,20 +4,26 @@
"description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
"author": "Chris Troutner <chris.troutner@gmail.com>",
"contributors": [
"Gabriel Cardona <gabriel@bitcoin.com>"
"Gabriel Cardona <gabriel@bitcoin.com>",
"Stoyan Zhekov <zh@zhware.net>"
],
"main": "src/bch-js",
"scripts": {
"test": "nyc mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
"test:integration": "npm run test:integration:bchn",
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v4/ && 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/v4/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"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:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration",
"test:integration:local:bchn": "RESTURL=http://localhost:3000/v4/ bash test/integration/test-bchn-integration.sh",
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/testnet",
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#getStatus' test/integration/",
"test:temp2": "mocha --timeout 30000 -g '#getStatus' test/unit/",
"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: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/",
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v5/ && mocha --timeout 30000 -g '#DSProof' test/integration/chains/",
"test:temp4": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
@@ -30,52 +36,36 @@
"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",
"apidoc": "^0.25.0",
"assert": "^2.0.0",
"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",
"@psf/bip21": "^2.0.1",
"@psf/bip32-utils": "^0.13.1",
"bip38": "^2.0.2",
"bip39": "^3.0.2",
"bip66": "^1.1.5",
"@psf/bitcoincash-ops": "^2.0.0",
"@psf/bitcoincashjs-lib": "^4.0.2",
"bitcoinjs-message": "^2.0.0",
"bs58": "^4.0.1",
"buffer": "^5.1.0",
"cashaddrjs": "^0.3.3",
"chalk": "^2.3.0",
"clear": "0.1.0",
"@psf/coininfo": "^4.0.0",
"commander": "^3.0.0",
"cp-file": "^7.0.0",
"ecurve": "^1.0.6",
"figlet": "^1.2.0",
"git-clone": "^0.1.0",
"ini": "^1.3.8",
"mkdirp": "^0.5.1",
"node-cmd": "^3.0.0",
"node-emoji": "^1.8.1",
"qrcode": "^1.4.1",
"randombytes": "^2.0.6",
"repl.history": "^0.1.4",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1",
"touch": "^3.1.0",
"wif": "^2.0.6"
},
"devDependencies": {
"apidoc": "^0.17.7",
"assert": "^2.0.0",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
@@ -90,7 +80,7 @@
"node-mocks-http": "^1.7.0",
"nyc": "^14.1.0",
"prettier": "^1.18.2",
"semantic-release": "^17.3.1",
"semantic-release": "^17.3.9",
"sinon": "^7.3.2",
"standard": "^16.0.3"
},
+1 -1
View File
@@ -6,7 +6,7 @@ const coininfo = require('@psf/coininfo')
class Address {
constructor (config) {
const tmp = {}
if (!config || !config.restURL) tmp.restURL = 'https://api.bchjs.cash/v4/'
if (!config || !config.restURL) tmp.restURL = 'https://api.bchjs.cash/v5/'
else tmp.restURL = config.restURL
this.restURL = tmp.restURL
+24 -11
View File
@@ -7,8 +7,8 @@
*/
// bch-api mainnet.
const DEFAULT_REST_API = 'https://api.fullstack.cash/v4/'
// const DEFAULT_REST_API = "http://localhost:3000/v4/"
const DEFAULT_REST_API = 'https://api.fullstack.cash/v5/'
// const DEFAULT_REST_API = "http://localhost:3000/v5/"
// local deps
const BitcoinCash = require('./bitcoincash')
@@ -26,29 +26,42 @@ const TransactionBuilder = require('./transaction-builder')
const ECPair = require('./ecpair')
const Script = require('./script')
const Price = require('./price')
const Socket = require('./socket')
const Schnorr = require('./schnorr')
const SLP = require('./slp/slp')
const IPFS = require('./ipfs')
const Encryption = require('./encryption')
const Utxo = require('./utxo')
const Transaction = require('./transaction')
const DSProof = require('./dsproof')
// Indexers
const OpenBazaar = require('./openbazaar')
const Ninsight = require('./ninsight')
const Electrumx = require('./electrumx')
class BCHJS {
constructor (config) {
// Try to retrieve the REST API URL from different sources.
if (config && config.restURL && config.restURL !== '') { this.restURL = config.restURL } else if (process.env.RESTURL && process.env.RESTURL !== '') { this.restURL = process.env.RESTURL } else this.restURL = DEFAULT_REST_API
if (config && config.restURL && config.restURL !== '') {
this.restURL = config.restURL
} else if (process.env.RESTURL && process.env.RESTURL !== '') {
this.restURL = process.env.RESTURL
} else this.restURL = DEFAULT_REST_API
// Retrieve the apiToken
this.apiToken = '' // default value.
if (config && config.apiToken && config.apiToken !== '') { this.apiToken = config.apiToken } else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') { this.apiToken = process.env.BCHJSTOKEN }
if (config && config.apiToken && config.apiToken !== '') {
this.apiToken = config.apiToken
} else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') {
this.apiToken = process.env.BCHJSTOKEN
}
// Retrieve the Basic Authentication password.
this.authPass = '' // default value.
if (config && config.authPass && config.authPass !== '') { this.authPass = config.authPass } else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') { this.authPass = process.env.BCHJSAUTHPASS }
if (config && config.authPass && config.authPass !== '') {
this.authPass = config.authPass
} else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') {
this.authPass = process.env.BCHJSAUTHPASS
}
// Generate a Basic Authentication token from an auth password
this.authToken = ''
@@ -68,9 +81,6 @@ class BCHJS {
// console.log(`apiToken: ${this.apiToken}`)
// Populate OpenBazaar endpoints
this.OpenBazaar = new OpenBazaar(libConfig)
// Bitcoin.com Ninsight indexer
this.Ninsight = new Ninsight(config)
@@ -98,13 +108,16 @@ class BCHJS {
this.TransactionBuilder = TransactionBuilder
this.TransactionBuilder.setAddress(this.Address)
this.Util = new Util(libConfig)
this.Socket = Socket
this.Schnorr = new Schnorr(libConfig)
this.SLP = new SLP(libConfig)
this.SLP.HDNode = this.HDNode
this.IPFS = new IPFS()
this.Utxo = new Utxo(libConfig)
this.Transaction = new Transaction(libConfig)
this.DSProof = new DSProof(libConfig)
}
}
+32 -22
View File
@@ -68,8 +68,8 @@ class Blockchain {
* @apiName getBlock
* @apiGroup Blockchain
* @apiDescription
* If verbose is false, returns a string that is serialized, hex-encoded data for block 'hash'. If verbose is true, returns an Object with information about block hash.
*
* If verbose is 0, returns a string that is serialized, hex-encoded data for block 'hash'. If verbose is 1, returns an Object with information about block hash.
* If verbose is 2, returns an Object with information about block hash and information about tx.
* @apiExample Example usage:
* (async () => {
* try {
@@ -80,28 +80,38 @@ class Blockchain {
* }
* })()
*
* // { hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
* // confirmations: 528236,
* // size: 216,
* // height: 1000,
* // version: 1,
* // versionHex: '00000001',
* // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
* // tx:
* // [ 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33' ],
* // time: 1232346882,
* // mediantime: 1232344831,
* // nonce: 2595206198,
* // bits: '1d00ffff',
* // difficulty: 1,
* // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9',
* // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d',
* // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6' }
* // {
* // hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
* // confirmations: 528236,
* // size: 216,
* // height: 1000,
* // version: 1,
* // versionHex: '00000001',
* // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
* // tx:
* // [ 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33' ],
* // time: 1232346882,
* // mediantime: 1232344831,
* // nonce: 2595206198,
* // bits: '1d00ffff',
* // difficulty: 1,
* // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9',
* // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d',
* // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6'
* // }
*/
async getBlock (blockhash, verbose = true) {
async getBlock (blockhash, verbosity = 1) {
try {
const response = await axios.get(
`${this.restURL}blockchain/getBlock/${blockhash}?verbose=${verbose}`,
// Input validation
if (!blockhash || typeof blockhash !== 'string') {
throw new Error('blockhash must be a string')
}
const response = await axios.post(
`${this.restURL}blockchain/getblock`,
{
blockhash,
verbosity
},
_this.axiosOptions
)
return response.data
+81
View File
@@ -0,0 +1,81 @@
const axios = require('axios')
let _this
class DSProof {
constructor (config) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.authToken = config.authToken
this.axios = axios
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}`
}
}
}
_this = this
}
/**
* @api DSProof.getDSProof() getDSProof()
* @apiName getDSProof
* @apiGroup DSProof
* @apiDescription Checks if a transaction generated a double-spend proof.
*
* If a double-spend is attempted, one of the transactions will generate a
* 'double spend proof'. This call can be used to check if a transaction
* generated such a proof.
*
* Merchants should wait 3-5 seconds after receiving notification of a
* transaction before calling this endpoint, to see if the TXID generated a
* proof. If this method returns no data, then the TX can be considered 'safe'
* and not a double spend. If proof data is returned by this method, then
* the transaction generated a proof and can be considered a 'double spend'.
*
* @apiExample Example usage:
* (async () => {
* try {
* const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
* const result = await bchjs.DSProof.getDSProof(txid)
* console.log(result);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* null
*/
async getDSProof (txid) {
try {
if (!txid) {
throw new Error('txid is required')
}
if (txid.length !== 64) {
throw new Error(`txid must be of length 64 (not ${txid.length})`)
}
const response = await _this.axios.get(
`${this.restURL}dsproof/getdsproof/${txid}`,
_this.axiosOptions
)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = DSProof
+111 -4
View File
@@ -2,9 +2,12 @@
This library interacts with the ElectrumX bch-api REST API endpoints operated
by FullStack.cash
*/
// Public npm libraries
const axios = require('axios')
// Local libraries.
const Blockchain = require('./blockchain')
let _this
class ElectrumX {
@@ -29,6 +32,8 @@ class ElectrumX {
}
}
this.blockchain = new Blockchain(config)
_this = this
}
@@ -279,7 +284,7 @@ class ElectrumX {
* }
*
*/
async transactions (address) {
async transactions (address, usrObj = null) {
try {
// Handle single address.
if (typeof address === 'string') {
@@ -294,7 +299,8 @@ class ElectrumX {
const response = await axios.post(
`${this.restURL}electrumx/transactions`,
{
addresses: address
addresses: address,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
@@ -452,7 +458,9 @@ class ElectrumX {
} catch (error) {
// console.log("error: ", error)
if (error.response && error.response.data) {
if (error.response && error.response.data) { throw new Error(error.response.data.error) } else throw error.response.data
if (error.response && error.response.data) {
throw new Error(error.response.data.error)
} else throw error.response.data
} else {
throw error
}
@@ -596,6 +604,105 @@ class ElectrumX {
else throw error
}
}
/**
* @api Electrumx.sortConfTxs() sortConfTxs()
* @apiName ElectrumX sortConfTxs
* @apiGroup ElectrumX
* @apiDescription Sort the output of Electrum.transactions() by block height.
*
* A simple sort function for the output of Electrum.transactions(). Ignores
* unconfirmed transactions.
*
* Sorts in 'DESCENDING' order by default, or 'ASCENDING' can be specified.
* Descending makes the first element the newest (largest block height).
*
* @apiExample Example usage:
* (async () => {
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
* const sortedTxs = bchjs.Electrumx.sortConfTxs(txs.transactions, 'ASCENDING')
* console.log(sortedTxs)
* })()
*
* // [
* // {
* // "height": 560430,
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
* // },
* // {
* // "height": 560534,
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
* // }
* // ]
*/
// Sort confirmed Transactions by the block height
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
try {
// 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)
}
return txs.sort((a, b) => a.height - b.height)
} catch (err) {
console.log('Error in util.js/sortConfTxs()')
throw err
}
}
/**
* @api Electrumx.sortAllTxs() sortAllTxs()
* @apiName ElectrumX sortAllTxs
* @apiGroup ElectrumX
* @apiDescription Sort the output of Electrum.transactions() by block height.
*
* A simple sort function for the output of Electrum.transactions().
* Assumes that unconfirmed transactions will make it into the next block. Any
* unconfirmed transactions have their block height with the height of the next
* block. Returns a Promise.
*
* Sorts in 'ASCENDING' order by default, or 'DESCENDING' can be specified.
*
* @apiExample Example usage:
* (async () => {
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
* const sortedTxs = await bchjs.Electrumx.sortAllTxs(txs.transactions, 'ASCENDING')
* console.log(sortedTxs)
* })()
*
* // [
* // {
* // "height": 560430,
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
* // },
* // {
* // "height": 560534,
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
* // }
* // ]
*/
// Substitute zero-conf txs with the current block-height + 1
async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
try {
// Calculate the height of the next block
const nextBlock = (await this.blockchain.getBlockCount()) + 1
// Replace the height of any zero-conf transactions with the height of
// the next block.
const modifiedTxs = txs.map(elem => {
if (elem.height <= 0) elem.height = nextBlock
return elem
})
// console.log(`modifiedTxs: ${JSON.stringify(modifiedTxs, null, 2)}`)
// Sort the modified array of transactions.
return this.sortConfTxs(modifiedTxs, sortingOrder)
} catch (err) {
console.log('Error in util.js/sort0ConfTxs')
throw err
}
}
}
module.exports = ElectrumX
+22 -22
View File
@@ -19,7 +19,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* bchjs.HDNode.fromSeed(seedBuffer);
*
@@ -28,7 +28,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* bchjs.HDNode.fromSeed(seedBuffer);
*/
@@ -54,7 +54,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to legacy address
@@ -66,7 +66,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to cash address
@@ -88,7 +88,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to cash address
@@ -100,7 +100,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to cash address
@@ -121,7 +121,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer);
* // to cash address
@@ -133,7 +133,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer);
* // to cash address
@@ -156,7 +156,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to WIF
@@ -168,7 +168,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to WIF
@@ -190,7 +190,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to extended public key
@@ -202,7 +202,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to extended public key
@@ -224,7 +224,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to extended private key
@@ -236,7 +236,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // to extended private key
@@ -258,7 +258,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create root seed buffer from mnemonic
* let rootSeed= bchjs.Mnemonic.toSeed(mnemonic);
* let rootSeed= await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from root seed
* let hdNode = bchjs.HDNode.fromSeed(rootSeed);
* // create public key buffer from HDNode
@@ -269,7 +269,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // create public key buffer from HDNode
@@ -290,7 +290,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create root seed buffer from mnemonic
* let rootSeed= bchjs.Mnemonic.toSeed(mnemonic);
* let rootSeed= await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from root seed
* let hdNode = bchjs.HDNode.fromSeed(rootSeed);
* // create public key buffer from HDNode
@@ -302,7 +302,7 @@ class HDNode {
* // create mnemonic from entropy
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // create public key buffer from HDNode
@@ -370,7 +370,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // derive hardened child HDNode
@@ -391,7 +391,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // derive unhardened child HDNode
@@ -412,7 +412,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create seed buffer from mnemonic
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create HDNode from seed buffer
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
* // derive hardened child HDNode
@@ -615,7 +615,7 @@ class HDNode {
* // create mnemonic
* let mnemonic = bchjs.Mnemonic.generate(128);
* // create root seed buffer
* let rootSeedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
* let rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
* // create master hd node
* let masterHDNode = bchjs.HDNode.fromSeed(rootSeedBuffer);
* // derive child node
+1 -1
View File
@@ -14,7 +14,7 @@ class IPFS {
constructor (config) {
this.IPFS_API = process.env.IPFS_API
? process.env.IPFS_API
: 'https://ipfs-api.fullstack.cash'
: 'https://ipfs-file-upload.fullstackcash.nl'
// : `http://localhost:5001`
// Default options when calling axios.
-229
View File
@@ -1,229 +0,0 @@
/*
This library interacts with the public REST API endpoints that OpenBazaar
provides for their application.
*/
const axios = require('axios')
let _this
class OpenBazaar {
constructor (config) {
this.restURL = config.restURL
this.apiToken = config.apiToken
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`
}
}
_this = this
}
/**
* @api OpenBazaar.balance() balance()
* @apiName OpenBazaar Balance
* @apiGroup OpenBazaar
* @apiDescription Return Balance about an address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let balance = await bchjs.OpenBazaar.balance('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
* console.log(balance)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* page: 1,
* totalPages: 1,
* itemsOnPage: 1000,
* addrStr: "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9",
* balance: "0.00001",
* totalReceived: "0.00001",
* totalSent: "0",
* unconfirmedBalance: "0",
* unconfirmedTxApperances: 0,
* txApperances: 1,
* transactions: [
* "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7"
* ]
* }
*
*/
async balance (address) {
try {
// Handle single address.
if (typeof address === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/address/${address}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/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 OpenBazaar.utxo() utxo()
* @apiName OpenBazaar Utxo
* @apiGroup OpenBazaar
* @apiDescription Return list of uxto for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.OpenBazaar.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* [
* {
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
* vout: 0,
* amount: "0.00001",
* satoshis: 1000,
* height: 602405,
* confirmations: 11
* }
* ]
*/
async utxo (address) {
try {
// Handle single address.
if (typeof address === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/utxo/${address}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/utxo/${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 OpenBazaar.tx() tx()
* @apiName OpenBazaar Tx
* @apiGroup OpenBazaar
* @apiDescription Get details on a transaction.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txDetails = await bchjs.OpenBazaar.tx(`2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7`);
* console.log(txDetails);
* } catch(error) {
* console.error(error)
* }
* })()
*
*{
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
* version: 2,
* vin: [
* {
* txid: "5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac",
* vout: 1,
* sequence: 4294967295,
* n: 0,
* scriptSig: {
* hex:
* "473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9"
* },
* addresses: ["bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe"],
* value: "0.00047504"
* }
* ],
* vout: [
* {
* value: "0.00001",
* n: 0,
* scriptPubKey: {
* hex: "76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac",
* addresses: ["bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9"]
* },
* spent: false
* },
* {
* value: "0.00046256",
* n: 1,
* scriptPubKey: {
* hex: "76a9142dbf5e1804c39a497b908c876097d63210c8490288ac",
* addresses: ["bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e"]
* },
* spent: false
* }
* ],
* blockhash: "0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5",
* blockheight: 602405,
* confirmations: 11,
* blocktime: 1569792892,
* valueOut: "0.00047256",
* valueIn: "0.00047504",
* fees: "0.00000248",
* hex:
* "0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000"
* }
*
*/
async tx (txid) {
try {
// Handle single txid.
if (typeof txid === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/tx/${txid}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/tx/${txid}`,
_this.axiosOptions
)
}
return response.data
}
throw new Error('Input txid must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = OpenBazaar
+35
View File
@@ -156,6 +156,41 @@ class Price {
else throw err
}
}
/**
* @api price.getBchUsd() getBchUsd()
* @apiName Price getBchUsd()
* @apiGroup Price
* @apiDescription Return current price of BCH in USD.
* This endpoint gets the USD price of BCH 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.getBchUsd();
* console.log(current);
* } catch(err) {
* console.err(err)
* }
*})()
*
* // 512.81
*/
async getBchUsd () {
try {
const response = await this.axios.get(
`${this.restURL}price/bchusd`,
_this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
return response.data.usd
} catch (err) {
if (err.response && err.response.data) throw err.response.data
else throw err
}
}
}
module.exports = Price
+124 -21
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,9 +111,7 @@ class RawTransactions {
data: {
hexes: hex
},
headers: {
authorization: `Token ${_this.apiToken}`
}
headers: this.axiosOptions.headers
}
const response = await axios(options)
@@ -122,7 +120,8 @@ class RawTransactions {
throw new Error('Input must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
}
}
@@ -167,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
@@ -178,9 +177,7 @@ class RawTransactions {
data: {
hexes: script
},
headers: {
authorization: `Token ${this.apiToken}`
}
headers: this.axiosOptions.headers
}
const response = await axios(options)
@@ -189,7 +186,8 @@ class RawTransactions {
throw new Error('Input must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
}
}
@@ -258,12 +256,12 @@ class RawTransactions {
* // time: 1547752564,
* // blocktime: 1547752564 } ]
*/
async getRawTransaction (txid, verbose = false) {
async getRawTransaction (txid, verbose = false, usrObj = null) {
try {
if (typeof txid === 'string') {
const response = await axios.get(
`${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=${verbose}`,
_this.axiosOptions
this.axiosOptions
)
return response.data
@@ -273,9 +271,10 @@ class RawTransactions {
url: `${this.restURL}rawtransactions/getRawTransaction`,
data: {
txids: txid,
verbose: verbose
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,7 +283,110 @@ class RawTransactions {
throw new Error('Input must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
}
}
// Given verbose transaction details, this function retrieves the transaction
// data for the inputs (the parent transactions). It returns an array of
// objects. Each object corresponds to a transaction input, and contains
// the address that generated that input UTXO.
//
// Assumes a single TX. Does not yet work with an array of TXs.
// This function returns an array of objects, each object if formated as follows:
// {
// vin: 0, // The position of the input for the given txid
// address: bitcoincash:qzhrpmu7nruyfcemeanqh5leuqcnf6zkjq4qm9nqh0
// }
async _getInputAddrs (txDetails) {
try {
const retArray = [] // Return array
for (let i = 0; i < txDetails.vin.length; i++) {
// The first input represents the sender of the BCH or tokens.
const vin = txDetails.vin[i]
const inputTxid = vin.txid
const inputVout = vin.vout
// Get the TX details for the input, in order to retrieve the address of
// the sender.
const txDetailsParent = await this.getRawTransaction(inputTxid, true)
// console.log(
// `txDetailsParent: ${JSON.stringify(txDetailsParent, null, 2)}`
// )
// The vout from the previous tx that represents the sender.
const voutSender = txDetailsParent.vout[inputVout]
retArray.push({
vin: i,
address: voutSender.scriptPubKey.addresses[0],
value: voutSender.value
})
}
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
}
}
/**
* @api RawTransactions.getTxData() getTxData()
* @apiName getTxData
* @apiGroup RawTransactions
* @apiDescription
* Returns an object of transaction data, including addresses for input UTXOs.
*
* This function is equivalent to running `getRawTransaction (txid, true)`,
* execept the `vin` array will be populated with an `address` property that
* contains the `bitcoincash:` address of the sender for each input.
*
* This function will only work with a single txid. It does not yet support an
* array of TXIDs.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.RawTransactions.getTxData("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*/
// Equivalent to running: async getRawTransaction (txid, verbose = true)
// Only handles a single TXID (not arrays).
// Appends the BCH address to the inputs of the transaction.
async getTxData (txid) {
try {
if (typeof txid !== 'string') {
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.
const txDetails = await this.getRawTransaction(txid, true)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
const inAddrs = await this._getInputAddrs(txDetails)
// console.log(`inAddrs: ${JSON.stringify(inAddrs, null, 2)}`)
// Add the input address to the transaction data.
for (let i = 0; i < inAddrs.length; i++) {
txDetails.vin[i].address = inAddrs[i].address
txDetails.vin[i].value = inAddrs[i].value
}
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
}
}
@@ -327,7 +429,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') {
@@ -348,16 +450,17 @@ 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
}
throw new Error('Input hex must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.error) throw new Error(error.error)
else if (error.response && error.response.data) throw error.response.data
else throw error
}
}
+100 -2
View File
@@ -8,16 +8,21 @@
(Parent) token.
*/
// Public npm libraries
const axios = require('axios')
// Local libraries.
const Address = require('./address')
// const BigNumber = require('bignumber.js')
const slpMdm = require('slp-mdm')
let _this
// const addy = new Address()
let addy
const TransactionBuilder = require('../transaction-builder')
class TokenType1 {
class Nft1 {
constructor (config) {
this.restURL = config.restURL
@@ -25,6 +30,8 @@ class TokenType1 {
// Instantiate the transaction builder.
TransactionBuilder.setAddress(addy)
_this = this
}
/**
@@ -412,6 +419,97 @@ class TokenType1 {
throw err
}
}
/**
* @api SLP.NFT1.listNFTGroupChildren() listNFTGroupChildren()
* @apiName listNFTGroupChildren
* @apiGroup SLP NFT1
* @apiDescription Return list of NFT children tokens in a NFT Group.
* It's assumed provided groupId parameter is for an NFT Group token (type=129)
*
* Returns an Array with GENESIS transaction IDs of the children tokens.
*
* @apiExample Example usage:
*
* const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
* const children = await bchjs.SLP.Nft1.listNFTGroupChildren(groupId)
*
* children = {
* "nftChildren": [
* "45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9",
* "928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55"
* ]
* }
*/
async listNFTGroupChildren (groupId) {
try {
if (typeof groupId === 'string') {
const response = await axios.get(
`${this.restURL}slp/nftChildren/${groupId}`,
_this.axiosOptions
)
return response.data
}
throw new Error('groupId must be a string.')
} catch (error) {
// console.log('Error in listNFTGroupChildren()')
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api SLP.NFT1.parentNFTGroup() parentNFTGroup()
* @apiName parentNFTGroup
* @apiGroup SLP NFT1
* @apiDescription Return parent NFT Group information for a given NFT child token.
* It's assumed provided groupId parameter is for an NFT Child token (type=65)
*
* Returns a JSON with NFT group information.
*
* @apiExample Example usage:
*
* const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
* const group = await bchjs.SLP.Nft1.parentNFTGroup(tokenId)
*
* group = {
* "nftGroup": {
* "decimals": 0,
* "timestamp": "2021-05-03 10:36:01",
* "timestamp_unix": 1620038161,
* "versionType": 129,
* "documentUri": "psfoundation.cash",
* "symbol": "PSF.TEST.GROUP",
* "name": "PSF Test NFT Group",
* "containsBaton": true,
* "id": "68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a",
* "documentHash": null,
* "initialTokenQty": 1000000,
* "blockCreated": 686117,
* "totalMinted": null,
* "totalBurned": null,
* "circulatingSupply": null
* }
* }
*/
async parentNFTGroup (tokenId) {
try {
if (typeof tokenId === 'string') {
const response = await axios.get(
`${this.restURL}slp/nftGroup/${tokenId}`,
_this.axiosOptions
)
return response.data
}
throw new Error('tokenId must be a string.')
} catch (error) {
// console.log('Error in parentNFTGroup()')
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = TokenType1
module.exports = Nft1
+1 -1
View File
@@ -41,7 +41,7 @@ class SLP {
this.Address = new Address(config)
this.ECPair = ECPair
this.TokenType1 = new TokenType1(config)
this.NFT1 = new NFT1(this.restURL)
this.NFT1 = new NFT1(config)
this.Utils = new Utils(config)
}
}
+8 -3
View File
@@ -118,11 +118,11 @@ class TokenType1 {
// Convert the send quantity to the format expected by slp-mdm.
const baseQty = sendQtyBig.toString()
// console.log(`baseQty: `, baseQty)
// console.log('baseQty: ', baseQty)
// Convert the change quantity to the format expected by slp-mdm.
const baseChange = change.toString()
// console.log(`baseChange: `, baseChange)
// console.log('baseChange: ', baseChange)
// Check for potential burns
const outputQty = new BigNumber(baseChange).plus(
@@ -220,7 +220,12 @@ class TokenType1 {
// Calculate the total amount of tokens owned by the wallet.
let totalTokens = 0
for (let i = 0; i < tokenUtxos.length; i++) {
totalTokens += tokenUtxos[i].tokenQty
totalTokens += parseFloat(tokenUtxos[i].tokenQty)
}
// Make sure burn quantity isn't bigger than the total amount in tokens
if (burnQty > totalTokens) {
burnQty = totalTokens
}
const remainder = totalTokens - burnQty
+472 -201
View File
@@ -1,12 +1,12 @@
/* eslint-disable no-useless-catch */
// Public npm libraries
const axios = require('axios')
const slpParser = require('slp-parser')
const BigNumber = require('bignumber.js')
// const Script = require("../script")
// const scriptLib = new Script()
// const BigNumber = require("bignumber.js")
// Local libraries
const Util = require('../util')
let _this
@@ -37,6 +37,8 @@ class Utils {
_this = this
this.whitelist = []
this.util = new Util(config)
}
/**
@@ -436,10 +438,11 @@ class Utils {
// will be like the examples above. If SLPDB has fallen behind real-time
// processing, it will return this output:
// [ null ]
async validateTxid (txid) {
async validateTxid (txid, usrObj = null) {
const path = `${this.restURL}slp/validateTxid`
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
// console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Handle a single TXID or an array of TXIDs.
let txids
@@ -450,7 +453,8 @@ class Utils {
const response = await _this.axios.post(
path,
{
txids: txids
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
@@ -602,7 +606,8 @@ class Utils {
* @api SLP.Utils.validateTxid3() validateTxid3()
* @apiName validateTxid3
* @apiGroup SLP Utils
* @apiDescription Validate that txid is an SLP transaction using the SLPDB whitelist server.
* @apiDescription
* Validate that txid is an SLP transaction using the SLPDB whitelist server.
* Same exact functionality as the validateTxid() function, but this function
* calls the whitelist SLPDB. It will only validate SLP tokens that are in the
* whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist()
@@ -648,10 +653,12 @@ class Utils {
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
* valid: true } ]
*/
async validateTxid3 (txid) {
async validateTxid3 (txid, usrObj = null) {
const path = `${this.restURL}slp/validateTxid3`
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
// console.log(`path: ${JSON.stringify(path, null, 2)}`)
// console.log('validateTxid3 usrObj: ', usrObj)
// Handle a single TXID or an array of TXIDs.
let txids
@@ -662,7 +669,8 @@ class Utils {
const response = await _this.axios.post(
path,
{
txids: txids
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
@@ -883,18 +891,17 @@ class Utils {
* @api SLP.Utils.decodeOpReturn() decodeOpReturn()
* @apiName decodeOpReturn
* @apiGroup SLP Utils
* @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
* @apiDescription
* Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
*
* Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser
* library maintained by JT Freeman. Outputs have slightly different format.
* Throws an error if given a non-SLP txid.
*
* If optional associative array parameter cache is used, will cache and
* reuse responses for the same input.
*
* Throws an error if given a non-SLP txid.
* A third optional input, `usrObj`, is used by bch-api for managing rate limits.
* It can be safely ignored when writing apps using this call.
*
* In a future version of bch-js, this method will replace the origonal
* decodeOpReturn() method.
*
* @apiExample Example usage:
*
@@ -921,9 +928,10 @@ class Utils {
* "99883300000000"
* ]
* }
*
*/
// Reimplementation of decodeOpReturn() using slp-parser.
async decodeOpReturn (txid, cache = null) {
async decodeOpReturn (txid, cache = null, usrObj = null) {
// The cache object is an in-memory cache (JS Object) that can be passed
// into this function. It helps if multiple vouts from the same TXID are
// being evaluated. In that case, it can significantly reduce the number
@@ -940,16 +948,27 @@ class Utils {
if (cachedVal) return cachedVal
}
// console.log(`decodeOpReturn usrObj: ${JSON.stringify(usrObj, null, 2)}`)
try {
// Validate the txid input.
if (!txid || txid === '' || typeof txid !== 'string') {
throw new Error('txid string must be included.')
}
// 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/${txid}?verbose=true`
const response = await _this.axios.get(path, _this.axiosOptions)
const txDetails = response.data
const path = `${this.restURL}rawtransactions/getRawTransaction`
const response = await _this.axios.post(
path,
{
verbose: true,
txids: [txid],
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
const txDetails = response.data[0]
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// SLP spec expects OP_RETURN to be the first output of the transaction.
@@ -996,8 +1015,16 @@ class Utils {
return tokenData
} catch (error) {
// Used for debugging
// console.log('decodeOpReturn error: ', error)
if (error.response && error.response.data) throw error.response.data
// console.log(`decodeOpReturn error.message: ${error.message}`)
// if (error.response && error.response.data) {
// console.log(
// `decodeOpReturn error.response.data: ${JSON.stringify(
// error.response.data
// )}`
// )
// }
throw error
}
}
@@ -1010,15 +1037,30 @@ class Utils {
*
* Expects an array of UTXO objects as input. Returns an array of equal size.
* Returns UTXO data hydrated with token information.
* If the
* UTXO does not belong to a SLP transaction, it will return an `isValid` property
* set to false.
* If the UTXO is part of an SLP transaction, it will return the UTXO object
* with additional SLP information attached. An `isValid` property will be included.
* If its value is true, the UTXO is a valid SLP UTXO.
* If the isValid value is null,
* then SLPDB has not yet processed that txid and validity has not been confirmed,
* or a 429 rate-limit error was enountered during the processing of the request.
*
* - If the UTXO does not belong to a SLP transaction, it will return an
* `isValid` property set to `false`.
*
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
* with additional SLP information attached. An `isValid` property will be
* included.
* - If the `isValid` property is `true`, the UTXO is a valid SLP UTXO.
* - If the `isValid` property is `null`, then SLPDB has not yet processed
* that txid and validity has not been confirmed, or a 429 rate-limit error
* was enountered during the processing of the request.
*
* An optional second input object, `usrObj`, allows the user to inject an
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
* a number, the call will delay by that number of milliseconds between
* processing UTXOs.
*
* This is an API-heavy call. If you get a lot of `null` values, then slow down
* the calls by using the usrObj.utxoDelay property, or request info on fewer
* UTXOs at a
* time. `null` indicates that the UTXO can *not* be safely spent, because
* a judgement as to weather it is a token UTXO has not been made. Spending it
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
*
*
* @apiExample Example usage:
*
@@ -1026,7 +1068,8 @@ class Utils {
* try {
* const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
*
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
* // Delay 100mS between processing UTXOs, to prevent rate-limit errors.
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos, { utxoDelay: 100 })
*
* console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
* } catch (error) {
@@ -1054,37 +1097,19 @@ class Utils {
* "tokenType": 1
* }
*/
// CT 1/11/20: Refactored to comply with this GitHub Issue:
// https://github.com/Bitcoin-com/slp-sdk/issues/84
// CT 5/31/20: Refactored to use slp-parse library.
async tokenUtxoDetails (utxos) {
async tokenUtxoDetails (utxos, usrObj = null) {
try {
// utxo list may have duplicate tx_hash, varying tx_pos
// only need to call decodeOpReturn once for those
const decodeOpReturnCache = {}
const cachedTxValidation = {}
// Throw error if input is not an array.
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
// console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Loop through each element in the array and validate the input before
// further processing.
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
if (!utxo.satoshis) {
// If Electrumx, convert the value to satoshis.
if (utxo.value) {
utxo.satoshis = utxo.value
} else {
// If there is neither a satoshis or value property, throw an error.
throw new Error(
`utxo ${i} does not have a satoshis or value property.`
)
}
}
// Ensure the UTXO has a txid or tx_hash property.
if (!utxo.txid) {
// If Electrumx, convert the tx_hash property to txid.
if (utxo.tx_hash) {
@@ -1109,6 +1134,160 @@ class Utils {
}
}
// Hydrate each UTXO with data from SLP OP_REUTRNs.
const outAry = await this._hydrateUtxo(utxos, usrObj)
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
// *After* each UTXO has been hydrated with SLP data,
// validate the TXID with SLPDB.
for (let i = 0; i < outAry.length; i++) {
const utxo = outAry[i]
// *After* the UTXO has been hydrated with SLP data,
// validate the TXID with SLPDB.
if (utxo.tokenType) {
// Only execute this code-path if the current UTXO has a 'tokenType'
// property. i.e. it has been successfully hydrated with SLP
// information.
// Validate using a 'waterfall' of validators.
utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
}
}
return outAry
} catch (error) {
// console.log('Error in tokenUtxoDetails()')
if (error.response && error.response.data) throw error.response.data
throw error
}
}
/**
* @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
* @apiName tokenUtxoDetailsWL
* @apiGroup SLP Utils
* @apiDescription
*
* Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
* validate UTXOs. This will result in a lot of `isValid: null` values,
* but much more performant handling of SLP tokens. Some wallet apps prefer
* the scaling performance over the breadth of supported tokens.
*
* An optional second input object, `usrObj`, allows the user to inject an
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
* a number, the call will delay by that number of milliseconds between
* processing UTXOs.
*
* This is an API-heavy call. If you get a lot of `null` values, then slow down
* the calls by using the usrObj.utxoDelay property, or request info on fewer
* UTXOs at a
* time. `null` indicates that the UTXO can *not* be safely spent, because
* a judgement as to weather it is a token UTXO has not been made. Spending it
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
*
*/
async tokenUtxoDetailsWL (utxos, usrObj = null) {
try {
// Throw error if input is not an array.
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
// Loop through each element in the array and validate the input before
// further processing.
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
// Ensure the UTXO has a txid or tx_hash property.
if (!utxo.txid) {
// If Electrumx, convert the tx_hash property to txid.
if (utxo.tx_hash) {
utxo.txid = utxo.tx_hash
} else {
// If there is neither a txid or tx_hash property, throw an error.
throw new Error(
`utxo ${i} does not have a txid or tx_hash property.`
)
}
}
// Ensure the UTXO has a vout or tx_pos property.
if (!Number.isInteger(utxo.vout)) {
if (Number.isInteger(utxo.tx_pos)) {
utxo.vout = utxo.tx_pos
} else {
throw new Error(
`utxo ${i} does not have a vout or tx_pos property.`
)
}
}
}
// Hydrate each UTXO with data from SLP OP_REUTRNs.
const outAry = await this._hydrateUtxo(utxos, usrObj)
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
// *After* each UTXO has been hydrated with SLP data,
// validate the TXID with SLPDB.
for (let i = 0; i < outAry.length; i++) {
const utxo = outAry[i]
// *After* the UTXO has been hydrated with SLP data,
// validate the TXID with SLPDB.
if (utxo.tokenType) {
// Only execute this block if the current UTXO has a 'tokenType'
// property. i.e. it has been successfully hydrated with SLP
// information.
// Validate against the whitelist SLPDB.
const whitelistResult = await this.validateTxid3(utxo.txid, usrObj)
// console.log(
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
// whitelistResult,
// null,
// 2
// )}`
// )
let isValid = null
// Safely retrieve the returned value.
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
utxo.isValid = isValid
}
}
return outAry
} catch (error) {
if (error.response && error.response.data) throw error.response.data
throw error
}
}
// This is a private function that is called by tokenUtxoDetails().
// It loops through an array of UTXOs and tries to hydrate them with SLP
// token information from the OP_RETURN data.
//
// This function makes several calls to decodeOpReturn() to retrieve SLP
// token data. If that call throws an error due to hitting rate limits, this
// function will not throw an error. Instead, it will mark the `isValid`
// property as `null`
//
// Exception to the above: It *will* throw an error if decodeOpReturn() throws
// an error while trying to get the Genesis transaction for a Send or Mint
// transaction. However, that is a rare occurence since the cache of
// decodeOpReturn() will minimize API calls for this case. This behavior
// could be changed, but right now it's a corner case of a corner case.
//
// If the usrObj has a utxoDelay property, then it will delay the loop for
// each UTXO by that many milliseconds.
async _hydrateUtxo (utxos, usrObj = null) {
try {
const decodeOpReturnCache = {}
// console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Output Array
const outAry = []
@@ -1116,15 +1295,28 @@ class Utils {
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
// If the user passes in a delay, then wait.
if (usrObj && usrObj.utxoDelay && !isNaN(Number(usrObj.utxoDelay))) {
const delayMs = Number(usrObj.utxoDelay)
await this.util.sleep(delayMs)
}
// Get raw transaction data from the full node and attempt to decode
// the OP_RETURN data.
// If there is no OP_RETURN, mark the UTXO as false.
let slpData = false
try {
slpData = await this.decodeOpReturn(utxo.txid, decodeOpReturnCache)
slpData = await this.decodeOpReturn(
utxo.txid,
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
} catch (err) {
// console.log(`error from decodeOpReturn(${utxo.txid}): `, err)
// console.log(
// `error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `,
// err
// )
// An error will be thrown if the txid is not SLP.
// If error is for some other reason, like a 429 error, mark utxo as 'null'
@@ -1136,7 +1328,7 @@ class Utils {
err.message.indexOf('trailing data') === -1)
) {
// console.log(
// `unknown error from decodeOpReturn(). Marking as 'null'`,
// "unknown error from decodeOpReturn(). Marking as 'null'",
// err
// )
@@ -1147,7 +1339,7 @@ class Utils {
// an SLP UTXO.
// Mark as false and continue the loop.
} else {
// console.log("marking as invalid")
// console.log('marking as invalid')
utxo.isValid = false
outAry.push(utxo)
}
@@ -1155,6 +1347,7 @@ class Utils {
// Halt the execution of the loop and increase to the next index.
continue
}
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
const txType = slpData.txType.toLowerCase()
@@ -1168,7 +1361,6 @@ class Utils {
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
) {
// Can safely be marked as false.
// outAry[i] = false
utxo.isValid = false
outAry[i] = utxo
} else {
@@ -1193,7 +1385,10 @@ class Utils {
utxo.decimals = slpData.decimals
utxo.tokenType = slpData.tokenType
// something
// Initial value is null until UTXO can be validated and confirmed
// to be valid (true) or not (false).
utxo.isValid = null
outAry[i] = utxo
}
}
@@ -1205,15 +1400,16 @@ class Utils {
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
) {
// Can safely be marked as false.
// outAry[i] = false
utxo.isValid = false
outAry[i] = utxo
} else {
// If UTXO passes validation, then return formatted token data.
const genesisData = await this.decodeOpReturn(
slpData.tokenId,
decodeOpReturnCache
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
@@ -1242,6 +1438,10 @@ class Utils {
utxo.mintBatonVout = slpData.mintBatonVout
// Initial value is null until UTXO can be validated and confirmed
// to be valid (true) or not (false).
utxo.isValid = null
outAry[i] = utxo
}
}
@@ -1254,18 +1454,19 @@ class Utils {
// Figure out what token quantity is represented by this utxo.
const tokenQty = slpData.amounts[utxo.vout - 1]
// console.log(`tokenQty: `, tokenQty)
// console.log('tokenQty: ', tokenQty)
if (!tokenQty) {
// outAry[i] = false
utxo.isValid = false
outAry[i] = utxo
} else {
// If UTXO passes validation, then return formatted token data.
const genesisData = await this.decodeOpReturn(
slpData.tokenId,
decodeOpReturnCache
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
@@ -1282,6 +1483,10 @@ class Utils {
utxo.decimals = genesisData.decimals
utxo.tokenType = slpData.tokenType
// Initial value is null until UTXO can be validated and confirmed
// to be valid (true) or not (false).
utxo.isValid = null
// Calculate the real token quantity.
const tokenQtyBig = new BigNumber(tokenQty).div(
@@ -1295,134 +1500,146 @@ class Utils {
outAry[i] = utxo
}
}
// *After* the UTXO has been hydrated with SLP data,
// validate the TXID with SLPDB.
if (outAry[i].tokenType) {
// Only execute this block if the current UTXO has a 'tokenType'
// property. i.e. it has been successfully hydrated with SLP
// information.
// CT 12/13/2020: Disabling the cache until I get processing of tokens
// to be more stable.
// If the value has been cached, use the cached version first.
let isValid = cachedTxValidation[utxo.txid]
if (!isValid) isValid = null
// let isValid = null
// There are two possible responses from SLPDB. If SLPDB is functioning
// correctly, then validateTxid() will return this:
// isValid: [
// {
// "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
// "valid": true
// }
// ]
//
// If SLPDB has fallen behind real-time processing, it will return this:
// isValid: [
// null
// ]
//
// Note: validateTxid3() has the same output as validateTxid().
// validateTxid2() uses slp-validate, which has a different output format.
// If not in the cache, try the general SLPDB.
if (isValid === null) {
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
isValid = await this.validateTxid(utxo.txid)
// console.log(
// `validateTxid() isValid: ${JSON.stringify(isValid, null, 2)}`
// )
// Handle corner case where SLPDB returns an array with a null element.
if (isValid[0] === null) {
isValid = [{ txid: utxo.txid, valid: null }]
}
isValid = isValid[0].valid
// Save the result to the local cache.
// cachedTxValidation[utxo.txid] = isValid
}
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
// console.log(
// `pre-validateTxid3() isValid: ${JSON.stringify(isValid, null, 2)}`
// )
// If still null, check the whitelist SLPDB
if (isValid === null) {
// console.log(
// `checking against whitelist SLPDB. outAry[${i}]: ${JSON.stringify(
// outAry[i],
// null,
// 2
// )}`
// )
// Figure out if the token UTXO is in the whitelist.
let utxoInWhitelist = false
for (let j = 0; j < this.whitelist.length; j++) {
if (outAry[i].tokenId === this.whitelist[j].tokenId) {
utxoInWhitelist = true
break
}
}
// If the utxo.tokenId is in the whitelist, check the validity with
// the whitelist SLPDB. This should still be functioning properly
// if the general SLPDB is not.
if (utxoInWhitelist) {
isValid = await this.validateTxid3(utxo.txid)
// console.log(
// `whitelist-SLPDB for ${utxo.txid}: ${JSON.stringify(
// isValid,
// null,
// 2
// )}`
// )
if (isValid[0] !== null) isValid = isValid[0].valid
// Save the result to the local cache.
// cachedTxValidation[utxo.txid] = isValid
}
}
// console.log(
// `pre-validateTxid2() isValid: ${JSON.stringify(isValid, null, 2)}`
// )
// If still null, as a last resort, check it against slp-validate
if (isValid === null) {
try {
isValid = await this.validateTxid2(utxo.txid)
} catch (err) {
// Mark as invalid if validateTxid2() throws an error.
isValid = null
}
// console.log(
// `slp-validate isValid: ${JSON.stringify(isValid, null, 2)}`
// )
if (isValid !== null) isValid = isValid.isValid
// Save the result to the local cache.
// cachedTxValidation[utxo.txid] = isValid
}
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
outAry[i].isValid = isValid
// Save the txid to the local cache to reduce API calls.
cachedTxValidation[utxo.txid] = isValid
}
}
return outAry
} catch (error) {
// console.log('_hydrateUtxo error: ', error)
throw error
}
}
/**
* @api SLP.Utils.waterfallValidateTxid() waterfallValidateTxid()
* @apiName waterfallValidateTxid
* @apiGroup SLP Utils
* @apiDescription Use multiple validators to validate an SLP TXID.
*
* This function aggregates all the available SLP token validation sources.
* It starts with the fastest, most-efficient source first, and continues
* to other validation sources until the txid is validated (true or false).
* If the txid goes through all sources and can't be validated, it will
* return null.
*
* Validation sources from most efficient to least efficient:
* - SLPDB with whitelist filter
* - SLPDB general purpose
* - slp-api
*
* Currently only supports a single txid at a time.
*
* @apiExample Example usage:
*
* // validate single SLP txid
* (async () => {
* try {
* let validated = await bchjs.SLP.Utils.waterfallValidateTxid(
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
* );
* console.log(validated);
* } catch (error) {
* console.error(error);
* }
* })();
*
* // returns
* true
*/
async waterfallValidateTxid (txid, usrObj = null) {
try {
// console.log('txid: ', txid)
const cachedTxValidation = {}
// If the value has been cached, use the cached version first.
let isValid = cachedTxValidation[txid]
if (!isValid && isValid !== false) {
isValid = null
} else {
return isValid
}
// There are two possible responses from SLPDB. If SLPDB is functioning
// correctly, then validateTxid() will return this:
// isValid: [
// {
// "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
// "valid": true
// }
// ]
//
// If SLPDB has fallen behind real-time processing, it will return this:
// isValid: [
// null
// ]
//
// Note: validateTxid3() has the same output as validateTxid().
// validateTxid2() uses slp-validate, which has a different output format.
// Validate against the whitelist SLPDB first.
const whitelistResult = await this.validateTxid3(txid, usrObj)
// console.log(
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
// whitelistResult,
// null,
// 2
// )}`
// )
// Safely retrieve the returned value.
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
// Exit if isValid is not null.
if (isValid !== null) {
// Save to the cache.
cachedTxValidation[txid] = isValid
return isValid
}
// Try the general SLPDB, if the whitelist returned null.
const generalResult = await this.validateTxid(txid, usrObj)
// console.log(
// `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
// )
// Safely retrieve the returned value.
if (generalResult[0] !== null) isValid = generalResult[0].valid
// Exit if isValid is not null.
if (isValid !== null) {
// Save to the cache.
cachedTxValidation[txid] = isValid
return isValid
}
// If still null, as a last resort, check it against slp-validate
let slpValidateResult = null
try {
slpValidateResult = await this.validateTxid2(txid)
} catch (err) {
/* exit quietly */
}
// console.log(
// `slpValidateResult: ${JSON.stringify(slpValidateResult, null, 2)}`
// )
// Exit if isValid is not null.
if (slpValidateResult !== null) {
isValid = slpValidateResult.isValid
// Save to the cache.
cachedTxValidation[txid] = isValid
return isValid
}
// If isValid is still null, return that value, signaling that the txid
// could not be validated.
return isValid
} catch (error) {
// console.log('Error in waterfallValidateTxid()')
if (error.response && error.response.data) throw error.response.data
throw error
}
@@ -1435,19 +1652,33 @@ class Utils {
* @apiDescription Hydrate a UTXO with SLP token metadata.
*
* The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting,
* which greatly reduces the number of API calls.
* which greatly increases the speed, since fewer API calls need to be made.
* However, internal API calls are still counted against your rate limits.
*
* Expects an array of UTXO objects as input. Returns an array of equal size.
* Returns UTXO data hydrated with token information.
* If the
* UTXO does not belong to a SLP transaction, it will return an `isValid` property
* set to false.
* If the UTXO is part of an SLP transaction, it will return the UTXO object
* with additional SLP information attached. An `isValid` property will be included.
* If its value is true, the UTXO is a valid SLP UTXO. \
* If the isValid value is null,
* then SLPDB has not yet processed that txid and validity has not been confirmed,
* or a 429 rate-limit error was enountered during the processing of the request.
* This function expects an array of UTXO objects as input. It returns an
* array of equal size. The UTXO data hydrated with token information.
* - If the UTXO does not belong to a SLP transaction, it will return an
* `isValid` property set to `false`.
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
* with additional SLP information attached. An `isValid` property will be
* included.
* - If `isValid` is `true`, the UTXO is a valid SLP UTXO.
* - If `isValid` is `null`, then SLPDB has not yet processed that txid
* and validity has not been confirmed,
* or a 429 rate-limit error was enountered during the processing of the
* request.
*
* An optional second input object, `usrObj`, allows the user to inject an
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
* a number, the call will delay by that number of milliseconds between
* processing UTXOs.
*
* This is an API-heavy call. If you get a lot of `null` values, then slow down
* the calls by using the usrObj.utxoDelay property, or request info on fewer
* UTXOs at a
* time. `null` indicates that the UTXO can *not* be safely spent, because
* a judgement as to weather it is a token UTXO has not been made. Spending it
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
*
* @apiExample Example usage:
*
@@ -1459,7 +1690,8 @@ class Utils {
* "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
* ])
*
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos)
* // Wait 100mS between processing UTXOs, to prevent rate limit errors.
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos, { utxoDelay: 100 })
*
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
* } catch (error) {
@@ -1593,13 +1825,52 @@ class Utils {
*/
// Same as tokenUtxoDetails(), but reduces API calls by having bch-api server
// do the heavy lifting.
async hydrateUtxos (utxos) {
async hydrateUtxos (utxos, usrObj) {
try {
// Throw error if input is not an array.
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
const response = await _this.axios.post(
`${this.restURL}slp/hydrateUtxos`,
{
utxos: utxos,
usrObj
},
_this.axiosOptions
)
return response.data
} catch (error) {
if (error.response && error.response.data) {
throw new Error(JSON.stringify(error.response.data, null, 2))
}
throw error
}
}
/**
* @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL()
* @apiName hydrateUtxosWL
* @apiGroup SLP Utils
* @apiDescription
* This call is exactly the same as `hydrateUtxos()`. This version hydrate a
* UTXO with SLP token metadata, but only uses the whitelist SLPDB for
* validation.
*
* Whitelist SLPDBs will return `isValid: null` for any token not in the
* 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable
* to operate.
*
*/
// Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server
// do the heavy lifting.
async hydrateUtxosWL (utxos) {
try {
// Throw error if input is not an array.
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
const response = await _this.axios.post(
`${this.restURL}slp/hydrateUtxosWL`,
{
utxos: utxos
},
-28
View File
@@ -1,28 +0,0 @@
const io = require('socket.io-client')
class Socket {
constructor (config = {}) {
if (typeof config === 'string') {
// TODO remove this check in v2.0
this.socket = io(`${config}`)
} else {
if (config.restURL) {
this.socket = io(`${config.restURL}`)
} else {
const restURL = 'https://rest.bitcoin.com'
this.socket = io(`${restURL}`)
}
if (config.callback) config.callback()
}
}
listen (endpoint, cb) {
this.socket.emit(endpoint)
if (endpoint === 'blocks') this.socket.on('blocks', msg => cb(msg))
else if (endpoint === 'transactions') { this.socket.on('transactions', msg => cb(msg)) }
}
}
module.exports = Socket
+149
View File
@@ -0,0 +1,149 @@
/*
High-level functions for working with Transactions
*/
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
class Transaction {
constructor (config) {
// Encapsulate dependencies
this.slpUtils = new SlpUtils(config)
this.rawTransaction = new RawTransaction(config)
}
/**
* @api Transaction.get() get()
* @apiName get
* @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.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.Transaction.get("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*/
async get (txid) {
try {
if (typeof txid !== 'string') {
throw new Error(
'Input to Transaction.get() must be a string containing a TXID.'
)
}
const txDetails = await this.rawTransaction.getTxData(txid)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// Setup default SLP properties.
txDetails.isValidSLPTx = false
// First get the token information for the output. If that fails, then
// this is not an SLP transaction, and this method can return false.
let outTokenData
try {
outTokenData = await this.slpUtils.decodeOpReturn(txid)
// console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`)
// Get Genesis data for this token.
const genesisData = await this.slpUtils.decodeOpReturn(
outTokenData.tokenId
// decodeOpReturnCache
// usrObj // pass user data when making an internal call.
)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
// Add token information to the tx details object.
txDetails.tokenTxType = outTokenData.txType
txDetails.tokenId = outTokenData.tokenId
txDetails.tokenTicker = genesisData.ticker
txDetails.tokenName = genesisData.name
txDetails.tokenDecimals = genesisData.decimals
txDetails.tokenUri = genesisData.documentUri
txDetails.tokenDocHash = genesisData.documentHash
// 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)
txDetails.vout[i + 1].tokenQty = realQty
}
// Add tokenQty = null to any outputs that don't have a value.
for (let i = 0; i < txDetails.vout.length; i++) {
const thisVout = txDetails.vout[i]
if (!thisVout.tokenQty && thisVout.tokenQty !== 0) {
thisVout.tokenQty = null
}
}
// Loop through each input and retrieve the token data.
for (let i = 0; i < txDetails.vin.length; i++) {
const thisVin = txDetails.vin[i]
try {
// If decodeOpReturn() throws an error, then this input is not
// from an SLP transaction and can be ignored.
const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid)
// console.log(
// `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)}`)
if (tokenQty) {
const realQty =
Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
thisVin.tokenQty = realQty
// txDetails.vin[i].tokenQty = tokenQty
} else {
thisVin.tokenQty = null
}
} catch (err) {
// If decodeOpReturn() throws an error, then this input is not
// from an SLP transaction and can be ignored.
// thisVin.tokenQty = null
thisVin.tokenQty = null
continue
}
}
// Finally, validate the SLP TX.
// this.slpUtils.waterfallValidateTxid(txid, usrObj)
txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid)
} catch (err) {
// console.log('Error: ', err)
// If decoding the op_return fails, then it's not an SLP transaction,
// and the non-hyrated TX details can be returned.
return txDetails
}
return txDetails
} catch (err) {
// console.error('Error in transactions.js/get(): ', err)
if (err.error) throw new Error(err.error)
throw err
}
}
}
module.exports = Transaction
+148 -1
View File
@@ -28,7 +28,154 @@ class Util {
}
/**
* @api util.validateAddress() validateAddress()
* @api Util.floor8() floor8()
* @apiName floor8
* @apiGroup Util
* @apiDescription Round a number down to 8 decimal places.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const num = 1.234567891111
* const result = bchjs.Util.floor8(num)
* console.log(result)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* 1.23456789
*/
// floor8 - round to 8 decimal places
// Takes a number and returns it, rounded to the nearest 8 decimal place.
floor8 (num) {
const thisNum = Number(num)
if (isNaN(thisNum)) throw new Error('input must be a number')
let tempNum = thisNum * 100000000
tempNum = Math.floor(tempNum)
tempNum = tempNum / 100000000
return tempNum
}
/**
* @api Util.floor2() floor2()
* @apiName floor2
* @apiGroup Util
* @apiDescription Round a number down to 2 decimal places.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const num = 1.234567891111
* const result = bchjs.Util.floor2(num)
* console.log(result)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* 1.23
*/
// floor2 - round down to 2 decimal places
// Takes a number and returns it, rounded to the nearest 2 decimal place.
floor2 (num) {
const thisNum = Number(num)
if (isNaN(thisNum)) throw new Error('input must be a number')
let tempNum = thisNum * 100
tempNum = Math.floor(tempNum)
tempNum = tempNum / 100
return tempNum
}
/**
* @api Util.chunk20() chunk20()
* @apiName chunk20
* @apiGroup Util
* @apiDescription chunk up an array into multiple arrays of 20 elements each.
* Input: arrayToSlice - a one-dimensional array of elements.
* Returns a two-dimensional array. An array of 20-element arrays.
*
* @apiExample Example usage:
* (async () => {
* try {
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]
*
* 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,12,13,14,15,16,17,18,19],
* [20,21,22,23,24,25,26]
* ]
*/
// chunk20 - chunk up an array into multiple arrays of 20 elements each.
// Input: arrayToSlice - a one-dimensional array of elements.
// Returns a two-dimensional array. An array of 20-element arrays.
chunk20 (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 20 elements.
while (offset < arrayToSlice.length) {
const chunk = arrayToSlice.slice(offset, offset + 20)
result.push(chunk)
offset = offset + 20
}
return result
} catch (err) {
console.error('Error in chunk20()')
throw err
}
}
/**
* @api Util.sleep() sleep()
* @apiName sleep
* @apiGroup Util
* @apiDescription Promise-based delay.
* Expects an integer as input, which represents milliseconds. This function
* will return a Promise that resolves that many milliseconds later.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const tenSeconds = 10000
* await bchjs.Util.sleep(tenSeconds)
* } catch(error) {
* console.error(error)
* }
* })()
*
*/
sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
/**
* @api Util.validateAddress() validateAddress()
* @apiName Validate Address.
* @apiGroup Util
* @apiDescription Return information about the given bitcoin address.
+350
View File
@@ -0,0 +1,350 @@
/*
High-level functions for working with UTXOs
TODO:
- Make a getWL() clone of get(), but uses hydrateUtxosWL()
*/
// Local libraries
const Electrumx = require('./electrumx')
const Slp = require('./slp/slp')
class UTXO {
constructor (config) {
// Encapsulate dependencies for easier mocking.
this.electrumx = new Electrumx(config)
this.slp = new Slp(config)
}
/**
* @api Utxo.get() get()
* @apiName get
* @apiGroup UTXO
* @apiDescription Get UTXOs for an address
*
* 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.
*
* Note: You can pass in an optional second Boolean argument. The default
* `false` will use the normal waterfall validation method. Set to `true`,
* SLP UTXOs will be validated with the whitelist filtered SLPDB. This will
* result is many more UTXOs in the `nullUtxos` array.
*
* @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
* }
* ],
* "nullUtxos": [],
* "slpUtxos": {
* "type1": {
* "mintBatons": [
* {
* "height": 674512,
* "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "tx_pos": 2,
* "value": 546,
* "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "vout": 2,
* "utxoType": "minting-baton",
* "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "tokenTicker": "SLPTEST",
* "tokenName": "SLP Test Token",
* "tokenDocumentUrl": "https://FullStack.cash",
* "tokenDocumentHash": "",
* "decimals": 8,
* "tokenType": 1,
* "isValid": true
* }
* ],
* "tokens": [
* {
* "height": 674512,
* "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "tx_pos": 1,
* "value": 546,
* "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "vout": 1,
* "utxoType": "token",
* "tokenQty": "100",
* "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
* "tokenTicker": "SLPTEST",
* "tokenName": "SLP Test Token",
* "tokenDocumentUrl": "https://FullStack.cash",
* "tokenDocumentHash": "",
* "decimals": 8,
* "tokenType": 1,
* "isValid": true
* }
* ]
* },
* "nft": {
* "groupMintBatons": [
* {
* "height": 674513,
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "tx_pos": 2,
* "value": 546,
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "vout": 2,
* "utxoType": "minting-baton",
* "transactionType": "mint",
* "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395",
* "tokenType": 129,
* "tokenTicker": "NFTGT",
* "tokenName": "NFT Test Group Token",
* "tokenDocumentUrl": "https://FullStack.cash",
* "tokenDocumentHash": "",
* "decimals": 0,
* "mintBatonVout": 2,
* "isValid": true
* }
* ],
* "groupTokens": [
* {
* "height": 674513,
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "tx_pos": 1,
* "value": 546,
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
* "vout": 1,
* "utxoType": "token",
* "tokenQty": "10",
* "transactionType": "mint",
* "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395",
* "tokenType": 129,
* "tokenTicker": "NFTGT",
* "tokenName": "NFT Test Group Token",
* "tokenDocumentUrl": "https://FullStack.cash",
* "tokenDocumentHash": "",
* "decimals": 0,
* "mintBatonVout": 2,
* "isValid": true
* }
* ],
* "tokens": [
* {
* "height": 674512,
* "tx_hash": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
* "tx_pos": 1,
* "value": 546,
* "txid": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
* "vout": 1,
* "utxoType": "token",
* "tokenQty": "1",
* "tokenId": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
* "tokenTicker": "NFT004",
* "tokenName": "NFT Child",
* "tokenDocumentUrl": "https://FullStack.cash",
* "tokenDocumentHash": "",
* "decimals": 0,
* "tokenType": 65,
* "isValid": true
* }
* ]
* }
* }
* }
* ]
*
*
*/
async get (address, useWhitelist = false) {
try {
// Convert address to an array if it is a string.
if (typeof address === 'string') address = [address]
// Throw an error if there are more than 20 addresses passed in at a time.
if (address.length > 20) throw new Error('Too many elements, 20 max.')
// Covert each address to a BCH address.
const addr = address.map(elem => this.slp.Address.toCashAddress(elem))
// Get the UTXOs associated with the address.
const utxoData = await this.electrumx.utxo(addr)
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
// Hydate the utxos with token information.
let hydratedUtxos
if (useWhitelist) {
hydratedUtxos = await this.slp.Utils.hydrateUtxosWL(utxoData.utxos)
} else {
hydratedUtxos = await this.slp.Utils.hydrateUtxos(utxoData.utxos)
}
// console.log(`hydratedUtxos: ${JSON.stringify(hydratedUtxos, null, 2)}`)
const retAry = [] // Return array
// Loop through each address.
for (let i = 0; i < hydratedUtxos.slpUtxos.length; i++) {
const thisAddr = hydratedUtxos.slpUtxos[i]
const addrObj = {
address: thisAddr.address
}
// Filter out the different types of UTXOs.
addrObj.bchUtxos = thisAddr.utxos.filter(elem => elem.isValid === false)
addrObj.nullUtxos = thisAddr.utxos.filter(elem => elem.isValid === null)
// Break down the SLP UTXOs.
addrObj.slpUtxos = {
type1: {},
nft: {}
}
// Token Type 1 Minting Batons.
addrObj.slpUtxos.type1.mintBatons = thisAddr.utxos.filter(elem => {
const isValid = elem.isValid === true
const tokenTypeIs1 = elem.tokenType === 1
const isMintingBaton = elem.utxoType === 'minting-baton'
return isValid && tokenTypeIs1 && isMintingBaton
})
// Token Type 1 tokens.
addrObj.slpUtxos.type1.tokens = thisAddr.utxos.filter(elem => {
const isValid = elem.isValid === true
const tokenTypeIs1 = elem.tokenType === 1
const isToken = elem.utxoType === 'token'
return isValid && tokenTypeIs1 && isToken
})
// NFT Group Minting Batons
addrObj.slpUtxos.nft.groupMintBatons = thisAddr.utxos.filter(elem => {
const isValid = elem.isValid === true
const tokenTypeIs129 = elem.tokenType === 129
const isMintingBaton = elem.utxoType === 'minting-baton'
return isValid && tokenTypeIs129 && isMintingBaton
})
// NFT Group tokens
addrObj.slpUtxos.nft.groupTokens = thisAddr.utxos.filter(elem => {
const isValid = elem.isValid === true
const tokenTypeIs129 = elem.tokenType === 129
const isToken = elem.utxoType === 'token'
return isValid && tokenTypeIs129 && isToken
})
// NFT (Child) tokens
addrObj.slpUtxos.nft.tokens = thisAddr.utxos.filter(elem => {
const isValid = elem.isValid === true
const tokenTypeIs65 = elem.tokenType === 65
const isToken = elem.utxoType === 'token'
return isValid && tokenTypeIs65 && isToken
})
retAry.push(addrObj)
}
return retAry
} catch (err) {
console.error('Error in bchjs.utxo.get()')
if (err.error) throw new Error(err.error)
throw err
}
}
/**
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
* @apiName findBiggestUtxo
* @apiGroup UTXO
* @apiDescription Get the biggest UTXO in an array.
*
* Given an array of BCH UTXOs, this method will return the biggest UTXO.
* This is often the simplest way to pick a UTXO for generating a transaction.
*
* @apiExample Example usage:
* (async () => {
* try {
* const utxos = await bchjs.Utxo.get('bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z');
* const utxo = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* {
* "height": 655431,
* "tx_hash": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
* "tx_pos": 0,
* "value": 800,
* "txid": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
* "vout": 0,
* "isValid": false,
* "satoshis": 800
* }
*/
// Returns the utxo with the biggest balance from an array of utxos.
findBiggestUtxo (utxos) {
let largestAmount = 0
let largestIndex = 0
if (!Array.isArray(utxos)) {
throw new Error('utxos input to findBiggestUtxo() must be an array')
}
for (let i = 0; i < utxos.length; i++) {
const thisUtxo = utxos[i]
// Give Elecrumx utxos a satoshis property.
if (thisUtxo.value) {
if (!thisUtxo.satoshis) thisUtxo.satoshis = Number(thisUtxo.value)
}
if (!thisUtxo.satoshis) {
throw new Error(
'UTXOs require a satoshis or value property for findBiggestUtxo()'
)
}
if (thisUtxo.satoshis > largestAmount) {
largestAmount = thisUtxo.satoshis
largestIndex = i
}
}
return utxos[largestIndex]
}
}
module.exports = UTXO
@@ -14,9 +14,9 @@
const assert = require('chai').assert
// const RESTURL = `https://abc.fullstack.cash/v4/`
const RESTURL = 'https://bchn.fullstack.cash/v4/'
// const RESTURL = `http://localhost:3000/v4/`
// const RESTURL = `https://abc.fullstack.cash/v5/`
const RESTURL = 'https://bchn.fullstack.cash/v5/'
// const RESTURL = `http://localhost:3000/v5/`
const BCHJS = require('../../../src/bch-js')
const bchjs = new BCHJS({ restURL: RESTURL })
@@ -13,9 +13,9 @@ const PRO_PASS = 'testpassword'
const BCHJS = require('../../../src/bch-js')
const bchjs = new BCHJS({
// restURL: `https://bchn.fullstack.cash/v4/`,
restURL: 'https://abc.fullstack.cash/v4/',
// restURL: `http://localhost:3000/v4/`,
// restURL: `https://bchn.fullstack.cash/v5/`,
restURL: 'https://abc.fullstack.cash/v5/',
// restURL: `http://localhost:3000/v5/`,
authPass: PRO_PASS
})
+2 -2
View File
@@ -18,8 +18,8 @@ const JWT_TOKEN =
const BCHJS = require('../../../src/bch-js')
const bchjs = new BCHJS({
restURL: 'https://api.fullstack.cash/v4/',
// restURL: `http://localhost:3000/v4/`,
restURL: 'https://api.fullstack.cash/v5/',
// restURL: `http://localhost:3000/v5/`,
apiToken: JWT_TOKEN
})
@@ -18,9 +18,9 @@ const JWT_TOKEN =
const BCHJS = require('../../../src/bch-js')
const bchjs = new BCHJS({
// restURL: `https://bchn.fullstack.cash/v4/`,
restURL: 'https:/abc.fullstack.cash/v4/',
// restURL: `http://localhost:3000/v4/`,
// restURL: `https://bchn.fullstack.cash/v5/`,
restURL: 'https:/abc.fullstack.cash/v5/',
// restURL: `http://localhost:3000/v5/`,
apiToken: JWT_TOKEN
})
@@ -65,7 +65,7 @@ describe('#full node rate limits', () => {
it('should throw error for more than 100 RPM to fullnode', async () => {
try {
console.log('This test usually doesn\'t pass, because of latency.')
console.log("This test usually doesn't pass, because of latency.")
for (let i = 0; i < 100; i++) await bchjs.Control.getNetworkInfo()
} catch (err) {
// console.log(`validating after 10th call`)
+3 -3
View File
@@ -14,9 +14,9 @@ const JWT_TOKEN =
const BCHJS = require('../../../src/bch-js')
const bchjs = new BCHJS({
// restURL: `https://bchn.fullstack.cash/v4/`,
restURL: 'https://abc.fullstack.cash/v4/',
// restURL: `http://localhost:3000/v4/`,
// restURL: `https://bchn.fullstack.cash/v5/`,
restURL: 'https://abc.fullstack.cash/v5/',
// restURL: `http://localhost:3000/v5/`,
apiToken: JWT_TOKEN
})
+37
View File
@@ -276,6 +276,43 @@ describe('#blockchain', () => {
assert.equal(result, null)
})
})
describe('#getBlock', () => {
it('should get block information with default verbosity', async () => {
const blockhash =
'0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536'
const result = await bchjs.Blockchain.getBlock(blockhash)
assert.hasAllKeys(result, [
'hash',
'confirmations',
'size',
'height',
'version',
'versionHex',
'merkleroot',
'tx',
'time',
'mediantime',
'nonce',
'bits',
'difficulty',
'chainwork',
'nTx',
'previousblockhash',
'nextblockhash'
])
assert.isArray(result.tx)
})
it('should get block information with verbosity 0', async () => {
const blockhash =
'0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536'
const verbosity = 0
const result = await bchjs.Blockchain.getBlock(blockhash, verbosity)
assert.isString(result)
})
})
})
function sleep (ms) {
@@ -8,7 +8,7 @@
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
const bchjs = new BCHJS({ restURL: process.env.RESTURL })
// Inspect utility used for debugging.
const util = require('util')
@@ -44,6 +44,7 @@ describe('#rawtransaction', () => {
assert.hasAllKeys(err, ['error'])
assert.include(err.error, 'bad-txns-inputs-missingorspent')
// assert.include(err.error, 'Missing inputs')
}
})
@@ -61,6 +62,7 @@ describe('#rawtransaction', () => {
assert.hasAllKeys(err, ['error'])
assert.include(err.error, 'bad-txns-inputs-missingorspent')
// assert.include(err.error, 'Missing inputs')
}
})
})
+4 -1
View File
@@ -27,7 +27,7 @@ describe('#SLP', () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
bchjs = new BCHJS()
bchjs = new BCHJS({ restURL: process.env.RESTURL })
})
describe('#util', () => {
@@ -113,6 +113,7 @@ describe('#SLP', () => {
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
// assert.equal(data[4].isValid, true)
assert.equal(data[4].isValid, null)
// Token send on ABC network
@@ -180,6 +181,7 @@ describe('#SLP', () => {
assert.equal(result[0].valid, null)
assert.equal(result[1].valid, null)
assert.equal(result[2].valid, true)
// assert.equal(result[2].valid, null)
assert.equal(result[3].valid, true)
assert.equal(result[4].valid, null)
})
@@ -209,6 +211,7 @@ describe('#SLP', () => {
assert.equal(result[0].valid, null)
assert.equal(result[1].valid, null)
assert.equal(result[2].valid, true)
// assert.equal(result[2].valid, null)
assert.equal(result[3].valid, true)
assert.equal(result[4].valid, null)
assert.equal(result[5].valid, true)
@@ -0,0 +1,38 @@
/*
Integration tests for the utxo.js library.
*/
const assert = require('chai').assert
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)
})
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
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)
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, 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(3000)
})
/*
@@ -25,12 +25,37 @@ 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(3000)
bchjs = new BCHJS()
})
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)}`)
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')
})
})
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
@@ -186,11 +211,13 @@ describe('#SLP', () => {
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid()
// True in validateTxid() (no whitelist SLPDB) but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
// True when no whitelist SLPDB is used.
assert.equal(result[4].valid, true)
})
})
@@ -0,0 +1,105 @@
/*
Integration tests for the utxo.js library.
*/
const assert = require('chai').assert
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(3000)
})
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
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)
})
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 () => {
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)
})
})
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.get(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)
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+25
View File
@@ -0,0 +1,25 @@
/*
Integration tests for bchjs dsproof library.
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
describe('#DSProof', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#getDSProof', () => {
it('should get TX info from the full node', async () => {
const txid =
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
const result = await bchjs.DSProof.getDSProof(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, null)
})
})
})
@@ -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(3000)
})
/*
+51 -1
View File
@@ -25,12 +25,37 @@ 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(3000)
bchjs = new BCHJS()
})
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)}`)
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')
})
})
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
@@ -239,6 +264,31 @@ describe('#SLP', () => {
})
})
})
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,125 @@
/*
Integration tests for the utxo.js library.
*/
const assert = require('chai').assert
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(3000)
})
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
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)
})
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 () => {
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)
})
it('should use the whitelist when flag is set', async () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const useWhitelist = true
const result = await bchjs.Utxo.get(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.get(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)
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@@ -12,11 +12,11 @@ const assert = chai.assert
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
// Inspect utility used for debugging.
+1 -1
View File
@@ -7,7 +7,7 @@ const assert = chai.assert
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
+14 -6
View File
@@ -5,7 +5,7 @@ const util = require('util')
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
@@ -68,7 +68,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
for (let i = 0; i < 25; i++) {
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
}
// const result = await bchjs.Electrumx.utxo(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -121,7 +123,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
for (let i = 0; i < 25; i++) {
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
}
const result = await bchjs.Electrumx.balance(addr)
@@ -175,7 +179,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
for (let i = 0; i < 25; i++) {
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
}
const result = await bchjs.Electrumx.transactions(addr)
@@ -236,7 +242,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
for (let i = 0; i < 25; i++) {
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
}
await bchjs.Electrumx.unconfirmed(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -355,7 +363,7 @@ describe('#ElectrumX', () => {
assert.property(err, 'success')
assert.equal(err.success, false)
assert.include(
err.error,
err.error.error,
'the transaction was rejected by network rules'
)
}
+130 -128
View File
@@ -1,9 +1,9 @@
const chai = require('chai')
const assert = chai.assert
// 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' })
// const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ ninsightURL: 'https://trest.bitcoin.com/v2' })
describe('#Ninsight', () => {
let sandbox
@@ -16,130 +16,132 @@ describe('#Ninsight', () => {
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')
})
})
// 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) {
@@ -10,11 +10,11 @@ const assert = chai.assert
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
// Inspect utility used for debugging.
+2 -2
View File
@@ -7,11 +7,11 @@ const assert = chai.assert
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
// Inspect utility used for debugging.
@@ -1,6 +1,6 @@
#!/bin/bash
export RESTURL=https://free-test.fullstack.cash/v4/
export RESTURL=https://free-test.fullstack.cash/v5/
export NETWORK=testnet
export IS_USING_FREE_TIER=true
+2 -2
View File
@@ -8,11 +8,11 @@ const assert = chai.assert
const RESTURL = process.env.RESTURL
? process.env.RESTURL
: 'https://testnet3.fullstack.cash/v4/'
: 'https://testnet3.fullstack.cash/v5/'
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
// Inspect utility used for debugging.
+31 -5
View File
@@ -63,7 +63,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
// const result = await bchjs.Electrumx.utxo(addr)
await bchjs.Electrumx.utxo(addr)
@@ -116,7 +118,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
// const result = await bchjs.Electrumx.balance(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -172,7 +176,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0')
}
// const result = await bchjs.Electrumx.transactions(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -235,7 +241,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
await bchjs.Electrumx.unconfirmed(addr)
@@ -351,15 +359,33 @@ describe('#ElectrumX', () => {
try {
await bchjs.Electrumx.broadcast(txHex)
} catch (err) {
// console.log('err: ', err)
assert.property(err, 'success')
assert.equal(err.success, false)
assert.include(
err.error,
err.error.error,
'the transaction was rejected by network rules'
)
}
})
})
describe('#sortAllTxs', () => {
it('should GET transaction history for a single address', async () => {
const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const txs = await bchjs.Electrumx.transactions(addr)
const sortedTransactions = await bchjs.Electrumx.sortAllTxs(
txs.transactions
)
// console.log(
// `sortedTransactions: ${JSON.stringify(sortedTransactions, null, 2)}`
// )
assert.isAbove(sortedTransactions[0].height, sortedTransactions[1].height)
})
})
})
function sleep (ms) {
-89
View File
@@ -1,89 +0,0 @@
/*
tests for OpenBazaar library.
*/
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
describe('#OpenBazaar', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
})
describe('#Balance', () => {
it('should GET balance for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'page',
'totalPages',
'itemsOnPage',
'addrStr',
'balance',
'totalReceived',
'totalSent',
'unconfirmedBalance',
'unconfirmedTxApperances',
'txApperances',
'transactions'
])
assert.isArray(result.transactions)
})
})
describe('#utxo', () => {
it('should GET utxos for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
'txid',
'vout',
'amount',
'height',
'confirmations',
'satoshis'
])
})
})
describe('#tx', () => {
it('should GET transactions for a single address', async () => {
const addr =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.OpenBazaar.tx(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'txid',
'version',
'vin',
'vout',
'blockhash',
'blockheight',
'confirmations',
'blocktime',
'valueOut',
'valueIn',
'fees',
'hex',
'time'
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+8
View File
@@ -33,6 +33,14 @@ describe('#price', () => {
assert.isNumber(result)
})
})
describe('#getBchUsd', () => {
it('should get the USD price of BCH', async () => {
const result = await bchjs.Price.getBchUsd()
console.log(result)
assert.isNumber(result)
})
})
describe('#rates', () => {
it('should get the price of BCH in several currencies', async () => {
+63 -5
View File
@@ -1,6 +1,5 @@
/*
Integration tests for the bchjs. Only covers calls made to
rest.bitcoin.com.
Integration tests for the bchjs.
TODO
*/
@@ -230,7 +229,7 @@ describe('#rawtransaction', () => {
below expect error messages returned from the server, but at least test
that the server is responding on those endpoints, and responds consistently.
*/
describe('sendRawTransaction', () => {
describe('#sendRawTransaction', () => {
it('should throw error on array size rate limit', async () => {
try {
const dataMock =
@@ -238,9 +237,9 @@ describe('#rawtransaction', () => {
const data = []
for (let i = 0; i < 25; i++) data.push(dataMock)
const result = await bchjs.RawTransactions.sendRawTransaction(data)
await bchjs.RawTransactions.sendRawTransaction(data)
console.log(`result: ${util.inspect(result)}`)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.hasAnyKeys(err, ['error'])
@@ -248,6 +247,65 @@ describe('#rawtransaction', () => {
}
})
})
describe('#_getInputAddrs', () => {
it('should return an array of input addresses', async () => {
// const txid = '32233db13f2ae6d82b6262f335643dccf09fc0bcfcef4bc3fbe023355f02e112'
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const txDetails = await bchjs.RawTransactions.getRawTransaction(
txid,
true
)
const result = await bchjs.RawTransactions._getInputAddrs(txDetails)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result.length, 1)
assert.property(result[0], 'vin')
assert.property(result[0], 'address')
assert.property(result[0], 'value')
})
})
describe('#getTxData', () => {
it('should return tx data with input addresses', async () => {
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const result = await bchjs.RawTransactions.getTxData(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
})
})
// describe('#getTxDataSlp', () => {
// it('should return tx data with SLP information', async () => {
// const txid = 'b438855cfcab64516b44097d7212df9cdb99226c8d7c7ab504d35fcfd834cb5b'
//
// const result = await bchjs.RawTransactions.getTxDataSlp(txid)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.property(result.vin[0], 'address')
// assert.property(result.vin[0], 'tokenQty')
//
// assert.equal(result.vin[0].tokenQty, null)
// assert.equal(result.vin[1].tokenQty, '100000000000')
// })
//
// it('should handle non-slp tx', async () => {
// const txid = '04a6aca328af2445327015895b8da9766093b1989b52e477559759eb8072fc0a'
//
// const result = await bchjs.RawTransactions.getTxDataSlp(txid)
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
//
// assert.equal(result, false)
// })
// })
})
function sleep (ms) {
+231 -5
View File
@@ -24,7 +24,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(3000)
bchjs = new BCHJS()
})
@@ -32,7 +32,8 @@ describe('#SLP', () => {
describe('#util', () => {
describe('#list', () => {
it('should get information on the Spice token', async () => {
const tokenId = '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
const tokenId =
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
const result = await bchjs.SLP.Utils.list(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
@@ -490,6 +491,116 @@ describe('#SLP', () => {
})
})
describe('#tokenUtxoDetailsWL', () => {
it('should return details for a simple SEND SLP token utxo', async () => {
const utxos = [
{
height: 660554,
tx_hash:
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.property(data[0], 'txid')
assert.property(data[0], 'vout')
assert.property(data[0], 'height')
assert.property(data[0], 'utxoType')
assert.property(data[0], 'tokenId')
assert.property(data[0], 'tokenTicker')
assert.property(data[0], 'tokenName')
assert.property(data[0], 'tokenDocumentUrl')
assert.property(data[0], 'tokenDocumentHash')
assert.property(data[0], 'decimals')
assert.property(data[0], 'tokenQty')
assert.property(data[0], 'isValid')
assert.equal(data[0].isValid, true)
})
it('should return false for BCH-only UTXOs', async () => {
const utxos = [
{
txid:
'a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b',
vout: 0,
amount: 0.00001,
satoshis: 1000,
confirmations: 0,
ts: 1578424704
},
{
txid:
'53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44',
vout: 0,
amount: 0.00001,
satoshis: 1000,
confirmations: 0,
ts: 1578424634
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.isArray(data)
assert.equal(data[0].isValid, false)
assert.equal(data[1].isValid, false)
})
it('should handle a dust attack', async () => {
// it("#dustattack", async () => {
const utxos = [
{
height: 655965,
tx_hash:
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
tx_pos: 151,
value: 547,
satoshis: 547,
txid:
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
vout: 151,
address: 'bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww',
hdIndex: 11
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.equal(data[0].isValid, false)
})
it('should handle null SLPDB validations', async () => {
const utxos = [
{
height: 665577,
tx_hash:
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
tx_pos: 1,
value: 546
},
{
height: 665577,
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.isArray(data)
// assert.equal(data[0].isValid, null)
})
})
describe('#balancesForAddress', () => {
it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => {
const balances = await bchjs.SLP.Utils.balancesForAddress(
@@ -528,7 +639,7 @@ describe('#SLP', () => {
})
})
describe('#hydrateUtxos', () => {
describe('#hydrateUtxos-', () => {
it('should hydrate UTXOs', async () => {
const utxos = [
{
@@ -655,6 +766,99 @@ describe('#SLP', () => {
})
})
describe('#hydrateUtxosWL', () => {
it('should hydrate UTXOs', async () => {
const utxos = [
{
utxos: [
{
txid:
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
vout: 1,
value: 546
}
]
}
]
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 1)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
})
it('should process data directly from Electrumx', async () => {
const addrs = [
'bitcoincash:qqnt53cfw990u6y38xgezm0lfa8hknw0wueezcpagp',
'bitcoincash:qrh3qgtax6adegzc7zxm6m4sgrv9wq28yqnfn33ce5',
'bitcoincash:qqcaee5w4ws77n8s2gzy6fwtma6uxd7ctq8553e495'
]
const utxos = await bchjs.Electrumx.utxo(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos.utxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 3)
assert.equal(result.slpUtxos[0].utxos.length, 1)
assert.equal(result.slpUtxos[1].utxos.length, 1)
assert.equal(result.slpUtxos[2].utxos.length, 2)
try {
// Test the expected values.
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
assert.equal(result.slpUtxos[1].utxos[0].isValid, true)
assert.equal(result.slpUtxos[1].utxos[0].tokenTicker, 'TROUT')
assert.equal(result.slpUtxos[2].utxos[0].isValid, false)
// assert.equal(result.slpUtxos[2].utxos[1].isValid, null)
assert.equal(result.slpUtxos[2].utxos[1].tokenTicker, 'VALENTINE')
} catch (err) {
console.error(
'The hydrateUtxos call may hitting rate limits, or SLPDB may be having issues if "isValid" results are "null"'
)
console.log('Error: ', err)
}
})
it('should handle null SLPDB validations', async () => {
const utxos = [
{
height: 665577,
tx_hash:
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
tx_pos: 1,
value: 546
},
{
height: 665577,
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.hydrateUtxosWL([{ utxos: utxos }])
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.isArray(data.slpUtxos)
assert.equal(data.slpUtxos[0].isValid, null)
})
})
describe('#validateTxid', () => {
// This test is not necessary.
it('should handle a null response from SLPDB', async () => {
@@ -735,6 +939,28 @@ describe('#SLP', () => {
assert.property(result, 'slpProcessedBlockHeight')
})
})
describe('#waterfallValidateTxid', () => {
it('should validate known good txid not in whitelist', async () => {
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, true)
})
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, false)
})
})
})
describe('#tokentype1', () => {
@@ -743,8 +969,8 @@ describe('#SLP', () => {
const tokenUtxos = [
{
tokenId:
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
decimals: 0,
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
decimals: 8,
tokenQty: 2
}
]
@@ -0,0 +1,60 @@
/*
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 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)}`)
// 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)
})
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)
})
})
})
+28 -7
View File
@@ -61,16 +61,40 @@ describe('#Blockchain', () => {
it('should get block by hash', done => {
const resolved = new Promise(resolve => resolve({ data: data }))
sandbox.stub(axios, 'get').returns(resolved)
sandbox.stub(axios, 'post').returns(resolved)
bchjs.Blockchain.getBlock(
const blockhash =
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09'
)
bchjs.Blockchain.getBlock(blockhash)
.then(result => {
assert.deepStrictEqual(data, result)
})
.then(done, done)
})
it('should throw error if blockhash is not provided', async () => {
try {
await bchjs.Blockchain.getBlock()
assert2.fail('Unexpected result')
} catch (err) {
assert2.include(err.message, 'blockhash must be a string')
}
})
it('should handle response error', async () => {
try {
const error = new Error()
error.response = {
data: 'Test Error'
}
sandbox.stub(axios, 'post').throws(error)
const blockhash =
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09'
await bchjs.Blockchain.getBlock(blockhash)
assert2.fail('Unexpected result')
} catch (err) {
assert2.include(err, 'Test Error')
}
})
})
describe('#getBlockchainInfo', () => {
@@ -355,10 +379,7 @@ describe('#Blockchain', () => {
try {
await bchjs.Blockchain.getTxOut('badtxid')
} catch (err) {
assert2.include(
err.message,
'txid needs to be a proper transaction ID'
)
assert2.include(err.message, 'txid needs to be a proper transaction ID')
}
})
+4 -8
View File
@@ -5,11 +5,11 @@ const bchjs = new BCHJS()
const sinon = require('sinon')
describe('#Control', () => {
describe('#getNetworkInfo', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#getNetworkInfo', () => {
it('should get info', done => {
const data = {
version: 170000,
@@ -36,10 +36,6 @@ describe('#Control', () => {
})
describe('#getMemoryInfo', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
it('should get memory info', done => {
const data = {
locked: {
+66
View File
@@ -0,0 +1,66 @@
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
// Unit under test (uut)
const BCHJS = require('../../src/bch-js')
const mockData = require('./fixtures/dsproof-mock')
let bchjs
const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
describe('#DSProof', () => {
let sandbox
beforeEach(() => {
bchjs = new BCHJS()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#getDSProof', () => {
it('should throw error if input is not provided', async () => {
try {
await bchjs.DSProof.getDSProof()
assert.equal(false, true, 'unexpected error')
} catch (err) {
assert.include(err.message, 'txid is required')
}
})
it('should throw error txid is invalid', async () => {
try {
await bchjs.DSProof.getDSProof('txid')
assert.equal(false, true, 'unexpected error')
} catch (err) {
assert.include(err.message, 'txid must be of length 64')
}
})
it('should handle error', async () => {
try {
sandbox.stub(bchjs.DSProof.axios, 'get').throws(new Error('test error'))
await bchjs.DSProof.getDSProof(txid)
assert.equal(false, true, 'unexpected error')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should get double spend proof', async () => {
try {
sandbox
.stub(bchjs.DSProof.axios, 'get')
.resolves({ data: mockData.dsproof })
const result = await bchjs.DSProof.getDSProof(txid)
assert.property(result, 'dspid')
assert.property(result, 'txid')
assert.property(result, 'outpoint')
assert.property(result, 'path')
assert.property(result, 'descendants')
assert.property(result.outpoint, 'txid')
assert.property(result.outpoint, 'vout')
} catch (error) {
assert.equal(false, true, 'unexpected error')
}
})
})
})
+73
View File
@@ -390,6 +390,7 @@ describe('#ElectrumX', () => {
assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs')
})
})
describe('#broadcast', () => {
it('should throw an error for improper input', async () => {
try {
@@ -402,6 +403,7 @@ describe('#ElectrumX', () => {
assert.include(err.message, 'Input txHex must be a string.')
}
})
it('should broadcast a single transaction', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.broadcast })
@@ -418,4 +420,75 @@ describe('#ElectrumX', () => {
assert.equal(result.txid, txid)
})
})
describe('#sortConfTxs', () => {
it('should sort in ascending order', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions, 'ASCENDING')
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isBelow(result[0].height, result[1].height)
})
it('should sort in descending order by default', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[0].height, result[1].height)
})
it('should ignore unconfirmed txs', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.txHistoryWithUnconfirmed.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[0].height, 0)
})
it('should handle errors', () => {
try {
bchjs.Electrumx.sortConfTxs('abc')
assert.fail('Unexpected result')
} catch (err) {
// console.log(err.message)
assert.equal(err.message, 'txs.filter is not a function')
}
})
})
// These tests use mocked data that contains unconfirmed transactions.
describe('#sortAllTxs', () => {
it('should sort in ascending', async () => {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions, 'ASCENDING')
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isBelow(result[0].height, result[1].height)
})
it('should sort in descending order by default', async () => {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[1].height, result[2].height)
})
it('should handle errors', async () => {
try {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
await bchjs.Electrumx.sortAllTxs('abc')
assert.fail('Unexpected result')
} catch (err) {
// console.log(err.message)
assert.equal(err.message, 'txs.map is not a function')
}
})
})
})
+19
View File
@@ -0,0 +1,19 @@
/*
Mock data used for unit testing.
*/
const dsproof = {
dspid: '6234fff2a9dbcaa50e2c50d74e1d725a2bed1757d7a05a2db7a82df659554397',
txid: 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e',
outpoint: {
txid: '17ebef34f7e9a0692317dd6fbb43ffccff45b7d688a2a754dbccfc5e6d93ce3e',
vout: 1
},
path: ['ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'],
descendants: [
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
]
}
module.exports = {
dsproof
}
+44 -8
View File
@@ -168,7 +168,8 @@ const txDetails = {
blocktime: 1578327094,
confirmations: 31861,
hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
hex:
'020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
locktime: 0,
size: 392,
time: 1578327094,
@@ -177,8 +178,10 @@ const txDetails = {
vin: [
{
scriptSig: {
asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
asm:
'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
hex:
'41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
@@ -186,8 +189,10 @@ const txDetails = {
},
{
scriptSig: {
asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
asm:
'347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
hex:
'41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
@@ -210,7 +215,8 @@ const txDetails = {
n: 1,
scriptPubKey: {
addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'],
asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac',
reqSigs: 1,
type: 'pubkeyhash'
@@ -221,7 +227,8 @@ const txDetails = {
n: 2,
scriptPubKey: {
addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'],
asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac',
reqSigs: 1,
type: 'pubkeyhash'
@@ -255,6 +262,34 @@ const broadcast = {
txid: txDetails.txid
}
const txHistoryWithUnconfirmed = {
success: true,
transactions: [
{
height: 668539,
tx_hash:
'fd9bffca3dd7a2d801c87cb36e8b43949d2e171bb8e25e3d92cf39ac5e2ba179'
},
{
height: 668544,
tx_hash:
'9d1b438aa55d75da8ebe9dcdfa4fa96714042ce0a19c8f3c10d86e1e3dcbccf5'
},
{
fee: 440,
height: 0,
tx_hash:
'30d3fa9bf78eab2162d4f1894223e417e7275fdf0d590fe5085381dd475ff077'
},
{
fee: 468,
height: -1,
tx_hash:
'4b93c26a4612b6819fde2bf5d1f6387bf92f49a3375c53a472001e7d76a80e59'
}
]
}
module.exports = {
utxo,
utxos,
@@ -267,5 +302,6 @@ module.exports = {
blockHeaders,
details,
detailsArray,
broadcast
broadcast,
txHistoryWithUnconfirmed
}
+62
View File
@@ -0,0 +1,62 @@
/*
Mocking data for the RawTransactions unit tests.
*/
const mockTx = {
txid: '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3',
vin: [
{
txid: '8a6b3b70569270f0bdbf68fd12a410aef8f7bf044bc88ab02386a1572024b2bd',
vout: 0,
scriptSig: {
asm:
'3044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba0[ALL|FORKID] 030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9',
hex:
'473044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba04121030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9'
},
sequence: 4294967295
}
]
}
const mockParentTx1 = {
vout: [
{
value: 0.04199959,
n: 0,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 d5258a73b5c8f94c939d7fe96f78ce97906083be OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914d5258a73b5c8f94c939d7fe96f78ce97906083be88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt']
}
},
{
value: 1.66296161,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 5e95c308c25c74c64c5ffe44a60a4d9b35743e90 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9145e95c308c25c74c64c5ffe44a60a4d9b35743e9088ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qp0ftscgcfw8f3jvtllyffs2fkdn2ap7jqreakn4ye']
}
}
]
}
const mockGetInputAddrsOutput = [
{
vin: 0,
address: 'bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt'
}
]
module.exports = {
mockTx,
mockParentTx1,
mockGetInputAddrsOutput
}
+127 -1
View File
@@ -119,6 +119,33 @@ const mockTokens = [
}
]
const mockNftChildrenList = {
nftChildren: [
'45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9',
'928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55'
]
}
const mockNftGroup = {
nftGroup: {
decimals: 0,
timestamp: '2021-05-03 10:36:01',
timestamp_unix: 1620038161,
versionType: 129,
documentUri: 'psfoundation.cash',
symbol: 'PSF.TEST.GROUP',
name: 'PSF Test NFT Group',
containsBaton: true,
id: '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a',
documentHash: null,
initialTokenQty: 1000000,
blockCreated: 686117,
totalMinted: null,
totalBurned: null,
circulatingSupply: null
}
}
const balancesForAddress = [
{
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
@@ -1290,10 +1317,106 @@ const slpdbStatus = {
}
}
const txDetailsSLPNftChild = {
txid: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
hash: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
version: 2,
size: 453,
locktime: 0,
vin: [
{
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
vout: 1,
scriptSig: {
asm:
'3045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
hex:
'483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
},
sequence: 4294967295
},
{
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
vout: 3,
scriptSig: {
asm:
'304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
hex:
'47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
},
sequence: 4294967295
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 65 47454e45534953 4e4654303034 4e4654204368696c64 68747470733a2f2f46756c6c537461636b2e63617368 0 0 0 0000000000000001',
hex:
'6a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c00080000000000000001',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
}
},
{
value: 0.0003923,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
}
}
],
hex:
'020000000295e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9010000006b483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff95e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9030000006a47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff030000000000000000476a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c0008000000000000000122020000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac3e990000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac00000000',
blockhash: '000000000000000001360fe42629f4136e9adec53546b278de0e7ef788eed328',
confirmations: 2113,
time: 1613181364,
blocktime: 1613181364
}
const mock429Error = new Error('Request failed with status code 429')
mock429Error.response = {
status: 429,
statusText: 'Too Many Requests',
data: {
error:
'Too many requests. Your limits are currently 100 requests per minute. Increase rate limits at https://fullstack.cash'
}
}
const mock503Error = new Error('Request failed with status code 503')
mock503Error.response = {
status: 503,
statusText: 'Service Unavailable',
data: {
error: 'Some error message'
}
}
module.exports = {
mockList,
mockToken,
mockTokens,
mockNftChildrenList,
mockNftGroup,
balancesForAddress,
balancesForAddresses,
mockRawTx,
@@ -1311,6 +1434,7 @@ module.exports = {
txDetailsSLPGenesisNoBaton,
txDetailsSLPSendAlt,
txDetailsSLPNftGenesis,
txDetailsSLPNftChild,
mockTxDetails,
mockDualValidation,
mockDualOpData,
@@ -1320,5 +1444,7 @@ module.exports = {
mockValidateTxid3Invalid,
whitelist,
slpdbStatus,
mockValidateTxidArray
mockValidateTxidArray,
mock429Error,
mock503Error
}
+184
View File
@@ -0,0 +1,184 @@
/*
Mocking data used in the transaction-unit.js tests.
*/
const nonSlpTxDetails = {
txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
hash: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
version: 2,
size: 225,
locktime: 0,
vin: [
{
txid: '5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac',
vout: 1,
scriptSig: {
asm:
'3044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c[ALL|FORKID] 038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9',
hex:
'473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9'
},
sequence: 4294967295,
address: 'bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe',
value: 0.00047504
}
],
vout: [
{
value: 0.00001,
n: 0,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9']
}
},
{
value: 0.00046256,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 2dbf5e1804c39a497b908c876097d63210c84902 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a9142dbf5e1804c39a497b908c876097d63210c8490288ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e']
}
}
],
hex:
'0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000',
blockhash: '0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5',
confirmations: 77741,
time: 1569792892,
blocktime: 1569792892
}
const slpTxDetails = {
txid: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1',
hash: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1',
version: 2,
size: 479,
locktime: 0,
vin: [
{
txid: 'abc685f1f2a95f51e5e05a350f3fb9c74676e9f78c835b2a019c888ac0a2a736',
vout: 2,
scriptSig: {
asm:
'3045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f7[ALL|FORKID] 0245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac',
hex:
'483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac'
},
sequence: 4294967295,
address: 'bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn',
value: 0.00000546
},
{
txid: '58c8576404c01c23a224053307399483d3a070599b3e9eb6d45be9714b8d6856',
vout: 1,
scriptSig: {
asm:
'30430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db12753[ALL|FORKID] 0209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505',
hex:
'4630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505'
},
sequence: 4294967295,
address: 'bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7',
value: 0.68369626
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 1145980243 497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7 0000000005f5e100 00005ad7e49d9100',
hex:
'6a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d9100',
type: 'nulldata'
}
},
{
value: 0.00000546,
n: 1,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 36be3b7d185a85b6cf6fc61d63c16f2f10e54260 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qqmtuwmarpdgtdk0dlrp6c7pduh3pe2zvqrkys2ex8']
}
},
{
value: 0.00000546,
n: 2,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 99e5a8229a9af0dbf3cadb25ea981df49c9d93bf OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn']
}
},
{
value: 0.68368564,
n: 3,
scriptPubKey: {
asm:
'OP_DUP OP_HASH160 422e1b3b400864bfd3146dd9c628de0f1da62289 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac',
reqSigs: 1,
type: 'pubkeyhash',
addresses: ['bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7']
}
}
],
hex:
'020000000236a7a2c08a889c012a5b838cf7e97646c7b93f0f355ae0e5515fa9f2f185c6ab020000006b483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07acffffffff56688d4b71e95bd4b69e3e9b5970a0d383943907330524a2231cc0046457c85801000000694630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505ffffffff040000000000000000406a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d910022020000000000001976a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac22020000000000001976a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88acb4381304000000001976a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac00000000',
blockhash: '0000000000000000015284202422a688554b7fc80c54f18122847a99c4f79607',
confirmations: 76722,
time: 1570392893,
blocktime: 1570392893
}
const mockOpReturnData01 = {
tokenType: 1,
txType: 'SEND',
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
amounts: ['100000000', '99883300000000']
}
const mockOpReturnData02 = {
tokenType: 1,
txType: 'GENESIS',
ticker: 'TOK-CH',
name: 'TokyoCash',
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
documentUri: '',
documentHash: '',
decimals: 8,
mintBatonVout: 0,
qty: '2100000000000000'
}
const mockOpReturnData03 = {
tokenType: 1,
txType: 'SEND',
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
amounts: ['1000000000', '99883400000000']
}
module.exports = {
nonSlpTxDetails,
slpTxDetails,
mockOpReturnData01,
mockOpReturnData02,
mockOpReturnData03
}
+289
View File
@@ -0,0 +1,289 @@
const mockUtxoData = {
success: true,
utxos: [
{
utxos: [
{
height: 569108,
tx_hash:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
tx_pos: 1,
value: 546
},
{
height: 674331,
tx_hash:
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
tx_pos: 0,
value: 1000
}
],
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
}
const mockHydratedUtxos = {
slpUtxos: [
{
utxos: [
{
height: 569108,
tx_hash:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
tx_pos: 1,
value: 546,
txid:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
utxoType: 'token',
transactionType: 'send',
tokenId:
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
tokenTicker: 'sleven',
tokenName: 'sleven',
tokenDocumentUrl: 'sleven',
tokenDocumentHash: '',
decimals: 7,
tokenType: 1,
tokenQty: '1',
isValid: true
},
{
height: 674331,
tx_hash:
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
tx_pos: 0,
value: 1000,
txid:
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
vout: 0,
isValid: false
}
],
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
}
const mockTwoHydratedAddrs = {
slpUtxos: [
{
utxos: [
{
height: 569108,
tx_hash:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
tx_pos: 1,
value: 546,
txid:
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
utxoType: 'token',
transactionType: 'send',
tokenId:
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
tokenTicker: 'sleven',
tokenName: 'sleven',
tokenDocumentUrl: 'sleven',
tokenDocumentHash: '',
decimals: 7,
tokenType: 1,
tokenQty: '1',
isValid: true
},
{
height: 674331,
tx_hash:
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
tx_pos: 0,
value: 1000,
txid:
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
vout: 0,
isValid: false
}
],
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
utxos: [
{
height: 602405,
tx_hash:
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
tx_pos: 0,
value: 1000,
txid:
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
vout: 0,
isValid: false
}
],
address: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
}
]
}
const mockEveryUtxoType = {
slpUtxos: [
{
utxos: [
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 1,
value: 546,
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 1,
utxoType: 'token',
tokenQty: '100',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tokenTicker: 'SLPTEST',
tokenName: 'SLP Test Token',
tokenDocumentUrl: 'https://FullStack.cash',
tokenDocumentHash: '',
decimals: 8,
tokenType: 1,
isValid: true
},
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 2,
value: 546,
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 2,
utxoType: 'minting-baton',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tokenTicker: 'SLPTEST',
tokenName: 'SLP Test Token',
tokenDocumentUrl: 'https://FullStack.cash',
tokenDocumentHash: '',
decimals: 8,
tokenType: 1,
isValid: true
},
{
height: 674512,
tx_hash:
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
tx_pos: 1,
value: 546,
txid:
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
vout: 1,
utxoType: 'token',
tokenQty: '1',
tokenId:
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
tokenTicker: 'NFT004',
tokenName: 'NFT Child',
tokenDocumentUrl: 'https://FullStack.cash',
tokenDocumentHash: '',
decimals: 0,
tokenType: 65,
isValid: true
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 1,
value: 546,
txid:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
vout: 1,
utxoType: 'token',
tokenQty: '10',
transactionType: 'mint',
tokenId:
'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
tokenType: 129,
tokenTicker: 'NFTGT',
tokenName: 'NFT Test Group Token',
tokenDocumentUrl: 'https://FullStack.cash',
tokenDocumentHash: '',
decimals: 0,
mintBatonVout: 2,
isValid: true
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 2,
value: 546,
txid:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
vout: 2,
utxoType: 'minting-baton',
transactionType: 'mint',
tokenId:
'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
tokenType: 129,
tokenTicker: 'NFTGT',
tokenName: 'NFT Test Group Token',
tokenDocumentUrl: 'https://FullStack.cash',
tokenDocumentHash: '',
decimals: 0,
mintBatonVout: 2,
isValid: true
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 3,
value: 38134,
txid:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
vout: 3,
isValid: false
}
],
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
}
]
}
const electrumxUtxos = {
success: true,
utxos: [
{
height: 0,
tx_hash:
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
tx_pos: 0,
value: 600
},
{
height: 0,
tx_hash:
'f913646f7c180f8de020cb1387951272e8a3f764a0f88e68f7fc1145a0bf02e9',
tx_pos: 0,
value: 700
},
{
height: 0,
tx_hash:
'7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b',
tx_pos: 0,
value: 800
}
]
}
module.exports = {
mockUtxoData,
mockHydratedUtxos,
mockTwoHydratedAddrs,
mockEveryUtxoType,
electrumxUtxos
}
-129
View File
@@ -1,129 +0,0 @@
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const axios = require('axios')
const sinon = require('sinon')
const mockData = require('./fixtures/openbazaar-mock')
describe('#OpenBazaar', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#Balance', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.OpenBazaar.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 GET balance for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.balance })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.balance(addr)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, [
'page',
'totalPages',
'itemsOnPage',
'addrStr',
'balance',
'totalReceived',
'totalSent',
'unconfirmedBalance',
'unconfirmedTxApperances',
'txApperances',
'transactions'
])
assert.isArray(result.transactions)
})
})
describe('#utxo', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.OpenBazaar.utxo(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 GET utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.utxo })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
'txid',
'vout',
'amount',
'height',
'confirmations',
'satoshis'
])
})
})
describe('#tx', () => {
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.OpenBazaar.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 GET tx details for a single txid', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.tx })
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.OpenBazaar.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'txid',
'version',
'vin',
'vout',
'blockhash',
'blockheight',
'confirmations',
'blocktime',
'valueOut',
'valueIn',
'fees',
'hex'
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
+22
View File
@@ -67,4 +67,26 @@ describe('#price', () => {
assert.isNumber(result)
})
})
describe('#getBchUsd', () => {
it('should get the USD price of BCH', async () => {
sandbox.stub(bchjs.Price.axios, 'get').resolves({ data: { usd: 510.39 } })
const result = await bchjs.Price.getBchUsd()
// console.log(result)
assert.isNumber(result)
})
it('should handle error', async () => {
try {
sandbox.stub(bchjs.Price.axios, 'get').throws(new Error('test error'))
await bchjs.Price.getBchUsd()
// console.log(result)
assert.equal(false, true, 'unexpected error')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+96 -6
View File
@@ -1,10 +1,10 @@
/*
TODO:
-Create a mocking library of data to compare unit and integration tests.
Unit tests for raw-transactions.js library.
*/
// Public npm libraries
const assert = require('assert')
const assert2 = require('chai').assert
const axios = require('axios')
const sinon = require('sinon')
// const nock = require("nock") // HTTP mocking
@@ -18,16 +18,19 @@ let bchjs
// const util = require("util")
// util.inspect.defaultOptions = { depth: 1 }
const mockData = require('./fixtures/rawtransaction-mock')
describe('#RawTransactions', () => {
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
bchjs = new BCHJS()
})
describe('#decodeRawTransaction', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
afterEach(() => sandbox.restore())
describe('#decodeRawTransaction', () => {
it('should decode raw transaction', done => {
const data = {
txid:
@@ -132,4 +135,91 @@ describe('#RawTransactions', () => {
// assert.strictEqual(data, result.data)
// })
})
describe('#_getInputAddrs', () => {
it('should return an array of input addresses', async () => {
sandbox
.stub(bchjs.RawTransactions, 'getRawTransaction')
.resolves(mockData.mockParentTx1)
const result = await bchjs.RawTransactions._getInputAddrs(mockData.mockTx)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.isArray(result)
assert2.equal(result.length, 1)
assert2.property(result[0], 'vin')
assert2.property(result[0], 'address')
})
it('should catch and throw and error', async () => {
try {
sandbox
.stub(bchjs.RawTransactions, 'getRawTransaction')
.rejects(new Error('test error'))
await bchjs.RawTransactions._getInputAddrs(mockData.mockTx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert2.equal(err.message, 'test error')
}
})
})
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)
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
const result = await bchjs.RawTransactions.getTxData(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.property(result.vin[0], 'address')
})
it('should throw an error for a non-txid input', async () => {
try {
await bchjs.RawTransactions.getTxData(1234)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert2.include(
err.message,
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
)
}
})
it('should catch and throw an error', async () => {
try {
// Force a network error.
sandbox
.stub(bchjs.RawTransactions, 'getRawTransaction')
.rejects(new Error('test error'))
const txid =
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
await bchjs.RawTransactions.getTxData(txid)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert2.equal(err.message, 'test error')
}
})
})
})
+93 -2
View File
@@ -4,7 +4,7 @@
const assert = require('chai').assert
const sinon = require('sinon')
// const axios = require("axios")
const axios = require('axios')
// Default to unit tests unless some other value for TEST is passed.
if (!process.env.TEST) process.env.TEST = 'unit'
@@ -14,7 +14,7 @@ const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
// Mock data used for unit tests
// const mockData = require("./fixtures/slp/mock-utils")
const mockData = require('./fixtures/slp/mock-utils')
// Default to unit tests unless some other value for TEST is passed.
if (!process.env.TEST) process.env.TEST = 'unit'
@@ -253,4 +253,95 @@ describe('#SLP NFT1', () => {
assert.isNumber(result.outputs)
})
})
describe('#listNFTGroupChildren', () => {
it('should throw an error for improper input', async () => {
try {
const groupId = 12345
await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'groupId must be a string'
)
}
})
it('should handle API response error', async () => {
try {
const error = new Error()
error.response = {
error: 'NFT group does not exists'
}
sandbox.stub(axios, 'get').throws(error)
const groupId = 'non-exists'
await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
} catch (err) {
assert.include(
err.response,
{ error: 'NFT group does not exists' }
)
}
})
it('should return array of children GENESIS transactions IDs', async () => {
sandbox.stub(axios, 'get').resolves({ data: mockData.mockNftChildrenList })
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 throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'tokenId must be a string'
)
}
})
it('should handle API response error', async () => {
try {
const error = new Error()
error.response = {
error: 'NFT child does not exists'
}
sandbox.stub(axios, 'get').throws(error)
const tokenId = 'non-exists'
await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
} catch (err) {
assert.include(
err.response,
{ error: 'NFT child does not exists' }
)
}
})
it('should return parent NFT group information', async () => {
sandbox.stub(axios, 'get').resolves({ data: mockData.mockNftGroup })
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')
})
})
})
+21 -2
View File
@@ -1178,6 +1178,25 @@ describe('#SLP TokenType1', () => {
})
})
describe('#generateSendOpReturn07', () => {
it('should generate send OP_RETURN code', () => {
// Mock UTXO.
const tokenUtxos = [
{
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
decimals: 8,
tokenQty: 2
}
]
const result = bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, 1.5)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, ['script', 'outputs'])
assert.isNumber(result.outputs)
})
})
describe('#generateBurnOpReturn', () => {
it('should generate burn OP_RETURN code', () => {
// Mock UTXO.
@@ -1198,11 +1217,11 @@ describe('#SLP TokenType1', () => {
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 8,
tokenQty: 7
tokenQty: '7'
}
]
const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 1)
const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 8)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result: `, result)
+533 -287
View File
File diff suppressed because it is too large Load Diff
+119
View File
@@ -0,0 +1,119 @@
/*
Unit tests for the transaction.js library.
*/
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/transaction-mock.js')
describe('#TransactionLib', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#get', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get()
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)
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[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)
})
it('should get details about a SLP transaction', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.slpTxDetails)
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'))
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
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)
})
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.get(txid)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+140
View File
@@ -1,4 +1,5 @@
const assert = require('assert')
const assert2 = require('chai').assert
const axios = require('axios')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
@@ -32,4 +33,143 @@ describe('#Util', () => {
.then(done, done)
})
})
describe('#floor8', () => {
it('should floor a number to 8 decimals', () => {
const num = 1.234567891111
const result = bchjs.Util.floor8(num)
// console.log(result)
assert2.equal(result, 1.23456789)
})
it('should throw an error for non-number input', () => {
try {
const num = 'string'
bchjs.Util.floor8(num)
assert2.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(err)
assert2.include(err.message, 'input must be a number')
}
})
it('should not effect a number with less than 8 decimals', () => {
const num = 1.23
const result = bchjs.Util.floor8(num)
// console.log(result)
assert2.equal(result, 1.23)
})
})
describe('#floor2', () => {
it('should round a number to 2 decimals', () => {
const num = 1.234567891111
const result = bchjs.Util.floor2(num)
// console.log(result)
assert2.equal(result, 1.23)
})
it('should throw an error for non-number input', () => {
try {
const num = 'string'
bchjs.Util.floor2(num)
assert2.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(err)
assert2.include(err.message, 'input must be a number')
}
})
it('should not effect a number with less than 8 decimals', () => {
const num = 1.2
const result = bchjs.Util.floor2(num)
// console.log(result)
assert2.equal(result, 1.2)
})
})
describe('#chunk20', () => {
const thirtyFiveElements = [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34
]
it('should split 35 elements into 2 arrays', () => {
const result = bchjs.Util.chunk20(thirtyFiveElements)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.isArray(result)
assert2.isArray(result[0])
assert2.equal(result.length, 2)
assert2.equal(result[0].length, 20)
})
it('should return accurately with an array of less than 20 elements', () => {
const elems = [0, 1, 2, 3, 4, 5]
const result = bchjs.Util.chunk20(elems)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert2.isArray(result)
assert2.isArray(result[0])
assert2.equal(result.length, 1)
assert2.equal(result[0].length, 6)
})
it('should throw an error for non-array input', () => {
try {
bchjs.Util.chunk20('string')
assert2.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(err)
assert2.include(err.message, 'input must be an array')
}
})
})
})
+191
View File
@@ -0,0 +1,191 @@
/*
Unit tests for the utxo.js library.
*/
const sinon = require('sinon')
const assert = require('chai').assert
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/utxo-mocks')
describe('#utxo', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// Mock dependencies.
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
sandbox
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
.resolves(mockData.mockHydratedUtxos)
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)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.rejects(new Error('test error'))
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
await bchjs.Utxo.get(addr)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle an array of addresses', async () => {
// Mock dependencies.
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} })
sandbox
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
.resolves(mockData.mockTwoHydratedAddrs)
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.equal(result.length, 2)
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 throw an error for array of 21 elements', async () => {
try {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const addrs = []
for (let i = 0; i < 21; i++) {
addrs.push(addr)
}
await bchjs.Utxo.get(addrs)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Too many elements, 20 max.')
}
})
it('should handle NFTs and minting batons', async () => {
// Mock dependencies.
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} })
sandbox
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
.resolves(mockData.mockEveryUtxoType)
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)
})
it('should use the whitelist when flag is set', async () => {
// Mock dependencies.
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
sandbox
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxosWL')
.resolves(mockData.mockHydratedUtxos)
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const useWhitelist = true
const result = await bchjs.Utxo.get(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)
})
})
describe('#findBiggestUtxo', () => {
it('should throw error for non-array input', async () => {
try {
await bchjs.Utxo.findBiggestUtxo({})
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(
err.message,
'utxos input to findBiggestUtxo() must be an array'
)
}
})
it('should throw an error if input does not have a value or satoshis property', async () => {
try {
const badUtxos = [
{
height: 0,
tx_hash:
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
tx_pos: 0
}
]
await bchjs.Utxo.findBiggestUtxo(badUtxos)
} catch (err) {
assert.include(
err.message,
'UTXOs require a satoshis or value property for findBiggestUtxo()'
)
}
})
it('should sort UTXOs from Electrumx', async () => {
const result = bchjs.Utxo.findBiggestUtxo(mockData.electrumxUtxos.utxos)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
})
})
})