Compare commits

..
105 Commits
Author SHA1 Message Date
Chris Troutner 5e5e636831 Merge pull request #239 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Fixing broken integration tests for token data
2022-05-09 09:49:02 -07:00
Chris Troutner 2adc2dfebb fix(tests): Fixing broken integration tests for token data 2022-05-09 09:45:28 -07:00
Chris Troutner f9bc44ccb7 Merge pull request #238 from Permissionless-Software-Foundation/ct-unstable
fix(token data): Returning CID rather than content itself
2022-05-07 20:59:55 -07:00
Chris Troutner 298b8f56ff Merge branch 'temp-token-data' into ct-unstable 2022-05-07 20:54:49 -07:00
Chris Troutner 139da8cf7b fix(token data): Returning CID rather than content itself 2022-05-07 20:54:20 -07:00
Chris Troutner 08e03c14d8 Merge pull request #234 from Permissionless-Software-Foundation/dh-psfSlpIndexer
feat(slpindexer): Added bchjs.PsfSlpIndexer.getTokenData()
2022-05-07 20:25:15 -07:00
Chris Troutner 7da010e7af Using config.restURL instead of hard-coded URL 2022-05-07 20:20:30 -07:00
Chris Troutner 7b6eebe7c7 Merge branch 'master' into dh-psfSlpIndexer 2022-05-07 20:05:25 -07:00
Chris Troutner 843f5eacef Merge pull request #237 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Added additional integration tests
2022-05-05 12:08:46 -07:00
Chris Troutner 416e302ca5 fix(Utxo.get()): Added additional integration tests 2022-05-05 12:06:59 -07:00
Chris Troutner 1c66da70b6 Merge pull request #236 from Permissionless-Software-Foundation/ct-unstable
fix(mintNFTGroupOpReturn()): Updating to use UTXOs from psf-slp-indexer
2022-05-05 10:45:59 -07:00
Chris Troutner 59e92adf0b fix(mintNFTGroupOpReturn()): Updating to use UTXOs from psf-slp-indexer 2022-05-05 10:43:40 -07:00
Chris Troutner f369411ccb Merge pull request #235 from Permissionless-Software-Foundation/ct-unstable
Updating Utxo.get()
2022-05-05 10:05:36 -07:00
Chris Troutner 0504cf7b8d fix(Utxo.get()): Adding filtering for Group batons 2022-05-05 10:03:35 -07:00
Chris Troutner 88ae47cade fix(Utxo.get()): Adding prototype NFT filtering 2022-05-04 17:44:53 -07:00
Daniel Gonzalez d144b13500 feat(slpindexer): Added bchjs.PsfSlpIndexer.getTokenData() 2022-05-04 18:29:54 -04:00
Chris Troutner 30d58bc337 Merge pull request #233 from Permissionless-Software-Foundation/ct-unstable
fix(readme): Adding backup information
2022-04-30 19:48:24 -07:00
Chris Troutner 651ae3f6ca fix(readme): Adding backup information 2022-04-30 19:37:30 -07:00
Chris Troutner 04cd90f0f4 Merge pull request #232 from Permissionless-Software-Foundation/ct-unstable
Updating README
2022-04-30 16:09:24 -07:00
Chris Troutner 552ee31b79 Adding QR to readme 2022-04-30 16:07:17 -07:00
Chris Troutner affa9619bb fix(README): Updating README 2022-04-30 16:04:22 -07:00
Chris Troutner 39bff131af fix(generateMintOpReturn): Adding additional input validation 2022-04-24 10:28:46 -07:00
Chris Troutner a84fecfe41 Merge pull request #231 from Permissionless-Software-Foundation/ct-unstable
fix(tokentype1.js): Fixing bug in mint OP_RETURN
2022-04-24 09:57:52 -07:00
Chris Troutner 0736c9f2a1 Merge branch 'master' into ct-unstable 2022-04-24 09:55:00 -07:00
Chris Troutner aa898b31e7 fix(tokentype1.js): Fixing bug in mint OP_RETURN 2022-04-24 09:54:14 -07:00
Chris Troutner 29c3381cc5 Merge pull request #230 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Hydrating mint batons for tokens
2022-04-24 09:16:59 -07:00
Chris Troutner f7ba6bb3ae fix(Utxo.get()): Hydrating mint batons for tokens 2022-04-24 09:15:10 -07:00
Chris Troutner 62056e1ac4 Merge pull request #229 from Permissionless-Software-Foundation/ct-unstable
fix(tokentype1.js): Mint OP_RETURN works with output from psf-slp-ind…
2022-04-24 08:57:56 -07:00
Chris Troutner a60e2fa2da fix(tokentype1.js): Mint OP_RETURN works with output from psf-slp-indexer 2022-04-24 08:51:49 -07:00
Chris Troutner 37fe679800 Merge pull request #228 from Permissionless-Software-Foundation/ct-unstable
fix(util.js): Fixing small bug with Util.validateAddress()
2022-04-23 17:41:04 -07:00
Chris Troutner 4250d841c5 Merge branch 'master' into ct-unstable 2022-04-23 17:36:16 -07:00
Chris Troutner 31079afc35 fix(util.js): Fixing small bug with Util.validateAddress() 2022-04-23 17:18:15 -07:00
Chris Troutner 62b44d5058 Merge pull request #227 from gitter-badger/gitter-badge
Add a Gitter chat badge to README.md
2022-04-05 05:31:20 -07:00
The Gitter Badger 150295cb43 Add Gitter badge 2022-04-05 12:10:09 +00:00
Chris Troutner 5de2f71ea5 Merge pull request #226 from Permissionless-Software-Foundation/ct-unstable
fix(Transaction.get()): Handling coinbase txs
2022-03-31 07:28:02 -07:00
Chris Troutner 50a1d60229 fix(Transaction.get()): Handling coinbase txs 2022-03-31 07:24:07 -07:00
Chris Troutner f3e25a0338 Merge pull request #225 from Permissionless-Software-Foundation/ct-unstable
fix(npm): Ran npm audit fix. 0 vulns
2022-03-29 16:56:09 -07:00
Chris Troutner 640c582724 fix(npm): Ran npm audit fix. 0 vulns 2022-03-29 16:53:49 -07:00
Chris Troutner b472fa2673 Merge pull request #224 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Fixing typo
2022-03-29 16:22:23 -07:00
Chris Troutner ea83335fd2 fix(Utxo.get()): Moving code to where it needs to be 2022-03-29 16:20:03 -07:00
Chris Troutner 3050819784 fix(Utxo.get()): Fixing typo 2022-03-29 16:15:47 -07:00
Chris Troutner 4ca9b1c00b Merge pull request #223 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Adding property expected by SLP.tokentype1 lib
2022-03-29 16:05:22 -07:00
Chris Troutner bbc1a45a9e fix(Utxo.get()): Adding property expected by SLP.tokentype1 lib 2022-03-29 15:59:55 -07:00
Chris Troutner 469d08e2b1 Merge pull request #222 from Permissionless-Software-Foundation/ct-unstable
Adding API docs for Transaction lib
2022-03-19 07:03:10 -07:00
Chris Troutner dcadad189b fix(getTokenInfo): Adding API documentation 2022-03-19 07:00:27 -07:00
Chris Troutner 02f81ee203 fix(transactoin): Adding API documentation 2022-03-19 06:58:49 -07:00
Chris Troutner 972bf604d1 Merge pull request #221 from Permissionless-Software-Foundation/ct-unstable
feat(slp): Removing more legacy code that dependended on SLPDB
2022-03-19 06:52:11 -07:00
Chris Troutner 82f084edec feat(slp): Removing more legacy code that dependended on SLPDB 2022-03-19 06:49:43 -07:00
Chris Troutner 4764463fc5 Merge pull request #220 from Permissionless-Software-Foundation/ct-unstable
Removing SLPDB endpoints
2022-03-14 15:54:49 -07:00
Chris Troutner 857458db57 feat(slpdb): Removing endpoints that expect SLPDB interface 2022-03-14 15:50:21 -07:00
Chris Troutner 5f8f05d194 Working on removing slpdb endpoints 2022-03-14 15:13:41 -07:00
Chris Troutner e455d9efec fix(dependencies): Ensuring I'm using exact versions. npm audit = 0 vulns 2022-03-14 14:49:28 -07:00
Chris Troutner ad4e05c289 Bumping to v6.0.0 2022-03-14 14:43:52 -07:00
Chris Troutner 0c1eda8e5a Merge pull request #219 from Permissionless-Software-Foundation/ct-unstable
Updates to tests
2022-03-13 19:12:18 -07:00
Chris Troutner f15bc7e454 fix(tests): Deleting unneeded integration tests 2022-03-13 17:25:51 -07:00
Chris Troutner e9113d864e fix(ecash): Removing SLPDB tests 2022-03-13 13:20:09 -07:00
Chris Troutner 277c9ce0fd Merge pull request #217 from Permissionless-Software-Foundation/ct-unstable
feat(ipfs): Removing the ipfs pinning libraries and dependencies
2022-03-10 16:47:43 -08:00
Chris Troutner e0c07d1542 feat(ipfs): Removing the ipfs pinning libraries and dependencies 2022-03-10 16:45:27 -08:00
Chris Troutner 56bc942e87 fix(tests): Fixing broken integration test 2022-03-10 16:38:41 -08:00
Chris Troutner 34b9cefb65 fix(psf-slp-indexer): Switching URL back 2022-03-10 16:37:11 -08:00
Chris Troutner 6a35091eec fix(psf-slp-indexer): Fixing issue with url 2022-03-10 16:24:12 -08:00
Chris Troutner 02e3ff4dcd Merge pull request #216 from Permissionless-Software-Foundation/ct-unstable
fix(psf-slp-indexer): Adding flag for tx history of tokens
2022-02-06 13:26:05 -08:00
Chris Troutner 64f8adea25 fix(psf-slp-indexer): Adding flag for tx history of tokens 2022-02-06 13:23:25 -08:00
Chris Troutner bcf817f5c7 Merge pull request #215 from Permissionless-Software-Foundation/ct-unstable
Fixing integration test
2022-01-31 21:22:25 -08:00
Chris Troutner 541377e548 Fixing integration test 2022-01-31 21:18:08 -08:00
Chris Troutner 84b44b299a Merge pull request #214 from Permissionless-Software-Foundation/ct-unstable
fix(UTXO.get()): Added detection of null-tagged UTXOs
2022-01-31 21:13:50 -08:00
Chris Troutner b6456df1f4 Fixing unit tests 2022-01-31 21:11:11 -08:00
Chris Troutner b1826c04cb fix(UTXO.get()): Added detection of null-tagged UTXOs 2022-01-31 20:57:36 -08:00
Chris Troutner 71755964c8 Merge pull request #213 from Permissionless-Software-Foundation/ct-unstable
Adding Util.chunk100()
2022-01-27 17:39:47 -08:00
Chris Troutner bd1c3b622d feat(chunk100): Adding chunk100 which works just like chunk20 2022-01-27 17:37:20 -08:00
Chris Troutner bdced13230 fix(Electrumx): Added commented code for debugging sorting 2022-01-27 08:52:12 -08:00
Chris Troutner c925bfce46 Merge pull request #212 from Permissionless-Software-Foundation/ct-unstable
Integration Test Maintenance
2022-01-23 07:11:32 -08:00
Chris Troutner 056a47f6b2 fix(integration tests): Got BCHN and ABC integration tests passing 2022-01-23 07:08:41 -08:00
Chris Troutner 51038681d3 fix(transaction.js): Added integrationt test 2022-01-23 06:46:06 -08:00
Chris Troutner 1a2f144baf Merge pull request #211 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Properly handling addresses with no SLP utxos
2022-01-19 14:48:29 -08:00
Chris Troutner 6d70568082 fix(Utxo.get()): Properly handling addresses with no SLP utxos 2022-01-19 14:46:00 -08:00
Chris Troutner 0b1cf57852 fix(integration): Updating integration tests 2022-01-19 12:20:08 -08:00
Chris Troutner 48f7f75c9c Merge pull request #210 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Hydrating SLP UTXOs with additional token data
2022-01-19 10:37:45 -08:00
Chris Troutner 0bc96fded5 fix(Utxo.get()): Hydrating SLP UTXOs with additional token data 2022-01-19 10:33:36 -08:00
Chris Troutner 9869211572 Merge pull request #209 from Permissionless-Software-Foundation/ct-unstable
Big changes
2022-01-18 11:53:27 -08:00
Chris Troutner 1056c3724c feat(Utxo.get()): Moving old function to getOld. New get() uses psf-slp-indexer 2022-01-18 11:41:52 -08:00
Chris Troutner 6b838ef9cd feat(Ninsight): Removing Ninsight libraries and tests 2022-01-18 08:45:20 -08:00
Chris Troutner f09573554d fix(PsfSlpIndexer.tx()): Added detection of blacklisted SLP Tokens 2022-01-18 08:32:45 -08:00
Chris Troutner 4a62ae64de Merge pull request #208 from Permissionless-Software-Foundation/ct-unstable
Replacing Transaction.get() with psf-slp-indexer
2022-01-17 19:21:49 -08:00
Chris Troutner 78de9e390e Merge branch 'master' into ct-unstable 2022-01-17 19:16:21 -08:00
Chris Troutner 7d8d0c2d69 Bumping major version to 5.0.0 2022-01-17 19:12:57 -08:00
Chris Troutner 656b0f6809 feat(Transaction.get()): Replacing call with proxy to psf-slp-indexer 2022-01-17 19:12:12 -08:00
Chris Troutner 48808aec14 Merge pull request #207 from Permissionless-Software-Foundation/ct-unstable
fix(slpdb): Removing slpdb from default integration tests
2022-01-17 15:10:26 -08:00
Chris Troutner d50f4a0d50 fix(slpdb): Removing slpdb from default integration tests 2022-01-17 15:07:06 -08:00
Chris Troutner f09f96d365 Merge pull request #206 from Permissionless-Software-Foundation/dh-slp-indexer
feat(indexer): Added PSF SLP Indexer to bch-js
2022-01-17 14:43:23 -08:00
Chris Troutner 7f4745f83c fix(psf-slp-indexer): Added integration tests 2022-01-17 14:39:52 -08:00
Chris Troutner dd5de1424c fix(PsfSlpIndexer.tx): Renaming txId() to tx() 2022-01-17 14:33:49 -08:00
Daniel Gonzalez 30604591bd feat(indexer): Added PSF SLP Indexer to bch-js 2022-01-17 13:37:29 -04:00
Chris Troutner aeb1efe23e Merge pull request #205 from Permissionless-Software-Foundation/ct-unstable
Adding eCash class library
2022-01-10 19:54:31 -08:00
Chris Troutner 6e43f795d7 feat(eCash): Adding eCash class library 2022-01-10 19:51:12 -08:00
Chris Troutner e07a5b9da0 Adding getBlockchainInfo() integration test 2022-01-10 14:48:43 -08:00
Chris Troutner 5ada76eace Merge branch 'master' into ct-unstable 2021-12-20 12:38:18 -08:00
Chris Troutner e605f6f386 Merge pull request #203 from Permissionless-Software-Foundation/temp
Temp
2021-12-15 05:47:56 -08:00
Chris Troutner eb9d0bea2b Merge pull request #202 from mr-zwets/documentation-toHash160
added documentation to function toHash160()
2021-12-15 05:45:17 -08:00
Chris Troutner f185f5de16 Updating integrat test scripts 2021-12-15 05:44:27 -08:00
Chris Troutner d795174916 Merge branch 'master' into documentation-toHash160 2021-12-15 05:39:47 -08:00
mathi 1294bc2149 added documentation toHash160() 2021-12-15 12:08:58 +01:00
Chris Troutner 8117448fd3 Merge pull request #201 from Permissionless-Software-Foundation/ct-unstable
fix(utxo.get()): Throwing error if address===undefined
2021-12-01 14:48:58 -08:00
Chris Troutner c7b51a38ff fix(utxo.get()): Throwing error if address===undefined 2021-12-01 14:45:39 -08:00
Chris Troutner 07d490134a Merge pull request #198 from Permissionless-Software-Foundation/salemkode
Salemkode
2021-11-30 06:38:15 -08:00
44 changed files with 7652 additions and 11546 deletions
+1
View File
@@ -4,6 +4,7 @@ wallet-info.txt
wallet.json
integration-test-sweet.sh
integration-test-ss.sh
integration-test-bitfinex.sh
docs/
coverage/
+8
View File
@@ -0,0 +1,8 @@
[
{
"srcDir": "",
"destDir": "",
"files": "**/*.js",
"command": "npm run lint"
}
]
+1 -2
View File
@@ -1,8 +1,7 @@
Copyright 2021 Permissionless Software Foundation contributors
Copyright 2022 Permissionless Software Foundation contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+27 -27
View File
@@ -3,9 +3,9 @@
[![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)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard) [![Join the chat at https://gitter.im/Permissionless-Software-Foundation/bch-js](https://badges.gitter.im/Permissionless-Software-Foundation/bch-js.svg)](https://gitter.im/Permissionless-Software-Foundation/bch-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[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.
[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) and eCash (XEC) blockchains. bch-js contains a toolbox of handy tools, and an easy API for talking with [bch-api REST API](https://github.com/Permissionless-Software-Foundation/bch-api). [FullStack.cash](https://fullstack.cash) offers paid cloud access to bch-api. You can run your own infrastructure by following documentation on [CashStack.info](https://cashstack.info).
### Quick Start Videos:
@@ -23,6 +23,7 @@ Here are two YouTube walk-through videos to help you get started:
- [FullStack.cash](https://fullstack.cash) - cloud-based infrastructure for application developers.
- [FullStack.cash Account](https://fullstack.cash/login) - Get your API key to unlock increased rate limits.
- [Permissionless Software Foundation](https://psfoundation.cash) - The organization that maintains this library.
- [CashStack.info](https://cashstack.info) - bch-js is part of the Cash Stack, a JavaScript framework for writing web 2 and web 3 business applications.
### Quick Notes
@@ -42,10 +43,10 @@ the [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) RES
- ABC Mainnet REST API server: https://abc.fullstack.cash/v5/
- Check server status: https://metrics.fullstack.cash
### API Key
### API Key (JWT Token)
The [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API hosted by [FullStack.cash](https://fullstack.cash) uses JWT tokens to pay for increased
rate limits when interacting with the back end server. See [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) if you want to understand the system-as-a-whole. The JWT token can be fed to bch-js _implicitly_ or _explicitly_.
rate limits when interacting with the back end server. See [this article](https://cashstack.info) if you want to understand the system-as-a-whole. The JWT token can be fed to bch-js _implicitly_ or _explicitly_.
- Implicitly: bch-js will detect your JWT token if you set the `BCHJSTOKEN` environment variable.
- Explicitly: You can directly feed in the JWT token with the `apiToken` property when instantiating the library. Here is an example:
@@ -58,38 +59,25 @@ let bchjs = new BCHJS({
})
```
### Gatsby
### Gatsby & Web Apps
bch-js is included in this [gatsby-ipfs-template](https://github.com/Permissionless-Software-Foundation/gatsby-ipfs-template) for building uncensorable web apps that can interact with the blockchain. When building a Gatsby (or other front-end app that uses Webpack), you'll need to add these lines to your `gatsby-node.js` file, as per [this issue](https://github.com/gatsbyjs/gatsby/issues/564):
[minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is a minimal wallet 'engine' that incorporates bch-js. It's compiled with Browserify for front end apps.
```
exports.onCreateWebpackConfig = ({ actions }) => {
actions.setWebpackConfig({
node: {
fs: 'empty'
}
})
}
```
[gatsby-theme-bch-wallet](https://github.com/Permissionless-Software-Foundation/gatsby-theme-bch-wallet) is a Gatsby Theme and [bch-wallet-starter](https://github.com/Permissionless-Software-Foundation/bch-wallet-starter) is a Gatsby Starter for building web wallets using minimal-slp-wallet.
This is because the new IPFS class in bch-js uses the fs library for uploading files, which is not supported by Gatsby.
We also provide [minimal-slp-wallet-web](https://www.npmjs.com/package/minimal-slp-wallet-web) as a basic Bitcoin Cash wallet with SLP support, for front end projects. bch-js is encapsulated inside the instantiation of the library Class.
[This gist](https://gist.github.com/christroutner/6cb9d1b615f3f9363af79723157bc434) shows how to include minimal-slp-wallet into a basic web page without using a framework.
## Features
- [ECMAScript 2017 standard JavaScript](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_-_ECMAScript_2017) used instead of TypeScript. Works
natively with node.js v10 or higher.
- Full SLP tokens support: bch-js has full support for all SLP token functionality, including send, mint, and genesis transactions. It also fully support all aspects of [non-fugible tokans (NFTs)](https://www.youtube.com/watch?v=vvlpYUx6HRs).
- Full SLP tokens support: bch-js has full support for all SLP token functionality, including send, mint, and genesis transactions. It also fully supports all aspects of [non-fugible tokans (NFTs)](https://www.youtube.com/watch?v=vvlpYUx6HRs).
- [Semantic Release](https://github.com/semantic-release/semantic-release) for
continuous delivery using semantic versioning.
- [Greenkeeper](https://greenkeeper.io/) automatic dependency management for
automatically maintaining the latest, most secure dependencies.
- [IPFS uploads](https://ipfs.io) of all files and dependencies, to backup
- [IPFS](https://ipfs.io) and [Radicle](https://radicle.xyz) uploads of all files and dependencies, to backup
dependencies in case they are ever inaccessible from GitHub or npm.
## Documentation:
@@ -109,14 +97,26 @@ live in the same repository. To generate the documentation:
Have questions? Need help? Join our community support
[Telegram channel](https://t.me/bch_js_toolkit)
## IPFS Releases
## Donate
This open source software is developed and maintained by the [Permissionless Software Foundation](https://psfoundation.cash). If this library provides value to you, please consider making a donation to support the PSF developers:
<div align="center">
<img src="./img/donation-qr.png" />
<p>bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr</p>
</div>
## IPFS & Radicle Releases
Copies of this repository are also published on [IPFS](https://ipfs.io).
- v4.5.4: QmWv3pxJy3MH8vU5nLUVyzqFxfNKXLQQEnz1rgStNuQijd
- v6.2.10: `bafybeifsioj3ba77u2763nsyuzq53gtbdxsnqpoipvdl4immj6ytznjaoy`
- (with dependencies, node v14.18.2 and npm v8.8.0): `bafybeihfendd4oj6uxvvecm7sluobwwhpb5wdcxhvhmx56e667nxdncd4a`
They are also posted to the Radicle:
- v6.2.10: `rad:git:hnrkkroqnbfwj6uxpfjuhspoxnfm4i8e6oqwy`
## License
[MIT](LICENSE.md)
test
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

+4755 -2492
View File
File diff suppressed because it is too large Load Diff
+11 -13
View File
@@ -1,7 +1,7 @@
{
"name": "@psf/bch-js",
"version": "4.0.0",
"description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
"version": "6.0.0",
"description": "A JavaScript library for working with Bitcoin Cash, eCash, and SLP Tokens",
"author": "Chris Troutner <chris.troutner@gmail.com>",
"contributors": [
"Gabriel Cardona <gabriel@bitcoin.com>",
@@ -14,12 +14,12 @@
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
"test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:decatur:abc": "export RESTURL=http://192.168.0.38:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
"test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
"test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
@@ -37,14 +37,12 @@
"url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
},
"dependencies": {
"@psf/bip21": "2.0.1",
"@chris.troutner/bip32-utils": "1.0.5",
"@psf/bip21": "2.0.1",
"@psf/bitcoincash-ops": "2.0.0",
"@psf/bitcoincashjs-lib": "4.0.2",
"@psf/coininfo": "4.0.0",
"@uppy/core": "1.10.4",
"@uppy/tus": "1.5.12",
"axios": "^0.21.4",
"axios": "0.26.1",
"bc-bip68": "1.0.5",
"bchaddrjs-slp": "0.2.5",
"bigi": "1.4.2",
@@ -65,7 +63,7 @@
"wif": "2.0.6"
},
"devDependencies": {
"apidoc": "0.17.7",
"apidoc": "0.50.5",
"assert": "2.0.0",
"chai": "4.1.2",
"coveralls": "3.0.2",
@@ -75,12 +73,12 @@
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "^4.3.8",
"husky": "4.3.8",
"lodash.clonedeep": "4.5.0",
"mocha": "9.1.3",
"mocha": "9.2.1",
"node-mocks-http": "1.7.0",
"nyc": "15.1.0",
"semantic-release": "18.0.0",
"semantic-release": "^19.0.2",
"sinon": "9.2.2",
"standard": "^16.0.4"
},
+15
View File
@@ -186,6 +186,21 @@ class Address {
return cashAddress.split(':')[1]
}
/**
* @api Address.toHash160() toHash160()
* @apiName toHash160
* @apiGroup Address
* @apiDescription Converts any address format to hash160
*
* @apiExample Example usage:
* // cash address mainnet p2pkh
* bchjs.Address.toHash160("bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh")
* // 573d93b475be4f1925f3b74ed951201b0147eac1
*
* // cash address mainnet p2sh
* bchjs.Address.toHash160("bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug")
* // 7dc85da64d1d93ef01ef62e0221c02f512e3942f
*/
// Converts any address format to hash160
toHash160 (address) {
const legacyAddress = this.toLegacyAddress(address)
+5 -6
View File
@@ -28,15 +28,15 @@ const Script = require('./script')
const Price = require('./price')
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')
const Ecash = require('./ecash')
// Indexers
const Ninsight = require('./ninsight')
const Electrumx = require('./electrumx')
const PsfSlpIndexer = require('./psf-slp-indexer')
class BCHJS {
constructor (config) {
@@ -81,9 +81,6 @@ class BCHJS {
// console.log(`apiToken: ${this.apiToken}`)
// Bitcoin.com Ninsight indexer
this.Ninsight = new Ninsight(config)
// ElectrumX indexer
this.Electrumx = new Electrumx(libConfig)
@@ -113,11 +110,13 @@ class BCHJS {
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)
this.eCash = new Ecash()
this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
Utility library for converting units with eCash.
*/
class eCash {
/**
* @api eCash.toSatoshi() toSatoshi()
* @apiName toSatoshi
* @apiGroup eCash
* @apiDescription
* Convert XEC units into satoshi units
*
* @apiExample Example usage:
* // convert 10,704.35 XEC to satoshis:
* bchjs.eCash.toSatoshi(10704.35)
* // 1070435
*/
toSatoshi (xec) {
if (typeof xec !== 'number') {
throw new Error('input must be a floating number representing XEC')
}
return Math.floor(xec * 100)
}
/**
* @api eCash.toXec() toXec()
* @apiName toXec
* @apiGroup eCash
* @apiDescription
* Convert satoshi units to XEC units
*
* @apiExample Example usage:
* // convert 1,070,435 satoshis to XEC:
* bchjs.eCash.toSatoshi(1070435)
* // 10704.35
*/
toXec (sats) {
if (typeof sats !== 'number') {
throw new Error('input must be a floating number representing satoshis')
}
return sats / 100
}
}
module.exports = eCash
+16 -3
View File
@@ -44,7 +44,7 @@ class ElectrumX {
* @apiDescription Return a list of uxtos for an address.
*
* @apiExample Example usage:
* (async () => {
* (async () => {
* try {
* let utxo = await bchjs.Electrumx.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
* console.log(utxo);
@@ -638,13 +638,24 @@ class ElectrumX {
// Sort confirmed Transactions by the block height
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
try {
// console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`)
// Filter out unconfirmed transactions, with a height of 0 or less.
txs = txs.filter(elem => elem.height > 0)
if (sortingOrder === 'DESCENDING') {
return txs.sort((a, b) => b.height - a.height)
// console.log('Sorting in descending order')
return txs.sort((a, b) => {
// console.log(`descending b.height: ${b.height}, a.height: ${a.height}`)
return b.height - a.height
})
}
return txs.sort((a, b) => a.height - b.height)
// console.log('Sorting in ascending order')
return txs.sort((a, b) => {
// console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`)
return a.height - b.height
})
} catch (err) {
console.log('Error in util.js/sortConfTxs()')
throw err
@@ -685,6 +696,8 @@ class ElectrumX {
// Substitute zero-conf txs with the current block-height + 1
async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
try {
// console.log(`sortingOrder: ${sortingOrder}`)
// Calculate the height of the next block
const nextBlock = (await this.blockchain.getBlockCount()) + 1
-454
View File
@@ -1,454 +0,0 @@
/*
This library interacts with the IPFS REST API at FullStack.cash for uploading
and downloading data from the IPFS network.
*/
const axios = require('axios')
const Uppy = require('@uppy/core')
const Tus = require('@uppy/tus')
const fs = require('fs')
let _this
class IPFS {
constructor (config) {
this.IPFS_API = process.env.IPFS_API
? process.env.IPFS_API
: 'https://ipfs-file-upload.fullstackcash.nl'
// : `http://localhost:5001`
// Default options when calling axios.
this.axiosOptions = {
headers: {
// authorization: `Token ${this.apiToken}`,
timeout: 15000 // Timeout if the server does not respond in time.
}
}
_this = this
_this.axios = axios
_this.fs = fs
// Initialize Uppy
_this.uppy = _this.initUppy()
}
// Initializes Uppy, which is used for file uploads.
initUppy () {
const uppy = Uppy({
allowMultipleUploads: false,
meta: { test: 'avatar' },
debug: false,
restrictions: {
maxFileSize: null,
maxNumberOfFiles: 1,
minNumberOfFiles: 1,
allowedFileTypes: null // type of files allowed to load
}
})
uppy.use(Tus, { endpoint: `${_this.IPFS_API}/uppy-files` })
return uppy
}
/**
* @api IPFS.createFileModelServer() createFileModelServer()
* @apiName createFileModelServer()
* @apiGroup IPFS
* @apiDescription Creates a new model on the server. If successful, will
* return an object with file data. That object contains a BCH address,
* payment amount, and _id required to be able to upload a file.
*
* @apiExample Example usage:
* (async () => {
* try {
* const path = `${__dirname}/ipfs.js`
* let fileData = await bchjs.IPFS.createFileModelServer(path);
* console.log(fileData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "success": true,
* "hostingCostBCH": 0.00004197,
* "hostingCostUSD": 0.01,
* "file": {
* "payloadLink": "",
* "hasBeenPaid": false,
* "_id": "5ec562319bfacc745e8d8a52",
* "schemaVersion": 1,
* "size": 4458,
* "fileName": "ipfs.js",
* "fileExtension": "js",
* "createdTimestamp": "1589994033.655",
* "hostingCost": 4196,
* "walletIndex": 49,
* "bchAddr": "bchtest:qzrpkevu7h2ayfa4rjx08r5elvpfu72dg567x3mh3c",
* "__v": 0
* }
*/
async createFileModelServer (path) {
try {
// Ensure the file exists.
if (!_this.fs.existsSync(path)) {
throw new Error(`Could not find this file: ${path}`)
}
// Read in the file.
const fileBuf = _this.fs.readFileSync(path)
// console.log(`fileBuf: `, fileBuf)
// console.log(`Buffer length: ${fileBuf.length}`)
// Get the file name from the path.
const splitPath = path.split('/')
const fileName = splitPath[splitPath.length - 1]
// Get the file extension.
const splitExt = fileName.split('.')
const fileExt = splitExt[splitExt.length - 1]
const fileObj = {
schemaVersion: 1,
size: fileBuf.length,
fileName: fileName,
fileExtension: fileExt
}
// console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`)
const fileData = await _this.axios.post(`${this.IPFS_API}/files`, {
file: fileObj
})
// console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`)
return fileData.data
} catch (err) {
console.error('Error in createFileModel()')
throw err
}
}
/**
* @api IPFS.uploadFileServer() uploadFileServer()
* @apiName uploadFile()
* @apiGroup IPFS
* @apiDescription Upload a file to the FullStack.cash IPFS server. If
* successful, it will return an object with an ID, a BCH address, and an
* amount of BCH to pay.
*
* @apiExample Example usage:
* (async () => {
* try {
* const path = `${__dirname}/ipfs.js`
* let fileData = await bchjs.IPFS.uploadFileServer(path, "5ec562319bfacc745e8d8a52");
* console.log(fileData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "schemaVersion": 1,
* "size": 2374,
* "fileId": "5ec562319bfacc745e8d8a52",
* "fileName": "ipfs.js",
* "fileExtension": "js"
* }
*/
async uploadFileServer (path, modelId) {
try {
// Ensure the file exists.
if (!_this.fs.existsSync(path)) {
throw new Error(`Could not find this file: ${path}`)
}
if (!modelId) {
throw new Error(
'Must include a file model ID in order to upload a file.'
)
}
// Read in the file.
const fileBuf = _this.fs.readFileSync(path)
// Get the file name from the path.
const splitPath = path.split('/')
const fileName = splitPath[splitPath.length - 1]
// Prepare the upload object. Get a file ID from Uppy.
const id = _this.uppy.addFile({
name: fileName,
data: fileBuf,
source: 'Local',
isRemote: false
})
// console.log(`id: ${JSON.stringify(id, null, 2)}`)
// Add the model ID, required to be allowed to upload the file.
_this.uppy.setFileMeta(id, { fileModelId: modelId })
// Upload the file to the server.
const upData = await _this.uppy.upload()
if (upData.failed.length) {
throw new Error('The file could not be uploaded')
}
if (upData.successful.length) {
delete upData.successful[0].data
// console.log(`upData: ${JSON.stringify(upData, null, 2)}`)
const fileObj = {
schemaVersion: 1,
size: upData.successful[0].progress.bytesTotal,
fileId: upData.successful[0].meta.fileModelId,
fileName: upData.successful[0].name,
fileExtension: upData.successful[0].extension
}
// console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`)
return fileObj
// return true
}
return false
} catch (err) {
console.error('Error in bch-js/src/ipfs.js/upload(): ')
throw err
}
}
/**
* @api IPFS.getStatus() getStatus()
* @apiName getStatus()
* @apiGroup IPFS
* @apiDescription Gets the status of the uploaded file. Will return an object
* that indicates the payment status. If the file is paid, it should contain
* an IPFS hash.
*
* @apiExample Example usage:
* (async () => {
* try {
* const modelId = "5ec7392c2acfe57aa62e945a"
* let fileData = await bchjs.IPFS.getStatus(modelId)
* console.log(fileData)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "hasBeenPaid": true,
* "satCost": 4403,
* "bchAddr": "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk",
* "ipfsHash": "QmRDHPhY5hCNVRMVQvS2H9uty8P1skdwgLaHpUAkEvsjcE",
* "fileId": "5ec7392c2acfe57aa62e945a",
* "fileName": "ipfs-e2e.js"
* }
*/
async getStatus (modelId) {
try {
if (!modelId) throw new Error('Must include a file model ID.')
const fileData = await _this.axios.get(
`${this.IPFS_API}/files/${modelId}`
)
// console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`)
const fileObj = {
hasBeenPaid: fileData.data.file.hasBeenPaid,
satCost: fileData.data.file.hostingCost,
bchAddr: fileData.data.file.bchAddr,
ipfsHash: fileData.data.file.payloadLink,
fileId: fileData.data.file._id,
fileName: fileData.data.file.fileName
}
return fileObj
} catch (err) {
console.error('Error in getStatus()')
throw err
}
}
/**
* @api IPFS.createFileModelWeb() createFileModelWeb()
* @apiName createFileModelWeb()
* @apiGroup IPFS
* @apiDescription Creates a new model on the server. If successful, will
* return an object with file data. That object contains a BCH address,
* payment amount, and _id required to be able to upload a file.
*
* @apiExample Example usage:
* (async () => {
* try {
* const content = ['PSF']
* const name = 'psf.txt' // Content can be Array , ArrayBuffer , Blob
* const options = { type: "text/plain" }
* const file = new File(content,name,options)
* let fileData = await bchjs.IPFS.createFileModelWeb(file);
* console.log(fileData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "success": true,
* "hostingCostBCH": 0.00004197,
* "hostingCostUSD": 0.01,
* "file": {
* "payloadLink": "",
* "hasBeenPaid": false,
* "_id": "5ec562319bfacc745e8d8a52",
* "schemaVersion": 1,
* "size": 4458,
* "fileName": "ipfs.js",
* "fileExtension": "js",
* "createdTimestamp": "1589994033.655",
* "hostingCost": 4196,
* "walletIndex": 49,
* "bchAddr": "bchtest:qzrpkevu7h2ayfa4rjx08r5elvpfu72dg567x3mh3c",
* "__v": 0
* }
*/
async createFileModelWeb (file) {
try {
if (!file) throw new Error('File is required')
const { name, size } = file
if (!name || typeof name !== 'string') {
throw new Error("File should have the property 'name' of string type")
}
if (!size || typeof size !== 'number') {
throw new Error("File should have the property 'size' of number type")
}
// Get the file extension.
const splitExt = name.split('.')
const fileExt = splitExt[splitExt.length - 1]
const fileObj = {
schemaVersion: 1,
size: size,
fileName: name,
fileExtension: fileExt
}
// console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`)
const fileData = await _this.axios.post(`${this.IPFS_API}/files`, {
file: fileObj
})
// console.log(`fileData.data: ${JSON.stringify(fileData.data, null, 2)}`)
return fileData.data
} catch (err) {
console.error('Error in createFileModelWeb()')
throw err
}
}
/**
* @api IPFS.uploadFileWeb() uploadFileWeb()
* @apiName uploadFileWeb()
* @apiGroup IPFS
* @apiDescription Upload a file to the FullStack.cash IPFS server. If
* successful, it will return an object with an ID, a BCH address, and an
* amount of BCH to pay.
*
* @apiExample Example usage:
* (async () => {
* try {
* * const content = ['PSF']
* const name = 'psf.txt' // Content can be Array , ArrayBuffer , Blob
* const options = { type: "text/plain" }
* const file = new File(content,name,options)
* let fileData = await bchjs.IPFS.uploadFile(file, "5ec562319bfacc745e8d8a52");
* console.log(fileData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "schemaVersion": 1,
* "size": 2374,
* "fileId": "5ec562319bfacc745e8d8a52",
* "fileName": "ipfs.js",
* "fileExtension": "js"
* }
*/
async uploadFileWeb (file, modelId) {
try {
if (!file) throw new Error('File is required')
const { name, type, size } = file
if (!name || typeof name !== 'string') {
throw new Error("File should have the property 'name' of string type")
}
if (!type || typeof type !== 'string') {
throw new Error("File should have the property 'type' of string type")
}
if (!size || typeof size !== 'number') {
throw new Error("File should have the property 'size' of number type")
}
if (!modelId) {
throw new Error(
'Must include a file model ID in order to upload a file.'
)
}
// Prepare the upload object. Get a file ID from Uppy.
const id = _this.uppy.addFile({
name: name,
data: file,
source: 'Local',
isRemote: false
})
// console.log(`id: ${JSON.stringify(id, null, 2)}`)
// Add the model ID, required to be allowed to upload the file.
_this.uppy.setFileMeta(id, { fileModelId: modelId })
// Upload the file to the server.
const upData = await _this.uppy.upload()
if (upData.failed.length) {
throw new Error('The file could not be uploaded')
}
if (upData.successful.length) {
delete upData.successful[0].data
// console.log(`upData: ${JSON.stringify(upData, null, 2)}`)
const fileObj = {
schemaVersion: 1,
size: upData.successful[0].progress.bytesTotal,
fileId: upData.successful[0].meta.fileModelId,
fileName: upData.successful[0].name,
fileExtension: upData.successful[0].extension
}
// console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`)
return fileObj
// return true
}
return false
} catch (err) {
console.error('Error in bch-js/src/ipfs.js/uploadFileWeb(): ')
throw err
}
}
}
module.exports = IPFS
-319
View File
@@ -1,319 +0,0 @@
/*
This library interacts with the ninsight REST API endpoints operated by
Bitcoin.com
*/
const axios = require('axios')
// let _this
class Ninsight {
constructor (config) {
// this.restURL = config.restURL
// this.apiToken = config.apiToken
// this.ninsightURL = `https://bch-explorer.api.bitcoin.com/v1/`
if (config) {
this.ninsightURL = config.ninsightURL
? config.ninsightURL
: 'https://rest.bitcoin.com/v2'
}
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`,
timeout: 15000
}
}
// _this = this
}
/**
* @api Ninsight.utxo() utxo()
* @apiName Ninsight Utxo
* @apiGroup Ninsight
* @apiDescription Return list of uxto for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.Ninsight.utxo('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
* // "vout": 1,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "height": 585570,
* // "confirmations": 10392
* // },
* // {
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
* // "vout": 0,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "height": 577125,
* // "confirmations": 18837
* // }
* // ]
*
*/
async utxo (address) {
try {
// Handle single address.
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/utxo`,
{
addresses: [address]
},
this.axiosOptions
)
return response.data
// Handle array of addresses.
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/utxo`,
{
addresses: address
},
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.unconfirmed() unconfirmed()
* @apiName Ninsight Unconfirmed Utxo
* @apiGroup Ninsight
* @apiDescription Returns a list of unconfirmed UTXOs for an address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.Ninsight.unconfirmed('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
* // "vout": 1,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "confirmations": 0
* // "ts": 1559670801
* // },
* // {
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
* // "vout": 0,
* // "amount": 0.03608203,
* // "satoshis": 3608203,
* // "confirmations": 0
* // "ts": 1559670902
* // }
* // ]
*
*/
async unconfirmed (address) {
try {
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/unconfirmed`,
{
addresses: [address]
},
this.axiosOptions
)
return response.data
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/unconfirmed`,
{
addresses: address
},
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.transactions() transactions()
* @apiName Ninsight TX History
* @apiGroup Ninsight
* @apiDescription Return transactions history for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txs = await bchjs.Ninsight.transactions('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
* console.log(txs);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "pagesTotal": 1,
* // "txs": [
* // {
* // "txid": "ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b",
* // "version": 2,
* // "locktime": 0,
* // "vin": [
* // {
* // "txid": "4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0",
* // "vout": 0,
* // "sequence": 4294967295,
* // "n": 0,
* // ...
* // }
* // ...
* // ]
* // ...
* // }
* // ],
* // "legacyAddress": "1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP",
* // "cashAddress": "bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n",
* // "currentPage": 0
* // }
* // ]
* //
*
*/
async transactions (address) {
try {
if (typeof address === 'string') {
const response = await axios.post(
`${this.ninsightURL}/address/transactions`,
{
addresses: [address]
},
this.axiosOptions
)
return response.data
} else if (Array.isArray(address)) {
const response = await axios.post(
`${this.ninsightURL}/address/transactions`,
{
addresses: address
},
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api Ninsight.txDetails() txDetails()
* @apiName Ninsight TX Details
* @apiGroup Ninsight
* @apiDescription Return transactions details for address(es).
*
* @apiExample Example usage:
* (async () => {
* try {
* let result = await bchjs.Ninsight.txDetails('fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33');
* console.log(result);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // [
* // {
* // "txid": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33",
* // "version": 1,
* // "locktime": 0,
* // "vin": [
* // {
* // "sequence": 4294967295,
* // "n": 0
* // }
* // ],
* // "vout": [
* // ...
* // ],
* // "blockhash": "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09",
* // "blockheight": 1000,
* // "confirmations": 659909,
* // "time": 1232346882,
* // "blocktime": 1232346882,
* // "firstSeenTime": null,
* // "valueOut": 50,
* // "size": 135
* // }
* // ]
*
*/
async txDetails (txid) {
try {
if (typeof txid === 'string') {
const response = await axios.post(
`${this.ninsightURL}/transaction/details`,
{
txids: [txid]
},
this.axiosOptions
)
return response.data
} else if (Array.isArray(txid)) {
const response = await axios.post(
`${this.ninsightURL}/transaction/details`,
{
txids: txid
},
this.axiosOptions
)
return response.data
}
throw new Error('Transaction ID must be a string or array of strings.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = Ninsight
+446
View File
@@ -0,0 +1,446 @@
/*
This library interacts with the PSF slp indexer REST API endpoints operated
by FullStack.cash
TODO:
- detect TXs from tokens in the blacklist.
*/
// Public npm libraries
const axios = require('axios')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
// let _this
class PsfSlpIndexer {
constructor (config = {}) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.authToken = config.authToken
if (this.authToken) {
// Add Basic Authentication token to the authorization header.
this.axiosOptions = {
headers: {
authorization: this.authToken
}
}
} else {
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`
}
}
}
// Encapsulate dependencies
this.rawTransaction = new RawTransaction(config)
this.slpUtils = new SlpUtils(config)
// _this = this
}
/**
* @api PsfSlpIndexer.status() status()
* @apiName Status
* @apiGroup PSF SLP
* @apiDescription Return status from psf slp indexer.
*
* @apiExample Example usage:
* (async () => {
* try {
* let status = await bchjs.PsfSlpIndexer.status()
* console.log(status)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "status": {
* "startBlockHeight": 543376,
* "syncedBlockHeight": 723249,
* "chainBlockHeight": 722679
* }
* }
*
*/
async status () {
try {
const response = await axios.get(
`${this.restURL}psf/slp/status`,
this.axiosOptions
)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.balance() balance()
* @apiName SLP Balance
* @apiGroup PSF SLP
* @apiDescription Return slp balance for a single address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n')
* console.log(balance)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* balance: {
* utxos: [
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* vout: 1,
* type: 'token',
* qty: '1800',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
* }
* ],
* txs: [
* {
* txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
* height: 717796
* },
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* height: 717832
* }
* ],
* balances: [
* {
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* qty: '1800'
* }
* ]
* }
* }
*
*/
async balance (address) {
try {
// console.log('balance() address: ', address)
// Handle single address.
if (typeof address === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/address`,
{ address },
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tokenStats() tokenStats()
* @apiName Token Stats
* @apiGroup PSF SLP
* @apiDescription Return list stats for a single slp token.
* The second input is a Boolean, which determins the the transaction history
* of the token is included in the returned data. The default is false.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2', true)
* console.log(tokenStats)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* tokenData: {
* type: 1,
* ticker: 'TROUT',
* name: "Trout's test token",
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* documentUri: 'troutsblog.com',
* documentHash: '',
* decimals: 2,
* mintBatonIsActive: true,
* tokensInCirculationBN: '100098953386',
* tokensInCirculationStr: '100098953386',
* blockCreated: 622414,
* totalBurned: '1046614',
* totalMinted: '100100000000'
* txs: [
* {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* height: 622414,
* type: 'GENESIS',
* qty: '100000000000'
* }
* ]
* }
* }
*
*/
async tokenStats (tokenId, withTxHistory = false) {
try {
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/token`,
{ tokenId, withTxHistory },
this.axiosOptions
)
return response.data
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tx() tx()
* @apiName SLP Transaction Data
* @apiGroup PSF SLP
* @apiDescription Return slp transaction data.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
* console.log(txData)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* txData: {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* version: 2,
* size: 339,
* locktime: 0,
* vin: [
* {
* txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
* vout: 0,
* scriptSig: {
* asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
* hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
* },
* sequence: 4294967295,
* address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
* value: 0.00051303,
* tokenQty: 0,
* tokenQtyStr: '0',
* tokenId: null
* }
* ],
* vout: [
* {
* value: 0,
* n: 0,
* scriptPubKey: {
* asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
* hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
* type: 'nulldata'
* },
* tokenQtyStr: '0',
* tokenQty: 0
* }
* ],
* hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
* blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
* confirmations: 97398,
* time: 1581773131,
* blocktime: 1581773131,
* blockheight: 622414,
* isSlpTx: true,
* tokenTxType: 'GENESIS',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* tokenType: 1,
* tokenTicker: 'TROUT',
* tokenName: "Trout's test token",
* tokenDecimals: 2,
* tokenUri: 'troutsblog.com',
* tokenDocHash: '',
* isValidSlp: true
* }
* }
*
*/
async tx (txid) {
try {
// console.log('txid: ', txid)
// Handle single address.
if (typeof txid === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/txid`,
{ txid },
this.axiosOptions
)
// console.log('response: ', response)
return response.data
}
throw new Error('Input txid must be a string.')
} catch (error) {
// console.log('error: ', error)
// Case: txid is not stored in the psf-slp-indexer tx database.
// Response: If it's not in the database, then it can be assumed the TX
// is not a token TX?
if (
error.response &&
error.response.data &&
error.response.data.error &&
error.response.data.error.includes('Key not found in database')
) {
// console.log(
// 'TX not found in psf-slp-indexer. Retrieving from full node.'
// )
// Check if this txid belongs to a blacklisted token.
const isInBlacklist = await this.checkBlacklist(txid)
// console.log('isInBlacklist: ', isInBlacklist)
// Get the TX Details from the full node.
const txDetails = await this.rawTransaction.getTxData(txid)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
if (isInBlacklist) {
txDetails.isValidSlp = null
} else {
txDetails.isValidSlp = false
}
const outObj = {
txData: txDetails
}
return outObj
} else throw error
}
}
// Check if the txid has an OP_RETURN containing a tokenID that is in the
// blacklist. In that case, the isValidSlp property should be marked as
// null, and not false.
async checkBlacklist (txid) {
try {
// TODO: Add endpoint to psf-slp-indexer to retrieve current blacklist.
// This should be done once at startup, and not each time this function
// is called.
const blacklist = [
'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9'
]
const outTokenData = await this.slpUtils.decodeOpReturn(txid)
// console.log('outTokenData: ', outTokenData)
// Loop through each token in the blacklist.
for (let i = 0; i < blacklist.length; i++) {
// If a match is found, return true.
if (outTokenData.tokenId === blacklist[i]) {
return true
}
}
// By default, return false.
return false
} catch (err) {
// console.log(err)
// Exit quietly.
return false
}
}
/**
* @api PsfSlpIndexer.getTokenData() getTokenData()
* @apiName Token Data
* @apiGroup PSF SLP
* @apiDescription Get mutable and immutable data if the token contains them.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenData = await bchjs.PsfSlpIndexer.getTokenData('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
* console.log(tokenData)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* genesisData: {
* type: 1,
* ticker: 'TROUT',
* name: "Trout's test token",
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* documentUri: 'troutsblog.com',
* documentHash: '',
* decimals: 2,
* mintBatonIsActive: true,
* tokensInCirculationBN: '100098953386',
* tokensInCirculationStr: '100098953386',
* blockCreated: 622414,
* totalBurned: '1046614',
* totalMinted: '100100000000'
* ]
* },
* immutableData :{
* issuer:"FullStack.cash.",
* website:"https://fullstack.cash/",
* dateCreated:"2022-01-11"
* },
* mutableData :{
* "tokenIcon":"https://gateway.ipfs.io/ipfs/bafybeiehitanirn5gmhqjg44xrmdtomn4n5lu5yjoepsvgpswk5mggaw6i/LP_logo-1.png",
* "about":"Mutable data managed with npm package: https://www.npmjs.com/package/slp-mutable-data"
* }
* }
*
*/
async getTokenData (tokenId) {
try {
const url = `${this.restURL}psf/slp/token/data`
// console.log(`url: ${url}`)
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
// 'https://bchn.fullstack.cash/v5/psf/slp/token/data/',
url,
{ tokenId },
this.axiosOptions
)
return response.data
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = PsfSlpIndexer
+15 -6
View File
@@ -381,6 +381,8 @@ class RawTransactions {
// Appends the BCH address to the inputs of the transaction.
async getTxData (txid) {
try {
// console.log('getTxData() txid: ', txid)
if (typeof txid !== 'string') {
throw new Error(
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
@@ -391,17 +393,24 @@ class RawTransactions {
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)}`)
try {
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
// 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
}
} catch (err) {
// Coinbase transactions will throw an error. Just ignore them and
// pass back the raw transaction data.
/* exit quietly */
}
return txDetails
} catch (error) {
// console.log('error: ', error)
if (error.error) throw new Error(error.error)
// This case handles rate limit errors.
+1 -1
View File
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
* bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
* // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
*
* // tesnet legacy
* // testnet legacy
* bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
* // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
*
+1 -1
View File
@@ -147,7 +147,7 @@ class Nft1 {
// Loop through the tokenUtxos array and find the minting baton.
let mintBatonUtxo
for (let i = 0; i < tokenUtxos.length; i++) {
if (tokenUtxos[i].utxoType === 'minting-baton') {
if (tokenUtxos[i].utxoType === 'minting-baton' || tokenUtxos[i].type === 'baton') {
mintBatonUtxo = tokenUtxos[i]
}
}
+7 -2
View File
@@ -375,11 +375,14 @@ class TokenType1 {
if (!Array.isArray(tokenUtxos)) {
throw new Error('tokenUtxos must be an array.')
}
if (!mintQty) {
throw new Error('mintQty must be a positive number.')
}
// Loop through the tokenUtxos array and find the minting baton.
let mintBatonUtxo
for (let i = 0; i < tokenUtxos.length; i++) {
if (tokenUtxos[i].utxoType === 'minting-baton') {
if (tokenUtxos[i].utxoType === 'minting-baton' || tokenUtxos[i].type === 'baton') {
mintBatonUtxo = tokenUtxos[i]
}
}
@@ -395,7 +398,7 @@ class TokenType1 {
if (!tokenId) {
throw new Error('tokenId property not found in mint-baton UTXO.')
}
if (!decimals) {
if (!decimals && decimals !== 0) {
throw new Error('decimals property not found in mint-baton UTXO.')
}
@@ -404,6 +407,8 @@ class TokenType1 {
baseQty = Math.floor(baseQty)
baseQty = baseQty.toString()
if (isNaN(baseQty)) throw new Error('baseQty is non a number!')
// Signal that the baton should be passed or detroyed.
let batonVout = 2
if (destroyBaton) batonVout = null
+1 -1763
View File
File diff suppressed because it is too large Load Diff
+14 -462
View File
@@ -2,18 +2,22 @@
High-level functions for working with Transactions
*/
const BigNumber = require('bignumber.js')
// Global npm libraries
// const BigNumber = require('bignumber.js')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
const Blockchain = require('./blockchain')
const PsfSlpIndexer = require('./psf-slp-indexer')
class Transaction {
constructor (config) {
constructor (config = {}) {
// Encapsulate dependencies
this.slpUtils = new SlpUtils(config)
this.rawTransaction = new RawTransaction(config)
this.blockchain = new Blockchain(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
}
/**
@@ -25,8 +29,6 @@ class Transaction {
* 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 () => {
@@ -38,481 +40,31 @@ class Transaction {
* }
* })()
*/
// CT 10/31/21: TODO: this function should be refactored to use get2(), but
// add waterfall validation of the TX and its inputs.
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)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(rawQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
txDetails.vout[i + 1].tokenQtyStr = realQty
txDetails.vout[i + 1].tokenQty = parseFloat(realQty)
}
// Add tokenQty = null to any outputs that don't have a value.
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]
// console.log(`thisVin: ${JSON.stringify(thisVin, null, 2)}`)
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)}`
// )
let tokenQty = 0
// Validate the input. Mark qty as null if not valid.
const vinIsValid = await this.slpUtils.waterfallValidateTxid(
thisVin.txid
)
if (!vinIsValid) {
// If the input is not a valid, then set qty as null.
tokenQty = null
} else if (inTokenData.txType === 'SEND') {
// Get the appropriate vout token amount. This may throw an error,
// which means this Vin is not actually a token UTXO, it was just
// associated with a previous token TX.
tokenQty = inTokenData.amounts[thisVin.vout - 1]
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
//
} else if (inTokenData.txType === 'GENESIS') {
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = inTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
}
} else if (inTokenData.txType === 'MINT') {
// vout=1 (second output) recieves the newly minted tokens.
if (thisVin.vout === 1) {
tokenQty = inTokenData.qty
} else {
tokenQty = null
}
//
} else {
console.log(
'Unexpected code path in Transaction.get(). What is the txType?'
)
console.log(inTokenData)
throw new Error('Unexpected code path')
}
if (tokenQty) {
// const realQty =
// Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
// txDetails.vin[i].tokenQty = tokenQty
// Add token ID to input
thisVin.tokenId = inTokenData.tokenId
} else {
thisVin.tokenQty = null
}
} 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)
// TODO: Convert the block hash to a block height. Add block height
// value to the transaction.
} catch (err) {
// console.log('Error: ', err)
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
// If decoding the op_return fails, then it's not an SLP transaction,
// and the non-hyrated TX details can be returned.
return txDetails
}
return txDetails
} catch (err) {
// console.error('Error in transactions.js/get(): ', err)
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
if (err.error) throw new Error(err.error)
throw err
}
// console.log('transaction.get() txid: ', txid)
return await this.psfSlpIndexer.tx(txid)
}
/**
* @api Transaction.get3() get3()
* @apiName get3
* @api Transaction.getTokenInfo() getTokenInfo()
* @apiName getTokenInfo
* @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.
* Given the TXID of a token transaction, it will return data about that
* token by retrieving the data from the Genesis transaction and docoding
* the OP_RETURN.
*
* This is an API heavy call. This function will only work with a single txid.
* It does not yet support an array of TXIDs.
*
* This is the same as get(), except it omits DAG validation of the TXID.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.Transaction.get2("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
* let txData = await bchjs.Transaction.getTokenInfo("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*/
async get3 (txid) {
try {
if (typeof txid !== 'string') {
throw new Error(
'Input to Transaction.get() must be a string containing a TXID.'
)
}
// Get TX data
const txDetails = await this.rawTransaction.getTxData(txid)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// Get the block height the transaction was mined in.
const blockHeader = await this.blockchain.getBlockHeader(
txDetails.blockhash
)
txDetails.blockheight = blockHeader.height
// console.log(`blockHeader: ${JSON.stringify(blockHeader, null, 2)}`)
// Set default as not an SLP tx
txDetails.isSlpTx = false
// Get Token Data
const txTokenData = await this.getTokenInfo(txid)
// console.log(`txTokenData: ${JSON.stringify(txTokenData, null, 2)}`)
// If not a token, return the tx data. Processing is complete.
if (!txTokenData) return txDetails
// Mark TX as an SLP tx. This does not mean it's valid, it just means
// the OP_RETURN passes a basic check.
txDetails.isSlpTx = true
// Get Genesis data
const genesisData = await this.getTokenInfo(txTokenData.tokenId)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
// Add token information to the tx details object.
txDetails.tokenTxType = txTokenData.txType
txDetails.tokenId = txTokenData.tokenId
txDetails.tokenTicker = genesisData.ticker
txDetails.tokenName = genesisData.name
txDetails.tokenDecimals = genesisData.decimals
txDetails.tokenUri = genesisData.documentUri
txDetails.tokenDocHash = genesisData.documentHash
// console.log(`txDetails before processing input and outputs: ${JSON.stringify(txDetails, null, 2)}`)
// Process TX Outputs
// Add the token quantity to each output.
// 'i' starts at 1, because vout[0] is the OP_RETURN
for (let i = 0; i < txDetails.vout.length; i++) {
const thisVout = txDetails.vout[i]
if (txTokenData.txType === 'SEND') {
// console.log(
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
// )
// First output is OP_RETURN, so tokenQty is null.
if (i === 0) {
thisVout.tokenQty = null
thisVout.tokenQtyStr = null
continue
}
// Non SLP outputs.
if (i > txTokenData.amounts.length) {
thisVout.tokenQty = null
thisVout.tokenQtyStr = null
continue
}
const rawQty = txTokenData.amounts[i - 1]
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(rawQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
txDetails.vout[i].tokenQtyStr = realQty
txDetails.vout[i].tokenQty = parseFloat(realQty)
// console.log(
// `thisVout ${i}: ${JSON.stringify(txDetails.vout[i], null, 2)}`
// )
} else if (
txTokenData.txType === 'GENESIS' ||
txTokenData.txType === 'MINT'
) {
// console.log(
// `output txTokenData: ${JSON.stringify(txTokenData, null, 2)}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis or Mint transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (i === 1) {
tokenQty = txTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVout.tokenQtyStr = realQty
thisVout.tokenQty = parseFloat(realQty)
// console.log(`thisVout[${i}]: ${JSON.stringify(thisVout, null, 2)}`)
} else if (i === txTokenData.mintBatonVout) {
// Optional Mint baton
thisVout.tokenQtyStr = '0'
thisVout.tokenQty = 0
thisVout.isMintBaton = true
} else {
thisVout.tokenQtyStr = '0'
thisVout.tokenQty = 0
}
} else {
throw new Error('Unknown SLP TX type for TX')
}
}
// Process TX inputs
for (let i = 0; i < txDetails.vin.length; i++) {
const thisVin = txDetails.vin[i]
// console.log(`thisVin[${i}]: ${JSON.stringify(thisVin, null, 2)}`)
const vinTokenData = await this.getTokenInfo(thisVin.txid)
// console.log(
// `vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
// Corner case: Ensure the token ID is the same.
const vinTokenIdIsTheSame = vinTokenData.tokenId === txDetails.tokenId
// If the input is not a token input, or if the tokenID is not the same,
// then mark the token output as null.
if (!vinTokenData || !vinTokenIdIsTheSame) {
thisVin.tokenQty = 0
thisVin.tokenQtyStr = '0'
thisVin.tokenId = null
continue
}
if (vinTokenData.txType === 'SEND') {
// console.log(
// `SEND vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
const tokenQty = vinTokenData.amounts[thisVin.vout - 1]
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (vinTokenData.txType === 'MINT') {
// console.log(
// `MINT vinTokenData ${i}: ${JSON.stringify(vinTokenData, null, 2)}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = vinTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
// Optional Mint baton
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = vinTokenData.tokenId
thisVin.isMintBaton = true
} else {
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = null
}
} else if (vinTokenData.txType === 'GENESIS') {
// console.log(
// `GENESIS vinTokenData ${i}: ${JSON.stringify(
// vinTokenData,
// null,
// 2
// )}`
// )
let tokenQty = 0 // Default value
// Only vout[1] of a Genesis transaction represents the tokens.
// Any other outputs in that transaction are normal BCH UTXOs.
if (thisVin.vout === 1) {
tokenQty = vinTokenData.qty
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
// Calculate the real quantity using a BigNumber, then convert it to a
// floating point number.
let realQty = new BigNumber(tokenQty).dividedBy(
10 ** parseInt(txDetails.tokenDecimals)
)
realQty = realQty.toString()
// realQty = parseFloat(realQty)
thisVin.tokenQtyStr = realQty
thisVin.tokenQty = parseFloat(realQty)
thisVin.tokenId = vinTokenData.tokenId
} else if (thisVin.vout === vinTokenData.mintBatonVout) {
// Optional Mint baton
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = vinTokenData.tokenId
thisVin.isMintBaton = true
} else {
thisVin.tokenQtyStr = '0'
thisVin.tokenQty = 0
thisVin.tokenId = null
}
} else {
console.log(
`Unknown vinTokenData: ${JSON.stringify(vinTokenData, null, 2)}`
)
throw new Error('Unknown token type in input')
}
}
return txDetails
} catch (err) {
console.error('Error in get3()')
// This case handles rate limit errors.
if (err.response && err.response.data && err.response.data.error) {
throw new Error(err.response.data.error)
}
if (err.error) throw new Error(err.error)
throw err
}
}
// A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx.
// Returns the token data if the txid is an SLP tx.
async getTokenInfo (txid) {
+55 -4
View File
@@ -150,6 +150,56 @@ class Util {
}
}
/**
* @api Util.chunk100() chunk100()
* @apiName chunk100
* @apiGroup Util
* @apiDescription chunk up an array into multiple arrays of 100 elements each.
* Input: arrayToSlice - a one-dimensional array of elements.
* Returns a two-dimensional array. An array of 100-element arrays.
*
* @apiExample Example usage:
* (async () => {
* try {
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,...,148, 149, 150]
*
* const chunked = bchjs.Util.chunk20(bigArray)
* console.log(chunked)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* [
* [0,1,2,3,4,5,6,7,8,9,10,11,...,98,99],
* [100,101,102,...,148,149,150]
* ]
*/
chunk100 (arrayToSlice) {
try {
// Validate inputs
if (!Array.isArray(arrayToSlice)) {
throw new Error('input must be an array')
}
let offset = 0
const result = []
// Loop over the array and slice off chunks of 100 elements.
while (offset < arrayToSlice.length) {
const chunk = arrayToSlice.slice(offset, offset + 100)
result.push(chunk)
offset = offset + 100
}
return result
} catch (err) {
console.error('Error in chunk100()')
throw err
}
}
/**
* @api Util.sleep() sleep()
* @apiName sleep
@@ -231,16 +281,17 @@ class Util {
// Array of blocks.
} else if (Array.isArray(address)) {
const options = {
const options1 = {
method: 'POST',
url: `${this.restURL}util/validateAddress`,
data: {
addresses: address
},
headers: {
authorization: `Token ${this.apiToken}`
}
// headers: {
// authorization: `Token ${this.apiToken}`
// }
}
const options = Object.assign({}, options1, this.axiosOptions)
const response = await axios(options)
return response.data
+190 -199
View File
@@ -8,19 +8,23 @@
// Local libraries
const Electrumx = require('./electrumx')
const Slp = require('./slp/slp')
const PsfSlpIndexer = require('./psf-slp-indexer')
const BigNumber = require('bignumber.js')
class UTXO {
constructor (config) {
constructor (config = {}) {
// Encapsulate dependencies for easier mocking.
this.electrumx = new Electrumx(config)
this.slp = new Slp(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
this.BigNumber = BigNumber
}
/**
* @api Utxo.get() get()
* @apiName get
* @apiGroup UTXO
* @apiDescription Get UTXOs for an address
* @apiDescription Get UTXOs for an address (from psf-slp-indexer)
*
* Given an address, this function will return an object with thre following
* properties:
@@ -37,10 +41,6 @@ class UTXO {
* - 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 () => {
@@ -67,221 +67,212 @@ class UTXO {
* "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) {
// This version of get() uses the psf-slp-indexer. It will replace the older
// get() function that uses SLPDB.
// TODO: NFT UTXOs are identified as non-token UTXOs, which will cause a wallet
// to burn them. The psf-slp-indexer needs to be updated to mark these UTXOs.
async get (address) {
try {
// Convert address to an array if it is a string.
if (typeof address === 'string') address = [address]
if (typeof address !== 'string') {
throw new Error('address input must be a string')
}
// 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))
// Ensure the address is a BCH address.
const addr = this.slp.Address.toCashAddress(address)
// Get the UTXOs associated with the address.
const utxoData = await this.electrumx.utxo(addr)
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
const utxos = utxoData.utxos
// 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)}`)
let slpUtxos = []
const retAry = [] // Return array
// Get SLP UTXOs from the psf-slp-indexer
try {
const slpUtxoData = await this.psfSlpIndexer.balance(addr)
// console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
// Loop through each address.
for (let i = 0; i < hydratedUtxos.slpUtxos.length; i++) {
const thisAddr = hydratedUtxos.slpUtxos[i]
slpUtxos = slpUtxoData.balance.utxos
} catch (err) {
// console.log('err: ', err)
const addrObj = {
address: thisAddr.address
// Exit quietly if address has no SLP UTXOs. Otherwise, throw the error.
if (err.error && !err.error.includes('Key not found in database')) {
throw err
}
// 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
// Loop through the Fulcrum UTXOs.
for (let i = 0; i < utxos.length; i++) {
const thisUtxo = utxos[i]
// Loop through the UTXOs from psf-slp-indexer.
for (let j = 0; j < slpUtxos.length; j++) {
const thisSlpUtxo = slpUtxos[j]
// If the non-hydrated UTXO matches the SLP UTXO, then combine the data
// and mark the UTXO as an SLP token.
if (
thisUtxo.tx_hash === thisSlpUtxo.txid &&
thisUtxo.tx_pos === thisSlpUtxo.vout
) {
thisUtxo.txid = thisUtxo.tx_hash
thisUtxo.vout = thisUtxo.tx_pos
thisUtxo.isSlp = true
thisUtxo.type = thisSlpUtxo.type
thisUtxo.qty = thisSlpUtxo.qty
thisUtxo.tokenId = thisSlpUtxo.tokenId
thisUtxo.address = thisSlpUtxo.address
thisUtxo.tokenType = thisSlpUtxo.tokenType
break
}
}
// If there was no match, then this is a normal BCH UTXO. Mark it as such.
if (!thisUtxo.isSlp) {
thisUtxo.txid = thisUtxo.tx_hash
thisUtxo.vout = thisUtxo.tx_pos
thisUtxo.address = addr
// Check the transaction to see if its a 'null' token, ignored by
// the indexer.
const txData = await this.psfSlpIndexer.tx(thisUtxo.tx_hash)
// console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
if (txData.txData.isValidSlp === null) {
thisUtxo.isSlp = null
} else {
thisUtxo.isSlp = false
}
// console.log(`thisUtxo.isSlp: ${thisUtxo.isSlp}`)
}
}
// Get token UTXOs
let type1TokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token' && x.tokenType === 1
)
// Hydrate the UTXOs with additional token data.
type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
// Collect and hydrate any type1 baton UTXOs
const bchUtxos = utxos.filter(x => x.isSlp === false)
let type1BatonUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 1
)
type1BatonUtxos = await this.hydrateTokenData(type1BatonUtxos)
// Collect and hydrate NFT Group tokens
let nftGroupTokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token' && x.tokenType === 129
)
nftGroupTokenUtxos = await this.hydrateTokenData(nftGroupTokenUtxos)
// Collect and hydrate any Group baton UTXOs
let groupBatonUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 129
)
groupBatonUtxos = await this.hydrateTokenData(groupBatonUtxos)
// Collect and hydrate NFT child tokens
let nftChildTokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token' && x.tokenType === 65
)
nftChildTokenUtxos = await this.hydrateTokenData(nftChildTokenUtxos)
// Isolate any UTXOs that are marked null by the SLP indexer.
const nullUtxos = utxos.filter(x => x.isSlp === null)
const outObj = {
address: addr,
bchUtxos,
slpUtxos: {
type1: {
tokens: type1TokenUtxos,
mintBatons: type1BatonUtxos
},
group: {
tokens: nftGroupTokenUtxos,
mintBatons: groupBatonUtxos
},
nft: {
tokens: nftChildTokenUtxos
}
},
nullUtxos
}
return outObj
} catch (err) {
console.error('Error in bchjs.utxo.get()')
console.error('Error in bchjs.Utxo.get(): ', err)
if (err.error) throw new Error(err.error)
throw err
}
}
// Hydrate an array of token UTXOs with token information.
// Returns an array of token UTXOs with additional data.
async hydrateTokenData (utxoAry) {
try {
// console.log('utxoAry: ', utxoAry)
// Create a list of token IDs without duplicates.
let tokenIds = utxoAry.map(x => x.tokenId)
// Remove duplicates. https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array
tokenIds = [...new Set(tokenIds)]
// console.log('tokenIds: ', tokenIds)
// Get Genesis data for each tokenId
const genesisData = []
for (let i = 0; i < tokenIds.length; i++) {
const thisTokenId = tokenIds[i]
const thisTokenData = await this.psfSlpIndexer.tokenStats(thisTokenId)
// console.log('thisTokenData: ', thisTokenData)
genesisData.push(thisTokenData)
}
// console.log('genesisData: ', genesisData)
// Hydrate each token UTXO with data from the genesis transaction.
for (let i = 0; i < utxoAry.length; i++) {
const thisUtxo = utxoAry[i]
// Get the genesis data for this token.
const genData = genesisData.filter(
x => x.tokenData.tokenId === thisUtxo.tokenId
)
// console.log('genData: ', genData)
thisUtxo.ticker = genData[0].tokenData.ticker
thisUtxo.name = genData[0].tokenData.name
thisUtxo.documentUri = genData[0].tokenData.documentUri
thisUtxo.documentHash = genData[0].tokenData.documentHash
thisUtxo.decimals = genData[0].tokenData.decimals
if (thisUtxo.type !== 'baton') {
// Calculate the real token quantity
const qty = new BigNumber(thisUtxo.qty).dividedBy(
10 ** parseInt(thisUtxo.decimals)
)
thisUtxo.qtyStr = qty.toString()
// tokenQty is property expected by SLP.tokentype1.js library
thisUtxo.tokenQty = thisUtxo.qtyStr
}
}
return utxoAry
} catch (err) {
console.log('Error in hydrateTokenData()')
throw err
}
}
/**
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
* @apiName findBiggestUtxo
+8
View File
@@ -313,6 +313,14 @@ describe('#blockchain', () => {
assert.isString(result)
})
})
describe('#getBlockchainInfo', () => {
it('should get info about the blockchain', async () => {
const result = await bchjs.Blockchain.getBlockchainInfo()
console.log(`blockchain info: ${JSON.stringify(result, null, 2)}`)
})
})
})
function sleep (ms) {
-226
View File
@@ -1,226 +0,0 @@
/*
Integration tests for the bchjs covering SLP tokens.
These tests are specific to the ABC chain.
*/
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../../../src/bch-js')
let bchjs
// Inspect utility used for debugging.
const util = require('util')
util.inspect.defaultOptions = {
showHidden: true,
colors: true,
depth: 1
}
describe('#SLP', () => {
// before(() => {
// console.log(`bchjs.SLP.restURL: ${bchjs.SLP.restURL}`)
// console.log(`bchjs.SLP.apiToken: ${bchjs.SLP.apiToken}`)
// })
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
bchjs = new BCHJS({ restURL: process.env.RESTURL })
})
describe('#util', () => {
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Malformed SLP tx
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Normal TX (non-SLP)
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Valid PSF SLP tx
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
// Valid SLP token not in whitelist
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
// 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
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, true)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
})
})
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
// "a0d6406eecfd8634158efa9314ff15b4cbf451938e9dc7b5678c46b41eabc6ed" // Mint baton
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
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)
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].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)
})
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@@ -2,10 +2,10 @@
Integration tests for the utxo.js library.
*/
const assert = require('chai').assert
// const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// const BCHJS = require('../../../../src/bch-js')
// const bchjs = new BCHJS()
describe('#UTXO', () => {
beforeEach(async () => {
@@ -14,6 +14,7 @@ describe('#UTXO', () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
/*
describe('#get', () => {
it('should get hydrated and filtered UTXOs for an address', async () => {
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
@@ -31,6 +32,7 @@ describe('#UTXO', () => {
assert.isArray(result[0].nullUtxos)
})
})
*/
})
function sleep (ms) {
@@ -1,71 +0,0 @@
/*
Integration tests for the bchjs. Only covers calls made to
rest.bitcoin.com.
TODO
*/
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// Inspect utility used for debugging.
const util = require('util')
util.inspect.defaultOptions = {
showHidden: true,
colors: true,
depth: 3
}
describe('#rawtransaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
})
/*
Testing sentRawTransaction isn't really possible with an integration test,
as the endpoint really needs an e2e test to be properly tested. The tests
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', () => {
it('should send a single transaction hex', async () => {
try {
const hex =
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
await bchjs.RawTransactions.sendRawTransaction(hex)
// console.log(`result ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: ${util.inspect(err)}`)
assert.hasAllKeys(err, ['error'])
assert.include(err.error, 'Missing inputs')
}
})
it('should send an array of tx hexes', async () => {
try {
const hexes = [
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000',
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
]
const result = await bchjs.RawTransactions.sendRawTransaction(hexes)
console.log(`result ${JSON.stringify(result, null, 2)}`)
} catch (err) {
// console.log(`err: ${util.inspect(err)}`)
assert.hasAllKeys(err, ['error'])
assert.include(err.error, 'Missing inputs')
}
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@@ -1,273 +0,0 @@
/*
Integration tests for the bchjs covering SLP tokens.
These tests are specific to the ABC chain.
*/
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../../../src/bch-js')
let bchjs
// Inspect utility used for debugging.
const util = require('util')
util.inspect.defaultOptions = {
showHidden: true,
colors: true,
depth: 1
}
describe('#SLP', () => {
// before(() => {
// console.log(`bchjs.SLP.restURL: ${bchjs.SLP.restURL}`)
// console.log(`bchjs.SLP.apiToken: ${bchjs.SLP.apiToken}`)
// })
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
bchjs = new BCHJS()
})
describe('#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 = [
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Malformed SLP tx
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Normal TX (non-SLP)
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Valid PSF SLP tx
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
// Valid SLP token not in whitelist
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
})
})
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() (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)
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
@@ -1,105 +0,0 @@
/*
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))
}
@@ -0,0 +1,151 @@
/*
Integration tests for the psf-slp-indexer.js library
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
let bchjs
describe('#psf-slp-indexer', () => {
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
bchjs = new BCHJS()
})
describe('#status', () => {
it('should return the status of the indexer.', async () => {
const result = await bchjs.PsfSlpIndexer.status()
// console.log('result: ', result)
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
})
describe('#balance', () => {
it('should get balance data for an address.', async () => {
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log('result: ', result)
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
})
})
describe('#tokenStats', () => {
it('should get stats on a token, without tx history', async () => {
const tokenId =
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log('result: ', result)
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'totalBurned')
})
it('should get stats on a token, with tx history', async () => {
const tokenId =
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId, true)
// console.log('result: ', result)
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'txs')
assert.property(result.tokenData, 'totalBurned')
})
})
describe('#tx', () => {
it('should get hydrated tx data for an SLP transaction', async () => {
const txid =
'947ccb2a0d62ca287bc4b0993874ab0f9f6afd454193e631e2bf84dca66731fc'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, true)
})
it('should mark non-SLP transaction as false', async () => {
const txid =
'03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, false)
})
// FlexUSD transactions
// Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means
// the wallet will burn them. There is a TODO in the code. This test will
// need to be changed when it is done.
it('should mark blacklisted token as null', async () => {
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, null)
})
it('should throw error for non-existent txid', async () => {
try {
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85e'
await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(err.message, 'No such mempool or blockchain transaction')
}
})
})
describe('#getTokenData', () => {
it('should get token data', async () => {
const tokenId =
'f055256b938f1ecfa270459d6f12c7c8c82b66d3263c03d5074445a2b1a498a3'
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
// console.log('result: ', result)
assert.property(result, 'genesisData')
assert.property(result, 'immutableData')
assert.property(result, 'mutableData')
assert.isObject(result.genesisData)
assert.isString(result.immutableData)
assert.isString(result.mutableData)
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+241 -237
View File
@@ -30,265 +30,269 @@ describe('#SLP', () => {
bchjs = new BCHJS()
})
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
if (process.env.TESTSLP) {
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
})
})
})
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
// Malformed SLP tx
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
// Normal TX (non-SLP)
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
})
})
// Valid PSF SLP tx
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
// Valid SLP token not in whitelist
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
assert.isArray(result)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId =
'68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId =
'45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
}
})
// Promise-based sleep function
@@ -0,0 +1,37 @@
/*
Integration tests for the transaction.js library.
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
describe('#Transaction', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#get', () => {
it('should get a tx details for a non-SLP TX with an OP_RETURN', async () => {
const txid =
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
const result = await bchjs.Transaction.get(txid)
// console.log('result: ', result)
assert.property(result.txData, 'txid')
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.equal(result.txData.isValidSlp, false)
})
it('should handle a coinbase transaction', async () => {
const txid = 'cca1d2dd3a533d2f501448dec03face2cb2814afd59a533a611e9a2909f2302b'
const details = await bchjs.Transaction.get(txid)
// console.log(`details: ${JSON.stringify(details, null, 2)}`)
assert.property(details.txData, 'txid')
})
})
})
@@ -6,6 +6,7 @@ const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
const bchjs = new BCHJS()
// const bchjs = new BCHJS({ restURL: 'http://192.168.2.129:3000/v5/' })
describe('#UTXO', () => {
beforeEach(async () => {
@@ -14,108 +15,111 @@ describe('#UTXO', () => {
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'
describe('#hydrateTokenData', () => {
it('should hydrate token UTXOs', async () => {
const utxos = [
{
txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
type: 'token',
qty: '10000000',
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
vout: 1,
type: 'token',
qty: '100000000',
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
vout: 5,
type: 'token',
qty: '4764',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
vout: 5,
type: 'token',
qty: '238',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
const result = await bchjs.Utxo.hydrateTokenData(utxos)
// console.log('result: ', result)
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)
assert.property(result[0], 'ticker')
assert.property(result[0], 'name')
assert.property(result[0], 'qtyStr')
assert.property(result[0], 'documentUri')
assert.property(result[0], 'documentHash')
})
})
describe('#findBiggestUtxo', () => {
it('should sort UTXOs from Electrumx', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
describe('#get', () => {
it('should hydrate address with BCH and SLP UTXOs', async () => {
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
// console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
// Assert expected properties exist.
assert.property(result, 'address')
assert.property(result, 'bchUtxos')
assert.property(result, 'slpUtxos')
assert.property(result.slpUtxos, 'type1')
assert.property(result.slpUtxos, 'nft')
assert.property(result, 'nullUtxos')
assert.isAbove(result.bchUtxos.length, 0)
assert.isAbove(result.slpUtxos.type1.tokens.length, 0)
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
})
it('should sort UTXOs from Utxos.get()', async () => {
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
it('should handle Type1 minting batons', async () => {
const addr = 'simpleledger:qz5l5yzz9r09hw9aadcz53elp2knx6gyg5qk3s8md7'
const utxos = await bchjs.Utxo.get(addr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'satoshis')
assert.equal(result.satoshis, 800)
// Assert that minting batons are correctly identified.
assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0)
})
it('should return UTXOs for address with no SLP tokens', async () => {
const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result.bchUtxos.length, 0)
assert.equal(result.slpUtxos.type1.tokens.length, 0)
})
it('should filter Group tokens and mint batons', async () => {
const addr = 'bitcoincash:qzeqcrpe5fcnslv8rfqjq4gh4gzdwytmdc4qmh0ztv'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result.slpUtxos.group.tokens.length, 0)
assert.isAbove(result.slpUtxos.group.mintBatons.length, 0)
})
it('should filter NFTs', async () => {
const addr = 'simpleledger:qq7vp2kvejsql898a2760kuq6xz00h0a5vuwu9lywu'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result.slpUtxos.nft.tokens.length, 0)
})
})
})
+475 -939
View File
File diff suppressed because it is too large Load Diff
+66 -63
View File
@@ -11,71 +11,74 @@ describe('#Transaction', () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#get', () => {
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
if (process.env.TESTSLP) {
describe('#getOld', () => {
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, false)
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, false)
})
it('should get details about a SLP transaction', async () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
})
// it('should get problematic transaction', async () => {
// const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
//
// const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// })
// TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
// an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
// that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
// but for separate tokens.
it('should get problematic transaction', async () => {
const txid =
'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// The token ID should equal the txid for this Vin.
assert.equal(result.vin[2].txid, result.vin[2].tokenId)
})
})
it('should get details about a SLP transaction', async () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vout[0], 'tokenQty')
assert.equal(result.vout[0].tokenQty, null)
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
assert.property(result.vin[0], 'tokenQty')
assert.property(result, 'isValidSLPTx')
assert.equal(result.isValidSLPTx, true)
})
// it('should get problematic transaction', async () => {
// const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
//
// const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// })
// TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
// an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
// that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
// but for separate tokens.
it('should get problematic transaction', async () => {
const txid = 'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
const result = await bchjs.Transaction.get(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// The token ID should equal the txid for this Vin.
assert.equal(result.vin[2].txid, result.vin[2].tokenId)
})
})
}
})
+56
View File
@@ -0,0 +1,56 @@
/*
Unit tests for eCash library.
*/
// Global npm libraries
const assert = require('chai').assert
const Ecash = require('../../src/ecash')
const uut = new Ecash()
describe('#eCash', () => {
describe('#toSatoshi', () => {
it('should convert XEC to satoshis', () => {
const xec = 10704.35
const result = uut.toSatoshi(xec)
assert.equal(result, 1070435)
})
it('should throw an error if input is not a number', () => {
try {
uut.toSatoshi('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing XEC'
)
}
})
})
describe('#toXec', () => {
it('should convert satoshis to XEC', () => {
const sats = 1070435
const result = uut.toXec(sats)
assert.equal(result, 10704.35)
})
it('should throw an error if input is not a number', () => {
try {
uut.toXec('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing satoshis'
)
}
})
})
})
-170
View File
@@ -1,170 +0,0 @@
/*
Mocking data for unit tests for the Ninsight library.
*/
const utxo = {
utxos: [
{
txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
vout: 0,
amount: 0.00001,
satoshis: 1000,
height: 602405,
confirmations: 36459
}
],
legacyAddress: '15NCRBJsHaJy8As5bX1oh2YauRejnZ1MKF',
cashAddress: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9',
slpAddress: 'simpleledger:qqh793x9au6ehvh7r2zflzguanlme760wuwzunhjfm',
scriptPubKey: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac',
asm:
'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG'
}
const unconfirmed = {
utxos: [
{
txid: '3904ffe6f8fba4ceda5e887130f60fcb18bdc7dcee10392a57f89475c5c108f1',
vout: 0,
amount: 0.03608203,
satoshis: 3608203,
confirmations: 0,
ts: 1559670801
}
],
legacyAddress: '1AyWs8U4HUnTLmxxFiGoJbsXauRsvBrcKW',
cashAddress: 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
slpAddress: 'simpleledger:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rkuk8mdxlf2',
scriptPubKey: '76a9146d695af951760b837c8e58b045b098f9c17e23b788ac'
}
const utxoPost = [utxo, utxo]
const unconfirmedPost = [unconfirmed, unconfirmed]
const transactions = {
pagesTotal: 1,
txs: [
{
txid: 'ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b',
version: 2,
locktime: 0,
vin: [
{
txid:
'4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0',
vout: 0,
sequence: 4294967295,
n: 0,
scriptSig: {
hex:
'483045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
asm:
'3045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
},
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
valueSat: 1111,
value: 0.00001111,
doubleSpentTxID: null
},
{
txid:
'126d62c299e7e14c66fe0b485d13082c23641f003690462046bc24ad2d1180c1',
vout: 0,
sequence: 4294967295,
n: 1,
scriptSig: {
hex:
'47304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
asm:
'304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
},
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
valueSat: 1000,
value: 0.00001,
doubleSpentTxID: null
}
],
vout: [
{
value: '0.00001736',
n: 0,
scriptPubKey: {
hex: '76a914d96ac75ca8df9729d278da50ccd7355c5785444e88ac',
asm:
'OP_DUP OP_HASH160 d96ac75ca8df9729d278da50ccd7355c5785444e OP_EQUALVERIFY OP_CHECKSIG',
addresses: ['1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP'],
type: 'pubkeyhash'
},
spentTxId: null,
spentIndex: null,
spentHeight: null
}
],
blockheight: -1,
confirmations: 0,
time: 1559673337,
valueOut: 0.00001736,
size: 339,
valueIn: 0.00002111,
fees: 0.00000375
}
],
legacyAddress: '1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP',
cashAddress: 'bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n',
currentPage: 0
}
const transactionsPost = [transactions, transactions]
const details = {
txid: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
version: 1,
locktime: 0,
vin: [
{
coinbase: '04ffff001d02fd04',
sequence: 4294967295,
n: 0
}
],
vout: [
{
value: '50.00000000',
n: 0,
scriptPubKey: {
hex:
'4104f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446aac',
asm:
'04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a OP_CHECKSIG',
addresses: ['1BW18n7MfpU35q4MTBSk8pse3XzQF8XvzT'],
type: 'pubkeyhash',
cashAddrs: ['bitcoincash:qpej6mkrwca4tvy2snq4crhrf88v4ljspysx0ueetk']
},
spentTxId: null,
spentIndex: null,
spentHeight: null
}
],
blockhash:
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
blockheight: 1000,
confirmations: 585610,
time: 1232346882,
blocktime: 1232346882,
isCoinBase: true,
valueOut: 50,
size: 135
}
const detailsPost = [details, details]
module.exports = {
utxo,
utxoPost,
unconfirmed,
unconfirmedPost,
transactions,
transactionsPost,
details,
detailsPost
}
+160
View File
@@ -0,0 +1,160 @@
/*
Mocking data for unit tests for the PSF SLP INDEXER library.
*/
'use strict'
const tokenStats = {
tokenData: {
type: 1,
ticker: 'TP03',
name: 'Test Plugin 03',
tokenId: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
documentUri: 'fullstack.cash',
documentHash: '',
decimals: 0,
mintBatonIsActive: false,
tokensInCirculationBN: '1',
tokensInCirculationStr: '1',
blockCreated: 722420,
totalBurned: '0',
totalMinted: '1',
txs: [
{
txid:
'13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
height: 722420,
type: 'GENESIS',
qty: '1'
}
]
}
}
const txData = {
txData: {
txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
version: 2,
size: 339,
locktime: 0,
vin: [
{
txid:
'8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
vout: 0,
scriptSig: {
asm:
'304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
hex:
'47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
},
sequence: 4294967295,
address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
value: 0.00051303,
tokenQty: 0,
tokenQtyStr: '0',
tokenId: null
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
asm:
'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
hex:
'6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
type: 'nulldata'
},
tokenQtyStr: '0',
tokenQty: 0
}
],
hex:
'0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
blockhash:
'0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
confirmations: 97398,
time: 1581773131,
blocktime: 1581773131,
blockheight: 622414,
isSlpTx: true,
tokenTxType: 'GENESIS',
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
tokenType: 1,
tokenTicker: 'TROUT',
tokenName: "Trout's test token",
tokenDecimals: 2,
tokenUri: 'troutsblog.com',
tokenDocHash: '',
isValidSlp: true
}
}
const balance = {
balance: {
utxos: [
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
vout: 1,
type: 'token',
qty: '1800',
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
}
],
txs: [
{
txid:
'078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
height: 717796
},
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
height: 717832
}
],
balances: [
{
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: '1800'
}
]
}
}
const status = {
status: {
startBlockHeight: 543376,
syncedBlockHeight: 722860,
chainBlockHeight: 722679
}
}
const tokenData01 = {
tokenType: 1,
txType: 'MINT',
tokenId: 'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9',
mintBatonVout: 2,
qty: {}
}
const tokenData = {
genesisData: tokenStats.tokenData,
immutableData: 'ipfs://bafybeidzfi3sictag4noy746gniyiwwcgio565xuzelmjfwxnga6qyiyai',
mutableData: 'ipfs://bafybeie6t5uyupddc7azms737xg4hxrj7i5t5ov3lb5g2qeehaujj6ak64'
}
module.exports = {
tokenStats,
txData,
balance,
status,
tokenData01,
tokenData
}
+205 -1
View File
@@ -280,10 +280,214 @@ const electrumxUtxos = {
]
}
const fulcrumUtxos01 = {
success: true,
utxos: [
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 1,
value: 546
},
{
height: 674512,
tx_hash:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
tx_pos: 2,
value: 546
},
{
height: 674512,
tx_hash:
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
tx_pos: 1,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 1,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 2,
value: 546
},
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 3,
value: 38134
}
]
}
const fulcrumUtxos02 = {
success: true,
utxos: [
{
height: 674513,
tx_hash:
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
tx_pos: 3,
value: 38134
}
]
}
const psfSlpIndexerUtxos01 = {
balance: {
utxos: [
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 1,
type: 'token',
qty: '10000000000',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
},
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
vout: 2,
type: 'baton',
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
}
],
txs: [
{
txid:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
height: 674512
}
],
balances: [
{
tokenId:
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
qty: '10000000000'
}
]
}
}
const tokenUtxos01 = [
{
txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
vout: 1,
type: 'token',
qty: '10000000',
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
vout: 1,
type: 'token',
qty: '100000000',
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
vout: 5,
type: 'token',
qty: '4764',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
},
{
txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
vout: 5,
type: 'token',
qty: '238',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
}
]
const genesisData01 = {
tokenData: {
type: 1,
ticker: 'sleven',
name: 'sleven',
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
documentUri: 'sleven',
documentHash: '',
decimals: 7,
mintBatonIsActive: false,
tokensInCirculationBN: '770059999999',
tokensInCirculationStr: '770059999999',
blockCreated: 555483,
totalBurned: '7711234568',
totalMinted: '777771234567'
}
}
const genesisData02 = {
tokenData: {
type: 1,
ticker: 'NAKAMOTO',
name: 'NAKAMOTO',
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
documentUri: '',
documentHash: '',
decimals: 8,
mintBatonIsActive: false,
tokensInCirculationBN: '2099260968799900',
tokensInCirculationStr: '2099260968799900',
blockCreated: 555671,
totalBurned: '739031200100',
totalMinted: '2100000000000000'
}
}
const genesisData03 = {
tokenData: {
type: 1,
ticker: 'AUDC',
name: 'AUD Coin',
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
documentUri: 'audcoino@gmail.com',
documentHash: '',
decimals: 6,
mintBatonIsActive: false,
tokensInCirculationBN: '974791786216512742',
tokensInCirculationStr: '974791786216512742',
blockCreated: 603311,
totalBurned: '1025208213783487258',
totalMinted: '2000000000000000000'
}
}
const noUtxoErr = {
success: false,
error:
'Key not found in database [bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7]'
}
module.exports = {
mockUtxoData,
mockHydratedUtxos,
mockTwoHydratedAddrs,
mockEveryUtxoType,
electrumxUtxos
electrumxUtxos,
fulcrumUtxos01,
fulcrumUtxos02,
psfSlpIndexerUtxos01,
tokenUtxos01,
genesisData01,
genesisData02,
genesisData03,
noUtxoErr
}
-443
View File
@@ -1,443 +0,0 @@
/*
Unit tests for the IPFS Class.
*/
const assert = require('chai').assert
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
let bchjs
const mockData = require('./fixtures/ipfs-mock')
describe('#IPFS', () => {
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
bchjs = new BCHJS()
})
afterEach(() => sandbox.restore())
describe('#initUppy', () => {
it('should initialize uppy', () => {
bchjs.IPFS.initUppy()
})
})
describe('#createFileModelServer', () => {
it('should throw an error if file does not exist', async () => {
try {
const path = '/non-existant-file'
await bchjs.IPFS.createFileModelServer(path)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'Could not find this file')
}
})
it('should create a new file model', async () => {
const path = `${__dirname.toString()}/ipfs.js`
sandbox
.stub(bchjs.IPFS.axios, 'post')
.resolves({ data: mockData.mockNewFileModel })
const result = await bchjs.IPFS.createFileModelServer(path)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'hostingCostBCH')
assert.property(result, 'hostingCostUSD')
assert.property(result, 'file')
assert.property(result.file, 'payloadLink')
assert.property(result.file, 'hasBeenPaid')
assert.property(result.file, '_id')
assert.property(result.file, 'schemaVersion')
assert.property(result.file, 'size')
assert.property(result.file, 'fileName')
assert.property(result.file, 'fileExtension')
assert.property(result.file, 'createdTimestamp')
assert.property(result.file, 'hostingCost')
assert.property(result.file, 'walletIndex')
assert.property(result.file, 'bchAddr')
})
})
describe('#uploadFileServer', () => {
it('should throw an error if file does not exist', async () => {
try {
const path = '/non-existant-file'
await bchjs.IPFS.uploadFileServer(path)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'Could not find this file')
}
})
it('should throw an error if modelId is not included', async () => {
try {
const path = `${__dirname.toString()}/ipfs.js`
await bchjs.IPFS.uploadFileServer(path)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'Must include a file model ID')
}
})
it('Should throw error if the file was not uploaded', async () => {
try {
const mock = {
successful: [],
failed: [
{
id: 'file id'
}
]
}
sandbox.stub(bchjs.IPFS.uppy, 'upload').resolves(mock)
const path = `${__dirname.toString()}/ipfs.js`
await bchjs.IPFS.uploadFileServer(path, '5ec562319bfacc745e8d8a52')
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(err)
assert.include(err.message, 'The file could not be uploaded')
}
})
it('should return file object if the file is uploaded', async () => {
try {
sandbox.stub(bchjs.IPFS.uppy, 'upload').resolves(mockData.uploadData)
const path = `${__dirname.toString()}/ipfs.js`
const result = await bchjs.IPFS.uploadFileServer(
path,
'5ec562319bfacc745e8d8a52'
)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'schemaVersion')
assert.property(result, 'size')
assert.property(result, 'fileId')
assert.property(result, 'fileName')
assert.property(result, 'fileExtension')
} catch (err) {
// console.log(err)
assert.equal(true, false, 'Unexpected result')
}
})
})
describe('#getStatus', () => {
it('should throw an error if modelId is not included', async () => {
try {
await bchjs.IPFS.getStatus()
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'Must include a file model ID')
}
})
it('should get data on an unpaid file', async () => {
const modelId = '5ec7392c2acfe57aa62e945a'
sandbox
.stub(bchjs.IPFS.axios, 'get')
.resolves({ data: mockData.unpaidFileData })
const result = await bchjs.IPFS.getStatus(modelId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'hasBeenPaid')
assert.property(result, 'satCost')
assert.property(result, 'bchAddr')
assert.property(result, 'ipfsHash')
assert.property(result, 'fileId')
assert.property(result, 'fileName')
})
it('should get data on an unpaid file', async () => {
const modelId = '5ec7392c2acfe57aa62e945a'
sandbox
.stub(bchjs.IPFS.axios, 'get')
.resolves({ data: mockData.paidFileData })
const result = await bchjs.IPFS.getStatus(modelId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'hasBeenPaid')
assert.property(result, 'satCost')
assert.property(result, 'bchAddr')
assert.property(result, 'ipfsHash')
assert.property(result, 'fileId')
assert.property(result, 'fileName')
})
})
describe('#createFileModelWeb', () => {
it('should throw an error if file is undefined', async () => {
try {
let file
await bchjs.IPFS.createFileModelWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'File is required')
}
})
it('should throw an error if file is empty', async () => {
try {
const file = {}
await bchjs.IPFS.createFileModelWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'name\' of string type'
)
}
})
it("should throw an error if 'name' property is not included", async () => {
try {
const file = {
size: 5000
}
await bchjs.IPFS.createFileModelWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'name\' of string type'
)
}
})
it("should throw an error if 'size' property is not included", async () => {
try {
const file = {
name: 'ipfs.js'
}
await bchjs.IPFS.createFileModelWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'size\' of number type'
)
}
})
it('should create a new file model', async () => {
const file = {
name: 'ipfs.js',
size: 5000,
type: 'text/plain'
}
sandbox
.stub(bchjs.IPFS.axios, 'post')
.resolves({ data: mockData.mockNewFileModel })
const result = await bchjs.IPFS.createFileModelWeb(file)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'hostingCostBCH')
assert.property(result, 'hostingCostUSD')
assert.property(result, 'file')
assert.property(result.file, 'payloadLink')
assert.property(result.file, 'hasBeenPaid')
assert.property(result.file, '_id')
assert.property(result.file, 'schemaVersion')
assert.property(result.file, 'size')
assert.property(result.file, 'fileName')
assert.property(result.file, 'fileExtension')
assert.property(result.file, 'createdTimestamp')
assert.property(result.file, 'hostingCost')
assert.property(result.file, 'walletIndex')
assert.property(result.file, 'bchAddr')
})
})
describe('#uploadFileWeb', () => {
it('should throw an error if file is undefined', async () => {
try {
let file
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'File is required')
}
})
it('should throw an error if file is empty', async () => {
try {
const file = {}
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'name\' of string type'
)
}
})
it("should throw an error if 'name' property is not included", async () => {
try {
const file = {
size: 5000,
type: 'text/plain'
}
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'name\' of string type'
)
}
})
it("should throw an error if 'size' property is not included", async () => {
try {
const file = {
name: 'ipfs.js',
type: 'text/plain'
}
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'size\' of number type'
)
}
})
it("should throw an error if 'type' property is not included", async () => {
try {
const file = {
name: 'ipfs.js',
size: 5000
}
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(
err.message,
'File should have the property \'type\' of string type'
)
}
})
it('should throw an error if modelId is not included', async () => {
try {
const file = {
name: 'ipfs.js',
size: 5000,
type: 'text/plain'
}
await bchjs.IPFS.uploadFileWeb(file)
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(`err.message: ${err.message}`)
assert.include(err.message, 'Must include a file model ID')
}
})
it('Should throw error if the file was not uploaded', async () => {
try {
const mock = {
successful: [],
failed: [
{
id: 'file id'
}
]
}
sandbox.stub(bchjs.IPFS.uppy, 'upload').resolves(mock)
const file = {
name: 'ipfs.js',
size: 5000,
type: 'text/plain'
}
await bchjs.IPFS.uploadFileWeb(file, '5ec562319bfacc745e8d8a52')
assert.equal(true, false, 'Unexpected result')
} catch (err) {
// console.log(err)
assert.include(err.message, 'The file could not be uploaded')
}
})
it('should return file object if the file is uploaded', async () => {
try {
sandbox.stub(bchjs.IPFS.uppy, 'upload').resolves(mockData.uploadData)
const file = {
name: 'ipfs.js',
size: 5000,
type: 'text/plain'
}
const result = await bchjs.IPFS.uploadFileWeb(
file,
'5ec562319bfacc745e8d8a52'
)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'schemaVersion')
assert.property(result, 'size')
assert.property(result, 'fileId')
assert.property(result, 'fileName')
assert.property(result, 'fileExtension')
} catch (err) {
// console.log(err)
assert.equal(true, false, 'Unexpected result')
}
})
})
})
-255
View File
@@ -1,255 +0,0 @@
const chai = require('chai')
const assert = chai.assert
const axios = require('axios')
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/ninsight-mock')
describe('#Ninsight', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#utxo', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.utxo(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should GET utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.utxo })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'utxos')
assert.property(result, 'legacyAddress')
assert.property(result, 'cashAddress')
assert.property(result, 'slpAddress')
assert.property(result, 'scriptPubKey')
assert.property(result, 'asm')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'txid')
assert.property(result.utxos[0], 'vout')
assert.property(result.utxos[0], 'amount')
assert.property(result.utxos[0], 'satoshis')
assert.property(result.utxos[0], 'height')
assert.property(result.utxos[0], 'confirmations')
})
it('should POST utxo details for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.utxoPost })
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.isArray(result[0].utxos)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
assert.property(result[0], 'asm')
})
})
describe('#unconfirmed', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.unconfirmed(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should POST utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmed })
const addr = 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5'
const result = await bchjs.Ninsight.unconfirmed(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'utxos')
assert.property(result, 'legacyAddress')
assert.property(result, 'cashAddress')
assert.property(result, 'slpAddress')
assert.property(result, 'scriptPubKey')
assert.isArray(result.utxos)
assert.property(result.utxos[0], 'txid')
assert.property(result.utxos[0], 'vout')
assert.property(result.utxos[0], 'amount')
assert.property(result.utxos[0], 'satoshis')
assert.property(result.utxos[0], 'confirmations')
assert.property(result.utxos[0], 'ts')
})
it('should POST utxo details for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmedPost })
const addr = [
'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.unconfirmed(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.isArray(result[0].utxos)
assert.property(result[0], 'utxos')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'slpAddress')
assert.property(result[0], 'scriptPubKey')
})
})
describe('#transactions', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.Ninsight.transactions(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Input address must be a string or array of strings.'
)
}
})
it('should POST transaction history for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
assert.property(result[0].txs[0], 'vin')
assert.property(result[0].txs[0], 'vout')
})
it('should POST transaction history for an array of addresses', async () => {
// Mock the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
const addr = [
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
]
const result = await bchjs.Ninsight.transactions(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'cashAddress')
assert.property(result[0], 'legacyAddress')
assert.property(result[0], 'txs')
assert.isArray(result[0].txs)
assert.property(result[0].txs[0], 'txid')
})
})
describe('#txDetails', () => {
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.Ninsight.txDetails(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(
err.message,
'Transaction ID must be a string or array of strings.'
)
}
})
it('should POST transaction details for a single TxID', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
const txid = 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'version')
assert.property(result[0], 'locktime')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
assert.property(result[0], 'blockhash')
assert.property(result[0], 'blockheight')
assert.property(result[0], 'confirmations')
assert.property(result[0], 'time')
assert.property(result[0], 'blocktime')
assert.property(result[0], 'isCoinBase')
assert.property(result[0], 'valueOut')
assert.property(result[0], 'size')
})
it('should POST transaction details for an array of TxIDs', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
const txid = [
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
]
const result = await bchjs.Ninsight.txDetails(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.property(result[0], 'vin')
assert.property(result[0], 'vout')
})
})
})
+414
View File
@@ -0,0 +1,414 @@
const chai = require('chai')
const assert = chai.assert
const axios = require('axios')
const sinon = require('sinon')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockData = require('./fixtures/psf-slp-indexer-mock')
describe('#PsfSlpIndexer', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#status', () => {
it('should GET status', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.status })
const result = await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'status')
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'get').throws(new Error('test error'))
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'get').throws(testErr)
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#balance', () => {
it('should GET balance', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.balance })
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'balance')
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
assert.isArray(result.balance.utxos)
assert.isArray(result.balance.txs)
assert.isArray(result.balance.balances)
})
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.PsfSlpIndexer.balance(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input address must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tokenStats', () => {
it('should GET token stats', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenStats })
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'tokenData')
assert.property(result.tokenData, 'type')
assert.property(result.tokenData, 'ticker')
assert.property(result.tokenData, 'name')
assert.property(result.tokenData, 'tokenId')
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'documentHash')
assert.property(result.tokenData, 'decimals')
assert.property(result.tokenData, 'mintBatonIsActive')
assert.property(result.tokenData, 'tokensInCirculationBN')
assert.property(result.tokenData, 'tokensInCirculationStr')
assert.property(result.tokenData, 'blockCreated')
assert.property(result.tokenData, 'totalBurned')
assert.property(result.tokenData, 'totalMinted')
assert.property(result.tokenData, 'txs')
})
it('should throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input tokenId must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tx', () => {
it('should GET transaction data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.txData })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'txData')
assert.property(result.txData, 'txid')
assert.property(result.txData, 'hash')
assert.property(result.txData, 'version')
assert.property(result.txData, 'size')
assert.property(result.txData, 'locktime')
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'hex')
assert.property(result.txData, 'blockhash')
assert.property(result.txData, 'confirmations')
assert.property(result.txData, 'time')
assert.property(result.txData, 'blocktime')
assert.property(result.txData, 'blockheight')
assert.property(result.txData, 'isSlpTx')
assert.property(result.txData, 'tokenTxType')
assert.property(result.txData, 'tokenId')
assert.property(result.txData, 'tokenType')
assert.property(result.txData, 'tokenTicker')
assert.property(result.txData, 'tokenName')
assert.property(result.txData, 'tokenDecimals')
assert.property(result.txData, 'tokenUri')
assert.property(result.txData, 'tokenDocHash')
assert.property(result.txData, 'isValidSlp')
})
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.PsfSlpIndexer.tx(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input txid must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should get tx from full node if not available from slp indexer', async () => {
// Stub the call to the SLP indexer.
const testErr = {
response: { data: { error: 'Key not found in database' } }
}
sandbox.stub(axios, 'post').rejects(testErr)
// Stub the call to the full node
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(false)
sandbox
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
.resolves({ txid: 'fakeTxid' })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txData.txid, 'fakeTxid')
assert.equal(result.txData.isValidSlp, false)
})
it('should return isValidSlp=null for blacklisted token', async () => {
// Stub the call to the SLP indexer.
const testErr = {
response: { data: { error: 'Key not found in database' } }
}
sandbox.stub(axios, 'post').rejects(testErr)
// Stub the call to the full node
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(true)
sandbox
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
.resolves({ txid: 'fakeTxid' })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txData.txid, 'fakeTxid')
assert.equal(result.txData.isValidSlp, null)
})
})
describe('#checkBlacklist', () => {
it('should return true if txid contains token in blacklist', async () => {
// Mock dependencies
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.resolves(mockData.tokenData01)
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
// console.log('result: ', result)
assert.equal(result, true)
})
it('should return false if there is an error', async () => {
// Force an error
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.rejects(new Error('test error'))
const result = await bchjs.PsfSlpIndexer.checkBlacklist()
assert.equal(result, false)
})
it('should return false if there is no tokenId match', async () => {
// Mock dependencies
mockData.tokenData01.tokenId = 'abc123'
sandbox
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
.resolves(mockData.tokenData01)
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
assert.equal(result, false)
})
})
describe('#getTokenData', () => {
it('should GET token data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenData })
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
assert.property(result, 'genesisData')
assert.property(result, 'immutableData')
assert.property(result, 'mutableData')
assert.isObject(result.genesisData)
assert.isString(result.immutableData)
assert.isString(result.mutableData)
assert.property(result.genesisData, 'type')
assert.property(result.genesisData, 'ticker')
assert.property(result.genesisData, 'name')
assert.property(result.genesisData, 'tokenId')
assert.property(result.genesisData, 'documentUri')
assert.property(result.genesisData, 'documentHash')
assert.property(result.genesisData, 'decimals')
assert.property(result.genesisData, 'mintBatonIsActive')
assert.property(result.genesisData, 'tokensInCirculationBN')
assert.property(result.genesisData, 'tokensInCirculationStr')
assert.property(result.genesisData, 'blockCreated')
assert.property(result.genesisData, 'totalBurned')
assert.property(result.genesisData, 'totalMinted')
})
it('should throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input tokenId must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
})
File diff suppressed because it is too large Load Diff
+9 -682
View File
@@ -5,704 +5,31 @@
// Public npm libraries
const assert = require('chai').assert
const sinon = require('sinon')
const cloneDeep = require('lodash.clonedeep')
// const cloneDeep = require('lodash.clonedeep')
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const mockDataLib = require('./fixtures/transaction-mock.js')
// const mockDataLib = require('./fixtures/transaction-mock.js')
describe('#TransactionLib', () => {
let sandbox, mockData
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
mockData = cloneDeep(mockDataLib)
// mockData = cloneDeep(mockDataLib)
})
afterEach(() => sandbox.restore())
describe('#get', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get()
it('should proxy psf-slp-indexer', async () => {
// console.log('bchjs.Transaction: ', bchjs.Transaction)
sandbox.stub(bchjs.Transaction.psfSlpIndexer, 'tx').resolves('test data')
assert.fail('Unexpected code path!')
} catch (err) {
assert.include(
err.message,
'Input to Transaction.get() must be a string containing a TXID.'
)
}
})
const result = await bchjs.Transaction.get()
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')
}
})
// This test case was created in response to a bug. When the input TX
// was a Genesis SLP transaction, the inputs of the transaction were not
// being hydrated properly.
it('should get input details when input is a genesis tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.genesisTestOpReturnData01)
.onCall(1)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(2)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(3)
.resolves(mockData.genesisTestOpReturnData02)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
const result = await bchjs.Transaction.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)
// Assert inputs values unique to a Genesis input have the proper values.
assert.equal(result.vin[0].tokenQty, 10000000)
assert.equal(result.vin[1].tokenQty, null)
})
it('should get input details when input is a mint tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData01)
.onCall(1)
.resolves(mockData.mintTestOpReturnData02)
.onCall(2)
.resolves(mockData.mintTestOpReturnData02)
.onCall(3)
.resolves(mockData.mintTestOpReturnData03)
.onCall(4)
.resolves(mockData.mintTestOpReturnData03)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
const result = await bchjs.Transaction.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)
// Assert inputs values unique to a Mint input have the proper values.
assert.equal(result.vin[0].tokenQty, 43545.34534)
assert.equal(result.vin[1].tokenQty, 2.34123)
assert.equal(result.vin[2].tokenQty, null)
})
// This test case was generated from the problematic transaction that
// used inputs in a 'non-standard' way.
it('should correctly assign quantities to mixed mint inputs', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.sendTestInputTx01)
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.sendTestOpReturnData01)
.onCall(1)
.resolves(mockData.sendTestOpReturnData02)
.onCall(2)
.resolves(mockData.sendTestOpReturnData03)
.onCall(3)
.resolves(mockData.sendTestOpReturnData03)
.onCall(4)
.resolves(mockData.sendTestOpReturnData04)
sandbox
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
.resolves(true)
const txid =
'6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
const result = await bchjs.Transaction.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)
// Assert inputs values unique to a Mint input have the proper values.
assert.equal(result.vin[0].tokenQty, 100000000)
assert.equal(result.vin[1].tokenQty, null)
assert.equal(result.vin[2].tokenQty, 99000000)
})
})
describe('#get3', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get3()
assert.fail('Unexpected code path!')
} catch (err) {
assert.include(
err.message,
'Input to Transaction.get() must be a string containing a TXID.'
)
}
})
it('should get details about a non-SLP transaction', async () => {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.nonSlpTxDetails)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 602405 })
sandbox.stub(bchjs.Transaction, 'getTokenInfo').resolves(false)
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[0].scriptPubKey, 'addresses')
// Assert that added properties exist.
assert.property(result.vin[0], 'address')
assert.property(result.vin[0], 'value')
// Assert blockheight is added
assert.equal(result.blockheight, 602405)
assert.equal(result.isSlpTx, false)
})
it('should get details about a SLP SEND tx with SEND input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.slpTxDetails)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 603424 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mockOpReturnData01)
.onCall(1)
.resolves(mockData.mockOpReturnData02)
.onCall(2)
.resolves(mockData.mockOpReturnData03)
.onCall(3)
.rejects(new Error('No OP_RETURN'))
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[0].tokenQtyStr, null)
assert.equal(result.vout[1].tokenQty, 1)
assert.equal(result.vout[1].tokenQtyStr, '1')
assert.equal(result.vout[2].tokenQty, 998833)
assert.equal(result.vout[2].tokenQtyStr, '998833')
assert.equal(result.vout[3].tokenQty, null)
assert.equal(result.vout[3].tokenQtyStr, null)
// Assert that inputs have expected properties
assert.equal(result.vin[0].tokenQtyStr, '998834')
assert.equal(result.vin[0].tokenQty, 998834)
assert.equal(
result.vin[0].tokenId,
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 603424)
assert.equal(result.isSlpTx, true)
})
it('should catch and throw error on network error', async () => {
try {
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
// Force an error
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.rejects(new Error('test error'))
await bchjs.Transaction.get3(txid)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
// This test case was created in response to a bug. When the input TX
// was a Genesis SLP transaction, the inputs of the transaction were not
// being hydrated properly.
it('should get details about a SLP SEND tx with GENSIS input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543409 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.genesisTestOpReturnData01)
.onCall(1)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(2)
.resolves(mockData.genesisTestOpReturnData02)
.onCall(3)
.resolves(mockData.genesisTestOpReturnData02)
const txid =
'874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties and values
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[0].tokenQtyStr, null)
assert.equal(result.vout[1].tokenQty, 5000000)
assert.equal(result.vout[1].tokenQtyStr, '5000000')
assert.equal(result.vout[2].tokenQty, 5000000)
assert.equal(result.vout[2].tokenQtyStr, '5000000')
assert.equal(result.vout[3].tokenQty, null)
assert.equal(result.vout[3].tokenQtyStr, null)
// Assert inputs have expected properties and values
assert.equal(result.vin[0].tokenQty, 10000000)
assert.equal(result.vin[0].tokenQtyStr, '10000000')
assert.equal(
result.vin[0].tokenId,
'323a1e35ae0b356316093d20f2d9fbc995d19314b5c0148b78dc8d9c0dab9d35'
)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 543409)
assert.equal(result.isSlpTx, true)
})
it('should get details about a SLP SEND tx with MINT (and GENESIS) input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543614 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData01)
.onCall(1)
.resolves(mockData.mintTestOpReturnData02)
.onCall(2)
.resolves(mockData.mintTestOpReturnData02)
.onCall(3)
.resolves(mockData.mintTestOpReturnData03)
.onCall(4)
.resolves(mockData.mintTestOpReturnData03)
const txid =
'4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert expected output properties and values exist.
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[1].tokenQty, 43547.68657)
assert.equal(result.vout[1].tokenQtyStr, '43547.68657')
assert.equal(result.vout[2].tokenQty, null)
// Assert expected input properties and values exist.
assert.equal(result.vin[0].tokenQty, 43545.34534)
assert.equal(result.vin[0].tokenQtyStr, '43545.34534')
assert.equal(
result.vin[0].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[1].tokenQty, 2.34123)
assert.equal(result.vin[1].tokenQtyStr, '2.34123')
assert.equal(
result.vin[1].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[2].tokenQty, 0)
assert.equal(result.vin[2].tokenQtyStr, '0')
assert.equal(result.vin[2].tokenId, null)
// Assert blockheight is added
assert.equal(result.blockheight, 543614)
assert.equal(result.isSlpTx, true)
})
// This test case was generated from the problematic transaction that
// used inputs in a 'non-standard' way.
it('should get details about a SLP SEND tx with MINT (and SEND) input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.sendTestInputTx01)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543957 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.sendTestOpReturnData01)
.onCall(1)
.resolves(mockData.sendTestOpReturnData02)
.onCall(2)
.resolves(mockData.sendTestOpReturnData03)
.onCall(3)
.resolves(mockData.sendTestOpReturnData03)
.onCall(4)
.resolves(mockData.sendTestOpReturnData04)
const txid =
'6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert the outputs have expected properties and values.
assert.equal(result.vout[0].tokenQty, null)
assert.equal(result.vout[1].tokenQty, 1000000)
assert.equal(result.vout[1].tokenQtyStr, '1000000')
assert.equal(result.vout[2].tokenQty, 198000000)
assert.equal(result.vout[2].tokenQtyStr, '198000000')
assert.equal(result.vout[3].tokenQty, null)
// Assert the inputs have expected properties and values.
assert.equal(result.vin[0].tokenQty, 100000000)
assert.equal(result.vin[0].tokenQtyStr, '100000000')
assert.equal(
result.vin[0].tokenId,
'550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35'
)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, 0)
assert.equal(result.vin[1].tokenId, null)
assert.equal(result.vin[2].tokenQty, 99000000)
assert.equal(result.vin[2].tokenQtyStr, '99000000')
assert.equal(
result.vin[2].tokenId,
'550d19eb820e616a54b8a73372c4420b5a0567d8dc00f613b71c5234dc884b35'
)
// Assert blockheight is added
assert.equal(result.blockheight, 543957)
assert.equal(result.isSlpTx, true)
})
// This was a problematic TX
it('should process MINT TX with GENESIS input', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.mintTestInputTx02)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 543614 })
sandbox
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
.onCall(0)
.resolves(mockData.mintTestOpReturnData04)
.onCall(1)
.resolves(mockData.mintTestOpReturnData05)
.onCall(2)
.resolves(mockData.mintTestOpReturnData05)
.onCall(3)
.resolves(mockData.mintTestOpReturnData05)
.onCall(4)
.resolves(mockData.mintTestOpReturnData05)
.onCall(5)
.resolves(mockData.mintTestOpReturnData05)
const txid =
'ee9d3cf5153599c134147e3fac9844c68e216843f4452a1ce15a29452af6db34'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert outputs have expected properties and values
assert.equal(result.vout[0].tokenQty, 0)
assert.equal(result.vout[0].tokenQty, '0')
assert.equal(result.vout[1].tokenQty, 2.34123)
assert.equal(result.vout[1].tokenQty, '2.34123')
assert.equal(result.vout[2].tokenQty, 0)
assert.equal(result.vout[2].tokenQty, '0')
assert.equal(result.vout[2].isMintBaton, true)
assert.equal(result.vout[3].tokenQty, 0)
assert.equal(result.vout[3].tokenQty, '0')
// Assert inputs have expected properties and values
assert.equal(result.vin[0].tokenQty, 0)
assert.equal(result.vin[0].tokenQtyStr, '0')
assert.equal(result.vin[0].tokenId, null)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(
result.vin[1].tokenId,
'938cc18e618967d787897bbc64b9a8d201b94ec7c69b1a9949eab0433ba5cdf8'
)
assert.equal(result.vin[1].isMintBaton, true)
// Assert added TX data exists.
assert.equal(result.blockheight, 543614)
assert.equal(result.isSlpTx, true)
})
// It should process a GENESIS tx
it('should process a GENESIS tx', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.genesisTestInputTx02)
sandbox
.stub(bchjs.Transaction.blockchain, 'getBlockHeader')
.resolves({ height: 571212 })
sandbox
.stub(bchjs.Transaction, 'getTokenInfo')
.onCall(0)
.resolves(mockData.genesisTestOpReturn03)
.onCall(1)
.resolves(mockData.genesisTestOpReturn03)
.onCall(2)
.resolves(false)
.onCall(3)
.resolves(false)
.onCall(4)
.resolves(false)
const txid =
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
const result = await bchjs.Transaction.get3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
assert.property(result, 'txid')
assert.property(result, 'vin')
assert.property(result, 'vout')
assert.property(result.vout[0], 'value')
assert.property(result.vout[1].scriptPubKey, 'addresses')
// Assert output have expected properties and values
assert.equal(result.vout[0].tokenQty, 0)
assert.equal(result.vout[0].tokenQtyStr, '0')
assert.equal(result.vout[0].isMintBaton, true)
assert.equal(result.vout[1].tokenQty, 1000000000)
assert.equal(result.vout[1].tokenQtyStr, '1000000000')
assert.equal(result.vout[2].tokenQty, 0)
assert.equal(result.vout[2].tokenQtyStr, '0')
// Assert input have expected properties and values
assert.equal(result.vin[0].tokenQty, 0)
assert.equal(result.vin[0].tokenQtyStr, '0')
assert.equal(result.vin[0].tokenId, null)
assert.equal(result.vin[1].tokenQty, 0)
assert.equal(result.vin[1].tokenQtyStr, '0')
assert.equal(result.vin[1].tokenId, null)
// Assert added TX data exists.
assert.equal(result.blockheight, 571212)
assert.equal(result.isSlpTx, true)
assert.equal(result, 'test data')
})
})
})
+117 -131
View File
@@ -15,137 +15,6 @@ describe('#utxo', () => {
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 {
@@ -188,4 +57,121 @@ describe('#utxo', () => {
assert.equal(result.satoshis, 800)
})
})
describe('#hydrateTokenData', () => {
it('should hydrate token UTXOs', async () => {
// Mock dependencies
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
.onCall(0)
.resolves(mockData.genesisData01)
.onCall(1)
.resolves(mockData.genesisData02)
.onCall(2)
.resolves(mockData.genesisData03)
const result = await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
// console.log('result: ', result)
assert.equal(result.length, 4)
assert.property(result[0], 'qtyStr')
assert.property(result[0], 'ticker')
assert.property(result[0], 'name')
assert.property(result[0], 'documentUri')
assert.property(result[0], 'documentHash')
})
it('should should catch and throw errors', async () => {
try {
// Force error
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
.rejects(new Error('test error'))
await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(err.message, 'test error')
}
})
})
describe('#get', () => {
it('should throw an error if input is not a string', async () => {
try {
const addr = 123
await bchjs.Utxo.get(addr)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'address input must be a string')
}
})
it('should return UTXO information', async () => {
// mock dependencies
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.resolves(mockData.fulcrumUtxos01)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
.resolves(mockData.psfSlpIndexerUtxos01)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
.resolves({ txData: { isValidSlp: false } })
// Mock function to return the same input. Good enough for this test.
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x)
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert expected properties exist
assert.property(result, 'address')
assert.property(result, 'bchUtxos')
assert.property(result, 'slpUtxos')
assert.property(result.slpUtxos, 'type1')
assert.property(result.slpUtxos.type1, 'tokens')
assert.property(result.slpUtxos.type1, 'mintBatons')
assert.property(result.slpUtxos, 'nft')
assert.property(result, 'nullUtxos')
assert.equal(result.bchUtxos.length, 4)
assert.equal(result.slpUtxos.type1.tokens.length, 1)
assert.equal(result.slpUtxos.type1.mintBatons.length, 1)
assert.equal(result.nullUtxos.length, 0)
})
it('should handle an address with no SLP UTXOs', async () => {
// mock dependencies
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.resolves(mockData.fulcrumUtxos02)
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
.resolves({ txData: { isValidSlp: false } })
// Force psf-slp-indexer to return no UTXOs
sandbox
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
.rejects(mockData.noUtxoErr)
// Mock function to return the same input. Good enough for this test.
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => [])
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.bchUtxos.length, 1)
assert.equal(result.slpUtxos.type1.tokens.length, 0)
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
assert.equal(result.nullUtxos.length, 0)
})
})
})