commit a9277bd19991e82c4e327c8c776ce4fa3dec13e5 Author: Chris Troutner Date: Mon Jul 27 21:32:51 2020 -0700 Forked from christroutner/bch-js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9b0b426 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,55 @@ +{ + "root": true, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["prettier"], + "rules": { + "no-debugger": ["warn"], + "no-regex-spaces": ["error"], + "no-unsafe-negation": ["error"], + "curly": ["error", "multi-or-nest", "consistent"], + "dot-location": ["error", "property"], + "dot-notation": ["error"], + "eqeqeq": ["error", "smart"], + "no-else-return": ["error"], + "no-extra-bind": ["error"], + "no-extra-label": ["error"], + "no-floating-decimal": ["error"], + "no-implicit-coercion": ["error", { "allow": ["!!"] }], + "wrap-iife": ["error", "inside"], + "strict": ["error", "global"], + "func-call-spacing": ["error", "never"], + "comma-style": ["error", "last"], + "keyword-spacing": ["error"], + "linebreak-style": ["error", "unix"], + "new-parens": ["error"], + "no-lonely-if": ["error"], + "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1 }], + "no-whitespace-before-property": ["error"], + "semi": ["error", "never"], + "arrow-body-style": ["error", "as-needed"], + "arrow-parens": ["error", "as-needed"], + "arrow-spacing": ["error"], + "no-useless-computed-key": ["error"], + "no-useless-rename": ["error"], + "no-var": ["off"], + "prefer-spread": ["off"], + "prefer-template": ["error"], + "rest-spread-spacing": ["error", "never"], + "prefer-const": ["warn", { "destructuring": "all" }], + "no-unreachable": ["warn"], + "no-unused-vars": ["warn", { "args": "none" }], + + "prettier/prettier": [ + "warn", + { + "printWidth": 80, + "trailingComma": "none", + "singleQuote": false, + "semi": false + } + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24df2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +node_modules/* +.nyc_output/* +wallet-info.txt +wallet.json + +docs/ +coverage/ + +# This paragraph comes last. Force includes specific files. +!docs/README.md diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..acfca19 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +# This is a node.js v8+ JavaScript project +language: node_js +node_js: + - "10" + +# Build on Ubuntu Xenial (16.04) +# https://docs.travis-ci.com/user/reference/trusty/#javascript-and-nodejs-images +dist: xenial +sudo: required + +# Use Docker +services: + - docker + +before_install: + #- ./install-mongo + #- npm install -g mocha + +# https://github.com/greenkeeperio/greenkeeper-lockfile/issues/156 +install: case $TRAVIS_BRANCH in greenkeeper*) npm i;; *) npm ci;; esac; + +# Send coverage data to Coveralls +after_success: + - npm run coverage + +deploy: + provider: script + skip_cleanup: true + script: + - npx semantic-release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cc6bd34 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,93 @@ +# Permissionless Software Foundation Community Contributing Guide 1.0 + +This document describes a very simple process suitable for most projects under +the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md). +Projects are encouraged to adopt this whether they +are hosted under the PSF or not. + +The goal of this document is to create a contribution process that: + +* Encourages new contributions. +* Encourages contributors to remain involved. +* Avoids unnecessary processes and bureaucracy whenever possible. +* Creates a transparent decision making process which makes it clear how +contributors can be involved in decision making. + +This document is based on much prior art in the Node.js community, io.js, +and the Node.js project. + +Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software). + +## Vocabulary + +* A **Contributor** is any individual creating or commenting on an issue or pull request. +* A **Committer** is a subset of contributors who have been given write access to the repository. +* A **TC (Technical Committee)** is a group of committers representing the required technical +expertise to resolve rare disputes. + +# Logging Issues + +Log an issue for any question or problem you might have. When in doubt, log an issue, +any additional policies about what to include will be provided in the responses. The only +exception is security disclosures which should be sent privately. + +Committers may direct you to another repository, ask for additional clarifications, and +add appropriate metadata before the issue is addressed. + +Please be courteous, respectful, and every participant is expected to follow the +project's Code of Conduct. + +# Contributions + +Any change to resources in this repository must be through pull requests. This applies to all changes +to documentation, code, binary files, etc. Even long term committers and TC members must use +pull requests. + +No pull request can be merged without being reviewed. + +For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that +contributors in other timezones have time to review. Consideration should also be given to +weekends and other holiday periods to ensure active committers all have reasonable time to +become involved in the discussion and review process if they wish. + +The default for each contribution is that it is accepted once no committer has an objection. +During review committers may also request that a specific contributor who is most versed in a +particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off" +process for contributions to land. Once all issues brought by committers are addressed it can +be landed by any committer. + +In the case of an objection being raised in a pull request by another committer, all involved +committers should seek to arrive at a consensus by way of addressing concerns being expressed +by discussion, compromise on the proposed change, or withdrawal of the proposed change. + +If a contribution is controversial and committers cannot agree about how to get it to land +or if it should land then it should be escalated to the TC. TC members should regularly +discuss pending contributions in order to find a resolution. It is expected that only a +small minority of issues be brought to the TC for resolution and that discussion and +compromise among committers be the default resolution mechanism. + +# Becoming a Committer + +All contributors who land a non-trivial contribution should be on-boarded in a timely manner, +and added as a committer, and be given write access to the repository. + +Committers are expected to follow this policy and continue to send pull requests, go through +proper review, and have other committers merge their pull requests. + +# TC Process + +The TC uses a "consensus seeking" process for issues that are escalated to the TC. +The group tries to find a resolution that has no open objections among TC members. +If a consensus cannot be reached that has no objections then a majority wins vote +is called. It is also expected that the majority of decisions made by the TC are via +a consensus seeking process and that voting is only used as a last-resort. + +Resolution may involve returning the issue to committers with suggestions on how to +move forward towards a consensus. It is not expected that a meeting of the TC +will resolve all issues on its agenda during that meeting and may prefer to continue +the discussion happening among the committers. + +Members can be added to the TC at any time. Any committer can nominate another committer +to the TC and the TC uses its standard consensus seeking process to evaluate whether or +not to add this new member. Members who do not participate consistently at the level of +a majority of the other members are expected to resign. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6cb7438 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,7 @@ +Copyright 2020 Chris Troutner + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2240516 --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# bch-js + +[![Build Status](https://travis-ci.org/christroutner/bch-js.svg?branch=master)](https://travis-ci.org/christroutner/bch-js) +[![Version](https://img.shields.io/npm/v/@chris.troutner/bch-js)](https://www.npmjs.com/package/@chris.troutner/bch-js) +[![Downloads/week](https://img.shields.io/npm/dw/@chris.troutner/bch-js)](https://npmjs.org/package/@chris.troutner/bch-js) +[![License](https://img.shields.io/npm/l/@chris.troutner/bch-js)](https://github.com/christroutner/bch-js/blob/master/LICENSE.md) + +[bch-js](https://www.npmjs.com/package/@chris.troutner/bch-js) is a JavaScript npm library for creating web and mobile apps for interacting with the Bitcoin Cash (BCH) blockchain. It can be used for free, but requires an account on [FullStack.cash](https://fullstack.cash) for increased rate limits. Find out more from [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer). + +### Quick Start Videos: +Here are two YouTube walk-through videos to help you get started: +- [Introduction to bch-js and the bch-js-examples repository](https://youtu.be/GD2i1ZUiyrk) +- [Working with the FullStack.cash JWT token](https://youtu.be/GD2i1ZUiyrk) + +### Quick Links +- [npm Library](https://www.npmjs.com/package/@chris.troutner/bch-js) +- [Documentation](https://bchjs.fullstack.cash/) +- [Examples](https://github.com/Permissionless-Software-Foundation/bch-js-examples) +- [api.fullstack.cash](https://api.fullstack.cash) - The REST API this library talks to by default. +- [FullStack.cash Account](https://fullstack.cash/login) - Get your API key to unlock increased rate limits. +- [FullStack.cash](https://fullstack.cash) - cloud-based infrastructure for application +developers. +- [Permissionless Software Foundation](https://psfoundation.cash) - The organization that maintains this library. + + +### Quick Notes + +- Install library: `npm install @chris.troutner/bch-js` + +- Instantiate the library in your code: +``` +const BCHJS = require("@chris.troutner/bch-js") +let bchjs = new BCHJS() + +// testnet +bchjs = new BCHJS({ restURL: 'https://tapi.fullstack.cash/v3/' }) +``` + +This is a fork of the [BITBOX SDK](https://github.com/Bitcoin-com/bitbox-sdk) (which is maintained by Bitcoin.com). This library is intended to be paired with +the [bch-api](https://github.com/christroutner/bch-api) REST API. + +If you need a backward-compatible instance of this library, you can use a +'shim'. Do it like this: +``` +const BCHJS = require("@chris.troutner/bch-js") +const bitbox = BCHJS.BitboxShim({ restURL: 'https://api.fullstack.cash/v3/' }) +``` + +### API Key +The [bch-api](https://github.com/christroutner/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*. + +- Implicitly: bch-js will detect your JWT token by setting 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: + +``` +const BCHJS = require("@chris.troutner/bch-js") +let bchjs = new BCHJS({ + restURL: 'https://api.fullstack.cash/v3/', + apiToken: 'eyJhbGciO...' // Your JWT token here. +}) +``` + +### Gatsby +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): +``` +exports.onCreateWebpackConfig = ({ actions }) => { + actions.setWebpackConfig({ + node: { + fs: 'empty' + } + }) +} +``` + +This is because the new IPFS class in bch-js uses the fs library for uploading files, which is not supported by Gatsby. + +## Features +This library sets itself apart from BITBOX with the following 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). + +- [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 +dependencies in case they are ever inaccessible from GitHub or npm. + + + +## Documentation: + +Full documentation for this library can be found here: +- [Documentation](https://bchjs.fullstack.cash/) + +Original documentation on BITBOX is available at: + +- [General docs](https://developer.bitcoin.com) +- [BITBOX Introduction](https://developer.bitcoin.com/bitbox) +- [BITBOX API Reference](https://developer.bitcoin.com/bitbox/docs/getting-started) + + +bch-js uses [APIDOC](http://apidocjs.com/) so that documentation and working code +live in the same repository. To generate the documentation: +- `npm run docs` +- Open the generated `docs/index.html` file in a web browser. + +## Support +Have questions? Need help? Join our community support +[Telegram channel](https://t.me/bch_js_toolkit) + +## IPFS Releases + +I will periodically publish IPFS releases of this repository, including all +dependencies in the `node_modules` folder. This ensures working copies of this +repository can be retrieved in case there is any drift in dependency files, or +if dependencies are pulled from npm or GitHub. + +- Initial fork on 5/9/2019: + - without node_modules folder: QmQFHfbBQdEHfhtiRLbXtX1NcgnfL45hZb7TbQimTXAuzG (4 MB) + - with node_modules folder: QmXq9Ds6Qdkg9xbRhcF8pay9KabA6QN2y7bx3wvSqiXifk (107 MB) + +- v1.0.0 - refactored to pure JavaScript: + - without node_modules folder: QmNjFsiTZRMAUa9rZpXqZqivv9JLaNicwLSPHjyLB7PVDk (1 MB) + - with node_modules folder: Qma9ScApwBtuL7dpdSk7jpBFTxbqRdiR921WjyP75SU7bT (100 MB) + +## License +[MIT](LICENSE.md) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..deb1c7d --- /dev/null +++ b/examples/README.md @@ -0,0 +1,4 @@ +**Note:** This directory is being deprecated. Examples have been moved to this +repository: + +https://github.com/Permissionless-Software-Foundation/bch-js-examples diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2976f0e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,12111 @@ +{ + "name": "@chris.troutner/bch-js", + "version": "3.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", + "integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==", + "requires": { + "@babel/highlight": "^7.10.1" + } + }, + "@babel/generator": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.2.tgz", + "integrity": "sha512-AxfBNHNu99DTMvlUPlt1h2+Hn7knPpH5ayJ8OqDWSeLld+Fi2AYBTC/IejWDM9Edcii4UzZRCsbUt0WlSDsDsA==", + "requires": { + "@babel/types": "^7.10.2", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz", + "integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.1", + "@babel/template": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz", + "integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==", + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz", + "integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==", + "requires": { + "@babel/types": "^7.10.1" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz", + "integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==" + }, + "@babel/highlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz", + "integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.2.tgz", + "integrity": "sha512-PApSXlNMJyB4JiGVhCOlzKIif+TKFTvu0aQAhnTvfP/z3vVSN6ZypH5bfUNwFXXjRQtUEBNFd2PtmCmG2Py3qQ==" + }, + "@babel/template": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz", + "integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==", + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1" + } + }, + "@babel/traverse": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz", + "integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==", + "requires": { + "@babel/code-frame": "^7.10.1", + "@babel/generator": "^7.10.1", + "@babel/helper-function-name": "^7.10.1", + "@babel/helper-split-export-declaration": "^7.10.1", + "@babel/parser": "^7.10.1", + "@babel/types": "^7.10.1", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@babel/types": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.2.tgz", + "integrity": "sha512-AD3AwWBSz0AWF0AkCN9VPiWrvldXq+/e3cHa4J89vo4ymjz1XwrBFFVZmkJTsQIPNk+ZVomPSXUJqq8yyjZsng==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.1", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@octokit/auth-token": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.1.tgz", + "integrity": "sha512-NB81O5h39KfHYGtgfWr2booRxp2bWOJoqbWwbyUg2hw6h35ArWYlAST5B3XwAkbdcx13yt84hFXyFP5X0QToWA==", + "dev": true, + "requires": { + "@octokit/types": "^4.0.1" + } + }, + "@octokit/endpoint": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.2.tgz", + "integrity": "sha512-xs1mmCEZ2y4shXCpFjNq3UbmNR+bLzxtZim2L0zfEtj9R6O6kc4qLDvYw66hvO6lUsYzPTM5hMkltbuNAbRAcQ==", + "dev": true, + "requires": { + "@octokit/types": "^4.0.1", + "is-plain-object": "^3.0.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } + } + } + }, + "@octokit/plugin-paginate-rest": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", + "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/plugin-request-log": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz", + "integrity": "sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw==", + "dev": true + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", + "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/request": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.4.tgz", + "integrity": "sha512-vqv1lz41c6VTxUvF9nM+a6U+vvP3vGk7drDpr0DVQg4zyqlOiKVrY17DLD6de5okj+YLHKcoqaUZTBtlNZ1BtQ==", + "dev": true, + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.0.0", + "@octokit/types": "^4.0.1", + "deprecation": "^2.0.0", + "is-plain-object": "^3.0.0", + "node-fetch": "^2.3.0", + "once": "^1.4.0", + "universal-user-agent": "^5.0.0" + }, + "dependencies": { + "@octokit/request-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.1.tgz", + "integrity": "sha512-5lqBDJ9/TOehK82VvomQ6zFiZjPeSom8fLkFVLuYL3sKiIb5RB8iN/lenLkY7oBmyQcGP7FBMGiIZTO8jufaRQ==", + "dev": true, + "requires": { + "@octokit/types": "^4.0.1", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "universal-user-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz", + "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } + } + } + }, + "@octokit/request-error": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", + "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", + "dev": true, + "requires": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "dependencies": { + "@octokit/types": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", + "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + } + } + }, + "@octokit/rest": { + "version": "16.43.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz", + "integrity": "sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==", + "dev": true, + "requires": { + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "2.4.0", + "@octokit/request": "^5.2.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" + } + }, + "@octokit/types": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.1.9.tgz", + "integrity": "sha512-hinM/BA2c1vebN2HSR3JtVdYtrSbmvn/doUBZXXuQuh/9o60hYwitQQAGTpJu+k6pjtjURskDHQxUFvqLvYCeA==", + "dev": true, + "requires": { + "@types/node": ">= 8" + } + }, + "@semantic-release/commit-analyzer": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-6.3.3.tgz", + "integrity": "sha512-Pyv1ZL2u5AIOY4YbxFCAB5J1PEh5yON8ylbfiPiriDGGW6Uu1U3Y8lysMtWu+FUD5x7tSnyIzhqx0+fxPxqbgw==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.7", + "debug": "^4.0.0", + "import-from": "^3.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@semantic-release/error": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-2.2.0.tgz", + "integrity": "sha512-9Tj/qn+y2j+sjCI3Jd+qseGtHjOAeg7dU2/lVcqIQ9TV3QDaDXDYXcoOHU+7o2Hwh8L8ymL4gfuO7KxDs3q2zg==", + "dev": true + }, + "@semantic-release/github": { + "version": "5.5.8", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-5.5.8.tgz", + "integrity": "sha512-YxbBXbCThs/Xk3E4QU01AMIUM8eb0UTvjHJtclTDR3/DEW7kUpmXQqBMnSh3qCTuk4scRFIoaF0fGU/0xByZug==", + "dev": true, + "requires": { + "@octokit/rest": "^16.27.0", + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "bottleneck": "^2.18.1", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^8.0.0", + "globby": "^10.0.0", + "http-proxy-agent": "^3.0.0", + "https-proxy-agent": "^4.0.0", + "issue-parser": "^5.0.0", + "lodash": "^4.17.4", + "mime": "^2.4.3", + "p-filter": "^2.0.0", + "p-retry": "^4.0.0", + "url-join": "^4.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + } + } + }, + "@semantic-release/npm": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-5.3.5.tgz", + "integrity": "sha512-AOREQ6rUT8OAiqXvWCp0kMNjcdnLLq1JdP0voZL4l5zf6Tgs/65YA7ctP+9shthW01Ps85Nu0pILW3p9GkaYuw==", + "dev": true, + "requires": { + "@semantic-release/error": "^2.2.0", + "aggregate-error": "^3.0.0", + "execa": "^3.2.0", + "fs-extra": "^8.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^4.0.0", + "npm": "^6.10.3", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^4.0.0", + "tempy": "^0.3.0" + }, + "dependencies": { + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + } + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "@semantic-release/release-notes-generator": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-7.3.5.tgz", + "integrity": "sha512-LGjgPBGjjmjap/76O0Md3wc04Y7IlLnzZceLsAkcYRwGQdRPTTFUJKqDQTuieWTs7zfHzQoZqsqPfFxEN+g2+Q==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^4.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.0.0", + "debug": "^4.0.0", + "get-stream": "^5.0.0", + "import-from": "^3.0.0", + "into-stream": "^5.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + } + } + }, + "@sinonjs/commons": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", + "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@transloadit/prettier-bytes": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@transloadit/prettier-bytes/-/prettier-bytes-0.0.7.tgz", + "integrity": "sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-VgNIkxK+j7Nz5P7jvUZlRvhuPSmsEfS03b0alKcq5V/STUKAa3Plemsn5mrQUO7am6OErJ4rhGEGJbACclrtRA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, + "@types/node": { + "version": "11.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", + "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "@types/tus-js-client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/tus-js-client/-/tus-js-client-1.8.0.tgz", + "integrity": "sha512-lWxu5+6qfyfwsW99GzUeJ9y9JeOSSLduKxgYMvaYM7sGTDKZsrIIHTUbHI2P016xhXtu9NxmUM3GrB4i14ie4A==" + }, + "@uppy/companion-client": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@uppy/companion-client/-/companion-client-1.4.5.tgz", + "integrity": "sha512-lHmw48rWpipLKBDoDVuMPkSWZPIPoQr9aaV3VSAsVIUg65IhYyJ0FFBEXCh8jtNZsQbkHuXE+xM0niD0kDH/5w==", + "requires": { + "@uppy/utils": "^3.0.0", + "namespace-emitter": "^2.0.1" + } + }, + "@uppy/core": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@uppy/core/-/core-1.10.5.tgz", + "integrity": "sha512-RweK/RSJAf5iEjBPt+Vxspnnavkvq2yI2OuAm9Sfz+035k+IPi8G59z0RcBVpWxYZrPkd1lxbd+9Bwql4P8JLw==", + "requires": { + "@transloadit/prettier-bytes": "0.0.7", + "@uppy/store-default": "^1.2.1", + "@uppy/utils": "^3.0.0", + "cuid": "^2.1.1", + "lodash.throttle": "^4.1.1", + "mime-match": "^1.0.2", + "namespace-emitter": "^2.0.1", + "preact": "8.2.9" + } + }, + "@uppy/store-default": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@uppy/store-default/-/store-default-1.2.1.tgz", + "integrity": "sha512-B7iKa9CMLTPgSu1lHQX+Z82NgxoBrzWfZg9WbZnvoJRHnoiwu8A3EFXPLq0OEslvJndBD4xPgUms0BPt4WbnWw==" + }, + "@uppy/tus": { + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/@uppy/tus/-/tus-1.5.13.tgz", + "integrity": "sha512-sjwHTtPsG0MHieTMfNS8qi/SHY2lkVUAIHXzaLwRwUW2PXatGbHXeAfTs5Wb7b5HZivMKmlqnWHWvD4Em4vY2g==", + "requires": { + "@types/tus-js-client": "^1.8.0", + "@uppy/companion-client": "^1.4.5", + "@uppy/utils": "^3.0.0", + "tus-js-client": "^1.8.0" + } + }, + "@uppy/utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@uppy/utils/-/utils-3.0.0.tgz", + "integrity": "sha512-QOXy2TrObmFBVOBFekQ3zgr7cKp6v5jUUSPBxN5GzxE9I8FDH6tOtDU4FwzatsC5g430e68DHEyiM1IgA1tTvA==", + "requires": { + "lodash.throttle": "^4.1.1" + } + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + } + } + }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, + "agent-base": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", + "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", + "dev": true + }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=" + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==" + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", + "dev": true + }, + "apidoc": { + "version": "0.17.7", + "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-0.17.7.tgz", + "integrity": "sha512-9Wf4bRPwCuWOIOxR42dDnsXnFw+rhJg5VrMQK+KmNxJwyIh30UqX6gvjjXSG6YO74MqE87F18bbQXUENK9dPGg==", + "requires": { + "apidoc-core": "~0.8.2", + "commander": "^2.19.0", + "fs-extra": "^7.0.0", + "lodash": "^4.17.10", + "markdown-it": "^8.3.1", + "winston": "^3.0.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + } + } + }, + "apidoc-core": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/apidoc-core/-/apidoc-core-0.8.3.tgz", + "integrity": "sha1-2dY1RYKd8lDSzKBJaDqH53U2S5Y=", + "requires": { + "fs-extra": "^3.0.1", + "glob": "^7.1.1", + "iconv-lite": "^0.4.17", + "klaw-sync": "^2.1.0", + "lodash": "~4.17.4", + "semver": "~5.3.0" + }, + "dependencies": { + "fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + } + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk=", + "dev": true + }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=" + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", + "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", + "requires": { + "es6-object-assign": "^1.1.0", + "is-nan": "^1.2.1", + "object-is": "^1.0.1", + "util": "^0.12.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob-lite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", + "integrity": "sha1-D+9a1G8b16hQLGVyfwNn1e5D1pY=", + "dev": true + }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "requires": { + "array-filter": "^1.0.0" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", + "dev": true + }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bc-bip68": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bc-bip68/-/bc-bip68-1.0.5.tgz", + "integrity": "sha512-GzaMlN7pNthrY5BhReVhnfr4Ixx+GUSfyNRHYh0QiMUF0d0+0YaD8MpEdv6AjFBksg/zlqL1fVCBBm6PpTt2Rg==" + }, + "bch-wallet-bridge-provider-interface": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bch-wallet-bridge-provider-interface/-/bch-wallet-bridge-provider-interface-0.1.1.tgz", + "integrity": "sha512-m8Mq/aJhZN11aoa91YyYs/B1GxWfpT5IIp64IU5VRgNxGXBuUiIfvybk4XQR+zrHaMW4SLrD1tI9n59ogmEw/A==" + }, + "bch-wallet-bridge.js": { + "version": "github:web3bch/bch-wallet-bridge.js#f73558eaf3a98ba2a23de3c9cbbd582b30ff19eb", + "from": "github:web3bch/bch-wallet-bridge.js#master", + "requires": { + "bch-wallet-bridge-provider-interface": "^0.1.1", + "bchaddrjs": "^0.2.2", + "bitcoincashjs": "^0.1.10" + } + }, + "bchaddrjs": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/bchaddrjs/-/bchaddrjs-0.2.3.tgz", + "integrity": "sha512-0DVW8q3UFQFhrvt8Fowpkk+WvkYTZTSD1vGCQHrtMHZjRL6G/SoW0mgrREmgO1F/8TJ+Julri4UBWA8Gr7C5Yw==", + "requires": { + "bs58check": "^2.1.2", + "cashaddrjs": "^0.2.9" + }, + "dependencies": { + "cashaddrjs": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.2.9.tgz", + "integrity": "sha512-DhJF4iAH0/RM3UjHDHKRxzs09YGL9px+oTyizzydanhC7jTyM2aJ+aLKA96vZGTTWayvvr2iDF2l13lpqXiRFg==", + "requires": { + "big-integer": "^1.6.34" + } + } + } + }, + "bchaddrjs-slp": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.8.tgz", + "integrity": "sha512-Is4NQG0xhrmW8ml40n8IjQzYlg1gMqst2RkF20SWjuhiSA8VDx1F53krthFqZFp5hpB+Ig/NPx6NX4VEH9W32Q==", + "requires": { + "bs58check": "^2.1.2", + "cashaddrjs-slp": "^0.2.12" + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" + }, + "before-after-hook": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.1.0.tgz", + "integrity": "sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==", + "dev": true + }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, + "big-integer": { + "version": "1.6.48", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz", + "integrity": "sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==" + }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==" + }, + "bigi": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", + "integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "bip-schnorr": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/bip-schnorr/-/bip-schnorr-0.3.0.tgz", + "integrity": "sha512-Sc1Hn2+1n+okPEW8G+JLjeaM12dsUOwr+oFlMDSKR9wYwNGMw0alskeBIHTmXxBxMZSWKhCW7PwKQVDyGmnaVg==", + "requires": { + "ecurve": "^1.0.6", + "js-sha256": "^0.9.0", + "random-bytes": "^1.0.0", + "safe-buffer": "^5.0.1" + } + }, + "bip21": { + "version": "github:Bitcoin-com/bip21#28f8d03a3a16ed11eb5b963248ed1be8c46c6d6f", + "from": "github:Bitcoin-com/bip21", + "requires": { + "qs": "^6.3.0" + } + }, + "bip32-utils": { + "version": "github:Bitcoin-com/bip32-utils#b8a33ebd0a0ac39de7cd987e5c3f77a8c5d84e58", + "from": "github:Bitcoin-com/bip32-utils#0.13.1", + "requires": { + "keccak": "^1.3.0", + "nyc": "*", + "standard": "^11.0.1", + "tape": "*" + } + }, + "bip38": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bip38/-/bip38-2.0.3.tgz", + "integrity": "sha512-HiABhYNhhRNAAeGmkt3LS2El6oonIftNkHq6uFxHCjaCIEbHr7I6RIBhKMQWw5AhGzIPpO5Mt7I8bFOfKqqh9w==", + "requires": { + "bigi": "^1.2.0", + "browserify-aes": "^1.0.1", + "bs58check": "<3.0.0", + "buffer-xor": "^1.0.2", + "create-hash": "^1.1.1", + "ecurve": "^1.0.0", + "scryptsy": "^2.0.0" + } + }, + "bip39": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz", + "integrity": "sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ==", + "requires": { + "@types/node": "11.11.6", + "create-hash": "^1.1.0", + "pbkdf2": "^3.0.9", + "randombytes": "^2.0.1" + } + }, + "bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "bitcoincash-ops": { + "version": "github:christroutner/bitcoincash-ops#0041f2d945aa0e893fbbb5c2ebdf6254f6ff0555", + "from": "github:christroutner/bitcoincash-ops" + }, + "bitcoincashjs": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/bitcoincashjs/-/bitcoincashjs-0.1.14.tgz", + "integrity": "sha512-x0wMVSCZ56ZQnSQ+Xim7XfS0IYiOyOwo0pdMCmR+cDNB7zRhICFuH5i3lNdLSgUw1e7Pc3FmUlC9wruQNlYMnA==" + }, + "bitcoincashjs-lib": { + "version": "github:christroutner/bitcoincashjs-lib#84dbcbce600af727a6f8b3eb23ebf6571651f367", + "from": "github:christroutner/bitcoincashjs-lib#master", + "requires": { + "bech32": "^1.1.2", + "bigi": "^1.4.0", + "bip66": "^1.1.0", + "bitcoincash-ops": "github:christroutner/bitcoincash-ops", + "bs58check": "^2.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.3", + "ecurve": "^1.0.0", + "merkle-lib": "^2.0.10", + "pushdata-bitcoin": "github:Bitcoin-com/pushdata-bitcoin#1.2.1", + "randombytes": "^2.0.1", + "safe-buffer": "^5.0.1", + "typeforce": "^1.18.0", + "varuint-bitcoin": "^1.0.4", + "wif": "^2.0.1" + }, + "dependencies": { + "bitcoincash-ops": { + "version": "github:christroutner/bitcoincash-ops#0041f2d945aa0e893fbbb5c2ebdf6254f6ff0555", + "from": "github:christroutner/bitcoincash-ops" + } + } + }, + "bitcoinjs-message": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.1.0.tgz", + "integrity": "sha512-xVL2YvyAJUI8ZwfNpi6Ju5zda3+QYGHTLUvISDb0VHWbsWn9Zyvd1o8XHRC/0r+DNwDIwenpXDSPl1XLCMGnMA==", + "requires": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + } + }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "requires": { + "base-x": "^3.0.2" + } + }, + "bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "requires": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "btoa-lite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", + "integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc=", + "dev": true + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "buffer-equals": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", + "integrity": "sha1-A1O1T9B/2VZBcGca5vZrnPENJ/U=" + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "buffer-from": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-0.1.2.tgz", + "integrity": "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==" + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "requires": { + "callsites": "^0.2.0" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=", + "dev": true, + "requires": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "cashaddrjs": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.3.11.tgz", + "integrity": "sha512-TXbSJVCPa5ER9jRId0S1ZgxvAqLYowciCpgzDC66hYarQKeDRZ5wvjAa8xKPZwLEDI8t5fBL3f8PyPzz7PoMnA==", + "requires": { + "big-integer": "1.6.36" + }, + "dependencies": { + "big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==" + } + } + }, + "cashaddrjs-slp": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/cashaddrjs-slp/-/cashaddrjs-slp-0.2.12.tgz", + "integrity": "sha512-n2TTIuW6vZZxYvjvsUAA+wOM0Zkj+3RRKUtDC1XSu4Ic4XVr0yFJkl1bzQkHWda7nkVT51sxjZneygz7D0SyrQ==", + "requires": { + "big-integer": "^1.6.34" + } + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==" + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clear": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/clear/-/clear-0.1.0.tgz", + "integrity": "sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==" + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "dev": true, + "requires": { + "colors": "1.0.3" + }, + "dependencies": { + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "dev": true + } + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "coininfo": { + "version": "github:Bitcoin-com/coininfo#eece2c6141d08c3e7783929f2a1e1e681aa1a82c", + "from": "github:Bitcoin-com/coininfo", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colornames": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", + "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "requires": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "combine-errors": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/combine-errors/-/combine-errors-3.0.3.tgz", + "integrity": "sha1-9N9nQAg+VwOjGBEQwrEFUfAD2oY=", + "requires": { + "custom-error-instance": "2.1.1", + "lodash.uniqby": "4.5.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "compare-func": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.4.tgz", + "integrity": "sha512-sq2sWtrqKPkEXAC8tEJA1+BqAH9GbFkGBtUOqrUX57VSfwp8xyktctk+uLoRy5eccTdxzDcVIztlYDpKs3Jv1Q==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + } + }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=" + }, + "conventional-changelog-angular": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.10.tgz", + "integrity": "sha512-k7RPPRs0vp8+BtPsM9uDxRl6KcgqtCJmzRD1wRtgqmhQ96g8ifBGo9O/TZBG23jqlXS/rg8BKRDELxfnQQGiaA==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "q": "^1.5.1" + } + }, + "conventional-changelog-writer": { + "version": "4.0.16", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-4.0.16.tgz", + "integrity": "sha512-jmU1sDJDZpm/dkuFxBeRXvyNcJQeKhGtVcFFkwTphUAzyYWcwz2j36Wcv+Mv2hU3tpvLMkysOPXJTLO55AUrYQ==", + "dev": true, + "requires": { + "compare-func": "^1.3.1", + "conventional-commits-filter": "^2.0.6", + "dateformat": "^3.0.0", + "handlebars": "^4.7.6", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^3.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "conventional-commits-filter": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.6.tgz", + "integrity": "sha512-4g+sw8+KA50/Qwzfr0hL5k5NWxqtrOVw4DDk3/h6L85a9Gz0/Eqp3oP+CWCNfesBvZZZEFHF7OTEbRe+yYSyKw==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.1.0.tgz", + "integrity": "sha512-RSo5S0WIwXZiRxUGTPuYFbqvrR4vpJ1BDdTlthFgvHt5kEdnd1+pdvwWphWn57/oIl4V72NMmOocFqqJ8mFFhA==", + "dev": true, + "requires": { + "JSONStream": "^1.0.4", + "is-text-path": "^1.0.1", + "lodash": "^4.17.15", + "meow": "^7.0.0", + "split2": "^2.0.0", + "through2": "^3.0.0", + "trim-off-newlines": "^1.0.0" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "dependencies": { + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "coveralls": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.0.tgz", + "integrity": "sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ==", + "dev": true, + "requires": { + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" + } + }, + "cp-file": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-7.0.0.tgz", + "integrity": "sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw==", + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "nested-error-stacks": "^2.0.0", + "p-event": "^4.1.0" + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, + "cuid": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/cuid/-/cuid-2.1.8.tgz", + "integrity": "sha512-xiEMER6E7TlTPnDxrM4eRiC6TRgjNX9xzEZ5U/Se2YJKr7Mq4pJn/2XEHjl3STcSh96GmkHPcBXLES8M29wyyg==" + }, + "custom-error-instance": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/custom-error-instance/-/custom-error-instance-2.1.1.tgz", + "integrity": "sha1-PPY5FIemYppiR+sMoM4ACBt+Nho=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "debug-log": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", + "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-equal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.3.tgz", + "integrity": "sha512-Spqdl4H+ky45I9ByyJtXteOm9CaIrPmnIPmOhrkKGNYWeDgCvJ8jNYVCTjChxW4FqGuZnLHADc8EKRMX6+CgvA==", + "requires": { + "es-abstract": "^1.17.5", + "es-get-iterator": "^1.1.0", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.0.5", + "isarray": "^2.0.5", + "object-is": "^1.1.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "deglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", + "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", + "requires": { + "find-root": "^1.0.0", + "glob": "^7.0.5", + "ignore": "^3.0.9", + "pkg-config": "^1.1.0", + "run-parallel": "^1.1.2", + "uniq": "^1.0.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "diagnostics": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", + "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", + "requires": { + "colorspace": "1.1.x", + "enabled": "1.0.x", + "kuler": "1.0.x" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "dijkstrajs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.1.tgz", + "integrity": "sha1-082BIh4+pAdCz83lVtTpnpjdxxs=" + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "requires": { + "esutils": "^2.0.2" + } + }, + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=", + "requires": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + } + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecurve": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.6.tgz", + "integrity": "sha512-/BzEjNfiSuB7jIWKcS/z8FK9jNjmEWvUV2YZ4RLSmcDtP7Lq0m6FvDuSnJpBlDpGRpfRQeTLGLBI8H+kEv0r+w==", + "requires": { + "bigi": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "elliptic": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "enabled": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", + "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", + "requires": { + "env-variable": "0.0.x" + } + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", + "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "0.3.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "engine.io-client": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz", + "integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==", + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "env-ci": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-4.5.2.tgz", + "integrity": "sha512-lS+edpNp2+QXEPkx6raEMIjKxKKWnJ4+VWzovYJ2NLYiJAYenSAXotFfVdgaFxdbVnvAbUI8epQDa1u12ERxfQ==", + "dev": true, + "requires": { + "execa": "^3.2.0", + "java-properties": "^1.0.0" + } + }, + "env-variable": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz", + "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + } + } + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-get-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", + "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", + "requires": { + "es-abstract": "^1.17.4", + "has-symbols": "^1.0.1", + "is-arguments": "^1.0.4", + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==" + }, + "es6-object-assign": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", + "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } + }, + "eslint-config-prettier": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-config-standard": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-11.0.0.tgz", + "integrity": "sha512-oDdENzpViEe5fwuRCWla7AXQd++/oyIp8zP+iP9jiUPG6NBj3SHgdgtl/kTn00AjeN+1HNvavTKmYbMo+xMOlw==" + }, + "eslint-config-standard-jsx": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-5.0.0.tgz", + "integrity": "sha512-rLToPAEqLMPBfWnYTu6xRhm2OWziS2n40QFqJ8jAM8NSVzeVKTa3nclhsU4DpPJQRY60F34Oo1wi/71PN/eITg==" + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-plugin-es": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz", + "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==", + "dev": true, + "requires": { + "eslint-utils": "^1.4.2", + "regexpp": "^2.0.1" + } + }, + "eslint-plugin-import": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz", + "integrity": "sha1-JgAu+/ylmJtyiKwEdQi9JPIXsWk=", + "requires": { + "builtin-modules": "^1.1.1", + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.1.1", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "eslint-plugin-node": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-9.2.0.tgz", + "integrity": "sha512-2abNmzAH/JpxI4gEOwd6K8wZIodK3BmHbTxz4s79OIYwwIt2gkpEXlAouJXu4H1c9ySTnRso0tsuthSOZbUMlA==", + "dev": true, + "requires": { + "eslint-plugin-es": "^1.4.1", + "eslint-utils": "^1.4.2", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", + "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-promise": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz", + "integrity": "sha512-2WO+ZFh7vxUKRfR0cOIMrWgYKdR6S1AlOezw6pC52B6oYpd5WFghN+QHxvrRdZMtbo8h3dfUZ2o1rWb0UPbKtg==" + }, + "eslint-plugin-react": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.7.0.tgz", + "integrity": "sha512-KC7Snr4YsWZD5flu6A5c0AcIZidzW3Exbqp7OT67OaD2AppJtlBr/GuPrW/vaQM/yfZotEvKAdrxrO+v8vwYJA==", + "requires": { + "doctrine": "^2.0.2", + "has": "^1.0.1", + "jsx-ast-utils": "^2.0.1", + "prop-types": "^15.6.0" + } + }, + "eslint-plugin-standard": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", + "integrity": "sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI=" + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz", + "integrity": "sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ==" + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.1.0.tgz", + "integrity": "sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw==" + } + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "external-editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-glob": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz", + "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "fastq": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", + "integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fecha": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", + "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" + }, + "figlet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.4.0.tgz", + "integrity": "sha512-CxxIjEKHlqGosgXaIA+sikGDdV6KZOOlzPJnYuPgQlOSHZP5h9WIghYI30fyXnwEVeSH7Hedy72gC6zJrFC+SQ==" + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "dependencies": { + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==" + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-clone": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/git-clone/-/git-clone-0.1.0.tgz", + "integrity": "sha1-DXYWN3gJOu9/HDAjjyqe8/B6Lrk=" + }, + "git-log-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.0.tgz", + "integrity": "sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo=", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "~0.6.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ=", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", + "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "dependencies": { + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "handlebars": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", + "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + } + } + }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hook-std": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", + "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==" + }, + "http-proxy-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-3.0.0.tgz", + "integrity": "sha512-uGuJaBWQWDQCJI5ip0d/VTYZW0nRrlLWXA4A7P1jrsa+f77rW2yXz315oBt6zGCF6l8C2tlMxY7ffULCj+5FhA==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", + "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", + "dev": true, + "requires": { + "agent-base": "5", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", + "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inquirer": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", + "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "into-stream": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-5.1.1.tgz", + "integrity": "sha512-krrAJ7McQxGGmvaYbB7Q1mcA+cRwg9Ij2RfWIeVesNBgVDZmzY/Fa4IpZUT3bmdRzMzdf/mzltCG2Dq99IZGBA==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.0.tgz", + "integrity": "sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==" + }, + "is-boolean-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.1.tgz", + "integrity": "sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==" + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==" + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", + "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==" + }, + "is-nan": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.0.tgz", + "integrity": "sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==" + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.0.tgz", + "integrity": "sha512-tZIpofR+P05k8Aocp7UI/2UTa9lTJSebCXpFFoR9aibpokDj/uXBsJ8luUu0tTVYKkMU6URDUuOfJZ7koewXvg==", + "dev": true, + "requires": { + "isobject": "^4.0.0" + } + }, + "is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-set": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", + "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-typed-array": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.3.tgz", + "integrity": "sha512-BSYUBOK/HJibQ30wWkWold5txYwMUXQct9YHAQJr8fSwvZoiglcqB0pd7vEN23+Tsi9IUEjztdOSzl4qLVYGTQ==", + "requires": { + "available-typed-arrays": "^1.0.0", + "es-abstract": "^1.17.4", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==" + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "issue-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-5.0.0.tgz", + "integrity": "sha512-q/16W7EPHRL0FKVz9NU++TUsoygXGj6JOi88oulyAcQG+IEZ0T6teVdE+VLbe19OfL/tbV8Wi3Dfo0HedeHW0Q==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "dependencies": { + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=", + "dev": true + } + } + }, + "java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true + }, + "js-base64": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", + "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==" + }, + "js-sha256": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz", + "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "requires": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" + } + }, + "just-extend": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.0.tgz", + "integrity": "sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==", + "dev": true + }, + "keccak": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-1.4.0.tgz", + "integrity": "sha512-eZVaCpblK5formjPjeTBik7TAg+pqnDrMHIffSvi9Lh7PQgM1+hSzakUeZFCk9DVVG0dacZJuaz2ntwlzZUIBw==", + "requires": { + "bindings": "^1.2.1", + "inherits": "^2.0.3", + "nan": "^2.2.1", + "safe-buffer": "^5.1.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "klaw-sync": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-2.1.0.tgz", + "integrity": "sha1-PTvNhgDnv971MjHHOf8FOu1WDkQ=", + "requires": { + "graceful-fs": "^4.1.11" + } + }, + "kuler": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", + "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", + "requires": { + "colornames": "^1.1.1" + } + }, + "lcov-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "linkify-it": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", + "integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + }, + "lodash._baseiteratee": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash._baseiteratee/-/lodash._baseiteratee-4.7.0.tgz", + "integrity": "sha1-NKm1VDVycnw9sueO2uPA6eZr0QI=", + "requires": { + "lodash._stringtopath": "~4.8.0" + } + }, + "lodash._basetostring": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-4.12.0.tgz", + "integrity": "sha1-kyfJ3FFYhmt/pLnUL0Y45XZt2d8=" + }, + "lodash._baseuniq": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz", + "integrity": "sha1-DrtE5FaBSveQXGIS+iybLVG4Qeg=", + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash._createset/-/lodash._createset-4.0.3.tgz", + "integrity": "sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY=" + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" + }, + "lodash._stringtopath": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.8.0.tgz", + "integrity": "sha1-lBvPDmQmbl/B1m/tCmlZVExXaCQ=", + "requires": { + "lodash._basetostring": "~4.12.0" + } + }, + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, + "lodash.set": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", + "integrity": "sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=", + "dev": true + }, + "lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" + }, + "lodash.toarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", + "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=" + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, + "lodash.uniqby": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.5.0.tgz", + "integrity": "sha1-o6F7v2LutiQPSRhG6XwcTipeHiE=", + "requires": { + "lodash._baseiteratee": "~4.7.0", + "lodash._baseuniq": "~4.6.0" + } + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "logform": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz", + "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==", + "requires": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^2.3.3", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "macos-release": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", + "integrity": "sha512-OHhSbtcviqMPt7yfw5ef5aghS2jzFVKEFyCJndQt2YpSQ9qRVSEv2axSJI1paVThEu+FFGs584h/1YhxjVqajA==", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "markdown-it": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", + "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "marked": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz", + "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==", + "dev": true + }, + "marked-terminal": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-3.3.0.tgz", + "integrity": "sha512-+IUQJ5VlZoAFsM5MHNT7g3RHSkA3eETqhRCdXv4niUMAKHQ7lb1yvAcuGPmm4soxhmtX13u4Li6ZToXtvSEH+A==", + "dev": true, + "requires": { + "ansi-escapes": "^3.1.0", + "cardinal": "^2.1.1", + "chalk": "^2.4.1", + "cli-table": "^0.3.1", + "node-emoji": "^1.4.1", + "supports-hyperlinks": "^1.0.1" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "meow": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz", + "integrity": "sha512-tBKIQqVrAHqwit0vfuFPY3LlzJYkEOFyKa3bPgxzNl6q/RtN8KQ+ALYEASYuFayzSAsjlhXj/JZ10rH85Q6TUw==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "arrify": "^2.0.1", + "camelcase": "^6.0.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "^4.0.2", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.13.1", + "yargs-parser": "^18.1.3" + }, + "dependencies": { + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true + } + } + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "merkle-lib": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", + "integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-match/-/mime-match-1.0.2.tgz", + "integrity": "sha1-P4fDHprxpf1IX7nbE0Qosju7e6g=", + "requires": { + "wildcard": "^1.1.0" + } + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" + }, + "namespace-emitter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/namespace-emitter/-/namespace-emitter-2.0.1.tgz", + "integrity": "sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==" + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha1-5tq3/r9a2Bbqgc9cYpxaDr3nLBo=", + "dev": true + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, + "nock": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", + "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", + "dev": true, + "requires": { + "chai": "^4.1.2", + "debug": "^4.1.0", + "deep-equal": "^1.0.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.5", + "mkdirp": "^0.5.0", + "propagate": "^1.0.0", + "qs": "^6.5.1", + "semver": "^5.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-cmd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-cmd/-/node-cmd-3.0.0.tgz", + "integrity": "sha1-OP/3CkqqT2WdID61eGJzcBjiT28=" + }, + "node-emoji": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.10.0.tgz", + "integrity": "sha512-Yt3384If5H6BYGVHiHwTL+99OzJKHhgp82S8/dktEK73T26BazdgZ4JZh92xSVtGNJvz9UbXdNAc5hcrXV42vw==", + "requires": { + "lodash.toarray": "^4.4.0" + } + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==", + "dev": true + }, + "node-mocks-http": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/node-mocks-http/-/node-mocks-http-1.8.1.tgz", + "integrity": "sha512-qtd9YwXzCTdLfqjP7XSOtFei3TggwnjFIppmYEneQBaDIuknwgJTpItLskC5/pWOpU3lsK5aqdo+5CfIKHkXLg==", + "dev": true, + "requires": { + "accepts": "^1.3.7", + "depd": "^1.1.0", + "fresh": "^0.5.2", + "merge-descriptors": "^1.0.1", + "methods": "^1.1.2", + "mime": "^1.3.4", + "parseurl": "^1.3.3", + "range-parser": "^1.2.0", + "type-is": "^1.6.18" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true + }, + "npm": { + "version": "6.14.6", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.6.tgz", + "integrity": "sha512-axnz6iHFK6WPE0js/+mRp+4IOwpHn5tJEw5KB6FiCU764zmffrhsYHbSHi2kKqNkRBt53XasXjngZfBD3FQzrQ==", + "dev": true, + "requires": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.7", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "^3.0.3", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.3.0", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.7", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.2", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.5", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.2", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.4", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.5", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.12", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "^1.0.5", + "read-installed": "~4.0.3", + "read-package-json": "^2.1.1", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "safe-buffer": "^5.1.2", + "semver": "^5.7.1", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.1", + "tar": "^4.4.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.3", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "dev": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "dev": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "dev": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true, + "dev": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "byline": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "cacache": { + "version": "12.0.3", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "cmd-shim": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "dev": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "dev": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "dev": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true, + "dev": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true, + "dev": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "gentle-fs": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true, + "dev": true + } + } + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "2.8.8", + "bundled": true, + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "ip": { + "version": "1.1.5", + "bundled": true, + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "is-ci": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true, + "dev": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true, + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "libcipm": { + "version": "4.0.7", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true, + "dev": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.2", + "bundled": true, + "dev": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true, + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true, + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true, + "dev": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true, + "dev": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true, + "dev": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.2", + "bundled": true, + "dev": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true, + "dev": true + } + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "bundled": true, + "dev": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "npm-install-checks": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.4", + "bundled": true, + "dev": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "6.1.1", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.4", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.5", + "bundled": true, + "dev": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "bundled": true, + "dev": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true, + "dev": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.12", + "bundled": true, + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true, + "dev": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true, + "dev": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "psl": { + "version": "1.1.29", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "dev": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true, + "dev": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "dev": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.7.1", + "bundled": true, + "dev": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.3.3", + "bundled": true, + "dev": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "dev": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true, + "dev": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "bundled": true, + "dev": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true, + "dev": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "through": { + "version": "2.3.8", + "bundled": true, + "dev": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true + }, + "umask": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.3", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.4.3", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "dev": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.1", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } + } + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "requires": { + "strip-bom": "^3.0.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "requires": { + "is-stream": "^1.0.1" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==" + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "requires": { + "html-escaper": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + } + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "octokit-pagination-methods": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", + "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "one-time": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-name": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", + "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "dev": true, + "requires": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "requires": { + "p-timeout": "^3.1.0" + } + }, + "p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "requires": { + "p-map": "^2.0.0" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + } + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true + }, + "p-retry": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.2.0.tgz", + "integrity": "sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==", + "dev": true, + "requires": { + "@types/retry": "^0.12.0", + "retry": "^0.12.0" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + } + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + } + } + }, + "pkg-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", + "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", + "requires": { + "debug-log": "^1.0.0", + "find-root": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "requires": { + "find-up": "^2.1.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==" + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "preact": { + "version": "8.2.9", + "resolved": "https://registry.npmjs.org/preact/-/preact-8.2.9.tgz", + "integrity": "sha512-ThuGXBmJS3VsT+jIP+eQufD3L8pRw/PY3FoCys6O9Pu6aF12Pn9zAJDX99TfwRAFOCEKm/P0lwiPTbqKMJp0fA==" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "propagate": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", + "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", + "dev": true + }, + "proper-lockfile": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-2.0.1.tgz", + "integrity": "sha1-FZ+wYZPTIAP0s2kd0uwaY0qoDR0=", + "requires": { + "graceful-fs": "^4.1.2", + "retry": "^0.10.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "pushdata-bitcoin": { + "version": "github:Bitcoin-com/pushdata-bitcoin#9b75eebe597853c6eeaec3e6c44b6d9c9cd7ee86", + "from": "github:Bitcoin-com/pushdata-bitcoin#1.2.1", + "requires": { + "bitcoincash-ops": "github:Bitcoin-com/bitcoincash-ops#2.0.0" + }, + "dependencies": { + "bitcoincash-ops": { + "version": "github:Bitcoin-com/bitcoincash-ops#6ab82cc7326c67236f3b2d9d0457258ac2ef48e3", + "from": "github:Bitcoin-com/bitcoincash-ops#2.0.0" + } + } + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qrcode": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.4.4.tgz", + "integrity": "sha512-oLzEC5+NKFou9P0bMj5+v6Z40evexeE29Z9cummZXZ9QXyMr3lphkURzxjXgPJC5azpxcshoDWV1xE46z+/c3Q==", + "requires": { + "buffer": "^5.4.3", + "buffer-alloc": "^1.2.0", + "buffer-from": "^1.1.1", + "dijkstrajs": "^1.0.1", + "isarray": "^2.0.1", + "pngjs": "^3.3.0", + "yargs": "^13.2.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==" + }, + "querystringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==" + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + } + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=", + "dev": true, + "requires": { + "esprima": "~4.0.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "registry-auth-token": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.1.1.tgz", + "integrity": "sha512-9bKS7nTl9+/A1s7tnPeGrUpRcVY+LUh7bfFgzpndALdPfXQBfQV77rQVtqgUV3ti4vc/Ik81Ex8UJDWDQ12zQA==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "requires": { + "es6-error": "^4.0.1" + } + }, + "repl.history": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/repl.history/-/repl.history-0.1.4.tgz", + "integrity": "sha1-gDZxcfN4HW5CmccXWMJTCX9dWDI=" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=" + } + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "requires": { + "through": "~2.3.4" + } + }, + "retry": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", + "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==" + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "requires": { + "rx-lite": "*" + } + }, + "rxjs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz", + "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "satoshi-bitcoin": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/satoshi-bitcoin/-/satoshi-bitcoin-1.0.4.tgz", + "integrity": "sha1-0AK2dwddXLvywhGo3zJUvN9QseQ=", + "requires": { + "big.js": "^3.1.3" + } + }, + "scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + }, + "secp256k1": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz", + "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==", + "requires": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.2", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + } + }, + "semantic-release": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-15.14.0.tgz", + "integrity": "sha512-Cn43W35AOLY0RMcDbtwhJODJmWg6YCs1+R5jRQsTmmkEGzkV4B2F/QXkjVZpl4UbH91r93GGH0xhoq9kh7I5PA==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^6.1.0", + "@semantic-release/error": "^2.2.0", + "@semantic-release/github": "^5.1.0", + "@semantic-release/npm": "^5.0.5", + "@semantic-release/release-notes-generator": "^7.1.2", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.0.0", + "env-ci": "^4.0.0", + "execa": "^3.2.0", + "figures": "^3.0.0", + "find-versions": "^3.0.0", + "get-stream": "^5.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^3.0.0", + "lodash": "^4.17.15", + "marked": "^0.7.0", + "marked-terminal": "^3.2.0", + "p-locate": "^4.0.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^6.0.0", + "signale": "^1.2.1", + "yargs": "^15.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "hosted-git-info": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-3.0.4.tgz", + "integrity": "sha512-4oT62d2jwSDBbLLFLZE+1vPuQ1h8p9wjrJ8Mqx5TjsyWmBMV5B13eJqn8pvluqubLf3cJPTfiYCIwNwDNmzScQ==", + "dev": true, + "requires": { + "lru-cache": "^5.1.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + } + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "requires": { + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + } + } + }, + "slp-mdm": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.6.tgz", + "integrity": "sha512-fbjlIg/o8OtzgK2JydC6POJp3Qup/rLgy4yB5hoLgxWRlERyJyE29ScwS3r9TTwPxe12qK55pyivAdNOZZXL0A==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "slp-parser": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slp-parser/-/slp-parser-0.0.4.tgz", + "integrity": "sha512-AvbslJumkzGfMGWNvuE2pWx2nyHEk/VgQ7l119kDKIFRTuRUWOkyOULLauw5laGRQsBRThg6NCx/TsR3grX6GA==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "socket.io": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split2": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", + "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", + "dev": true, + "requires": { + "through2": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "standard": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/standard/-/standard-11.0.1.tgz", + "integrity": "sha512-nu0jAcHiSc8H+gJCXeiziMVZNDYi8MuqrYJKxTgjP4xKXZMKm311boqQIzDrYI/ktosltxt2CbDjYQs9ANC8IA==", + "requires": { + "eslint": "~4.18.0", + "eslint-config-standard": "11.0.0", + "eslint-config-standard-jsx": "5.0.0", + "eslint-plugin-import": "~2.9.0", + "eslint-plugin-node": "~6.0.0", + "eslint-plugin-promise": "~3.7.0", + "eslint-plugin-react": "~7.7.0", + "eslint-plugin-standard": "~3.0.1", + "standard-engine": "~8.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "eslint": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.2.tgz", + "integrity": "sha512-qy4i3wODqKMYfz9LUI8N2qYDkHkoieTbiHpMrYUI/WbjhXJQr7lI4VngixTgaG+yHX+NBCv7nW4hA0ShbvaNKw==", + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + } + }, + "eslint-plugin-node": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-6.0.1.tgz", + "integrity": "sha512-Q/Cc2sW1OAISDS+Ji6lZS2KV4b7ueA/WydVWd1BECTQwVvfQy5JAi3glhINoKzoMnfnuRgNP+ZWKrGAbp3QDxw==", + "requires": { + "ignore": "^3.3.6", + "minimatch": "^3.0.4", + "resolve": "^1.3.3", + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "standard-engine": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-8.0.1.tgz", + "integrity": "sha512-LA531C3+nljom/XRvdW/hGPXwmilRkaRkENhO3FAGF1Vtq/WtCXzgmnc5S6vUHHsgv534MRy02C1ikMwZXC+tw==", + "requires": { + "deglob": "^2.1.0", + "get-stdin": "^6.0.0", + "minimist": "^1.1.0", + "pkg-conf": "^2.0.0" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string.prototype.trim": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz", + "integrity": "sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimleft": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz", + "integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimstart": "^1.0.0" + } + }, + "string.prototype.trimright": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz", + "integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "string.prototype.trimend": "^1.0.0" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "supports-hyperlinks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz", + "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==", + "dev": true, + "requires": { + "has-flag": "^2.0.0", + "supports-color": "^5.0.0" + }, + "dependencies": { + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + } + } + }, + "table": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", + "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "tape": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.0.1.tgz", + "integrity": "sha512-wVsOl2shKPcjdJdc8a+PwacvrOdJZJ57cLUXlxW4TQ2R6aihXwG0m0bKm4mA4wjtQNTaLMCrYNEb4f9fjHKUYQ==", + "requires": { + "deep-equal": "^2.0.3", + "defined": "^1.0.0", + "dotignore": "^0.1.2", + "for-each": "^0.3.3", + "function-bind": "^1.1.1", + "glob": "^7.1.6", + "has": "^1.0.3", + "inherits": "^2.0.4", + "is-regex": "^1.0.5", + "minimist": "^1.2.5", + "object-inspect": "^1.7.0", + "object-is": "^1.1.2", + "object.assign": "^4.1.0", + "resolve": "^1.17.0", + "resumer": "^0.0.0", + "string.prototype.trim": "^1.2.1", + "through": "^2.3.8" + } + }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "dev": true + }, + "tempy": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.3.0.tgz", + "integrity": "sha512-WrH/pui8YCwmeiAoxV+lpRH9HpRtgBhSR2ViBPgpGb/wnYDzp21R4MN45fsCGvLROvY67o3byhJRYRONJyImVQ==", + "dev": true, + "requires": { + "temp-dir": "^1.0.0", + "type-fest": "^0.3.1", + "unique-string": "^1.0.0" + }, + "dependencies": { + "type-fest": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz", + "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==", + "dev": true + } + } + }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "dev": true, + "requires": { + "readable-stream": "2 || 3" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "requires": { + "nopt": "~1.0.10" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "traverse": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.6.tgz", + "integrity": "sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=", + "dev": true + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", + "dev": true + }, + "trim-off-newlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", + "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=", + "dev": true + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tus-js-client": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/tus-js-client/-/tus-js-client-1.8.0.tgz", + "integrity": "sha512-qPX3TywqzxocTxUZtcS8X7Aik72SVMa0jKi4hWyfvRV+s9raVzzYGaP4MoJGaF0yOgm2+b6jXaVEHogxcJ8LGw==", + "requires": { + "buffer-from": "^0.1.1", + "combine-errors": "^3.0.3", + "extend": "^3.0.2", + "js-base64": "^2.4.9", + "lodash.throttle": "^4.1.1", + "proper-lockfile": "^2.0.1", + "url-parse": "^1.4.3" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "uglify-js": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.4.tgz", + "integrity": "sha512-8RZBJq5smLOa7KslsNsVcSH+KOXf1uDU8yqLeNuVKwmT0T3FA0ZoXlinQfRad7SDcbZZRZE4ov+2v71EnxNyCA==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.3" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "optional": true + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "universal-user-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", + "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", + "dev": true, + "requires": { + "os-name": "^3.1.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "util": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", + "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "varuint-bitcoin": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "requires": { + "safe-buffer": "^5.1.1" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz", + "integrity": "sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==", + "requires": { + "is-bigint": "^1.0.0", + "is-boolean-object": "^1.0.0", + "is-number-object": "^1.0.3", + "is-string": "^1.0.4", + "is-symbol": "^1.0.2" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "which-typed-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.2.tgz", + "integrity": "sha512-KT6okrd1tE6JdZAy3o2VhMoYPh3+J6EMZLyrxBQsZflI1QCZIxMrIYLkosd8Twf+YfknVIHmYQPgJt238p8dnQ==", + "requires": { + "available-typed-arrays": "^1.0.2", + "es-abstract": "^1.17.5", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", + "requires": { + "bs58check": "<3.0.0" + } + }, + "wildcard": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-1.1.2.tgz", + "integrity": "sha1-pwIEUwhNjNLv5wup02liY94XEKU=" + }, + "windows-release": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.1.tgz", + "integrity": "sha512-Pngk/RDCaI/DkuHPlGTdIkDiTAnAkyMjoQMZqRsxydNl1qGXNIoZrB7RK8g53F2tEgQBMqQJHQdYZuQEEAu54A==", + "dev": true, + "requires": { + "execa": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "winston": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz", + "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==", + "requires": { + "async": "^2.6.1", + "diagnostics": "^1.1.1", + "is-stream": "^1.1.0", + "logform": "^2.1.1", + "one-time": "0.0.4", + "readable-stream": "^3.1.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.3.0" + } + }, + "winston-transport": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz", + "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==", + "requires": { + "readable-stream": "^2.3.6", + "triple-beam": "^1.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" + }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==", + "dev": true + }, + "yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0716313 --- /dev/null +++ b/package.json @@ -0,0 +1,96 @@ +{ + "name": "@chris.troutner/bch-js", + "version": "3.0.0", + "description": "Bitcoin Cash JavaScript Library based on BITBOX", + "author": "Chris Troutner ", + "contributors": [ + "Gabriel Cardona " + ], + "main": "src/bch-js", + "scripts": { + "test": "nyc mocha --timeout 30000 test/unit/", + "test:integration": "mocha --timeout 30000 test/integration/", + "test:integration:testnet": "mocha --timeout 30000 test/integration/testnet/", + "test:integration:local": "RESTURL=http://localhost:3000/v3/ mocha --timeout 30000 test/integration", + "test:integration:testnet:local": "RESTURL=http://localhost:4000/v3/ mocha --timeout 30000 test/integration/testnet", + "test:integration:free": "bash test/integration/test-free-tier.sh", + "test:integration:free:testnet": "bash test/integration/testnet/test-free-tier.sh", + "test:temp": "nyc mocha --timeout 30000 test/unit/ipfs.js", + "test:integration:api": "RESTURL=https://api.fullstack.cash/v3/ mocha --timeout 30000 test/integration", + "coverage": "nyc report --reporter=text-lcov | coveralls", + "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", + "docs": "./node_modules/.bin/apidoc -i src/ -o docs" + }, + "license": "MIT", + "homepage": "https://github.com/christroutner/bch-js", + "repository": { + "type": "git", + "url": "git+https://github.com/christroutner/bch-js.git" + }, + "dependencies": { + "@uppy/core": "^1.10.4", + "@uppy/tus": "^1.5.12", + "apidoc": "^0.17.7", + "assert": "^2.0.0", + "axios": "^0.19.0", + "bc-bip68": "^1.0.5", + "bch-wallet-bridge.js": "github:web3bch/bch-wallet-bridge.js#master", + "bchaddrjs-slp": "^0.2.5", + "bigi": "^1.4.2", + "bignumber.js": "^9.0.0", + "bip-schnorr": "^0.3.0", + "bip21": "Bitcoin-com/bip21", + "bip32-utils": "Bitcoin-com/bip32-utils#0.13.1", + "bip38": "^2.0.2", + "bip39": "^3.0.2", + "bip66": "^1.1.5", + "bitcoincash-ops": "christroutner/bitcoincash-ops", + "bitcoincashjs-lib": "christroutner/bitcoincashjs-lib#master", + "bitcoinjs-message": "^2.0.0", + "bs58": "^4.0.1", + "buffer": "^5.1.0", + "cashaddrjs": "^0.3.3", + "chalk": "^2.3.0", + "clear": "0.1.0", + "coininfo": "Bitcoin-com/coininfo", + "commander": "^3.0.0", + "cp-file": "^7.0.0", + "ecurve": "^1.0.6", + "figlet": "^1.2.0", + "git-clone": "^0.1.0", + "ini": "^1.3.5", + "mkdirp": "^0.5.1", + "node-cmd": "^3.0.0", + "node-emoji": "^1.8.1", + "qrcode": "^1.4.1", + "randombytes": "^2.0.6", + "repl.history": "^0.1.4", + "safe-buffer": "^5.1.2", + "satoshi-bitcoin": "^1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "socket.io": "^2.1.1", + "socket.io-client": "^2.1.1", + "touch": "^3.1.0", + "wif": "^2.0.6" + }, + "devDependencies": { + "chai": "^4.1.2", + "coveralls": "^3.0.2", + "eslint": "5.16.0", + "eslint-config-prettier": "^6.0.0", + "eslint-plugin-node": "^9.1.0", + "eslint-plugin-prettier": "^3.1.0", + "mocha": "^6.1.4", + "nock": "^10.0.6", + "node-mocks-http": "^1.7.0", + "nyc": "^14.1.0", + "prettier": "^1.18.2", + "semantic-release": "^15.13.19", + "sinon": "^7.3.2" + }, + "apidoc": { + "title": "bch-js", + "url": "bchjs." + } +} diff --git a/src/address.js b/src/address.js new file mode 100644 index 0000000..f0dc2e4 --- /dev/null +++ b/src/address.js @@ -0,0 +1,739 @@ +//const axios = require("axios") +const Bitcoin = require("bitcoincashjs-lib") +const cashaddr = require("cashaddrjs") +const coininfo = require("coininfo") + +class Address { + constructor(config) { + const tmp = {} + if (!config || !config.restURL) tmp.restURL = `https://api.bchjs.cash/v3/` + else tmp.restURL = config.restURL + + this.restURL = tmp.restURL + this.apiToken = tmp.apiToken + } + + /** + * @api Address.toLegacyAddress() toLegacyAddress() - Convert to Legacy Address + * @apiName toLegacyAddress + * @apiGroup Address + * @apiDescription Convert cashaddr to legacy address format + * + * @apiExample Example usage: + * // mainnet w/ prefix + * bchjs.Address.toLegacyAddress('bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // mainnet w/ no prefix + * bchjs.Address.toLegacyAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // testnet w/ prefix + * bchjs.Address.toLegacyAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + * + * // testnet w/ no prefix + * bchjs.Address.toLegacyAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + */ + // Translate address from any address format into a specific format. + toLegacyAddress(address) { + const { prefix, type, hash } = this._decode(address) + + let bitcoincash + switch (prefix) { + case "bitcoincash": + bitcoincash = coininfo.bitcoincash.main + break + case "bchtest": + bitcoincash = coininfo.bitcoincash.test + break + case "bchreg": + bitcoincash = coininfo.bitcoincash.regtest + break + default: + throw `unsupported prefix : ${prefix}` + } + + let version + switch (type) { + case "P2PKH": + version = bitcoincash.versions.public + break + case "P2SH": + version = bitcoincash.versions.scripthash + break + default: + throw `unsupported address type : ${type}` + } + + const hashBuf = Buffer.from(hash) + + return Bitcoin.address.toBase58Check(hashBuf, version) + } + + /** + * @api Address.toCashAddress() toCashAddress() - Convert to bitcoincash: format + * @apiName toCashAddress + * @apiGroup Address + * @apiDescription Convert legacy to cashAddress format + * + * @apiExample Example usage: + * // mainnet + * bchjs.Address.toCashAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN') + * // bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // mainnet no prefix + * bchjs.Address.toCashAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN', false) + * // qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // tesnet + * bchjs.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx') + * // bchtest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + * + * // testnet no prefix + * bchjs.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx', false) + * // qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + */ + toCashAddress(address, prefix = true, regtest = false) { + const decoded = this._decode(address) + + let prefixString + if (regtest) prefixString = "bchreg" + else prefixString = decoded.prefix + + const cashAddress = cashaddr.encode( + prefixString, + decoded.type, + decoded.hash + ) + + if (prefix) return cashAddress + return cashAddress.split(":")[1] + } + + // Converts any address format to hash160 + toHash160(address) { + const legacyAddress = this.toLegacyAddress(address) + const bytes = Bitcoin.address.fromBase58Check(legacyAddress) + return bytes.hash.toString("hex") + } + + /** + * @api Address.hash160ToLegacy() hash160ToLegacy() - Convert hash160 to legacy address. + * @apiName hash160ToLegacy + * @apiGroup Address + * @apiDescription Convert hash160 to legacy address. + * + * @apiExample Example usage: + * // legacy mainnet p2pkh + * bchjs.Address.hash160ToLegacy("573d93b475be4f1925f3b74ed951201b0147eac1") + * // 18xHZ8g2feo4ceejGpvzHkvXT79fi2ZdTG + * + * // legacy mainnet p2sh + * bchjs.Address.hash160ToLegacy("7dc85da64d1d93ef01ef62e0221c02f512e3942f", 0x05) + * // 3DA6RBcFgLwLTpnF6BRAee8w6a9H6JQLCm + * + * // legacy testnet p2pkh + * bchjs.Address.hash160ToLegacy("155187a3283b08b30519db50bc23bbba9f4b6657", 0x6f) + * // mhTg9sgNgvAGfmJs192oUzQWqAXHH5nqLE + */ + // Converts hash160 to Legacy Address + hash160ToLegacy(hash160, network = Bitcoin.networks.bitcoin.pubKeyHash) { + const buffer = Buffer.from(hash160, "hex") + const legacyAddress = Bitcoin.address.toBase58Check(buffer, network) + return legacyAddress + } + + /** + * @api Address.hash160ToCash() hash160ToCash() - Convert hash160 to cash address. + * @apiName hash160ToCash + * @apiGroup Address + * @apiDescription Convert hash160 to cash address. + * + * @apiExample Example usage: + * bchjs.Address.hash160ToCash("573d93b475be4f1925f3b74ed951201b0147eac1") + * 'bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh' + * bchjs.Address.hash160ToCash("7dc85da64d1d93ef01ef62e0221c02f512e3942f", 0x05) + * 'bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug' + * bchjs.Address.hash160ToCash("155187a3283b08b30519db50bc23bbba9f4b6657", 0x6f) + * 'bchtest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u22nzt946' + */ + // Converts hash160 to Cash Address + hash160ToCash( + hash160, + network = Bitcoin.networks.bitcoin.pubKeyHash, + regtest = false + ) { + const legacyAddress = this.hash160ToLegacy(hash160, network) + return this.toCashAddress(legacyAddress, true, regtest) + } + + _decode(address) { + try { + return this._decodeLegacyAddress(address) + } catch (error) {} + + try { + return this._decodeCashAddress(address) + } catch (error) {} + + try { + return this._encodeAddressFromHash160(address) + } catch (error) {} + + throw new Error(`Unsupported address format : ${address}`) + } + + _decodeLegacyAddress(address) { + const { version, hash } = Bitcoin.address.fromBase58Check(address) + const info = coininfo.bitcoincash + + switch (version) { + case info.main.versions.public: + return { + prefix: "bitcoincash", + type: "P2PKH", + hash: hash, + format: "legacy" + } + case info.main.versions.scripthash: + return { + prefix: "bitcoincash", + type: "P2SH", + hash: hash, + format: "legacy" + } + case info.test.versions.public: + return { + prefix: "bchtest", + type: "P2PKH", + hash: hash, + format: "legacy" + } + case info.test.versions.scripthash: + return { + prefix: "bchtest", + type: "P2SH", + hash: hash, + format: "legacy" + } + default: + throw new Error(`Invalid format : ${address}`) + } + } + + _decodeCashAddress(address) { + if (address.indexOf(":") !== -1) { + const decoded = cashaddr.decode(address) + decoded.format = "cashaddr" + return decoded + } + + const prefixes = ["bitcoincash", "bchtest", "bchreg"] + for (let i = 0; i < prefixes.length; ++i) { + try { + const decoded = cashaddr.decode(`${prefixes[i]}:${address}`) + decoded.format = "cashaddr" + return decoded + } catch (error) {} + } + + throw new Error(`Invalid format : ${address}`) + } + + _encodeAddressFromHash160(address) { + try { + return { + legacyAddress: this.hash160ToLegacy(address), + cashAddress: this.hash160ToCash(address), + format: "hash160" + } + } catch (error) {} + + throw new Error(`Invalid format : ${address}`) + } + + /** + * @api Address.isLegacyAddress() isLegacyAddress() - Detect if legacy address. + * @apiName isLegacyAddress + * @apiGroup Address + * @apiDescription Detect if legacy base58check encoded address. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.isLegacyAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // w/ no cashaddr prefix + * bchjs.Address.isLegacyAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // false + * + * // legacy + * bchjs.Address.isLegacyAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN') + * // true + * + * // testnet w/ cashaddr prefix + * bchjs.Address.isLegacyAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isLegacyAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // legacy testnet + * bchjs.Address.isLegacyAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + */ + // Test for address format. + isLegacyAddress(address) { + return this.detectAddressFormat(address) === "legacy" + } + + /** + * @api Address.isCashAddress() isCashAddress() - Detect if cashAddr address. + * @apiName isCashAddress + * @apiGroup Address + * @apiDescription Detect if cashAddr encoded address. + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.Address.isCashAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet w/ no cashaddr prefix + * bchjs.Address.isCashAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet legacy + * bchjs.Address.isCashAddress('18HEMuar5ZhXDFep1gEiY1eoPPcBLxfDxj') + * // false + * + * // testnet w/ cashaddr prefix + * bchjs.Address.isCashAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isCashAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet legacy + * bchjs.Address.isCashAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + isCashAddress(address) { + return this.detectAddressFormat(address) === "cashaddr" + } + + /** + * @api Address.isHash160() isHash160() - Detect if an addess is a hash160. + * @apiName isHash160 + * @apiGroup Address + * @apiDescription Detect if an addess is a hash160. + * + * @apiExample Example usage: + * let hash160Address = '428df38e23fc879a25819427995c3e6355b12d33'; + * bchjs.Address.isHash160(hash160Address); + * // true + * + * let notHash160Address = 'bitcoincash:pz8a837lttkvjksg0jjmmulqvfkgpqrcdgufy8ns5s'; + * bchjs.Address.isHash160(notHash160Address); + * // false + */ + isHash160(address) { + return this.detectAddressFormat(address) === "hash160" + } + + /** + * @api Address.isMainnetAddress() isMainnetAddress() - Detect if mainnet address. + * @apiName isMainnetAddress + * @apiGroup Address + * @apiDescription Detect if mainnet address . + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.Address.isMainnetAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet cashaddr w/ no prefix + * bchjs.Address.isMainnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet legacy + * bchjs.Address.isMainnetAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // true + * + * // testnet cashaddr + * bchjs.Address.isMainnetAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isMainnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet legacy + * bchjs.Address.isMainnetAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + // Test for address network. + isMainnetAddress(address) { + if (address[0] === "x") return true + else if (address[0] === "t") return false + + return this.detectAddressNetwork(address) === "mainnet" + } + + /** + * @api Address.isTestnetAddress() isTestnetAddress() - Detect if testnet address. + * @apiName isTestnetAddress + * @apiGroup Address + * @apiDescription Detect if testnet address. + * + * @apiExample Example usage: + * // cashaddr mainnet + * bchjs.Address.isTestnetAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * //false + * + * // w/ no cashaddr prefix + * bchjs.Address.isTestnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // legacy mainnet + * bchjs.Address.isTestnetAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // false + * + * // cashaddr testnet + * bchjs.Address.isTestnetAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isTestnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet legacy + * bchjs.Address.isTestnetAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + */ + isTestnetAddress(address) { + if (address[0] === "x") return false + else if (address[0] === "t") return true + + return this.detectAddressNetwork(address) === "testnet" + } + + /** + * @api Address.isRegTestAddress() isRegTestAddress() - Detect if regtest address. + * @apiName isRegTestAddress + * @apiGroup Address + * @apiDescription Detect if regtest address. + * + * @apiExample Example usage: + * // regtest + * bchjs.Address.isRegTestAddress('bchreg:qzq9je6pntpva3wf6scr7mlnycr54sjgequ54zx9lh') + * // true + * + * // regtest w/ no prefix + * bchjs.Address.isRegTestAddress('qzq9je6pntpva3wf6scr7mlnycr54sjgequ54zx9lh') + * // true + * + * // cashaddr mainnet + * bchjs.Address.isRegTestAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * //false + * + * // w/ no cashaddr prefix + * bchjs.Address.isRegTestAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // legacy mainnet + * bchjs.Address.isRegTestAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // false + * + * // cashaddr testnet + * bchjs.Address.isRegTestAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isRegTestAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + */ + isRegTestAddress(address) { + return this.detectAddressNetwork(address) === "regtest" + } + + /** + * @api Address.isP2PKHAddress() isP2PKHAddress() - Detect if p2pkh address. + * @apiName isP2PKHAddress + * @apiGroup Address + * @apiDescription Detect if p2pkh address. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.isP2PKHAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // w/ no cashaddr prefix + * bchjs.Address.isP2PKHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // legacy + * bchjs.Address.isP2PKHAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // true + * + * // legacy testnet + * bchjs.Address.isP2PKHAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + * + * // testnet w/ no cashaddr prefix + * bchjs.Address.isP2PKHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // legacy testnet + * bchjs.Address.isP2PKHAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + */ + + // Test for address type. + isP2PKHAddress(address) { + return this.detectAddressType(address) === "p2pkh" + } + + /** + * @api Address.isP2SHAddress() isP2SHAddress() - Detect if p2sh address. + * @apiName isP2SHAddress + * @apiGroup Address + * @apiDescription Detect if p2sh address. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.isP2SHAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // cashaddr w/ no prefix + * bchjs.Address.isP2SHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // legacy + * bchjs.Address.isP2SHAddress('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // false + * + * // cashaddr testnet + * bchjs.Address.isP2SHAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // cashaddr testnet w/ no prefix + * bchjs.Address.isP2SHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // legacy testnet + * bchjs.Address.isP2SHAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + + isP2SHAddress(address) { + return this.detectAddressType(address) === "p2sh" + } + + /** + * @api Address.detectAddressFormat() detectAddressFormat() - Detect address format. + * @apiName detectAddressFormat + * @apiGroup Address + * @apiDescription Detect address format. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.detectAddressFormat('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // cashaddr + * + * // cashaddr w/ no prefix + * bchjs.Address.detectAddressFormat('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // cashaddr + * + * // legacy + * bchjs.Address.detectAddressFormat('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // legacy + * + * // cashaddr testnet + * bchjs.Address.detectAddressFormat('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // cashaddr + * + * // cashaddr testnet w/ no prefix + * bchjs.Address.detectAddressFormat('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // cashaddr + * + * // legacy testnet + * bchjs.Address.detectAddressFormat('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // legacy + */ + // Detect address format. + detectAddressFormat(address) { + const decoded = this._decode(address) + + return decoded.format + } + + /** + * @api Address.detectAddressNetwork() detectAddressNetwork() - Detect address network. + * @apiName detectAddressNetwork + * @apiGroup Address + * @apiDescription Detect address network. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.detectAddressNetwork('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // mainnet + * + * // cashaddr w/ no prefix + * bchjs.Address.detectAddressNetwork('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // mainnet + * + * // legacy + * bchjs.Address.detectAddressNetwork('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // mainnet + * + * // cashaddr testnet + * bchjs.Address.detectAddressNetwork('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // testnet + * + * // cashaddr testnet w/ no prefix + * bchjs.Address.detectAddressNetwork('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // testnet + * + * // legacy testnet + * bchjs.Address.detectAddressNetwork('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // testnet + */ + // Detect address network. + detectAddressNetwork(address) { + if (address[0] === "x") return "mainnet" + else if (address[0] === "t") return "testnet" + + const decoded = this._decode(address) + + switch (decoded.prefix) { + case "bitcoincash": + return "mainnet" + case "bchtest": + return "testnet" + case "bchreg": + return "regtest" + default: + throw new Error(`Invalid prefix : ${decoded.prefix}`) + } + } + + /** + * @api Address.detectAddressType() detectAddressType() - Detect address type. + * @apiName detectAddressType + * @apiGroup Address + * @apiDescription Detect address type. + * + * @apiExample Example usage: + * // cashaddr + * bchjs.Address.detectAddressType('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s'); + * // p2pkh + * + * // cashaddr w/ no prefix + * bchjs.Address.detectAddressType('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s'); + * // p2pkh + * + * // legacy + * bchjs.Address.detectAddressType('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74'); + * // p2pkh + * + * // cashaddr testnet + * bchjs.Address.detectAddressType('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy'); + * // p2pkh + * + * // cashaddr testnet w/ no prefix + * bchjs.Address.detectAddressType('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy'); + * // p2pkh + * + * // legacy testnet + * bchjs.Address.detectAddressType('mqc1tmwY2368LLGktnePzEyPAsgADxbksi'); + * // p2pkh + */ + // Detect address type. + detectAddressType(address) { + const decoded = this._decode(address) + + return decoded.type.toLowerCase() + } + + /** + * @api Address.fromXPub() fromXPub() - Generates an address (xpub). + * @apiName fromXPub + * @apiGroup Address + * @apiDescription Generates an address for an extended public key (xpub). + * + * @apiExample Example usage: + * // generate 5 mainnet external change addresses for xpub6DTNmB7gWa8RtQAfmy8wSDikM5mky4fhsnqQd9AqoCaLcekqNgRZW5JCSXwXkLDkABHTD1qx7kqrbGzT6xBGfAvCJSj2rwvKWP8eZBR2EVA + * let xpub = 'xpub6DTNmB7gWa8RtQAfmy8wSDikM5mky4fhsnqQd9AqoCaLcekqNgRZW5JCSXwXkLDkABHTD1qx7kqrbGzT6xBGfAvCJSj2rwvKWP8eZBR2EVA'; + * for(let i = 0; i <= 4; i++) { + * console.log(bchjs.Address.fromXPub(xpub, "0/" + i)) + * } + * // bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh + * // bitcoincash:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsn9h5vuvr + * // bitcoincash:qpkfg4kck99wksyss6nvaqtafeahfnyrpsj0ed372t + * // bitcoincash:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvy0c2jvx7 + * // bitcoincash:qryj8x4s7vfsc864jm0xaak9qfe8qgk245y9ska57l + * + * // generate 5 testnet external change addresses for tpubDCrnMSKwDMAbxg82yqDt97peMvftCXk3EfBb9WgZh27mPbHGkysU3TW7qX5AwydmnVQfaGeNhUR6okQ3dS5AJTP9gEP7jk2Wcj6Xntc6gNh + * let xpub = 'tpubDCrnMSKwDMAbxg82yqDt97peMvftCXk3EfBb9WgZh27mPbHGkysU3TW7qX5AwydmnVQfaGeNhUR6okQ3dS5AJTP9gEP7jk2Wcj6Xntc6gNh'; + * for(let i = 0; i <= 4; i++) { + * console.log(bchjs.Address.fromXPub(xpub, "0/" + i)) + * } + * // bchtest:qrth8470sc9scek9u0jj2d0349t62gxzdstw2jukl8 + * // bchtest:qpm56zc5re0nhms96r7p985aajthp0vxvg6e4ux3kc + * // bchtest:qqtu3tf6yyd73ejhk3a2ylqynpl3mzzhwuzt299jfd + * // bchtest:qzd7dvlnfukggjqsf5ju0qqwwltakfumjsck33js6m + * // bchtest:qq322ataqeas4n0pdn4gz2sdereh5ae43ylk4qdvus + */ + fromXPub(xpub, path = "0/0") { + const HDNode = Bitcoin.HDNode.fromBase58( + xpub, + Bitcoin.networks[this.detectAddressNetwork(xpub)] + ) + const address = HDNode.derivePath(path) + return this.toCashAddress(address.getAddress()) + } + + /** + * @api Address.fromOutputScript() fromOutputScript() - Detect an addess from an OutputScript.. + * @apiName fromOutputScript + * @apiGroup Address + * @apiDescription Detect an addess from an OutputScript.. + * + * @apiExample Example usage: + * const script = bchjs.Script.encode([ + * Buffer.from("BOX", "ascii"), + * bchjs.Script.opcodes.OP_CAT, + * Buffer.from("BITBOX", "ascii"), + * bchjs.Script.opcodes.OP_EQUAL + * ]); + * const p2sh_hash160 = bchjs.Crypto.hash160(script); + * const scriptPubKey = bchjs.Script.scriptHash.output.encode(p2sh_hash160); + * + * // mainnet address from output script + * bchjs.Address.fromOutputScript(scriptPubKey); + * // bitcoincash:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqncnufkrl + * + * // testnet address from output script + * bchjs.Address.fromOutputScript(scriptPubKey, 'testnet'); + * // bchtest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqh2hmtpyr + */ + fromOutputScript(scriptPubKey, network = "mainnet") { + let netParam + if (network !== "bitcoincash" && network !== "mainnet") + netParam = Bitcoin.networks.testnet + + const regtest = network === "bchreg" + + return this.toCashAddress( + Bitcoin.address.fromOutputScript(scriptPubKey, netParam), + true, + regtest + ) + } +} + +module.exports = Address diff --git a/src/bch-js.js b/src/bch-js.js new file mode 100644 index 0000000..43b6532 --- /dev/null +++ b/src/bch-js.js @@ -0,0 +1,191 @@ +/* + This is the primary library file for bch-js. This file combines all the other + libraries in order to create the BCHJS class. + + The primary server used has switched to fullstack.cash. Go there to sign up + for an account that gives you increased rate limits. +*/ + +// bch-api mainnet. +// const DEFAULT_REST_API = "https://api.bchjs.cash/v3/" +const DEFAULT_REST_API = "https://api.fullstack.cash/v3/" + +// local deps +const BitcoinCash = require("./bitcoincash") +const Crypto = require("./crypto") +const Util = require("./util") +const Blockchain = require("./blockchain") +const Control = require("./control") +const Generating = require("./generating") +const Mining = require("./mining") +const RawTransactions = require("./raw-transactions") +const Mnemonic = require("./mnemonic") +const Address = require("./address") +const HDNode = require("./hdnode") +const TransactionBuilder = require("./transaction-builder") +const ECPair = require("./ecpair") +const Script = require("./script") +const Price = require("./price") +const Socket = require("./socket") +const Wallet = require("./wallet") +const Schnorr = require("./schnorr") +const SLP = require("./slp/slp") +const IPFS = require("./ipfs") +const Encryption = require("./encryption") + +// Indexers +const Blockbook = require("./blockbook") +const OpenBazaar = require("./openbazaar") +const Ninsight = require("./ninsight") +const Electrumx = require("./electrumx") + +class BCHJS { + constructor(config) { + // Try to retrieve the REST API URL from different sources. + if (config && config.restURL && config.restURL !== "") + this.restURL = config.restURL + else if (process.env.RESTURL && process.env.RESTURL !== "") + this.restURL = process.env.RESTURL + else this.restURL = DEFAULT_REST_API + + // Retrieve the apiToken + this.apiToken = "" // default value. + if (config && config.apiToken && config.apiToken !== "") + this.apiToken = config.apiToken + else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== "") + this.apiToken = process.env.BCHJSTOKEN + + const libConfig = { + restURL: this.restURL, + apiToken: this.apiToken + } + + // console.log(`apiToken: ${this.apiToken}`) + + // Populate Blockbook endpoints. + this.Blockbook = new Blockbook(libConfig) + + // Populate OpenBazaar endpoints + this.OpenBazaar = new OpenBazaar(libConfig) + + // Bitcoin.com Ninsight indexer + this.Ninsight = new Ninsight() + + // ElectrumX indexer + this.Electrumx = new Electrumx(libConfig) + + // Populate Full Node + this.Control = new Control(libConfig) + this.Mining = new Mining(libConfig) + this.RawTransactions = new RawTransactions(libConfig) + + // Populate utility functions + this.Address = new Address(libConfig) + this.BitcoinCash = new BitcoinCash(this.Address) + this.Blockchain = new Blockchain(libConfig) + this.Crypto = Crypto + this.ECPair = ECPair + this.ECPair.setAddress(this.Address) + this.encryption = new Encryption(libConfig) + this.Generating = new Generating(libConfig) + this.HDNode = new HDNode(this.Address) + this.Mnemonic = new Mnemonic(this.Address) + this.Price = new Price() + this.Script = new Script() + this.TransactionBuilder = TransactionBuilder + this.TransactionBuilder.setAddress(this.Address) + this.Util = new Util(libConfig) + this.Socket = Socket + this.Wallet = Wallet + this.Schnorr = new Schnorr(libConfig) + + this.SLP = new SLP(libConfig) + this.SLP.HDNode = this.HDNode + + this.IPFS = new IPFS() + } + + static BitboxShim() { + return BitboxShim + } +} + +// Creat a shim so that all the endpoints match BITBOX SDK and this library +// can be used as a drop-in replacement for BITBOX. +class BitboxShim { + constructor(config) { + // Try to retrieve the REST API URL from different sources. + if (config && config.restURL && config.restURL !== "") + this.restURL = config.restURL + else if (process.env.RESTURL && process.env.RESTURL !== "") + this.restURL = process.env.RESTURL + else this.restURL = DEFAULT_REST_API + + // Retrieve the apiToken + this.apiToken = "" // default value. + if (config && config.apiToken && config.apiToken !== "") + this.apiToken = config.apiToken + else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== "") + this.apiToken = process.env.BCHJSTOKEN + + const libConfig = { + restURL: this.restURL, + apiToken: this.apiToken + } + + // Populate utility functions + this.Address = new Address(libConfig) + this.BitcoinCash = new BitcoinCash(this.Address) + this.Blockchain = new Blockchain(libConfig) + this.Crypto = Crypto + this.ECPair = ECPair + this.ECPair.setAddress(this.Address) + this.Generating = new Generating(libConfig) + this.HDNode = new HDNode(this.Address) + this.Mnemonic = new Mnemonic(this.Address) + this.Price = new Price() + this.Script = new Script() + this.TransactionBuilder = TransactionBuilder + this.TransactionBuilder.setAddress(this.Address) + this.Util = new Util(libConfig) + this.Socket = Socket + this.Wallet = Wallet + this.Schnorr = new Schnorr(libConfig) + + // Populate the SLP endpoints. + this.SLP = new SLP(libConfig) + this.Address = this.SLP.Address + + this.ECPair.toSLPAddress = this.SLP.ECPair.toSLPAddress + + this.Util.list = this.SLP.Utils.list + this.Util.balancesForAddress = this.SLP.Utils.balancesForAddress + this.Util.balancesForToken = this.SLP.Utils.balancesForToken + this.Util.balance = this.SLP.Utils.balance + this.Util.validateTxid = this.SLP.Utils.validateTxid + this.Util.tokenStats = this.SLP.Utils.tokenStats + this.Util.transactions = this.SLP.Utils.transactions + this.Util.burnTotal = this.SLP.Utils.burnTotal + this.Util.decodeOpReturn = this.SLP.Utils.decodeOpReturn + this.Util.isTokenUtxo = this.SLP.Utils.isTokenUtxo + this.Util.tokenUtxoDetails = this.SLP.Utils.tokenUtxoDetails + this.Util.txDetails = this.SLP.Utils.txDetails + + // Populate Full Node + this.Control = new Control(libConfig) + this.Mining = new Mining(libConfig) + this.RawTransactions = new RawTransactions(libConfig) + + // Populate Blockbook endpoints. + this.Blockbook = new Blockbook(libConfig) + + // Populate OpenBazaar endpoints + this.OpenBazaar = new OpenBazaar(libConfig) + + // Bitcoin.com Ninsight indexer + this.Ninsight = new Ninsight() + this.Electrumx = new Electrumx(libConfig) + } +} + +module.exports = BCHJS diff --git a/src/bitcoincash.js b/src/bitcoincash.js new file mode 100644 index 0000000..020443b --- /dev/null +++ b/src/bitcoincash.js @@ -0,0 +1,522 @@ +const Bitcoin = require("bitcoincashjs-lib") +const sb = require("satoshi-bitcoin") +const bitcoinMessage = require("bitcoinjs-message") +const bs58 = require("bs58") +const bip21 = require("bip21") +const coininfo = require("coininfo") +const bip38 = require("bip38") +const wif = require("wif") + +const Buffer = require("safe-buffer").Buffer + +class BitcoinCash { + constructor(address) { + this._address = address + } + + /** + * @api BitcoinCash.toSatoshi() toSatoshi() - Converting Bitcoin Cash units to satoshi units. + * @apiName toSatoshi + * @apiGroup BitcoinCash + * @apiDescription + * Converting Bitcoin Cash units to satoshi units. + * + * @apiExample Example usage: + * // convert 9 $BCH to satoshis + * bchjs.BitcoinCash.toSatoshi(9) + * // 900000000 + * + * // convert 1 $BCH to satoshis + * bchjs.BitcoinCash.toSatoshi(1) + * // 100000000 + * + * // convert 100 $BCH to satoshis + * bchjs.BitcoinCash.toSatoshi(100) + * // 10000000000 + * + * // convert 42 $BCH to satoshis + * bchjs.BitcoinCash.toSatoshi(42) + * // 4200000000 + * + * // convert 507 $BCH to satoshis + * bchjs.BitcoinCash.toSatoshi(507) + * // 50700000000 + */ + // Translate coins to satoshi value + toSatoshi(coins) { + return sb.toSatoshi(coins) + } + + /** + * @api BitcoinCash.toBitcoinCash() toBitcoinCash() - Converting satoshi units to Bitcoin Cash units. + * @apiName toBitcoinCash + * @apiGroup BitcoinCash + * @apiDescription + * Converting satoshi units to Bitcoin Cash units. + * + * @apiExample Example usage: + * // convert 900000000 satoshis to $BCH + * bchjs.BitcoinCash.toBitcoinCash(900000000) + * // 9 + * + * // convert 100000000 satoshis to $BCH + * bchjs.BitcoinCash.toBitcoinCash(100000000) + * // 1 + * + * // convert 10000000000 satoshis to $BCH + * bchjs.BitcoinCash.toBitcoinCash(10000000000) + * // 100 + * + * // convert 4200000000 satoshis to $BCH + * bchjs.BitcoinCash.toBitcoinCash(4200000000) + * // 42 + * + * // convert 50700000000 satoshis to $BCH + * bchjs.BitcoinCash.toBitcoinCash(50700000000) + * // 507 + */ + // Translate satoshi to coin value + toBitcoinCash(satoshis) { + return sb.toBitcoin(satoshis) + } + + /** + * @api BitcoinCash.toBits() toBits() - Converting satoshi units to Bits denomination. + * @apiName toBits + * @apiGroup BitcoinCash + * @apiDescription + * Converting satoshi units to Bits denomination. + * + * @apiExample Example usage: + * // convert 4242323400 satoshis to 42423.234 bits + * bchjs.BitcoinCash.toBits(4242323400) + * // 42423.234 + * // convert 100000000 satoshis to 1000 bits + * bchjs.BitcoinCash.toBits(100000000) + * // 1000 + * // convert 314000000 satoshis to 3140 bits + * bchjs.BitcoinCash.toBits(314000000) + * // 3140 + * // convert 987600000000 satoshis to 9876000 bits + * bchjs.BitcoinCash.toBits(987600000000) + * // 9876000 + * // convert 12300 satoshis to 0.123 bits + * bchjs.BitcoinCash.toBits(12300) + * // 0.123 + */ + // Translate satoshi to bits denomination + toBits(satoshis) { + return parseFloat(satoshis) / 100 + } + + // Translate satoshi to bits denomination + // TODO remove in 2.0 + satsToBits(satoshis) { + return parseFloat(satoshis) / 100 + } + + // Translate bits to satoshi denomination + // TODO remove in 2.0 + // fromBits(bits) { + // return this.toInteger(bits * 100); + // } + // + // // Translate bits to satoshi denomination + // satsFromBits(bits) { + // return this.toInteger(bits * 100); + // } + // + // toInteger(number){ + // return Math.round( // round to nearest integer + // Number(number) // type cast your input + // ); + // } + + /** + * @api BitcoinCash.signMessageWithPrivKey() signMessageWithPrivKey() - Sign message with private key. + * @apiName signMessageWithPrivKey + * @apiGroup BitcoinCash + * @apiDescription + * Sign message with private key. + * + * @apiExample Example usage: + * bchjs.BitcoinCash.signMessageWithPrivKey( + * 'KxtpRDUJDiutLaTV8Vuavhb6h7zq9YV9ZKA3dU79PCgYmNVmkkvS', + * 'EARTH' + * ) + * // IIYVhlo2Z6TWFjYX1+YM+7vQKz0m+zYdSe4eYpFLuAQDEZXqll7lZC8Au22VI2LLP5x+IerZckVk3QQPsA3e8/8= + */ + // sign message + signMessageWithPrivKey(privateKeyWIF, message) { + const network = privateKeyWIF.charAt(0) === "c" ? "testnet" : "mainnet" + let bitcoincash + if (network === "mainnet") bitcoincash = coininfo.bitcoincash.main + else bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + const keyPair = Bitcoin.ECPair.fromWIF( + privateKeyWIF, + bitcoincashBitcoinJSLib + ) + const privateKey = keyPair.d.toBuffer(32) + return bitcoinMessage + .sign(message, privateKey, keyPair.compressed) + .toString("base64") + } + + /** + * @api BitcoinCash.verifyMessage() verifyMessage() - Verify message. + * @apiName verifyMessage + * @apiGroup BitcoinCash + * @apiDescription + * Verify message. + * + * @apiExample Example usage: + * bchjs.BitcoinCash.verifyMessage( + * 'bitcoincash:qp2zvw3zpk5xx43w4tve7mtekd9kaxwj4uenq9eupv', + * 'IIYVhlo2Z6TWFjYX1+YM+7vQKz0m+zYdSe4eYpFLuAQDEZXqll7lZC8Au22VI2LLP5x+IerZckVk3QQPsA3e8/8=', + * 'EARTH' + * ) + * // true + */ + // verify message + verifyMessage(address, signature, message) { + return bitcoinMessage.verify( + message, + this._address.toLegacyAddress(address), + signature + ) + } + + /** + * @api BitcoinCash.encodeBase58Check() encodeBase58Check() - Encodes hex string as base58Check. + * @apiName encodeBase58Check + * @apiGroup BitcoinCash + * @apiDescription + * Encodes hex string as base58Check. + * + * @apiExample Example usage: + * // encode 0079bd35d306f648350818470c9f18903df6e06902a026f2a7 as base58check + * let hex = '0079bd35d306f648350818470c9f18903df6e06902a026f2a7' + * bchjs.BitcoinCash.encodeBase58Check(hex) + * // 1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar + * + * // encode 006da742680accf2282df5fade8e9b7a01a517e779289b52cc as base58check + * let hex = '006da742680accf2282df5fade8e9b7a01a517e779289b52cc' + * bchjs.BitcoinCash.encodeBase58Check(hex) + * // 1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK + * + * // encode 00c68a6a07ccdaf1669cfd8d244d80ff36b713551c6208f672 as base58check + * let hex = '00c68a6a07ccdaf1669cfd8d244d80ff36b713551c6208f672' + * bchjs.BitcoinCash.encodeBase58Check(hex) + * // 1K6ncAmMEyQrKUYosZRD9swyZNXECu2aKs + * + * // encode 00d0a6b5e3dd43d0fb895b3b3df565bb8266c5ab00a25dbeb5 as base58check + * let hex = '00d0a6b5e3dd43d0fb895b3b3df565bb8266c5ab00a25dbeb5' + * bchjs.BitcoinCash.encodeBase58Check(hex) + * // 1L2FG9hH3bwchhxHaCs5cg1QNbhmbaeAs6 + * + * // encode 00db04c2e6f104997cb04c956bf25da6078e559d303127f08b as base58check + * let hex = '00db04c2e6f104997cb04c956bf25da6078e559d303127f08b' + * bchjs.BitcoinCash.encodeBase58Check(hex) + * // 1Ly4gqPddveYHMNkfjoXHanVszXpD3duKg + */ + // encode base58Check + encodeBase58Check(hex) { + return bs58.encode(Buffer.from(hex, "hex")) + } + + /** + * @api BitcoinCash.decodeBase58Check() decodeBase58Check() - Decodes base58Check encoded string to hex. + * @apiName decodeBase58Check + * @apiGroup BitcoinCash + * @apiDescription + * Decodes base58Check encoded string to hex. + * + * @apiExample Example usage: + * // decode 1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar to hex + * let base58check = '1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar' + * bchjs.BitcoinCash.decodeBase58Check(base58check) + * // 0079bd35d306f648350818470c9f18903df6e06902a026f2a7 + * + * // decode 1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK to hex + * let base58check = '1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK' + * bchjs.BitcoinCash.decodeBase58Check(base58check) + * // 006da742680accf2282df5fade8e9b7a01a517e779289b52cc + * + * // decode 1K6ncAmMEyQrKUYosZRD9swyZNXECu2aKs to hex + * let base58check = '1K6ncAmMEyQrKUYosZRD9swyZNXECu2aKs' + * bchjs.BitcoinCash.decodeBase58Check(base58check) + * // 00c68a6a07ccdaf1669cfd8d244d80ff36b713551c6208f672 + * + * // decode 1L2FG9hH3bwchhxHaCs5cg1QNbhmbaeAs6 to hex + * let base58check = '1L2FG9hH3bwchhxHaCs5cg1QNbhmbaeAs6' + * bchjs.BitcoinCash.decodeBase58Check(base58check) + * // 00d0a6b5e3dd43d0fb895b3b3df565bb8266c5ab00a25dbeb5 + * + * // decode 1Ly4gqPddveYHMNkfjoXHanVszXpD3duKg to hex + * let base58check = '1Ly4gqPddveYHMNkfjoXHanVszXpD3duKg' + * bchjs.BitcoinCash.decodeBase58Check(base58check) + * // 00db04c2e6f104997cb04c956bf25da6078e559d303127f08b + */ + // decode base58Check + decodeBase58Check(address) { + return bs58.decode(address).toString("hex") + } + + /** + * @api BitcoinCash.encodeBIP21() encodeBIP21() - Encodes address and options as BIP21 uri. + * @apiName encodeBIP21 + * @apiGroup BitcoinCash + * @apiDescription + * Encodes address and options as BIP21 uri. + * + * @apiExample Example usage: + * let address = 'bitcoincash:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s' + * let options = { + * amount: 1, + * label: '#BCHForEveryone', + * } + * bchjs.BitcoinCash.encodeBIP21(address, options) + * // bitcoincash:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s?amount=1&label=%23BCHForEveryone + * + * let address = '1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar' + * let options = { + * amount: 12.5, + * label: 'coinbase donation', + * message: "and ya don't stop", + * } + * bchjs.BitcoinCash.encodeBIP21(address, options) + * // bitcoincash:qpum6dwnqmmysdggrprse8ccjq7ldcrfqgmmtgcmny?amount=12.5&label=coinbase%20donation&message=and%20ya%20don%27t%20stop + * + * let address = 'qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03' + * let options = { + * amount: 42, + * label: 'no prefix', + * } + * bchjs.BitcoinCash.encodeBIP21(address, options) + * // bitcoincash:qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03?amount=42&label=no%20prefix + */ + // encode bip21 url + encodeBIP21(address, options, regtest = false) { + return bip21.encode( + this._address.toCashAddress(address, true, regtest), + options + ) + } + + /** + * @api BitcoinCash.decodeBIP21() decodeBIP21() - Decodes BIP21 uri. + * @apiName decodeBIP21 + * @apiGroup BitcoinCash + * @apiDescription + * Decodes BIP21 uri. + * + * @apiExample Example usage: + * let bip21 = + * 'bitcoincash:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s?amount=1&label=%23BCHForEveryone' + * bchjs.BitcoinCash.decodeBIP21(bip21) + * // { address: 'qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s', options: { amount: 1, label: '#BCHForEveryone' } } + * + * let bip21 = + * 'bitcoincash:qpum6dwnqmmysdggrprse8ccjq7ldcrfqgmmtgcmny?amount=12.5&label=coinbase%20donation&message=and%20ya%20don%27t%20stop' + * bchjs.BitcoinCash.decodeBIP21(bip21) + * // { address: 'qpum6dwnqmmysdggrprse8ccjq7ldcrfqgmmtgcmny', + * // options: + * // { amount: 12.5, + * // label: 'coinbase donation', + * // message: 'and ya don\'t stop' + * // } + * // } + * + * let bip21 = + * 'bitcoincash:qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03?amount=42&label=no%20prefix' + * bchjs.BitcoinCash.decodeBIP21(bip21) + * // { address: 'qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03', options: { amount: 42, label: 'no prefix' } } + */ + // decode bip21 url + decodeBIP21(url) { + return bip21.decode(url) + } + + /** + * @api BitcoinCash.getByteCount() getByteCount() - Get byte count of transaction. + * @apiName getByteCount + * @apiGroup BitcoinCash + * @apiDescription + * Get byte count of transaction. + * + * @apiExample Example usage: + * // 1 P2PKH input + * let inputs = { + * P2PKH: 1, + * } + * // 1 P2SH output + * let outputs = { + * P2SH: 1, + * } + * bchjs.BitcoinCash.getByteCount(inputs, outputs) + * // 190 + * + * // 4 MULTISIG-P2SH 2-of-4 and 10 P2PKH inputs + * let inputs = { + * 'MULTISIG-P2SH:2-4': 4, + * P2PKH: 10, + * } + * // 23 P2PKH outputs + * let outputs = { + * P2PKH: 23, + * } + * bchjs.BitcoinCash.getByteCount(inputs, outputs) + * // 2750 + * + * // 2 MULTISIG-P2SH 3-of-5 inputs + * let inputs = { + * 'MULTISIG-P2SH:3-5': 2, + * } + * // 2 P2PKH outputs + * let outputs = { + * P2PKH: 2, + * } + * bchjs.BitcoinCash.getByteCount(inputs, outputs) + * // 565 + * + * // 111 P2PKH inputs + * let inputs = { + * P2PKH: 111, + * } + * // 2 P2PKH outputs + * let outputs = { + * P2PKH: 2, + * } + * bchjs.BitcoinCash.getByteCount(inputs, outputs) + * // 16506 + * + * // 10 P2PKH and 1 MULTISIG-P2SH 1-of-2 input + * let inputs = { + * P2PKH: 10, + * 'MULTISIG-P2SH:1-2': 1, + * } + * // 2 P2PKH and 1 P2SH outputs + * let outputs = { + * P2PKH: 2, + * P2SH: 1, + * } + * bchjs.BitcoinCash.getByteCount(inputs, outputs) + * // 1780 + */ + getByteCount(inputs, outputs) { + // from https://github.com/bitcoinjs/bitcoinjs-lib/issues/921#issuecomment-354394004 + let totalWeight = 0 + let hasWitness = false + // assumes compressed pubkeys in all cases. + const types = { + inputs: { + "MULTISIG-P2SH": 49 * 4, + "MULTISIG-P2WSH": 6 + 41 * 4, + "MULTISIG-P2SH-P2WSH": 6 + 76 * 4, + P2PKH: 148 * 4, + P2WPKH: 108 + 41 * 4, + "P2SH-P2WPKH": 108 + 64 * 4 + }, + outputs: { + P2SH: 32 * 4, + P2PKH: 34 * 4, + P2WPKH: 31 * 4, + P2WSH: 43 * 4 + } + } + + Object.keys(inputs).forEach(function(key) { + if (key.slice(0, 8) === "MULTISIG") { + // ex. "MULTISIG-P2SH:2-3" would mean 2 of 3 P2SH MULTISIG + const keyParts = key.split(":") + if (keyParts.length !== 2) throw new Error(`invalid input: ${key}`) + const newKey = keyParts[0] + const mAndN = keyParts[1].split("-").map(function(item) { + return parseInt(item) + }) + + totalWeight += types.inputs[newKey] * inputs[key] + const multiplyer = newKey === "MULTISIG-P2SH" ? 4 : 1 + totalWeight += (73 * mAndN[0] + 34 * mAndN[1]) * multiplyer + } else { + totalWeight += types.inputs[key] * inputs[key] + } + if (key.indexOf("W") >= 0) hasWitness = true + }) + + Object.keys(outputs).forEach(function(key) { + totalWeight += types.outputs[key] * outputs[key] + }) + + if (hasWitness) totalWeight += 2 + + totalWeight += 10 * 4 + + return Math.ceil(totalWeight / 4) + } + + /** + * @api BitcoinCash.encryptBIP38() encryptBIP38() - BIP38 encrypt privkey WIFs. + * @apiName encryptBIP38 + * @apiGroup BitcoinCash + * @apiDescription + * BIP38 encrypt privkey WIFs. + * + * @apiExample Example usage: + * // mainnet + * bchjs.BitcoinCash.encryptBIP38( + * 'L1phBREbhL4vb1uHHHCAse8bdGE5c7ic2PFjRxMawLzQCsiFVbvu', + * '9GKVkabAHBMyAf' + * ) + * // 6PYU2fDHRVF2194gKDGkbFbeu4mFgkWtVvg2RPd2Sp6KmZx3RCHFpgBB2G + * + * // testnet + * bchjs.BitcoinCash.encryptBIP38( + * 'cSx7KzdH9EcvDEireu2WYpGnXdFYpta7sJUNt5kVCJgA7kcAU8Gm', + * '1EBPIyj55eR8bVUov9' + * ) + * // 6PYUAPLwLSEjWSAfoe9NTSPkMZXnJA8j8EFJtKaeSnP18RCouutBrS2735 + */ + encryptBIP38(privKeyWIF, passphrase) { + const decoded = wif.decode(privKeyWIF) + + return bip38.encrypt(decoded.privateKey, decoded.compressed, passphrase) + } + + /** + * @api BitcoinCash.decryptBIP38() decryptBIP38() - BIP38 encrypt privkey WIFs. + * @apiName decryptBIP38 + * @apiGroup BitcoinCash + * @apiDescription + * BIP38 encrypt privkey WIFs. + * + * @apiExample Example usage: + * // mainnet + * bchjs.BitcoinCash.decryptBIP38( + * '6PYU2fDHRVF2194gKDGkbFbeu4mFgkWtVvg2RPd2Sp6KmZx3RCHFpgBB2G', + * '9GKVkabAHBMyAf', + * 'mainnet' + * ) + * // L1phBREbhL4vb1uHHHCAse8bdGE5c7ic2PFjRxMawLzQCsiFVbvu + * + * // testnet + * bchjs.BitcoinCash.decryptBIP38( + * '6PYUAPLwLSEjWSAfoe9NTSPkMZXnJA8j8EFJtKaeSnP18RCouutBrS2735', + * '1EBPIyj55eR8bVUov9', + * 'testnet' + * ) + * // cSx7KzdH9EcvDEireu2WYpGnXdFYpta7sJUNt5kVCJgA7kcAU8Gm + */ + decryptBIP38(encryptedKey, passphrase, network = "mainnet") { + const decryptedKey = bip38.decrypt(encryptedKey, passphrase) + let prefix + if (network === "testnet") prefix = 0xef + else prefix = 0x80 + + return wif.encode(prefix, decryptedKey.privateKey, decryptedKey.compressed) + } +} + +module.exports = BitcoinCash diff --git a/src/blockbook.js b/src/blockbook.js new file mode 100644 index 0000000..0a3f50d --- /dev/null +++ b/src/blockbook.js @@ -0,0 +1,357 @@ +/* + This library interacts with the REST API endpoints in bch-api that communicate + with the Blockbook API. +*/ + +const axios = require("axios") + +let _this + +class Blockbook { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + // console.log(`Blockbook apiToken: ${this.apiToken}`) + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api Blockbook.balance() balance() - Balance about an address. + * @apiName Blockbook Balance + * @apiGroup Blockbook + * @apiDescription Return Balance about an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let balance = await bchjs.Blockbook.balance('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c'); + * console.log(balance) + * } catch(error) { + * console.error(error) + * } + * })() + * + * //{ + * // "page": 1, + * // "totalPages": 1, + * // "itemsOnPage": 1000, + * // "address": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", + * // "balance": "6000000", + * // "totalReceived": "10185868", + * // "totalSent": "4185868", + * // "unconfirmedBalance": "0", + * // "unconfirmedTxs": 0, + * // "txs": 41, + * // "txids": [ + * // "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b", + * // "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa", + * // "2f902dec880568511cefa87b9dd761563edeba9c8ba784dc9fca2f7c8c4e6f97", + * // "eea57285462dd70dadcd431fc814857b3f81fe4d0a059a8c02c12fd7d33c02d1", + * // "282b3b296b6aed7122586ed69f7a57d35584eaf94a4d1b1ad7d1b05d36cb79d1", + * // "ac444896b3e32d17824fa6573eed3b89768c5c9085b7a71f3ba88e9d5ba67355", + * // "a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1", + * // "5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e", + * // "54edaa42ff3d6559884a84ebb9bf5ef255635902f5f23b4854245d6b093d41d4", + * // "2b0825188e909410a20a6fbdc58ff5ccf368844273f93f551222c91e6d0fa888", + * // "7a12ea2c83d0c8a5d0f643974b0f04bc19be185c9011ed8fc33255a61d3198bb", + * // "bd6aeeb0748251bb5dba7252ac766fb208c9909d7663c099568225d3bc998a7f", + * // "7898bb1a8c5d933f9c2b24270522e8705b897c2f8d5b1c3477a6b952f2fe22ae", + * // "8d35668a6838de8faa8dff5d8dedeb114bd8ffe6ae73d926264bc8328b2c46b5", + * // "741b4692a14088438ab142cf0865fcbf3977aa7dbd2aafa9bf5f45e75e5e199a", + * // "15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e", + * // "376de807f63253584c3edb80f92832ea90e8fdd7d8e68b2602e230d1b015e311", + * // "eb5d0902e4a303f38223ecebb06bbf14da8a07d1ebb2d77a89dfb9e00e286c10", + * // "11205c557af139f382c43f1f09f223cf28c64d939951fd569e5a30eee6178ccd", + * // "ae05a78086b8d64db26d3047de3d6959a68f2a1ed3017ddf927e2c3b20a80b31", + * // "7e0fc3cac7504d45b0f2ce68e807e592f0edf17914ab618a4cb4d93403d11c98", + * // "1cafaacdfb85b0c33f496bf2c03c4f2ede508479bbad56ae99164dfd823a823f", + * // "788add6a5cd961bdf7ca6145a3112462e0e51523d84a4a48047407a795433947", + * // "49f09b616824a05eb14ae50c9fbc5d6a0c414d66a7d0b217aa75340c00ed8b85", + * // "00f075805adf2a34563ecca33071d660a4c1e91d7d7045282c54554ee4844739", + * // "8d048fd00cf375ba9bbea43730da1ce86fc3cb4026e1dd0e751c07fa50a652c6", + * // "ac0e82ea84f93444602a99199dd80793f79a8ece5ac86156d2fff34f0bad44b2", + * // "342f6815845797e0748f4716c923a0dc9b87de649b69925b36422f6d2dc23b7f", + * // "0839a16b2411a1220c64c4d32a4c9b1e77ac5f47558921cf7c0a2adf25e0eb41", + * // "62f42995711cb0308dac4203c5deaa2985f120559131e66aebc5164426fb6cee", + * // "2ada093fe13c9c04da9582bb304923ce08312eaec92560b9128cdb9e93c5f52f", + * // "e0aadd861a06993e39af932bb0b9ad69e7b37ef5843a13c6724789e1c94f3513", + * // "925490ce60334aca204d82a61b634d89fe4cc9de429c7b6cb5ec420df3e3be5e", + * // "60cb69f29c150378abe21d157858713f82c4e2122867597a2573474763a9e94e", + * // "369a589173969c1c882cfb2d82b1e0ec90076ec827ff9d0f32ffc115690c93c3", + * // "43324ef3f5fdd55b645ba14de8c0667be9b223d60b3d1dcd76cf8fdeb0fd32e4", + * // "d2985c9b1c5c18fc2f6a963b7cb850606c4a18fcb85619057211ce3c8bcec696", + * // "dded59fe377517e52918deae8912a096658ebf5ae61992d39953c8bc3932a11b", + * // "2dc053f55a666a3d2a08b1c680b704d62a55506d14ad884add87edcc56b9277d", + * // "544c15ce35c0f2e808d28f29d6587f1ec9276233e29856b7f2938cf0daef0026", + * // "81039b1d7b855b133f359f9dc65f776bd105650153a941675fedc504228ddbd3" + * // ] + * //} + * + *(async () => { + * try { + * let balance = await bchjs.Blockbook.balance(['bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c','1BFHGm4HzqgXXyNX8n7DsQno5DAC4iLMRA']); + * console.log(balance) + * } catch(error) { + * console.error(error) + * } + * })() + * + * //[ + * // { + * // "page": 1, + * // "totalPages": 1, + * // "itemsOnPage": 1000, + * // "address": "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c", + * // "balance": "6000000", + * // "totalReceived": "10185868", + * // "totalSent": "4185868", + * // "unconfirmedBalance": "0", + * // "unconfirmedTxs": 0, + * // "txs": 41, + * // "txids": [ + * // "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b", + * // "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa", + * // "2f902dec880568511cefa87b9dd761563edeba9c8ba784dc9fca2f7c8c4e6f97", + * // "eea57285462dd70dadcd431fc814857b3f81fe4d0a059a8c02c12fd7d33c02d1", + * // "282b3b296b6aed7122586ed69f7a57d35584eaf94a4d1b1ad7d1b05d36cb79d1", + * // "ac444896b3e32d17824fa6573eed3b89768c5c9085b7a71f3ba88e9d5ba67355", + * // "a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1", + * // "5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e", + * // "54edaa42ff3d6559884a84ebb9bf5ef255635902f5f23b4854245d6b093d41d4", + * // "2b0825188e909410a20a6fbdc58ff5ccf368844273f93f551222c91e6d0fa888", + * // "7a12ea2c83d0c8a5d0f643974b0f04bc19be185c9011ed8fc33255a61d3198bb", + * // "bd6aeeb0748251bb5dba7252ac766fb208c9909d7663c099568225d3bc998a7f", + * // "7898bb1a8c5d933f9c2b24270522e8705b897c2f8d5b1c3477a6b952f2fe22ae", + * // "8d35668a6838de8faa8dff5d8dedeb114bd8ffe6ae73d926264bc8328b2c46b5", + * // "741b4692a14088438ab142cf0865fcbf3977aa7dbd2aafa9bf5f45e75e5e199a", + * // "15f6a584080b04911121fbaca7bfcf3dd64ef2bfa5a01daf31e05a296c3e5e9e", + * // "376de807f63253584c3edb80f92832ea90e8fdd7d8e68b2602e230d1b015e311", + * // "eb5d0902e4a303f38223ecebb06bbf14da8a07d1ebb2d77a89dfb9e00e286c10", + * // "11205c557af139f382c43f1f09f223cf28c64d939951fd569e5a30eee6178ccd", + * // "ae05a78086b8d64db26d3047de3d6959a68f2a1ed3017ddf927e2c3b20a80b31", + * // "7e0fc3cac7504d45b0f2ce68e807e592f0edf17914ab618a4cb4d93403d11c98", + * // "1cafaacdfb85b0c33f496bf2c03c4f2ede508479bbad56ae99164dfd823a823f", + * // "788add6a5cd961bdf7ca6145a3112462e0e51523d84a4a48047407a795433947", + * // "49f09b616824a05eb14ae50c9fbc5d6a0c414d66a7d0b217aa75340c00ed8b85", + * // "00f075805adf2a34563ecca33071d660a4c1e91d7d7045282c54554ee4844739", + * // "8d048fd00cf375ba9bbea43730da1ce86fc3cb4026e1dd0e751c07fa50a652c6", + * // "ac0e82ea84f93444602a99199dd80793f79a8ece5ac86156d2fff34f0bad44b2", + * // "342f6815845797e0748f4716c923a0dc9b87de649b69925b36422f6d2dc23b7f", + * // "0839a16b2411a1220c64c4d32a4c9b1e77ac5f47558921cf7c0a2adf25e0eb41", + * // "62f42995711cb0308dac4203c5deaa2985f120559131e66aebc5164426fb6cee", + * // "2ada093fe13c9c04da9582bb304923ce08312eaec92560b9128cdb9e93c5f52f", + * // "e0aadd861a06993e39af932bb0b9ad69e7b37ef5843a13c6724789e1c94f3513", + * // "925490ce60334aca204d82a61b634d89fe4cc9de429c7b6cb5ec420df3e3be5e", + * // "60cb69f29c150378abe21d157858713f82c4e2122867597a2573474763a9e94e", + * // "369a589173969c1c882cfb2d82b1e0ec90076ec827ff9d0f32ffc115690c93c3", + * // "43324ef3f5fdd55b645ba14de8c0667be9b223d60b3d1dcd76cf8fdeb0fd32e4", + * // "d2985c9b1c5c18fc2f6a963b7cb850606c4a18fcb85619057211ce3c8bcec696", + * // "dded59fe377517e52918deae8912a096658ebf5ae61992d39953c8bc3932a11b", + * // "2dc053f55a666a3d2a08b1c680b704d62a55506d14ad884add87edcc56b9277d", + * // "544c15ce35c0f2e808d28f29d6587f1ec9276233e29856b7f2938cf0daef0026", + * // "81039b1d7b855b133f359f9dc65f776bd105650153a941675fedc504228ddbd3" + * // ] + * // }, + * // { + * // "page": 1, + * // "totalPages": 1, + * // "itemsOnPage": 1000, + * // "address": "bitcoincash:qpcxf2sv9hjw08nvpgffpamfus9nmksm3chv5zqtnz", + * // "balance": "0", + * // "totalReceived": "36781097", + * // "totalSent": "36781097", + * // "unconfirmedBalance": "0", + * // "unconfirmedTxs": 0, + * // "txs": 11, + * // "txids": [ + * // "11205c557af139f382c43f1f09f223cf28c64d939951fd569e5a30eee6178ccd", + * // "ae05a78086b8d64db26d3047de3d6959a68f2a1ed3017ddf927e2c3b20a80b31", + * // "7e0fc3cac7504d45b0f2ce68e807e592f0edf17914ab618a4cb4d93403d11c98", + * // "60cb69f29c150378abe21d157858713f82c4e2122867597a2573474763a9e94e", + * // "f737485aaee3c10b13013fa109bb6294b099246134ca9885f4cc332dbc6c9bb4", + * // "decd5b9c0c959e4e543182093e8f7f8bc7a6ecd96a8a062daaeff3667f8feca7", + * // "94e69a627a34ae27fca81d15fff4323a7ce1f7c275c7485762ce018221017632", + * // "e67c70787af7f3506263c9eda007f3d2d24bd750ff95b5c50a120d9118dfd807", + * // "8e5e00704a147d54028f94d52df7730e821b9c6cd4bd29494e5636f49c199d6a", + * // "15102827c108566ea5daf725c09079c1a3f42ef99d1eb68ea8c584f7b16ab87a", + * // "cc27be8846276612dfce5924b7be96556212f0f0e62bd17641732175edb9911e" + * // ] + * // } + * // + * + */ + + async balance(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}blockbook/balance/${address}`, + _this.axiosOptions + ) + + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}blockbook/balance`, + { + 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 Blockbook.utxo() utxo() - Get list of uxto for address. + * @apiName Blockbook Utxo + * @apiGroup Blockbook + * @apiDescription Return list of uxto for address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let utxo = await bchjs.Blockbook.utxo('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * [{ + * "txid": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "vout": 0, + * "value": "1000", + * "height": 604392, + * "confirmations": 25530, + * "satoshis": 1000 + * }] + * + * + * (async () => { + * try { + * let utxo = await bchjs.Blockbook.utxo([ + * "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + * "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" + * ]); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * [ + * [{ + * "txid": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "vout": 0, + * "value": "1000", + * "height": 604392, + * "confirmations": 25530, + * "satoshis": 1000 + * }], + * [{ + * "txid": "d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5", + * "vout": 0, + * "value": "6000", + * "confirmations": 0, + * "satoshis": 6000 + * }] + * ] + */ + async utxo(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}blockbook/utxos/${address}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}blockbook/utxos`, + { + 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 Blockbook.tx() tx() - Get details on a transaction. + * @apiName Blockbook Tx + * @apiGroup Blockbook + * @apiDescription Get details on a transaction. + * + * @apiExample Example usage: + * (async () => { + * try { + * let txDetails = await bchjs.Blockbook.tx(`5fe9b74056319a8c87f45cc745030715a6180758b94938dbf90d639d55652392`); + * console.log(txDetails); + * } catch(error) { + * console.error(error) + * } + * })() + * + */ + async tx(txid) { + try { + // Handle single txid. + if (typeof txid === "string") { + const response = await axios.get( + `${this.restURL}blockbook/tx/${txid}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(txid)) { + const response = await axios.post( + `${this.restURL}blockbook/tx`, + { + txids: txid + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input txid 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 = Blockbook diff --git a/src/blockchain.js b/src/blockchain.js new file mode 100644 index 0000000..12a1f72 --- /dev/null +++ b/src/blockchain.js @@ -0,0 +1,811 @@ +/* + TODO + - Add blockhash functionality back into getTxOutProof +*/ + +const axios = require("axios") + +let _this + +class Blockchain { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api Blockchain.getBestBlockHash() getBestBlockHash() - Returns the hash of the best (tip) block in the longest blockchain. + * @apiName getBestBlockHash + * @apiGroup Blockchain + * @apiDescription + * Returns the hash of the best (tip) block in the longest blockchain. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBestBlockHash = await bchjs.Blockchain.getBestBlockHash(); + * console.log(getBestBlockHash); + * } catch(error) { + * console.error(error) + * } + * })() + * // 241decef88889efac8e6ce428a8ac696fdde5972eceed97e1fb58d6106af31d5 + */ + async getBestBlockHash() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getBestBlockHash`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getBlock() getBlock() - Return information about block hash. + * @apiName getBlock + * @apiGroup Blockchain + * @apiDescription + * If verbose is false, returns a string that is serialized, hex-encoded data for block 'hash'. If verbose is true, returns an Object with information about block hash. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBlock = await bchjs.Blockchain.getBlock("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"); + * console.log(getBlock); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', + * // confirmations: 528236, + * // size: 216, + * // height: 1000, + * // version: 1, + * // versionHex: '00000001', + * // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33', + * // tx: + * // [ 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33' ], + * // time: 1232346882, + * // mediantime: 1232344831, + * // nonce: 2595206198, + * // bits: '1d00ffff', + * // difficulty: 1, + * // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9', + * // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d', + * // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6' } + */ + async getBlock(blockhash, verbose = true) { + try { + const response = await axios.get( + `${this.restURL}blockchain/getBlock/${blockhash}?verbose=${verbose}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getBlockchainInfo() getBlockchainInfo() - Returns an object containing various state info regarding blockchain processing. + * @apiName getBlockchainInfo + * @apiGroup Blockchain + * @apiDescription + * Returns an object containing various state info regarding blockchain processing. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBlockchainInfo = await bchjs.Blockchain.getBlockchainInfo(); + * console.log(getBlockchainInfo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { chain: 'main', + * // blocks: 529235, + * // headers: 529235, + * // bestblockhash: '00000000000000000108641af52e01a447b1f9d801571f93a0f20a8cbf80c236', + * // difficulty: 702784497476.8376, + * // mediantime: 1525727823, + * // verificationprogress: 0.9999892037620548, + * // chainwork: '00000000000000000000000000000000000000000099f5e1cf7d4e462a493a51', + * // pruned: false, + * // softforks: + * // [ { id: 'bip34', version: 2, reject: [Object] }, + * // { id: 'bip66', version: 3, reject: [Object] }, + * // { id: 'bip65', version: 4, reject: [Object] } ], + * // bip9_softforks: + * // { csv: + * // { status: 'active', + * // startTime: 1462060800, + * // timeout: 1493596800, + * // since: 419328 } } } + */ + async getBlockchainInfo() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getBlockchainInfo`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getBlockCount() getBlockCount() - Returns the number of blocks in the longest blockchain. + * @apiName getBlockCount + * @apiGroup Blockchain + * @apiDescription + * Returns the number of blocks in the longest blockchain. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBlockCount = await bchjs.Blockchain.getBlockCount(); + * console.log(getBlockCount); + * } catch(error) { + * console.error(error) + * } + * })() + * // 529235 + */ + async getBlockCount() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getBlockCount`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getBlockHash() getBlockHash() - Returns hash of block in best-block-chain at height provided. + * @apiName getBlockHash + * @apiGroup Blockchain + * @apiDescription + * Returns hash of block in best-block-chain at height provided. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBlockHash = await bchjs.Blockchain.getBlockHash([0]); + * console.log(getBlockHash); + * } catch(error) { + * console.error(error) + * } + * })() + * // [ '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f' ] + */ + async getBlockHash(height = 1) { + if (typeof height !== "string") height = JSON.stringify(height) + + try { + const response = await axios.get( + `${this.restURL}blockchain/getBlockHash/${height}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getBlockHeader() getBlockHeader() - Return information about blockheader hash. + * @apiName getBlockHeader + * @apiGroup Blockchain + * @apiDescription + * If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. If verbose is true, returns an Object with information about blockheader hash. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getBlockHeader = await bchjs.Blockchain.getBlockHeader(["00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"]); + * console.log(getBlockHeader); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [{ hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09', + * // confirmations: 528236, + * // height: 1000, + * // version: 1, + * // versionHex: '00000001', + * // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33', + * // time: 1232346882, + * // mediantime: 1232344831, + * // nonce: 2595206198, + * // bits: '1d00ffff', + * // difficulty: 1, + * // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9', + * // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d', + * // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6' }] + */ + async getBlockHeader(hash, verbose = true) { + try { + // Handle single hash. + if (typeof hash === "string") { + const response = await axios.get( + `${this.restURL}blockchain/getBlockHeader/${hash}?verbose=${verbose}`, + _this.axiosOptions + ) + + return response.data + + // Handle array of hashes. + } else if (Array.isArray(hash)) { + // Dev note: must use axios.post for unit test stubbing. + const response = await axios.post( + `${this.restURL}blockchain/getBlockHeader`, + { + hashes: hash, + verbose: verbose + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input hash must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getChainTips() getChainTips() - Return information about all known tips in the block tree. + * @apiName getChainTips + * @apiGroup Blockchain + * @apiDescription + * Return information about all known tips in the block tree, including the main chain as well as orphaned branches. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getChainTips = await bchjs.Blockchain.getChainTips(); + * console.log(getChainTips); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ { height: 529235, + * // hash: '00000000000000000108641af52e01a447b1f9d801571f93a0f20a8cbf80c236', + * // branchlen: 0, + * // status: 'active' }, + * // { height: 527442, + * // hash: '0000000000000000014cbf7b7aa12e52dd97db4b1ba5f39dccae37773af9272e', + * // branchlen: 1, + * // status: 'invalid' }, + * // { height: 526861, + * // hash: '00000000000000000225b070818bbafd95842ecbd25edf39bff54a7aa5c8fd10', + * // branchlen: 1, + * // status: 'valid-headers' } ] + */ + async getChainTips() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getChainTips`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getDifficulty() getDifficulty() - Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + * @apiName getDifficulty + * @apiGroup Blockchain + * @apiDescription + * Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getDifficulty = await bchjs.Blockchain.getDifficulty(); + * console.log(getDifficulty); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // 702784497476.8376 + */ + async getDifficulty() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getDifficulty`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + // getMempoolAncestors + async getMempoolAncestors(txid, verbose = false) { + if (typeof txid !== "string") txid = JSON.stringify(txid) + + try { + const response = await axios.get( + `${this.restURL}blockchain/getMempoolAncestors/${txid}?verbose=${verbose}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async getMempoolDescendants(txid, verbose = false) { + if (typeof txid !== "string") txid = JSON.stringify(txid) + + try { + const response = await axios.get( + `${this.restURL}blockchain/getMempoolDescendants/${txid}?verbose=${verbose}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getMempoolEntry() getMempoolEntry() - Returns mempool data for given transaction. + * @apiName getMempoolEntry + * @apiGroup Blockchain + * @apiDescription + * Returns mempool data for given transaction. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getMempoolEntry = await bchjs.Blockchain.getMempoolEntry("fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"); + * console.log(getMempoolEntry); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { + * // "size": 372, + * // "fee": 0.00000374, + * // "modifiedfee": 0.00000374, + * // "time": 1547738850, + * // "height": 565716, + * // "startingpriority": 26524545.3974359, + * // "currentpriority": 26524545.3974359, + * // "descendantcount": 1, + * // "descendantsize": 372, + * // "descendantfees": 374, + * // "ancestorcount": 1, + * // "ancestorsize": 372, + * // "ancestorfees": 374, + * // "depends": [] + * // } + * + * (async () => { + * try { + * let getMempoolEntry = await bchjs.Blockchain.getMempoolEntry([ + * "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33", + * "defea04c38ee00cf73ad402984714ed22dc0dd99b2ae5cb50d791d94343ba79b" + * ]); + * console.log(getMempoolEntry); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ + * // { + * // "size": 372, + * // "fee": 0.00000374, + * // "modifiedfee": 0.00000374, + * // "time": 1547738850, + * // "height": 565716, + * // "startingpriority": 26524545.3974359, + * // "currentpriority": 26524545.3974359, + * // "descendantcount": 1, + * // "descendantsize": 372, + * // "descendantfees": 374, + * // "ancestorcount": 1, + * // "ancestorsize": 372, + * // "ancestorfees": 374, + * // "depends": [] + * // }, + * // { + * // "size": 372, + * // "fee": 0.00000374, + * // "modifiedfee": 0.00000374, + * // "time": 1547738850, + * // "height": 565716, + * // "startingpriority": 26524545.3974359, + * // "currentpriority": 26524545.3974359, + * // "descendantcount": 1, + * // "descendantsize": 372, + * // "descendantfees": 374, + * // "ancestorcount": 1, + * // "ancestorsize": 372, + * // "ancestorfees": 374, + * // "depends": [] + * // } + * // ] + */ + async getMempoolEntry(txid) { + //if (typeof txid !== "string") txid = JSON.stringify(txid) + + try { + if (typeof txid === "string") { + const response = await axios.get( + `${this.restURL}blockchain/getMempoolEntry/${txid}`, + _this.axiosOptions + ) + + return response.data + } else if (Array.isArray(txid)) { + // Dev note: must use axios.post for unit test stubbing. + const response = await axios.post( + `${this.restURL}blockchain/getMempoolEntry`, + { + txids: txid + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getMempoolInfo() getMempoolInfo() - Returns details on the active state of the TX memory pool. + * @apiName getMempoolInfo + * @apiGroup Blockchain + * @apiDescription + * Returns details on the active state of the TX memory pool. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getMempoolInfo = await bchjs.Blockchain.getMempoolInfo(); + * console.log(getMempoolInfo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { size: 257, + * // bytes: 98257, + * // usage: 365840, + * // maxmempool: 300000000, + * // mempoolminfee: 0 } + */ + async getMempoolInfo() { + try { + const response = await axios.get( + `${this.restURL}blockchain/getMempoolInfo`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getRawMempool() getRawMempool() - Returns all transaction ids in memory pool as a json array of string transaction ids. + * @apiName getRawMempool + * @apiGroup Blockchain + * @apiDescription + * Returns all transaction ids in memory pool as a json array of string transaction ids. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getRawMempool = await bchjs.Blockchain.getRawMempool(true); + * console.log(getRawMempool); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ {'2ae541af20db6f2b50410f418af56e349d08877d685f6cf54df54658e892db7a': + * // { size: 237, + * // fee: 0.00000238, + * // modifiedfee: 0.00000238, + * // time: 1525732015, + * // height: 529235, + * // startingpriority: 0, + * // currentpriority: 0, + * // descendantcount: 10, + * // descendantsize: 2376, + * // descendantfees: 2380, + * // ancestorcount: 3, + * // ancestorsize: 712, + * // ancestorfees: 714, + * // depends: + * // [ 'e25682caafc7000645d59f4c11d8d594b2943979b9d8fafb9f946e2b35c21b7e' ] },] + */ + async getRawMempool(verbose = false) { + try { + const response = await axios.get( + `${this.restURL}blockchain/getRawMempool?vebose=${verbose}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getTxOut() getTxOut() - Returns details about an unspent transaction output. + * @apiName getTxOut + * @apiGroup Blockchain + * @apiDescription + * Returns details about an unspent transaction output. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getTxOut = await bchjs.Blockchain.getTxOut("e25682caafc7000645d59f4c11d8d594b2943979b9d8fafb9f946e2b35c21b7e", 1); + * console.log(getTxOut); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // null + */ + async getTxOut(txid, n, include_mempool = true) { + try { + // Input validation + if (typeof txid !== "string" || txid.length !== 64) + throw new Error(`txid needs to be a proper transaction ID`) + + if (isNaN(n)) throw new Error(`n must be an integer`) + + if (typeof include_mempool !== "boolean") + throw new Error(`include_mempool input must be of type boolean`) + + // Send the request to the REST API. + // const response = await axios.get( + // `${this.restURL}blockchain/getTxOut/${txid}/${n}?include_mempool=${include_mempool}`, + // _this.axiosOptions + // ) + const response = await axios.post( + `${this.restURL}blockchain/getTxOut`, + { + txid: txid, + vout: n, + mempool: include_mempool + }, + _this.axiosOptions + ) + + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.getTxOutProof() getTxOutProof() - Returns a hex-encoded proof that "txid" was included in a block. + * @apiName getTxOutProof + * @apiGroup Blockchain + * @apiDescription + * Returns a hex-encoded proof that "txid" was included in a block. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getTxOutProof = await bchjs.Blockchain.getTxOutProof("e25682caafc7000645d59f4c11d8d594b2943979b9d8fafb9f946e2b35c21b7e"); + * console.log(getTxOutProof); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // "0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01" + * + * (async () => { + * try { + * let getTxOutProof = await bchjs.Blockchain.getTxOutProof([ + * "e25682caafc7000645d59f4c11d8d594b2943979b9d8fafb9f946e2b35c21b7e", + * "d16662463fd98eb96c8f6898d58a4461ac3d0120f4d0aea601d72b37759f261c" + * ]); + * console.log(getTxOutProof); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ + * // "010000007de867cc8adc5cc8fb6b898ca4462cf9fd667d7830a275277447e60800000000338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe021f7449ffff001d36b4af9a0100000001338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe0101", + * // "010000007de867cc8adc5cc8fb6b898ca4462cf9fd667d7830a275277447e60800000000338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe021f7449ffff001d36b4af9a0100000001338f121232e169d3100edd82004dc2a1f0e1f030c6c488fa61eafa930b0528fe0101" + * // ] + */ + async getTxOutProof(txids) { + try { + // Single txid. + if (typeof txids === "string") { + const path = `${this.restURL}blockchain/getTxOutProof/${txids}` + //if (blockhash) path = `${path}?blockhash=${blockhash}` + + const response = await axios.get(path, _this.axiosOptions) + return response.data + + // Array of txids. + } else if (Array.isArray(txids)) { + // Dev note: must use axios.post for unit test stubbing. + const response = await axios.post( + `${this.restURL}blockchain/getTxOutProof`, + { + txids: txids + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async preciousBlock(blockhash) { + try { + const response = await axios.get( + `${this.restURL}blockchain/preciousBlock/${blockhash}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async pruneBlockchain(height) { + try { + const response = await axios.post( + `${this.restURL}blockchain/pruneBlockchain/${height}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async verifyChain(checklevel = 3, nblocks = 6) { + try { + const response = await axios.get( + `${this.restURL}blockchain/verifyChain?checklevel=${checklevel}&nblocks=${nblocks}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api Blockchain.verifyTxOutProof() verifyTxOutProof() - Verifies that a proof points to a transaction in a block. + * @apiName verifyTxOutProof + * @apiGroup Blockchain + * @apiDescription + * Verifies that a proof points to a transaction in a block, returning the transaction it commits to and throwing an RPC error if the block is not in our best chain. + * + * @apiExample Example usage: + * (async () => { + * try { + * const proof = "0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01" + * let verifyTxOutProof = await bchjs.Blockchain.verifyTxOutProof(proof); + * console.log(verifyTxOutProof); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ + * // "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + * // ] + * + * (async () => { + * try { + * const proof = "0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01" + * let verifyTxOutProof = await bchjs.Blockchain.verifyTxOutProof([proof, proof]); + * console.log(verifyTxOutProof); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ + * // "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7", + * // "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + * // ] + */ + async verifyTxOutProof(proof) { + try { + // Single block + if (typeof proof === "string") { + const response = await axios.get( + `${this.restURL}blockchain/verifyTxOutProof/${proof}`, + _this.axiosOptions + ) + return response.data + + // Array of hashes. + } else if (Array.isArray(proof)) { + // Dev note: must use axios.post for unit test stubbing. + const response = await axios.post( + `${this.restURL}blockchain/verifyTxOutProof`, + { + proofs: proof + }, + _this.axiosOptions + ) + + return response.data + } + + throw new Error(`Input 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 = Blockchain diff --git a/src/control.js b/src/control.js new file mode 100644 index 0000000..93ff478 --- /dev/null +++ b/src/control.js @@ -0,0 +1,108 @@ +/* + API endpoints for basic control and information of the full node. +*/ + +const axios = require("axios") + +let _this // Global reference to the instance of this class. + +class Control { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api Control.getNetworkInfo() getNetworkInfo() - Get Network info + * @apiName getNetworkInfo + * @apiGroup Control + * @apiDescription Returns an object containing various network info. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getInfo = await bchjs.Control.getNetworkInfo(); + * console.log(getInfo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // returns + * { version: 190500, + * subversion: '/Bitcoin ABC:0.19.5(EB32.0)/', + * protocolversion: 70015, + * localservices: '0000000000000425', + * localrelay: true, + * timeoffset: 0, + * networkactive: true, + * connections: 17, + * networks: + * [ { name: 'ipv4', + * limited: false, + * reachable: true, + * proxy: '', + * proxy_randomize_credentials: false }, + * { name: 'ipv6', + * limited: false, + * reachable: true, + * proxy: '', + * proxy_randomize_credentials: false }, + * { name: 'onion', + * limited: true, + * reachable: false, + * proxy: '', + * proxy_randomize_credentials: false } ], + * relayfee: 0.00001, + * excessutxocharge: 0, + * warnings: + * 'Warning: Unknown block versions being mined! It\'s possible unknown rules are in effect' }} + */ + async getNetworkInfo() { + try { + const response = await axios.get( + `${this.restURL}control/getNetworkInfo`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async getMemoryInfo() { + try { + const response = await axios.get( + `${this.restURL}control/getMemoryInfo`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + // + // stop() { + // // Stop Bitcoin Cash server. + // return axios.post(`${this.restURL}control/stop`) + // .then((response) => { + // return response.data; + // }) + // .catch((error) => { + // return JSON.stringify(error.response.data.error.message); + // }); + // } +} + +module.exports = Control diff --git a/src/crypto.js b/src/crypto.js new file mode 100644 index 0000000..02edebf --- /dev/null +++ b/src/crypto.js @@ -0,0 +1,138 @@ +const randomBytes = require("randombytes") +const Bitcoin = require("bitcoincashjs-lib") + +class Crypto { + /** + * @api Crypto.sha256() sha256() - Utility for creating sha256 hash. + * @apiName sha256 + * @apiGroup Crypto + * @apiDescription Utility for creating sha256 hash digests of data + * + * @apiExample Example usage: + * // buffer from hex + * let buffer = Buffer.from('0101010101010101', 'hex') + * bchjs.Crypto.sha256(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') + * bchjs.Crypto.sha256(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') + * bchjs.Crypto.sha256(buffer) + * // + * + * */ + // Translate address from any address format into a specific format. + static sha256(buffer) { + return Bitcoin.crypto.sha256(buffer) + } + + /** + * @api Crypto.ripemd160() ripemd160()-Utility for creating ripemd160 hash. + * @apiName ripemd160 + * @apiGroup Crypto + * @apiDescription Utility for creating ripemd160 hash digests of data + * + * @apiExample Example usage: + * // buffer from hex + * let buffer = Buffer.from('0101010101010101', 'hex') + * bchjs.Crypto.ripemd160(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab', 'hex') + * bchjs.Crypto.ripemd160(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de', 'hex') + * bchjs.Crypto.ripemd160(buffer) + * // + * */ + static ripemd160(buffer) { + return Bitcoin.crypto.ripemd160(buffer) + } + + /** + * @api Crypto.hash256() hash256() - Utility for creating double sha256 hash. + * @apiName hash256 + * @apiGroup Crypto + * @apiDescription Utility for creating double sha256 hash digests of data. + * + * @apiExample Example usage: + * // buffer from hex + * let buffer = Buffer.from('0101010101010101', 'hex') + * bchjs.Crypto.hash256(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') + * bchjs.Crypto.hash256(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') + * bchjs.Crypto.hash256(buffer) + * // + * */ + static hash256(buffer) { + return Bitcoin.crypto.hash256(buffer) + } + + /** + * @api Crypto.hash160() hash160() - Utility for creating ripemd160(sha256()) hash. + * @apiName hash160 + * @apiGroup Crypto + * @apiDescription Utility for creating ripemd160(sha256()) hash digests of data. + * + * @apiExample Example usage: + * // buffer from hex + * let buffer = Buffer.from('0101010101010101', 'hex') + * bchjs.Crypto.hash160(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') + * bchjs.Crypto.hash160(buffer) + * // + * + * // buffer from hex + * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') + * bchjs.Crypto.hash160(buffer) + * + * */ + static hash160(buffer) { + return Bitcoin.crypto.hash160(buffer) + } + + /** + * @api Crypto.randomBytes() randomBytes() - Generates cryptographically strong pseudo-random data. + * @apiName randomBytes + * @apiGroup Crypto + * @apiDescription Generates cryptographically strong pseudo-random data. The size argument is a number indicating the number of bytes to generate. + * + * @apiExample Example usage: + * bchjs.Crypto.randomBytes(16) + * // + * + * bchjs.Crypto.randomBytes(20) + * // + * + * bchjs.Crypto.randomBytes(24) + * // + * + * bchjs.Crypto.randomBytes(28) + * // + * + * bchjs.Crypto.randomBytes(32) + * // + * */ + static randomBytes(size = 16) { + return randomBytes(size) + } +} + +module.exports = Crypto diff --git a/src/ecpair.js b/src/ecpair.js new file mode 100644 index 0000000..5b60df4 --- /dev/null +++ b/src/ecpair.js @@ -0,0 +1,165 @@ +const Bitcoin = require("bitcoincashjs-lib") +const coininfo = require("coininfo") + +class ECPair { + static setAddress(address) { + ECPair._address = address + } + /** + * @api Ecpair.fromWIF() fromWIF() - Generates an ECPair from a private key in wallet import format (WIF). + * @apiName fromWIF + * @apiGroup ECPair + * @apiDescription Generates an ECPair from a private key in wallet import format (WIF). Follow these steps to go from a private key to a WIF. This method only works with a compressed private key. + * + * @apiExample Example usage: + * // mainnet WIF + * let wif = 'L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1'; + * bchjs.ECPair.fromWIF(wif); + * + * // testnet WIF + * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW' + * bchjs.ECPair.fromWIF(wif) + * */ + static fromWIF(privateKeyWIF) { + let network + if (privateKeyWIF[0] === "L" || privateKeyWIF[0] === "K") + network = "mainnet" + else if (privateKeyWIF[0] === "c") network = "testnet" + + let bitcoincash + if (network === "mainnet") bitcoincash = coininfo.bitcoincash.main + else bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + + return Bitcoin.ECPair.fromWIF(privateKeyWIF, bitcoincashBitcoinJSLib) + } + /** + * @api Ecpair.toWIF() toWIF() - Gets a private key in wallet import format from an ECPair. + * @apiName toWIF + * @apiGroup ECPair + * @apiDescription Gets a private key in wallet import format from an ECPair. + * + * @apiExample Example usage: + * // mainnet wif + * let wif = 'L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // wif from ecpair + * bchjs.ECPair.toWIF(ecpair); + * // L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1 + * + * // testnet wif + * let wif = 'cT3tJP7BnjFJSAHbooMXrY8E9t2AFj37amSBAYFMeHfqPqPgD4ZA'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // wif from ecpair + * bchjs.ECPair.toWIF(ecpair); + * // cT3tJP7BnjFJSAHbooMXrY8E9t2AFj37amSBAYFMeHfqPqPgD4ZA + * */ + static toWIF(ecpair) { + return ecpair.toWIF() + } + + static sign(ecpair, buffer) { + return ecpair.sign(buffer) + } + + static verify(ecpair, buffer, signature) { + return ecpair.verify(buffer, signature) + } + /** + * @api Ecpair.fromPublicKey() fromPublicKey() - Generates an ECPair from a public key buffer. + * @apiName fromPublicKey + * @apiGroup ECPair + * @apiDescription Generates an ECPair from a public key buffer. + * + * @apiExample Example usage: + * // create ECPair from mainnet pubkeyBuffer + * let pubkeyBuffer = Buffer.from("02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb", 'hex'); + * bchjs.ECPair.fromPublicKey(pubkeyBuffer); + * + * // create ECPair from testnet pubkeyBuffer + * let pubkeyBuffer = Buffer.from("024a6d0737a23c472d078d78c1cbc3c2bbf8767b48e72684ff03a911b463da7fa6", 'hex'); + * bchjs.ECPair.fromPublicKey(pubkeyBuffer); + * */ + static fromPublicKey(pubkeyBuffer) { + return Bitcoin.ECPair.fromPublicKeyBuffer(pubkeyBuffer) + } + /** + * @api Ecpair.toPublicKey() toPublicKey() - Get the public key of an ECPair as a buffer. + * @apiName toPublicKey + * @apiGroup ECPair + * @apiDescription Get the public key of an ECPair as a buffer. + * + * @apiExample Example usage: + * // create ecpair from mainnet public key buffer + * let ecpair = bchjs.ECPair.fromPublicKey(Buffer.from('02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b', 'hex')); + * // create public key buffer + * bchjs.ECPair.toPublicKey(ecpair); + * // + * + * // create ecpair from testnet public key buffer + * let ecpair = bchjs.ECPair.fromPublicKey(Buffer.from('024a6d0737a23c472d078d78c1cbc3c2bbf8767b48e72684ff03a911b463da7fa6', 'hex')); + * // create public key buffer + * bchjs.ECPair.toPublicKey(ecpair); + * // + * */ + static toPublicKey(ecpair) { + return ecpair.getPublicKeyBuffer() + } + /** + * @api Ecpair.toLegacyAddress() toLegacyAddress() - Get legacy address of ECPair. + * @apiName toLegacyAddress + * @apiGroup ECPair + * @apiDescription Get legacy address of ECPair. + * + * @apiExample Example usage: + * // mainnet wif + * let wif = 'L5GPEGxCmojgzFoBLUUqT2GegLGqobiYhTZzfLtpkLTfTb9E9NRn'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // to legacy address + * bchjs.ECPair.toLegacyAddress(ecpair); + * // 1DgxdA5bbMcCNWg3yB2MgKqFazV92BXgxK + * + * // testnet wif + * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // to legacy address + * bchjs.ECPair.toLegacyAddress(ecpair); + * // mg4PygFcXoyNJGJkM2Dcpe25av9wXzz1My + * */ + static toLegacyAddress(ecpair) { + return ecpair.getAddress() + } + /** + * @api Ecpair.toCashAddress() toCashAddress() - Get cash address of ECPair. + * @apiName toCashAddress + * @apiGroup ECPair + * @apiDescription Get cash address of ECPair. + * + * @apiExample Example usage: + * // mainnet wif + * let wif = 'L5GPEGxCmojgzFoBLUUqT2GegLGqobiYhTZzfLtpkLTfTb9E9NRn'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // to legacy address + * bchjs.ECPair.toCashAddress(ecpair); + * // bitcoincash:qz9nq206kteyv2t7trhdr4vzzkej60kqtytn7sxkxm + * + * // testnet wif + * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW'; + * // ecpair from wif + * let ecpair = bchjs.ECPair.fromWIF(wif); + * // to legacy address + * bchjs.ECPair.toCashAddress(ecpair); + * // bchtest:qqzly4vrcxcjw62u4yq4nv86ltk2mc9v0yvq8mvj6m + * */ + static toCashAddress(ecpair, regtest = false) { + return ECPair._address.toCashAddress(ecpair.getAddress(), true, regtest) + } +} + +module.exports = ECPair diff --git a/src/electrumx.js b/src/electrumx.js new file mode 100644 index 0000000..361b3ed --- /dev/null +++ b/src/electrumx.js @@ -0,0 +1,304 @@ +/* + This library interacts with the ElectrumX bch-api REST API endpoints operated + by FullStack.cash +*/ + +const axios = require("axios") + +let _this + +class ElectrumX { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}`, + timeout: 15000 + } + } + + _this = this + } + + /** + * @api Electrumx.utxo() utxo() - Get a list of uxto for an address. + * @apiName ElectrumX Utxo + * @apiGroup ElectrumX + * @apiDescription Return a list of uxtos for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let utxo = await bchjs.Electrumx.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * utxo = { + * "success": true, + * "utxos": [ + * { + * "height": 602405, + * "tx_hash": "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * "tx_pos": 0, + * "value": 1000 + * } + * ] + * } + * + * (async () => { + * try { + * let utxo = await bchjs.Electrumx.utxo(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * utxos = { + * "success": true, + * "utxos": [ + * { + * "utxos": [ + * { + * "height": 604392, + * "tx_hash": "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + * "tx_pos": 0, + * "value": 1000 + * }, + * { + * "height": 630834, + * "tx_hash": "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + * "tx_pos": 0, + * "value": 6000 + * } + * ], + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "utxos": [], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async utxo(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}electrumx/utxos/${address}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}electrumx/utxos`, + { + 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 Electrumx.balance() balance() - Get the balance for an address. + * @apiName ElectrumX Balance + * @apiGroup ElectrumX + * @apiDescription Return a list of balances for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let balance = await bchjs.Electrumx.balance('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * balance = { + * "success": true, + * "balance": { + * "confirmed": 1000, + * "unconfirmed": 0 + * } + * } + * + * (async () => { + * try { + * let balance = await bchjs.Electrumx.balance(['bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * balance = { + * "success": true, + * "balances": [ + * { + * "balance": { + * "confirmed": 7000, + * "unconfirmed": 0 + * }, + * "address": "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + * }, + * { + * "balance": { + * "confirmed": 0, + * "unconfirmed": 0 + * }, + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async balance(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}electrumx/balance/${address}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}electrumx/balance`, + { + 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 Electrumx.transactions() transactions() - Get the transaction history for an address. + * @apiName ElectrumX Transactions + * @apiGroup ElectrumX + * @apiDescription Return a transaction history for an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let transactions = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "success": true, + * "transactions": [ + * { + * "height": 560430, + * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * }, + * { + * "height": 560534, + * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * } + * ] + * } + * + * (async () => { + * try { + * let transactions = await bchjs.Electrumx.transactions(['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj', 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v']); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * transactions = { + * "success": true, + * "transactions": [ + * { + * "transactions": [ + * { + * "height": 631219, + * "tx_hash": "ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f" + * } + * ], + * "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" + * }, + * { + * "transactions": [ + * { + * "height": 560430, + * "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + * }, + * { + * "height": 560534, + * "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + * } + * ], + * "address": "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + * } + * ] + * } + * + */ + async transactions(address) { + try { + // Handle single address. + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}electrumx/transactions/${address}`, + _this.axiosOptions + ) + return response.data + + // Handle array of addresses. + } else if (Array.isArray(address)) { + const response = await axios.post( + `${this.restURL}electrumx/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 + } + } +} + +module.exports = ElectrumX diff --git a/src/encryption.js b/src/encryption.js new file mode 100644 index 0000000..4749bef --- /dev/null +++ b/src/encryption.js @@ -0,0 +1,44 @@ +/* + This library contains useful functions that deal with encryption. +*/ + +const axios = require("axios") + +let _this + +class Encryption { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + this.axios = axios + // console.log(`Blockbook apiToken: ${this.apiToken}`) + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + // Search the blockchain for a public key associated with a BCH address. + async getPubKey(addr) { + try { + if (!addr || typeof addr !== "string") + throw new Error(`Input must be a valid Bitcoin Cash address.`) + + const response = await _this.axios.get( + `${this.restURL}encryption/publickey/${addr}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = Encryption diff --git a/src/generating.js b/src/generating.js new file mode 100644 index 0000000..e465607 --- /dev/null +++ b/src/generating.js @@ -0,0 +1,34 @@ +const axios = require("axios") + +let _this + +class Generating { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + async generateToAddress(blocks, address, maxtries = 1000000) { + try { + const response = await axios.post( + `${this.restURL}generating/generateToAddress/${blocks}/${address}?maxtries=${maxtries}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = Generating diff --git a/src/hdnode.js b/src/hdnode.js new file mode 100644 index 0000000..4e0b1a1 --- /dev/null +++ b/src/hdnode.js @@ -0,0 +1,640 @@ +const Bitcoin = require("bitcoincashjs-lib") +const coininfo = require("coininfo") +const bip32utils = require("bip32-utils") +const bchaddrjs = require("bchaddrjs-slp") + +class HDNode { + constructor(address) { + this._address = address + } + + /** + * @api HDNode.fromSeed() fromSeed() - Create HDNode from Seed Buffer. + * @apiName fromSeed + * @apiGroup HDNode + * @apiDescription + * HDNode stands for Hierarchically Deterministic node which can be used to create a HD wallet. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * bchjs.HDNode.fromSeed(seedBuffer); + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * bchjs.HDNode.fromSeed(seedBuffer); + */ + fromSeed(rootSeedBuffer, network = "mainnet") { + let bitcoincash + if (network === "bitcoincash" || network === "mainnet") + bitcoincash = coininfo.bitcoincash.main + else bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + return Bitcoin.HDNode.fromSeedBuffer( + rootSeedBuffer, + bitcoincashBitcoinJSLib + ) + } + + /** + * @api HDNode.toLegacyAddress() toLegacyAddress() - Get legacy address of HDNode. + * @apiName toLegacyAddress + * @apiGroup HDNode + * @apiDescription + * Get legacy address of HDNode + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to legacy address + * bchjs.HDNode.toLegacyAddress(hdNode); + * // 14apxtw2LDQmXWsS5k4JEhG93Jzjswhvma + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to cash address + * bchjs.HDNode.toLegacyAddress(hdNode); + * // 14mVsq3H5Ep2Jb6AqoKsmY1BFHKCBGPDLi + */ + toLegacyAddress(hdNode) { + return hdNode.getAddress() + } + + /** + * @api HDNode.toCashAddress() toCashAddress() - Get cash address of HDNode + * @apiName toCashAddress + * @apiGroup HDNode + * @apiDescription + * Get cash address of HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to cash address + * bchjs.HDNode.toCashAddress(hdNode); + * // bitcoincash:qqrz6kqw6nvhwgwrt4g7fggepvewtkr7nukkeqf4rw + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to cash address + * bchjs.HDNode.toCashAddress(hdNode); + * // bitcoincash:qq549jxsjv66kw0smdju4es2axnk7hhe9cquhjg4gt + */ + toCashAddress(hdNode, regtest = false) { + return this._address.toCashAddress(hdNode.getAddress(), true, regtest) + } + + /** + * @api SLP.HDNode.toSLPAddress() toSLPAddress() - Get slp address of HDNode. + * @apiName toSLPAddress + * @apiGroup SLP + * @apiDescription Get slp address of HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer); + * // to cash address + * bchjs.SLP.HDNode.toSLPAddress(hdNode); + * // simpleledger:qpst7ganm0ucmj3yl7jxvdqrm7tg3zhveg89xjh25d + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer); + * // to cash address + * bchjs.SLP.HDNode.toSLPAddress(hdNode); + * // simpleledger:qqxh2z2z397m4c6u9s5x6wjtku742q8rpvm6al2nrf + */ + toSLPAddress(hdNode) { + const cashAddr = this.toCashAddress(hdNode) + return bchaddrjs.toSlpAddress(cashAddr) + } + + /** + * @api HDNode.toWIF() toWIF() - Get private key in wallet import format (WIF) of HDNode. + * @apiName toWIF + * @apiGroup HDNode + * @apiDescription + * Get private key in wallet import format (WIF) of HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to WIF + * bchjs.HDNode.toWIF(hdNode); + * // L5E8QjFnLukp8BuF4uu9gmvvSrbafioURGdBve5tA3Eq5ptzbMCJ + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to WIF + * bchjs.HDNode.toWIF(hdNode); + * // KwobPFhv3AuXc3ps6YtWfMVRpLBDBA7jnJddurfELTyTNcFhZYpJ + */ + toWIF(hdNode) { + return hdNode.keyPair.toWIF() + } + + /** + * @api HDNode.toXPub() toXPub() - Get extended public key of HDNode. + * @apiName toXPub + * @apiGroup HDNode + * @apiDescription + * Get extended public key of HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to extended public key + * bchjs.HDNode.toXPub(hdNode); + * // xpub661MyMwAqRbcG4CnhNYoK1r1TKLwQQ1UdC3LHoWFK61rsnzh7Hx35qQ9Z53ucYcE5WvA7GEDXhqqKjSY2e6Y8n7WNVLYHpXCuuX945VPuYn + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to extended public key + * bchjs.HDNode.toXPub(hdNode); + * // xpub661MyMwAqRbcFuMLeHkSbTNwNHG9MQyrAZqV1Q4MEAsmj9MYa5sxg8WC2LKqW6EHviHVucBjWi1n38juZpDDeX3U6YrsMeACdcNSTHkM8BQ + */ + toXPub(hdNode) { + return hdNode.neutered().toBase58() + } + + /** + * @api HDNode.toXPriv() toXPriv() - Get extended private key of HDNode. + * @apiName toXPriv + * @apiGroup HDNode + * @apiDescription + * Get extended private key of HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to extended private key + * bchjs.HDNode.toXPriv(hdNode); + * // xprv9s21ZrQH143K2eMCcbT4qwwRhw6qZaPaEDWB792bnrxQZPoP2JUk4kfEx9eeV1uGTAWAfCqYr4wDWo52qALiukizKwQzvEyNR1fWZJi97Kv + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // to extended private key + * bchjs.HDNode.toXPriv(hdNode); + * // xprv9s21ZrQH143K2b5GPP6zHz22E6LeCgQXJtwNbC3MA3Kz7Se7tveKo96EhqwFtSkYWkyenVcMqM7uq35PcUNG8cUdpsJEgwKG3dvfP7TmL3v + */ + toXPriv(hdNode) { + return hdNode.toBase58() + } + + /** + * @api HDNode.toKeyPair() toKeyPair() - Get the ECPair of an HDNode. + * @apiName toKeyPair + * @apiGroup HDNode + * @apiDescription + * Get the ECPair of an HDNode. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create root seed buffer from mnemonic + * let rootSeed= bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from root seed + * let hdNode = bchjs.HDNode.fromSeed(rootSeed); + * // create public key buffer from HDNode + * bchjs.HDNode.toKeyPair(hdNode); + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // create public key buffer from HDNode + * bchjs.HDNode.toKeyPair(hdNode); + */ + toKeyPair(hdNode) { + return hdNode.keyPair + } + + /** + * @api HDNode.toPublicKey() toPublicKey() - Get the public key of an HDNode as a buffer. + * @apiName toPublicKey + * @apiGroup HDNode + * @apiDescription + * Get the public key of an HDNode as a buffer. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create root seed buffer from mnemonic + * let rootSeed= bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from root seed + * let hdNode = bchjs.HDNode.fromSeed(rootSeed); + * // create public key buffer from HDNode + * bchjs.HDNode.toPublicKey(hdNode); + * // + * + * // generate entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // create mnemonic from entropy + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // create public key buffer from HDNode + * bchjs.HDNode.toPublicKey(hdNode); + * // + */ + toPublicKey(hdNode) { + return hdNode.getPublicKeyBuffer() + } + + /** + * @api HDNode.fromXPriv() fromXPriv() - Generate HDNode from extended private key. + * @apiName fromXPriv + * @apiGroup HDNode + * @apiDescription + * Generate HDNode from extended private key. + * + * @apiExample Example usage: + * // mainnet xpriv + * bchjs.HDNode.fromXPriv('xprv9s21ZrQH143K2b5GPP6zHz22E6LeCgQXJtwNbC3MA3Kz7Se7tveKo96EhqwFtSkYWkyenVcMqM7uq35PcUNG8cUdpsJEgwKG3dvfP7TmL3v'); + * + * // testnet xpriv + * bchjs.HDNode.fromXPriv('tprv8gQ3zr1F5pRHMebqqhorrorYNvUG3XkcZjSWVs2cEtRwwJy1TRhgRx4XcF8dYHM2eyTbTCcdKYNhqgyBQphxwRoVyVKr9zuyoA8WxNDRvom'); + */ + fromXPriv(xpriv) { + let bitcoincash + if (xpriv[0] === "x") bitcoincash = coininfo.bitcoincash.main + else if (xpriv[0] === "t") bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + return Bitcoin.HDNode.fromBase58(xpriv, bitcoincashBitcoinJSLib) + } + + /** + * @api HDNode.fromXPub() fromXPub() - Generate HDNode from extended public key. + * @apiName fromXPub + * @apiGroup HDNode + * @apiDescription + * Generate HDNode from extended public key. + * + * @apiExample Example usage: + * // mainnet xpub + * bchjs.HDNode.fromXPub('xpub661MyMwAqRbcFuMLeHkSbTNwNHG9MQyrAZqV1Q4MEAsmj9MYa5sxg8WC2LKqW6EHviHVucBjWi1n38juZpDDeX3U6YrsMeACdcNSTHkM8BQ'); + * + * // testnet xpub + * bchjs.HDNode.fromXPub('tpubDD669G3VEC6xF7ddjMUTGDWewwzCCrwX933HnP4ufAELmoDn5pXGcSgPnLodjFvWQwRXkG94f77BatEDA8dfQ99yy97kRYynUpNLENEqTBo'); + */ + fromXPub(xpub) { + let bitcoincash + if (xpub[0] === "x") bitcoincash = coininfo.bitcoincash.main + else if (xpub[0] === "t") bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + return Bitcoin.HDNode.fromBase58(xpub, bitcoincashBitcoinJSLib) + } + + /** + * @api HDNode.derivePath() derivePath() - Derive child HDNode from path. + * @apiName derivePath + * @apiGroup HDNode + * @apiDescription + * Derive child HDNode from path + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // derive hardened child HDNode + * bchjs.HDNode.derivePath(hdNode, "m/44'/145'/0'"); + */ + derivePath(hdnode, path) { + return hdnode.derivePath(path) + } + + /** + * @api HDNode.derive() derive() - Derive non hardened child HDNode. + * @apiName derive + * @apiGroup HDNode + * @apiDescription + * Derive non hardened child HDNode + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // derive unhardened child HDNode + * bchjs.HDNode.derive(hdNode, 0); + */ + derive(hdnode, path) { + return hdnode.derive(path) + } + + /** + * @api HDNode.deriveHardened() deriveHardened() - Derive hardened child HDNode. + * @apiName deriveHardened + * @apiGroup HDNode + * @apiDescription + * Derive hardened child HDNode + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create seed buffer from mnemonic + * let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create HDNode from seed buffer + * let hdNode = bchjs.HDNode.fromSeed(seedBuffer); + * // derive hardened child HDNode + * bchjs.HDNode.deriveHardened(hdNode, 0); + */ + deriveHardened(hdnode, path) { + return hdnode.deriveHardened(path) + } + + /** + * @api HDNode.sign() sign() - Sign 32 byte hash encoded as a buffer. + * @apiName sign + * @apiGroup HDNode + * @apiDescription + * Sign 32 byte hash encoded as a buffer. + * + * @apiExample Example usage: + * // mainnet xpriv + * let xpriv = 'xprv9z2uWrGjbYPxc728rvtMi4jt4SudRiSfYn6Tdif5XN17pJ1NTbHoHK6JePkPLY1NHXLaQcA6sWudpZDm7DwKhbsGQieAp9wx46Wbio4iXg9'; + * // hdnode from xpriv + * let hdnode = bchjs.HDNode.fromXPriv(xpriv); + * // 32 byte buffer + * let buf = Buffer.from(bchjs.Crypto.sha256('EARTH'), 'hex'); + * // sign + * bchjs.HDNode.sign(hdnode, buf); + * + * // testnet xpriv + * let xpriv = 'tprv8ggxJ8SG5EdqakzVUeLa9Gr7sqCdEcJPUNDmtdJscNxfmxoXvU36ZguiUWukJVEWEixAUr8pJabJkCt33wzxFQA587gqN51Lxdxx97zAzuG'; + * // hdnode from xpriv + * let hdnode = bchjs.HDNode.fromXPriv(xpriv); + * // 32 byte buffer + * let buf = Buffer.from(bchjs.Crypto.sha256('EARTH'), 'hex'); + * // sign + * bchjs.HDNode.sign(hdnode, buf); + */ + sign(hdnode, buffer) { + return hdnode.sign(buffer) + } + + /** + * @api HDNode.verify() verify() - Verify signed 32 byte hash encoded as a buffer. + * @apiName verify + * @apiGroup HDNode + * @apiDescription + * Verify signed 32 byte hash encoded as a buffer. + * + * @apiExample Example usage: + * // mainnet xprivs + * let xpriv1 = 'xprv9ys4cvcoU8RoqvzxGj886r4Ey3w1WfVNYH8sMnVPVzyQtaPPM6Q8pHm3D9WPWvEupGEgcJ1xLaGaZDcvKfoAurE2AzHRRRup5FuHzDr8n15'; + * let xpriv2 = 'xprv9ys4cvcoU8RoxqkZ7Fgt33te4LPHgcsKwyoZYVorkzp9uonWxWgP9wiSQhPeBUqVHbdAyov4Yi55RywBkDfZKdJFRqA51Anz6v72zGaMGZp'; + * // hdnodes from xprivs + * let hdnode1 = bchjs.HDNode.fromXPriv(xpriv1); + * let hdnode2 = bchjs.HDNode.fromXPriv(xpriv2); + * // 32 byte buffer + * let buf = Buffer.from(bchjs.Crypto.sha256('EARTH'), 'hex'); + * // sign + * let signature = bchjs.HDNode.sign(hdnode1, buf); + * // verify + * bchjs.HDNode.verify(hdnode1, buf, signature); + * // true + * bchjs.HDNode.verify(hdnode2, buf, signature); + * // false + * + * // testnet xprivs + * let xpriv1 = 'tprv8ggxJ8SG5EdqakzVUeLa9Gr7sqCdEcJPUNDmtdJscNxfmxoXvU36ZguiUWukJVEWEixAUr8pJabJkCt33wzxFQA587gqN51Lxdxx97zAzuG'; + * let xpriv2 = 'tprv8ggxJ8SG5EdqiM6Dn63QwHScQ7HS5hXqUMxSD1NEbDyPw6VtoUMFZBAohpTMsPz9cYbpHELmA4Zm79NKRvEvFdhWRX2bSmu7V7PiNb364nv'; + * // hdnodes from xprivs + * let hdnode1 = bchjs.HDNode.fromXPriv(xpriv1); + * let hdnode2 = bchjs.HDNode.fromXPriv(xpriv2); + * // 32 byte buffer + * let buf = Buffer.from(bchjs.Crypto.sha256('EARTH'), 'hex'); + * // sign + * let signature = bchjs.ECPair.sign(hdnode1, buf); + * // verify + * bchjs.HDNode.verify(hdnode1, buf, signature); + * // true + * bchjs.HDNode.verify(hdnode2, buf, signature); + * // false + */ + verify(hdnode, buffer, signature) { + return hdnode.verify(buffer, signature) + } + + /** + * @api HDNode.isPublic() isPublic() - Check if an HDNode can only derive public keys and children. + * @apiName isPublic + * @apiGroup HDNode + * @apiDescription + * Check if an HDNode can only derive public keys and children + * + * @apiExample Example usage: + * // mainnet xpub + * let xpub = 'xpub6DWfGUo4cjC8oWmgZdpyFMH6v3oeyADfdUPhsehzn5jX44zpazivha3JxUtkcCvBEB1c6DGaiUmpyz2m1DRfGDEVZ5VxLLW2UNEbZ5iTRvi'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.isPublic(node); + * // true + * + * // mainnet xpriv + * let xpriv = 'xprv9ys4cvcoU8RoxqkZ7Fgt33te4LPHgcsKwyoZYVorkzp9uonWxWgP9wiSQhPeBUqVHbdAyov4Yi55RywBkDfZKdJFRqA51Anz6v72zGaMGZp'; + * let node = bchjs.HDNode.fromXPriv(xpriv); + * bchjs.HDNode.isPublic(node); + * // false + * + * // testnet xpub + * let xpub = 'tpubDCxmZ3qLVVphg6NpsnAjQFqDPwr9HYqSgoAcUYAfqSgo32dL6NA8QXqWsS6XTjoGggohZKvujsAv2F2ugej9qfUYau2jSUB4JaYnfMsx3MJ'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.isPublic(node); + * // true + * + * // testnet xpriv + * let xpriv = 'tprv8ggxJ8SG5EdqakzVUeLa9Gr7sqCdEcJPUNDmtdJscNxfmxoXvU36ZguiUWukJVEWEixAUr8pJabJkCt33wzxFQA587gqN51Lxdxx97zAzuG'; + * let node = bchjs.HDNode.fromXPriv(xpriv); + * bchjs.HDNode.isPublic(node); + * // false + */ + isPublic(hdnode) { + return hdnode.isNeutered() + } + + /** + * @api HDNode.isPrivate() isPrivate() - Check if an HDNode can derive both public and private keys and children. + * @apiName isPrivate + * @apiGroup HDNode + * @apiDescription + * Check if an HDNode can derive both public and private keys and children + * + * @apiExample Example usage: + * // mainnet xpub + * let xpub = 'xpub6DWfGUo4cjC8oWmgZdpyFMH6v3oeyADfdUPhsehzn5jX44zpazivha3JxUtkcCvBEB1c6DGaiUmpyz2m1DRfGDEVZ5VxLLW2UNEbZ5iTRvi'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.isPrivate(node); + * // false + * + * // mainnet xpriv + * let xpriv = 'xprv9ys4cvcoU8RoxqkZ7Fgt33te4LPHgcsKwyoZYVorkzp9uonWxWgP9wiSQhPeBUqVHbdAyov4Yi55RywBkDfZKdJFRqA51Anz6v72zGaMGZp'; + * let node = bchjs.HDNode.fromXPriv(xpriv); + * bchjs.HDNode.isPrivate(node); + * // true + * + * // testnet xpub + * let xpub = 'tpubDCxmZ3qLVVphg6NpsnAjQFqDPwr9HYqSgoAcUYAfqSgo32dL6NA8QXqWsS6XTjoGggohZKvujsAv2F2ugej9qfUYau2jSUB4JaYnfMsx3MJ'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.isPrivate(node); + * // false + * + * // testnet xpriv + * let xpriv = 'tprv8ggxJ8SG5EdqakzVUeLa9Gr7sqCdEcJPUNDmtdJscNxfmxoXvU36ZguiUWukJVEWEixAUr8pJabJkCt33wzxFQA587gqN51Lxdxx97zAzuG'; + * let node = bchjs.HDNode.fromXPriv(xpriv); + * bchjs.HDNode.isPrivate(node); + * // true + */ + isPrivate(hdnode) { + return !hdnode.isNeutered() + } + + /** + * @api HDNode.toIdentifier() toIdentifier() - hash160 of Node’s public key. + * @apiName toIdentifier + * @apiGroup HDNode + * @apiDescription + * hash160 of Node’s public key. The same value you would see in a scriptPubKey. + * + * @apiExample Example usage: + * // mainnet + * let xpub = 'xpub6DWfGUo4cjC8oWmgZdpyFMH6v3oeyADfdUPhsehzn5jX44zpazivha3JxUtkcCvBEB1c6DGaiUmpyz2m1DRfGDEVZ5VxLLW2UNEbZ5iTRvi'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.toIdentifier(node); + * // + * // the same as if we hash160ed it's publicKey + * let publicKeyBuffer = bchjs.HDNode.toPublicKey(node); + * bchjs.Crypto.hash160(publicKeyBuffer); + * // + * + * // testnet + * let xpub = 'tpubDCxmZ3qLVVphg6NpsnAjQFqDPwr9HYqSgoAcUYAfqSgo32dL6NA8QXqWsS6XTjoGggohZKvujsAv2F2ugej9qfUYau2jSUB4JaYnfMsx3MJ'; + * let node = bchjs.HDNode.fromXPub(xpub); + * bchjs.HDNode.toIdentifier(node); + * // + * // the same as if we hash160ed it's publicKey + * let publicKeyBuffer = bchjs.HDNode.toPublicKey(node); + * bchjs.Crypto.hash160(publicKeyBuffer); + * // + */ + toIdentifier(hdnode) { + return hdnode.getIdentifier() + } + + fromBase58(base58, network) { + return Bitcoin.HDNode.fromBase58(base58, network) + } + + /** + * @api HDNode.createAccount() createAccount() - Create BIP32 account. + * @apiName createAccount + * @apiGroup HDNode + * @apiDescription + * Has getChainAddress and nextChainAddress helper methods. + * + * @apiExample Example usage: + * // create mnemonic + * let mnemonic = bchjs.Mnemonic.generate(128); + * // create root seed buffer + * let rootSeedBuffer = bchjs.Mnemonic.toSeed(mnemonic); + * // create master hd node + * let masterHDNode = bchjs.HDNode.fromSeed(rootSeedBuffer); + * // derive child node + * let childNode = masterHDNode.derivePath("m/44'/145'/0'/0"); + * // create account + * let account = bchjs.HDNode.createAccount([childNode]); + */ + createAccount(hdNodes) { + const arr = hdNodes.map( + (item, index) => new bip32utils.Chain(item.neutered()) + ) + return new bip32utils.Account(arr) + } + + createChain(hdNode) { + return new bip32utils.Chain(hdNode) + } +} + +module.exports = HDNode diff --git a/src/ipfs.js b/src/ipfs.js new file mode 100644 index 0000000..8b124a1 --- /dev/null +++ b/src/ipfs.js @@ -0,0 +1,275 @@ +/* + 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 + : // : `http://localhost:5001` + `https://ipfs-api.fullstack.cash` + + // 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.createFileModel() createFileModel() - Create a file model + * @apiName createFileModel() + * @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.uploadFile(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 createFileModel(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.uploadFile() uploadFile() - Upload a file to IPFS + * @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.uploadFile(path, "5ec562319bfacc745e8d8a52"); + * console.log(fileData); + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * "schemaVersion": 1, + * "size": 2374, + * "fileId": "5ec562319bfacc745e8d8a52", + * "fileName": "ipfs.js", + * "fileExtension": "js" + * } + */ + async uploadFile(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() - Check status of file upload + * @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 + } + } +} + +module.exports = IPFS diff --git a/src/mining.js b/src/mining.js new file mode 100644 index 0000000..fe9475b --- /dev/null +++ b/src/mining.js @@ -0,0 +1,73 @@ +const axios = require("axios") + +let _this + +class Mining { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + async getBlockTemplate(template_request) { + try { + const response = await axios.get( + `${this.restURL}mining/getBlockTemplate/${template_request}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async getMiningInfo() { + try { + const response = await axios.get( + `${this.restURL}mining/getMiningInfo`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async getNetworkHashps(nblocks = 120, height = 1) { + try { + const response = await axios.get( + `${this.restURL}mining/getNetworkHashps?nblocks=${nblocks}&height=${height}`, + _this.axiosOptions + ) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + async submitBlock(hex, parameters) { + let path = `${this.restURL}mining/submitBlock/${hex}` + if (parameters) path = `${path}?parameters=${parameters}` + + try { + const response = await axios.post(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = Mining diff --git a/src/mnemonic.js b/src/mnemonic.js new file mode 100644 index 0000000..6112abf --- /dev/null +++ b/src/mnemonic.js @@ -0,0 +1,516 @@ +const BIP39 = require("bip39") +const randomBytes = require("randombytes") +const Bitcoin = require("bitcoincashjs-lib") +const Buffer = require("safe-buffer").Buffer +const wif = require("wif") + +class Mnemonic { + constructor(address) { + this._address = address + } + + /** + * @api Mnemonic.generate() generate() - Generate BIP39 mnemonic from entropy. + * @apiName generate + * @apiGroup Mnemonic + * @apiDescription + * Generate BIP39 mnemonic from entropy. + * + * @apiExample Example usage: + * // generate 12 word mnemonic + * bchjs.Mnemonic.generate(128); + * // boil lonely casino manage habit where total glory muffin name limit mansion + * + * // generate 15 word mnemonic + * bchjs.Mnemonic.generate(160); + * // steak prevent estate save dance design close noise cheap season among train sleep ketchup gas + * + * // generate 18 word mnemonic + * bchjs.Mnemonic.generate(192); + * // fever endorse purpose normal fashion desert blood robust prevent clean guard display raise virtual again unit banana rich + * + * // generate 21 word mnemonic + * bchjs.Mnemonic.generate(224); + * // scan pink shock describe chicken edit budget exit camera morning awesome silk inner pair sea few flock walnut write mountain surface + * + * // generate 24 word mnemonic + * bchjs.Mnemonic.generate(256); + * // disagree tide elbow citizen jazz cinnamon bridge certain april settle pact film always inmate border inform solution that submit produce cloth balcony upper maid + * + * // generate 12 french word mnemonic + * bchjs.Mnemonic.generate(128, bitbox.Mnemonic.wordLists().french); + * // annonce ampleur sanglier peser acheter cultiver abroger embellir résoudre dialogue grappin lanterne + * + * // generate 256 bit korean word mnemonic + * bchjs.Mnemonic.generate(256, bitbox.Mnemonic.wordLists().korean) + * // 기능 단추 교육 비난 시집 근육 운동 코미디 숟가락 과목 한동안 유적 시리즈 삼월 앞날 유난히 흰색 사실 논문 장사 어른 논문 의논 장차 + */ + generate(bits = 128, wordlist) { + return BIP39.generateMnemonic(bits, randomBytes, wordlist) + } + + /** + * @api Mnemonic.fromEntropy() fromEntropy() - Create mnemonic from entropy. + * @apiName fromEntropy + * @apiGroup Mnemonic + * @apiDescription + * Create mnemonic from entropy. + * + * @apiExample Example usage: + * // generate 16 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(16); + * // + * // turn entropy to 12 word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy) + * // security question relief cruel nephew jump chest copper axis assist gift correct + * + * // generate 20 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(20); + * // + * // turn entropy to 15 word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy) + * // impact hub pattern turkey cruel adult short moment make toe one actress roast yellow hurt + * + * // generate 24 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(24); + * // + * // turn entropy to 18 word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy) + * // bid quantum chronic marriage swing affair record amateur enhance heart object mind spoon speak toast piece chef real + * + * // generate 28 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(28); + * // + * // turn entropy to 21 word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy) + * // orchard rural giant okay tape pipe luggage clap bring wear ticket slot fiscal seminar crazy robot distance current dizzy swarm barrel + * + * // generate 32 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // + * // turn entropy to 24 word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy) + * // vibrant solution level obtain cheap damage october giant chalk cushion assist fossil spawn artist rice edit proof hotel process survey gas sausage mouse property + * + * // generate 16 bytes of entropy + * let entropy = bchjs.Crypto.randomBytes(16); + * // + * // turn entropy to 12 japanese word mnemonic + * bchjs.Mnemonic.fromEntropy(entropy.toString('hex'), bchjs.Mnemonic.wordLists().japanese) + * // ぱそこん にあう にんめい きどく ちそう せんきょ かいが きおく いれる いねむり しいく きかんしゃ + */ + fromEntropy(bytes, wordlist) { + return BIP39.entropyToMnemonic(bytes, wordlist) + } + + /** + * @api Mnemonic.toEntropy() toEntropy() - Turn mnemonic to entropy. + * @apiName toEntropy + * @apiGroup Mnemonic + * @apiDescription + * Turn mnemonic to entropy. + * + * @apiExample Example usage: + * // turn 12 word mnemonic to entropy + * let mnemonic = 'security question relief cruel nephew jump chest copper axis assist gift correct'; + * bchjs.Mnemonic.toEntropy(mnemonic) + * // + * + * // turn 15 word mnemonic to entropy + * let mnemonic = 'impact hub pattern turkey cruel adult short moment make toe one actress roast yellow hurt'; + * bchjs.Mnemonic.toEntropy(mnemonic) + * // + * + * // turn 18 word mnemonic to entropy + * let mnemonic = 'bid quantum chronic marriage swing affair record amateur enhance heart object mind spoon speak toast piece chef real'; + * bchjs.Mnemonic.toEntropy(mnemonic) + * // + * + * // turn 21 word mnemonic to entropy + * let mnemonic = 'orchard rural giant okay tape pipe luggage clap bring wear ticket slot fiscal seminar crazy robot distance current dizzy swarm barrel'; + * bchjs.Mnemonic.toEntropy(mnemonic) + * // + * + * // turn 24 word mnemonic to entropy + * let mnemonic = 'vibrant solution level obtain cheap damage october giant chalk cushion assist fossil spawn artist rice edit proof hotel process survey gas sausage mouse property'; + * bchjs.Mnemonic.toEntropy(mnemonic) + * // + */ + toEntropy(mnemonic, wordlist) { + return Buffer.from(BIP39.mnemonicToEntropy(mnemonic, wordlist), "hex") + } + + /** + * @api Mnemonic.validate() validate() - Validate mnemonic. + * @apiName validate + * @apiGroup Mnemonic + * @apiDescription + * Validate mnemonic. + * + * @apiExample Example usage: + * bchjs.Mnemonic.validate('ca', bchjs.Mnemonic.wordLists().english) + * // ca is not in wordlist, did you mean cabbage? + * + * bchjs.Mnemonic.validate('boil lonely casino manage habit where total glory muffin name limit mansion', bitbox.Mnemonic.wordLists().english) + * // Valid mnemonic + * + * bchjs.Mnemonic.validate('boil lonely casino manage habit where total glory muffin name limit mansion boil lonely casino manage habit where total glory muffin name limit mansion', bitbox.Mnemonic.wordLists().english) + * // Invalid mnemonic + */ + validate(mnemonic, wordlist) { + // Preprocess the words + const words = mnemonic.split(" ") + // Detect blank phrase + if (words.length === 0) return "Blank mnemonic" + + // Check each word + for (let i = 0; i < words.length; i++) { + const word = words[i] + if (wordlist.indexOf(word) === -1) { + // Finding closest match to word + const nearestWord = this.findNearestWord(word, wordlist) + return `${word} is not in wordlist, did you mean ${nearestWord}?` + } + } + // Check the words are valid + //const properPhrase = words.join() + const isValid = BIP39.validateMnemonic(mnemonic, wordlist) + if (!isValid) return "Invalid mnemonic" + + return "Valid mnemonic" + } + + /** + * @api Mnemonic.toSeed() toSeed() - Create root seed from mnemonic + * @apiName toSeed + * @apiGroup Mnemonic + * @apiDescription + * Create root seed from mnemonic. Returns a Promise. + * + * @apiExample Example usage: + * await bchjs.Mnemonic.toSeed('enable stem left method one submit coach bid inspire cluster armed bracket') + * // + * + * await bchjs.Mnemonic.toSeed('vendor talk alone sick balance tissue number armor frequent plug transfer chest', 'password'); + * // + * + * await bchjs.Mnemonic.toSeed('idea relax weird defense body bronze champion ancient vocal peanut similar dose grit company peasant gate sunset deal library act include penalty annual main', ''); + * // + * + * await bchjs.Mnemonic.toSeed('bus aware census desk orphan zebra fashion host try muscle pig close jealous slice elegant prison reject ship great program trumpet syrup tray remove', ''); + * // + * + * await bchjs.Mnemonic.toSeed('frost deliver coin clutch upon round scene wonder various wise luggage country', 'yayayayay'); + * // + */ + toSeed(mnemonic, password = "") { + return BIP39.mnemonicToSeed(mnemonic, password) + } + + /** + * @api Mnemonic.wordLists() wordLists() - Return mnemonic word lists. + * @apiName wordLists + * @apiGroup Mnemonic + * @apiDescription + * Return mnemonic word lists. + * + * @apiExample Example usage: + * bchjs.Mnemonic.wordLists(); + * // { + * // EN: [], + * // JA: [], + * // chinese_simplified: [], + * // chinese_traditional: [], + * // english: [], + * // french: [], + * // italian: [], + * // japanese: [], + * // korean: [], + * // spanish: [] + * // } + */ + wordLists() { + return BIP39.wordlists + } + + /** + * @api Mnemonic.toKeypairs() toKeypairs() - Returns an array of privateKeyWIF/publicAddress pairs. + * @apiName toKeypairs + * @apiGroup Mnemonic + * @apiDescription + * Returns an array of privateKeyWIF/publicAddress pairs. It generates the addresses as the nth external change address of the first account from that mnemonic w/ this derivation path: m/44’/145’/0’/0/n + * + * @apiExample Example usage: + * // First create a mnemonic from 32 bytes of random entropy + * let entropy = bchjs.Crypto.randomBytes(32); + * // + * let mnemonic = bchjs.Mnemonic.fromEntropy(entropy); + * // rural pistol giant label nominee curtain egg crystal famous only drill van place unit attitude oven memory fade mix sun shrug soon steak easily + * + * // Then call toKeypairs and pass in your mnemonic and how many keypairs you'd like + * bchjs.Mnemonic.toKeypairs(mnemonic, 5) + * // [ { privateKeyWIF: 'KwuSgSuV6m3U1oahRQEhSQ6e4gRE6LZXNGDTETGPGotKQJdH7ADd', + * // address: 'bitcoincash:qqvk7aculs8r6t29pj23de35t43tupks2ua6wmc2hy' }, + * // { privateKeyWIF: 'L34pfoBm2swLBX5vAx1ReeYbSnpsvu7DRVaiLW8e9wNEJw5p3mV5', + * // address: 'bitcoincash:qzt8ju6au2075cpzrhzwe5n96ycqnurarur5k92nd5' }, + * // { privateKeyWIF: 'L2nCRgDzmTRrQzSssFvVA7xiYHBJyfj62jdDwu1bTjHKVoLGxsqs', + * // address: 'bitcoincash:qpdjwtyvqqaapykxr3pr6cty4gpww30aucam9l0qzn' }, + * // { privateKeyWIF: 'KyDLLa4RZKhnBP78Ue6557B55Jmffu1y8mH8p8WKA12knJUjiq4u', + * // address: 'bitcoincash:qq8kee4k4h9fn22xya9p5u203vg69aat3usqdvkdkn' }, + * // { privateKeyWIF: 'L5gB66JqhfouEtZG5aRMQ9JaVS2ggkK3YozGfzZegBupaPXqdfaz', + * // address: 'bitcoincash:qphwlpu2wzjxrjts94pn4wh778fwsu2afg2aj5her9' } ] + */ + async toKeypairs(mnemonic, numberOfKeypairs = 1, regtest = false) { + const rootSeedBuffer = await this.toSeed(mnemonic, "") + const hdNode = Bitcoin.HDNode.fromSeedBuffer(rootSeedBuffer) + const HDPath = `44'/145'/0'/0/` + + const accounts = [] + + for (let i = 0; i < numberOfKeypairs; i++) { + const childHDNode = hdNode.derivePath(`${HDPath}${i}`) + + let prefix = 128 + if (regtest === true) prefix = 239 + + accounts.push({ + privateKeyWIF: wif.encode( + prefix, + childHDNode.keyPair.d.toBuffer(32), + true + ), + address: this._address.toCashAddress( + childHDNode.getAddress(), + true, + regtest + ) + }) + } + return accounts + } + + /** + * @api Mnemonic.findNearestWord() findNearestWord() - Returns nearest matching word from provided word list. + * @apiName findNearestWord + * @apiGroup Mnemonic + * @apiDescription + * Returns nearest matching word from provided word list. + * + * @apiExample Example usage: + * // english + * let word = 'ab'; + * let wordlist = bchjs.Mnemonic.wordLists().english; + * bchjs.Mnemonic.findNearestWord(word, wordlist); + * // abandon + * + * // french + * let word = 'octu'; + * let wordlist = bchjs.Mnemonic.wordLists().french; + * bchjs.Mnemonic.findNearestWord(word, wordlist); + * // octupler + * + * // spanish + * let word = 'foobaro'; + * let wordlist = bchjs.Mnemonic.wordLists().spanish; + * bchjs.Mnemonic.findNearestWord(word, wordlist); + * // forro + * + * // italian + * let word = 'nv'; + * let wordlist = bchjs.Mnemonic.wordLists().italian; + * bchjs.Mnemonic.findNearestWord(word, wordlist); + * // neve + */ + findNearestWord(word, wordlist) { + let minDistance = 99 + let closestWord = wordlist[0] + for (let i = 0; i < wordlist.length; i++) { + const comparedTo = wordlist[i] + if (comparedTo.indexOf(word) === 0) return comparedTo + + const distance = Levenshtein.get(word, comparedTo) + if (distance < minDistance) { + closestWord = comparedTo + minDistance = distance + } + } + return closestWord + } +} + +module.exports = Mnemonic + +// The following code is from: https://raw.githubusercontent.com/iancoleman/bip39/7ff86d4c983f1e8c80b87b31acfd69fcf98c1b82/src/js/levenshtein.js + +/** + * Extend an Object with another Object's properties. + * + * The source objects are specified as additional arguments. + * + * @param dst Object the object to extend. + * + * @return Object the final object. + */ + +const _extend = function(dst) { + const sources = Array.prototype.slice.call(arguments, 1) + for (let i = 0; i < sources.length; ++i) { + const src = sources[i] + for (const p in src) if (src.hasOwnProperty(p)) dst[p] = src[p] + } + return dst +} + +/** + * Defer execution of given function. + * @param {Function} func + */ +const _defer = function(func) { + if (typeof setImmediate === "function") return setImmediate(func) + + return setTimeout(func, 0) +} + +/** + * Based on the algorithm at http://en.wikipedia.org/wiki/Levenshtein_distance. + */ +var Levenshtein = { + /** + * Calculate levenshtein distance of the two strings. + * + * @param str1 String the first string. + * @param str2 String the second string. + * @return Integer the levenshtein distance (0 and above). + */ + get: function(str1, str2) { + // base cases + if (str1 === str2) return 0 + if (str1.length === 0) return str2.length + if (str2.length === 0) return str1.length + + // two rows + const prevRow = new Array(str2.length + 1) + let curCol, nextCol, i, j, tmp + + // initialise previous row + for (i = 0; i < prevRow.length; ++i) prevRow[i] = i + + // calculate current row distance from previous row + for (i = 0; i < str1.length; ++i) { + nextCol = i + 1 + + for (j = 0; j < str2.length; ++j) { + curCol = nextCol + + // substution + nextCol = prevRow[j] + (str1.charAt(i) === str2.charAt(j) ? 0 : 1) + // insertion + tmp = curCol + 1 + if (nextCol > tmp) nextCol = tmp + + // deletion + tmp = prevRow[j + 1] + 1 + if (nextCol > tmp) nextCol = tmp + + // copy current col value into previous (in preparation for next iteration) + prevRow[j] = curCol + } + + // copy last col value into previous (in preparation for next iteration) + prevRow[j] = nextCol + } + + return nextCol + }, + + /** + * Asynchronously calculate levenshtein distance of the two strings. + * + * @param str1 String the first string. + * @param str2 String the second string. + * @param cb Function callback function with signature: function(Error err, int distance) + * @param [options] Object additional options. + * @param [options.progress] Function progress callback with signature: function(percentComplete) + */ + getAsync: function(str1, str2, cb, options) { + options = _extend( + {}, + { + progress: null + }, + options + ) + + // base cases + if (str1 === str2) return cb(null, 0) + if (str1.length === 0) return cb(null, str2.length) + if (str2.length === 0) return cb(null, str1.length) + + // two rows + const prevRow = new Array(str2.length + 1) + let curCol, nextCol, i, j, tmp, startTime, currentTime + + // initialise previous row + for (i = 0; i < prevRow.length; ++i) prevRow[i] = i + + nextCol = 1 + i = 0 + j = -1 + + var __calculate = function() { + // reset timer + startTime = new Date().valueOf() + currentTime = startTime + + // keep going until one second has elapsed + while (currentTime - startTime < 1000) { + // reached end of current row? + if (str2.length <= ++j) { + // copy current into previous (in preparation for next iteration) + prevRow[j] = nextCol + + // if already done all chars + if (str1.length <= ++i) return cb(null, nextCol) + + // else if we have more left to do + + nextCol = i + 1 + j = 0 + } + + // calculation + curCol = nextCol + + // substution + nextCol = prevRow[j] + (str1.charAt(i) === str2.charAt(j) ? 0 : 1) + // insertion + tmp = curCol + 1 + if (nextCol > tmp) nextCol = tmp + + // deletion + tmp = prevRow[j + 1] + 1 + if (nextCol > tmp) nextCol = tmp + + // copy current into previous (in preparation for next iteration) + prevRow[j] = curCol + + // get current time + currentTime = new Date().valueOf() + } + + // send a progress update? + if (null !== options.progress) { + try { + options.progress.call(null, (i * 100.0) / str1.length) + } catch (err) { + return cb(`Progress callback: ${err.toString()}`) + } + } + + // next iteration + _defer(__calculate) + } + + __calculate() + } +} diff --git a/src/ninsight.js b/src/ninsight.js new file mode 100644 index 0000000..6b0f41c --- /dev/null +++ b/src/ninsight.js @@ -0,0 +1,81 @@ +/* + 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/` + this.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() - Get list of uxto for address. + * @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, + * // "value": "1000000", + * // "height": 585570, + * // "confirmations": 10392 + * // }, + * // { + * // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa", + * // "vout": 0, + * // "value": "1000000", + * // "height": 577125, + * // "confirmations": 18837 + * // } + * // ] + * + */ + async utxo(address) { + try { + if (typeof address !== "string") + throw new Error(`address needs to be a string.`) + + const response = await axios.get( + `${this.ninsightURL}/address/utxo/${address}`, + _this.axiosOptions + ) + + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = Ninsight diff --git a/src/openbazaar.js b/src/openbazaar.js new file mode 100644 index 0000000..159d116 --- /dev/null +++ b/src/openbazaar.js @@ -0,0 +1,229 @@ +/* + This library interacts with the public REST API endpoints that OpenBazaar + provides for their application. +*/ + +const axios = require("axios") + +let _this + +class OpenBazaar { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api OpenBazaar.balance() balance() - Balance about an address. + * @apiName OpenBazaar Balance + * @apiGroup OpenBazaar + * @apiDescription Return Balance about an address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let balance = await bchjs.OpenBazaar.balance('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(balance) + * } catch(error) { + * console.error(error) + * } + * })() + * + * { + * page: 1, + * totalPages: 1, + * itemsOnPage: 1000, + * addrStr: "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9", + * balance: "0.00001", + * totalReceived: "0.00001", + * totalSent: "0", + * unconfirmedBalance: "0", + * unconfirmedTxApperances: 0, + * txApperances: 1, + * transactions: [ + * "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7" + * ] + * } + * + */ + async balance(address) { + try { + // Handle single address. + if (typeof address === "string") { + let response + if (process.env.NETWORK === "testnet") { + response = await axios.get( + `https://tbch.blockbook.api.openbazaar.org/api/address/${address}`, + _this.axiosOptions + ) + } else { + response = await axios.get( + `https://bch.blockbook.api.openbazaar.org/api/address/${address}`, + _this.axiosOptions + ) + } + + return response.data + } + + throw new Error(`Input address must be a string.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api OpenBazaar.utxo() utxo() - Get list of uxto for address. + * @apiName OpenBazaar Utxo + * @apiGroup OpenBazaar + * @apiDescription Return list of uxto for address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let utxo = await bchjs.OpenBazaar.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'); + * console.log(utxo); + * } catch(error) { + * console.error(error) + * } + * })() + * + * [ + * { + * txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * vout: 0, + * amount: "0.00001", + * satoshis: 1000, + * height: 602405, + * confirmations: 11 + * } + * ] + */ + async utxo(address) { + try { + // Handle single address. + if (typeof address === "string") { + let response + if (process.env.NETWORK === "testnet") { + response = await axios.get( + `https://tbch.blockbook.api.openbazaar.org/api/utxo/${address}`, + _this.axiosOptions + ) + } else { + response = await axios.get( + `https://bch.blockbook.api.openbazaar.org/api/utxo/${address}`, + _this.axiosOptions + ) + } + return response.data + } + throw new Error(`Input address must be a string.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api OpenBazaar.tx() tx() - Get details on a transaction. + * @apiName OpenBazaar Tx + * @apiGroup OpenBazaar + * @apiDescription Get details on a transaction. + * + * @apiExample Example usage: + * (async () => { + * try { + * let txDetails = await bchjs.OpenBazaar.tx(`2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7`); + * console.log(txDetails); + * } catch(error) { + * console.error(error) + * } + * })() + * + *{ + * txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + * version: 2, + * vin: [ + * { + * txid: "5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac", + * vout: 1, + * sequence: 4294967295, + * n: 0, + * scriptSig: { + * hex: + * "473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9" + * }, + * addresses: ["bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe"], + * value: "0.00047504" + * } + * ], + * vout: [ + * { + * value: "0.00001", + * n: 0, + * scriptPubKey: { + * hex: "76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac", + * addresses: ["bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9"] + * }, + * spent: false + * }, + * { + * value: "0.00046256", + * n: 1, + * scriptPubKey: { + * hex: "76a9142dbf5e1804c39a497b908c876097d63210c8490288ac", + * addresses: ["bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e"] + * }, + * spent: false + * } + * ], + * blockhash: "0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5", + * blockheight: 602405, + * confirmations: 11, + * blocktime: 1569792892, + * valueOut: "0.00047256", + * valueIn: "0.00047504", + * fees: "0.00000248", + * hex: + * "0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000" + * } + * + */ + async tx(txid) { + try { + // Handle single txid. + if (typeof txid === "string") { + let response + if (process.env.NETWORK === "testnet") { + response = await axios.get( + `https://tbch.blockbook.api.openbazaar.org/api/tx/${txid}`, + _this.axiosOptions + ) + } else { + response = await axios.get( + `https://bch.blockbook.api.openbazaar.org/api/tx/${txid}`, + _this.axiosOptions + ) + } + return response.data + } + + throw new Error(`Input txid must be a string.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = OpenBazaar diff --git a/src/price.js b/src/price.js new file mode 100644 index 0000000..c322a8e --- /dev/null +++ b/src/price.js @@ -0,0 +1,34 @@ +const axios = require("axios") +/** + * @api price.current() current() - Get current price. + * @apiName Price. + * @apiGroup Price + * @apiDescription Return current price of BCH in multiple currencies. + * + * @apiExample Example usage: + *(async () => { + * try { + * let current = await bchjs.Price.current('usd'); + * console.log(current); + * } catch(error) { + * console.error(error) + * } + *})() + * + * // 26681 + */ +class Price { + async current(currency = "usd") { + try { + const response = await axios.get( + `https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}` + ) + return response.data.price + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = Price diff --git a/src/raw-transactions.js b/src/raw-transactions.js new file mode 100644 index 0000000..858173a --- /dev/null +++ b/src/raw-transactions.js @@ -0,0 +1,357 @@ +const axios = require("axios") + +let _this + +class RawTransactions { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api RawTransactions.decodeRawTransaction() decodeRawTransaction() - Return an Array of JSON objects representing the serialized, hex-encoded transactions. + * @apiName decodeRawTransaction + * @apiGroup RawTransactions + * @apiDescription + * Return an Array of JSON objects representing the serialized, hex-encoded transactions. + * + * @apiExample Example usage: + * (async () => { + * try { + * let decodeRawTransaction = await bchjs.RawTransactions.decodeRawTransaction('01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'); + * console.log(decodeRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { txid: 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // hash: + * // 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // size: 191, + * // version: 1, + * // locktime: 0, + * // vin: + * // [ { txid: + * // '4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b', + * // vout: 0, + * // scriptSig: [Object], + * // sequence: 4294967295 } ], + * // vout: [ { value: 12.5, n: 0, scriptPubKey: [Object] } ] } + * + * (async () => { + * try { + * let decodeRawTransaction = await bchjs.RawTransactions.decodeRawTransaction([ + * '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000', + * '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000' + * ]); + * console.log(decodeRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ { txid: + * // 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // hash: + * // 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // size: 191, + * // version: 1, + * // locktime: 0, + * // vin: [ [Object] ], + * // vout: [ [Object] ] }, + * // { txid: + * // 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // hash: + * // 'd86c34adaeae19171fd98fe0ffd89bfb92a1e6f0339f5e4f18d837715fd25758', + * // size: 191, + * // version: 1, + * // locktime: 0, + * // vin: [ [Object] ], + * // vout: [ [Object] ] } ] + */ + async decodeRawTransaction(hex) { + try { + // Single hex + if (typeof hex === "string") { + const response = await axios.get( + `${this.restURL}rawtransactions/decodeRawTransaction/${hex}`, + _this.axiosOptions + ) + + return response.data + + // Array of hexes + } else if (Array.isArray(hex)) { + const options = { + method: "POST", + url: `${this.restURL}rawtransactions/decodeRawTransaction`, + data: { + hexes: hex + }, + headers: { + authorization: `Token ${_this.apiToken}` + } + } + const response = await axios(options) + + return response.data + } + + throw new Error(`Input must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api RawTransactions.decodeScript() decodeScript() - Decode hex-encoded scripts. + * @apiName decodeScript + * @apiGroup RawTransactions + * @apiDescription + * Decode hex-encoded scripts. + * + * @apiExample Example usage: + * (async () => { + * try { + * let decodeScript = await bchjs.RawTransactions.decodeScript('4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16'); + * console.log(decodeScript); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { asm: '30450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed59201 02e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16', type: 'nonstandard', p2sh: 'bitcoincash:pqwndulzwft8dlmqrteqyc9hf823xr3lcc7ypt74ts' } + * + * + * (async () => { + * try { + * let decodeScript = await bchjs.RawTransactions.decodeScript(['4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16']); + * console.log(decodeScript); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [{ asm: '30450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed59201 02e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16', + * // type: 'nonstandard', + * // p2sh: 'bitcoincash:pqwndulzwft8dlmqrteqyc9hf823xr3lcc7ypt74ts' }] + */ + async decodeScript(script) { + //if (typeof script !== "string") script = JSON.stringify(script) + + try { + if (typeof script === "string") { + const response = await axios.get( + `${this.restURL}rawtransactions/decodeScript/${script}`, + _this.axiosOptions + ) + + return response.data + } else if (Array.isArray(script)) { + const options = { + method: "POST", + url: `${this.restURL}rawtransactions/decodeScript`, + data: { + hexes: script + }, + headers: { + authorization: `Token ${this.apiToken}` + } + } + const response = await axios(options) + + return response.data + } + + throw new Error(`Input must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api RawTransactions.getRawTransaction() getRawTransaction() - Return the raw transaction data. + * @apiName getRawTransaction + * @apiGroup RawTransactions + * @apiDescription + * Return the raw transaction data. If verbose is 'true', returns an Object with information about 'txid'. If verbose is 'false' or omitted, returns a string that is serialized, hex-encoded data for 'txid'. + * + * @apiExample Example usage: + * (async () => { + * try { + * let getRawTransaction = await bchjs.RawTransactions.getRawTransaction("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); + * console.log(getRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000 + * + * (async () => { + * try { + * let getRawTransaction = await bchjs.RawTransactions.getRawTransaction([ + * "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", + * "b25d24fbb42d84812ed2cb55797f10fdec41afc7906ab563d1ec8c8676a2037f" + * ], true); + * console.log(getRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [ { hex: + * // '01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000', + * // txid: + * // '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098', + * // hash: + * // '0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098', + * // size: 134, + * // version: 1, + * // locktime: 0, + * // vin: [ [Object] ], + * // vout: [ [Object] ], + * // blockhash: + * // '00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048', + * // confirmations: 581882, + * // time: 1231469665, + * // blocktime: 1231469665 }, + * // { hex: + * // '01000000010f3cb469bc82f931ee77d80b3dd495d02f9ed7cdc455cea3e7baa4bdeea6a78d000000006a47304402205ce3e1dfe4b5207818ce27035bc7cc03a5631f806d351535b32ce77c8d136aed02204e66e1fa4c2e12feab0d41a5593aff9629cdbc6ccb6126bc3d1a20404be7760c412103d44946d17e00179bbfc3b723aedc1831d8604e6a04bbd91170f1d894d04657bbffffffff02e6ec8500000000001976a914b5befddad83d9180fd4082c5528cf5a779b0fa6688acdf220000000000001976a9142c21a1be4239eeed678a456627a08d5f813d5c9288ac00000000', + * // txid: + * // 'b25d24fbb42d84812ed2cb55797f10fdec41afc7906ab563d1ec8c8676a2037f', + * // hash: + * // 'b25d24fbb42d84812ed2cb55797f10fdec41afc7906ab563d1ec8c8676a2037f', + * // size: 225, + * // version: 1, + * // locktime: 0, + * // vin: [ [Object] ], + * // vout: [ [Object], [Object] ], + * // blockhash: + * // '000000000000000003a09a7d68a0d62fd0ab51c368372e46bac84277e2df47e2', + * // confirmations: 16151, + * // time: 1547752564, + * // blocktime: 1547752564 } ] + */ + async getRawTransaction(txid, verbose = false) { + try { + if (typeof txid === "string") { + const response = await axios.get( + `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=${verbose}`, + _this.axiosOptions + ) + + return response.data + } else if (Array.isArray(txid)) { + const options = { + method: "POST", + url: `${this.restURL}rawtransactions/getRawTransaction`, + data: { + txids: txid, + verbose: verbose + }, + headers: { + authorization: `Token ${this.apiToken}` + } + } + const response = await axios(options) + + return response.data + } + + throw new Error(`Input must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } + + /** + * @api RawTransactions.sendRawTransaction() sendRawTransaction() - Submits raw transaction (serialized, hex-encoded) to local node and network. + * @apiName sendRawTransaction + * @apiGroup RawTransactions + * @apiDescription + * Submits raw transaction (serialized, hex-encoded) to local node and network. Also see createrawtransaction and signrawtransaction calls. + * + * For bulk uploads, transactions must use different UTXOs. + * + * @apiExample Example usage: + * // single tx + * (async () => { + * try { + * let sendRawTransaction = await bchjs.RawTransactions.sendRawTransaction("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"); + * console.log(sendRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * // 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 + * + * // single tx as array + * (async () => { + * try { + * let sendRawTransaction = await bchjs.RawTransactions.sendRawTransaction(["01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"]); + * console.log(sendRawTransaction); + * } catch(error) { + * console.error(error) + * } + * })() + * // ['0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098'] + */ + async sendRawTransaction(hex, allowhighfees = false) { + try { + // Single tx hex. + if (typeof hex === "string") { + const response = await axios.get( + `${this.restURL}rawtransactions/sendRawTransaction/${hex}`, + _this.axiosOptions + ) + + if (response.data === "66: insufficient priority") { + console.warn( + `WARN: Insufficient Priority! This is likely due to a fee that is too low, or insufficient funds. + Please ensure that there is BCH in the given wallet. If you are running on the testnet, get some + BCH from the testnet faucet at https://developer.bitcoin.com/faucets/bch` + ) + } + + return response.data + + // Array input + } else if (Array.isArray(hex)) { + const options = { + method: "POST", + url: `${this.restURL}rawtransactions/sendRawTransaction`, + data: { + hexes: hex + }, + headers: { + authorization: `Token ${this.apiToken}` + } + } + const response = await axios(options) + + return response.data + } + + throw new Error(`Input hex must be a string or array of strings.`) + } catch (error) { + if (error.response && error.response.data) throw error.response.data + else throw error + } + } +} + +module.exports = RawTransactions diff --git a/src/schnorr.js b/src/schnorr.js new file mode 100644 index 0000000..e86aa6a --- /dev/null +++ b/src/schnorr.js @@ -0,0 +1,502 @@ +const schnorr = require("bip-schnorr") + +class Schnorr { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + } + + /** + * @api Schnorr.sign() sign() - Sign a 32-byte message with the private key, returning a 64-byte signature. + * @apiName sign + * @apiGroup Schnorr + * @apiDescription + * Sign a 32-byte message with the private key, returning a 64-byte signature. + * + * @apiExample Example usage: + * const Buffer = require("safe-buffer").Buffer + * const BigInteger = require("bigi") + * + * // signing + * const privateKey = BigInteger.fromHex( + * "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" + * ) + * const message = Buffer.from( + * "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + * "hex" + * ) + * const createdSignature = bchjs.Schnorr.sign(privateKey, message) + * console.log("The signature is: " + createdSignature.toString("hex")) + * // The signature is: 2a298dacae57395a15d0795ddbfd1dcb564da82b0f269bc70a74f8220429ba1d1e51a22ccec35599b8f266912281f8365ffc2d035a230434a1a64dc59f7013fd + */ + sign(privateKey, message) { + return schnorr.sign(privateKey, message) + } + + /** + * @api Schnorr.verify() verify() - Verify a 64-byte signature of a 32-byte message against the public key. + * @apiName verify + * @apiGroup Schnorr + * @apiDescription + * Verify a 64-byte signature of a 32-byte message against the public key. Throws an Error if verification fails. + * + * @apiExample Example usage: + * const Buffer = require("safe-buffer").Buffer + * const publicKey = Buffer.from( + * "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + * "hex" + * ) + * const message = Buffer.from( + * "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + * "hex" + * ) + * const signatureToVerify = Buffer.from( + * "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + * "hex" + * ) + * try { + * bchjs.Schnorr.verify(publicKey, message, signatureToVerify) + * console.log("The signature is valid.") + * } catch (e) { + * console.error("The signature verification failed: " + e) + * } + */ + verify(publicKey, message, signatureToVerify) { + return schnorr.verify(publicKey, message, signatureToVerify) + } + + /** + * @api Schnorr.batchVerify() batchVerify() - Verify a list of 64-byte signatures as a batch operation. + * @apiName batchVerify + * @apiGroup Schnorr + * @apiDescription + * Verify a list of 64-byte signatures as a batch operation. Throws an Error if verification fails. + * + * @apiExample Example usage: + * const Buffer = require("safe-buffer").Buffer + * const publicKeys = [ + * Buffer.from( + * "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + * "hex" + * ), + * Buffer.from( + * "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", + * "hex" + * ), + * Buffer.from( + * "026D7F1D87AB3BBC8BC01F95D9AECE1E659D6E33C880F8EFA65FACF83E698BBBF7", + * "hex" + * ) + * ] + * const messages = [ + * Buffer.from( + * "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + * "hex" + * ), + * Buffer.from( + * "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C", + * "hex" + * ), + * Buffer.from( + * "B2F0CD8ECB23C1710903F872C31B0FD37E15224AF457722A87C5E0C7F50FFFB3", + * "hex" + * ) + * ] + * const signatures = [ + * Buffer.from( + * "2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD", + * "hex" + * ), + * Buffer.from( + * "00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380", + * "hex" + * ), + * Buffer.from( + * "68CA1CC46F291A385E7C255562068357F964532300BEADFFB72DD93668C0C1CAC8D26132EB3200B86D66DE9C661A464C6B2293BB9A9F5B966E53CA736C7E504F", + * "hex" + * ) + * ] + * try { + * bchjs.Schnorr.batchVerify(publicKeys, messages, signatures) + * console.log("The signatures are valid.") + * } catch (e) { + * console.error("The signature verification failed: " + e) + * } + */ + batchVerify(publicKeys, messages, signaturesToVerify) { + return schnorr.batchVerify(publicKeys, messages, signaturesToVerify) + } + + /** + * @api Schnorr.nonInteractive() nonInteractive() - Aggregates multiple signatures of different private keys + * @apiName nonInteractive + * @apiGroup Schnorr + * @apiDescription + * Aggregates multiple signatures of different private keys over the same message into a single 64-byte signature using a scheme that is safe from rogue-key attacks. + * + * This non-interactive scheme requires the knowledge of all private keys that are participating in the multi-signature creation. + * + * @apiExample Example usage: + * const Buffer = require("safe-buffer").Buffer + * const BigInteger = require("bigi") + * + * const privateKey1 = BigInteger.fromHex( + * "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF" + * ) + * const privateKey2 = BigInteger.fromHex( + * "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7" + * ) + * const message = Buffer.from( + * "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + * "hex" + * ) + * const aggregatedSignature = bchjs.Schnorr.nonInteractive( + * [privateKey1, privateKey2], + * message + * ) + * + * // verifying an aggregated signature + * const publicKey1 = Buffer.from( + * "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + * "hex" + * ) + * const publicKey2 = Buffer.from( + * "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B", + * "hex" + * ) + * const X = bchjs.Schnorr.publicKeyCombine([publicKey1, publicKey2]) + * try { + * bchjs.Schnorr.verify(X, message, aggregatedSignature) + * console.log("The signature is valid.") + * } catch (e) { + * console.error("The signature verification failed: " + e) + * } + */ + nonInteractive(privateKeys, message) { + return schnorr.muSig.nonInteractive(privateKeys, message) + } + + /** + * @api Schnorr.computeEll() computeEll() - Generate ell which is the hash over all public keys participating in a session. + * @apiName computeEll + * @apiGroup Schnorr + * @apiDescription + * Generate ell which is the hash over all public keys participating in a session. + * + * @apiExample Example usage: + * const Buffer = require("safe-buffer").Buffer + * const BigInteger = require("bigi") + * + * const publicData = { + * pubKeys: [ + * Buffer.from( + * "03846f34fdb2345f4bf932cb4b7d278fb3af24f44224fb52ae551781c3a3cad68a", + * "hex" + * ), + * Buffer.from( + * "02cd836b1d42c51d80cef695a14502c21d2c3c644bc82f6a7052eb29247cf61f4f", + * "hex" + * ), + * Buffer.from( + * "03b8c1765111002f09ba35c468fab273798a9058d1f8a4e276f45a1f1481dd0bdb", + * "hex" + * ) + * ], + * message: bchjs.Schnorr.hash(Buffer.from("muSig is awesome!", "utf8")), + * pubKeyHash: null, + * pubKeyCombined: null, + * commitments: [], + * nonces: [], + * nonceCombined: null, + * partialSignatures: [], + * signature: null + * } + * + * // data only known by the individual party, these values are never shared + * // between the signers! + * const signerPrivateData = [ + * // signer 1 + * { + * privateKey: BigInteger.fromHex( + * "add2b25e2d356bec3770305391cbc80cab3a40057ad836bcb49ef3eed74a3fee" + * ), + * session: null + * }, + * // signer 2 + * { + * privateKey: BigInteger.fromHex( + * "0a1645eef5a10e1f5011269abba9fd85c4f0cc70820d6f102fb7137f2988ad78" + * ), + * session: null + * }, + * // signer 3 + * { + * privateKey: BigInteger.fromHex( + * "2031e7fed15c770519707bb092a6337215530e921ccea42030c15d86e8eaf0b8" + * ), + * session: null + * } + * ] + * + * // ----------------------------------------------------------------------- + * // Step 1: Combine the public keys + * // The public keys P_i are combined into the combined public key P. + * // This can be done by every signer individually or by the initializing + * // party and then be distributed to every participant. + * // ----------------------------------------------------------------------- + * publicData.pubKeyHash = bchjs.Schnorr.computeEll(publicData.pubKeys) + */ + computeEll(publicKeys) { + return schnorr.muSig.computeEll(publicKeys) + } + + /** + * @api Schnorr.publicKeyCombine() publicKeyCombine() - Creates a special rogue-key-resistant. + * @apiName publicKeyCombine + * @apiGroup Schnorr + * @apiDescription + * Creates the special rogue-key-resistant combined public key P by applying the MuSig coefficient to each public key P_i before adding them together. + * + * @apiExample Example usage: + * // continued from above + * publicData.pubKeyCombined = bchjs.Schnorr.publicKeyCombine( + * publicData.pubKeys, + * publicData.pubKeyHash + * ) + */ + publicKeyCombine(publicKeys, publicKeyHash) { + return schnorr.muSig.pubKeyCombine(publicKeys, publicKeyHash) + } + + /** + * @api Schnorr.sessionInitialize() sessionInitialize() - Creates a signing session. + * @apiName sessionInitialize + * @apiGroup Schnorr + * @apiDescription + * Creates a signing session. Each participant must create a session and must not share the content of the session apart from the commitment and later the nonce. + * + * @apiExample Example usage: + * // continued from above + * // ----------------------------------------------------------------------- + * // Step 2: Create the private signing session + * // Each signing party does this in private. The session ID *must* be + * // unique for every call to sessionInitialize, otherwise it's trivial for + * // an attacker to extract the secret key! + * // ----------------------------------------------------------------------- + * signerPrivateData.forEach((data, idx) => { + * const sessionId = bchjs.Crypto.randomBytes(32) // must never be reused between sessions! + * data.session = bchjs.Schnorr.sessionInitialize( + * sessionId, + * data.privateKey, + * publicData.message, + * publicData.pubKeyCombined, + * publicData.pubKeyHash, + * idx + * ) + * }) + * const signerSession = signerPrivateData[0].session + */ + sessionInitialize(sessionId, privateKey, message, pubKeyCombined, ell, idx) { + return schnorr.muSig.sessionInitialize( + sessionId, + privateKey, + message, + pubKeyCombined, + ell, + idx + ) + } + + /** + * @api Schnorr.sessionNonceCombine() sessionNonceCombine() - Combines multiple nonces R_i into the combined nonce R. + * @apiName sessionNonceCombine + * @apiGroup Schnorr + * @apiDescription + * Combines multiple nonces R_i into the combined nonce R. + * + * @apiExample Example usage: + * // continued from above + * // ----------------------------------------------------------------------- + * // Step 3: Exchange commitments (communication round 1) + * // The signers now exchange the commitments H(R_i). This is simulated here + * // by copying the values from the private data to public data array. + * // ----------------------------------------------------------------------- + * for (let i = 0; i < publicData.pubKeys.length; i++) { + * publicData.commitments[i] = signerPrivateData[i].session.commitment + * } + * + * // ----------------------------------------------------------------------- + * // Step 4: Get nonces (communication round 2) + * // Now that everybody has commited to the session, the nonces (R_i) can be + * // exchanged. Again, this is simulated by copying. + * // ----------------------------------------------------------------------- + * for (let i = 0; i < publicData.pubKeys.length; i++) { + * publicData.nonces[i] = signerPrivateData[i].session.nonce + * } + * + * // ----------------------------------------------------------------------- + * // Step 5: Combine nonces + * // The nonces can now be combined into R. Each participant should do this + * // and keep track of whether the nonce was negated or not. This is needed + * // for the later steps. + * // ----------------------------------------------------------------------- + * publicData.nonceCombined = bchjs.Schnorr.sessionNonceCombine( + * signerSession, + * publicData.nonces + * ) + * signerPrivateData.forEach( + * data => (data.session.nonceIsNegated = signerSession.nonceIsNegated) + * ) + */ + sessionNonceCombine(session, nonces) { + return schnorr.muSig.sessionNonceCombine(session, nonces) + } + + /** + * @api Schnorr.partialSign() partialSign() - Creates a partial signature s_i for a participant. + * @apiName partialSign + * @apiGroup Schnorr + * @apiDescription + * Creates a partial signature s_i for a participant. + * + * @apiExample Example usage: + * // continued from above + * // ----------------------------------------------------------------------- + * // Step 6: Generate partial signatures + * // Every participant can now create their partial signature s_i over the + * // given message. + * // ----------------------------------------------------------------------- + * signerPrivateData.forEach(data => { + * data.session.partialSignature = bchjs.Schnorr.partialSign( + * data.session, + * publicData.message, + * publicData.nonceCombined, + * publicData.pubKeyCombined + * ) + * }) + */ + partialSign(session, message, nonceCombined, pubKeyCombined) { + return schnorr.muSig.partialSign( + session, + message, + nonceCombined, + pubKeyCombined + ) + } + + /** + * @api Schnorr.partialSignatureVerify() partialSignatureVerify() - Verifies a partial signature s_i against the participant's public key P_i. + * @apiName partialSignatureVerify + * @apiGroup Schnorr + * @apiDescription + * Verifies a partial signature s_i against the participant's public key P_i. Throws an Error if verification fails. + * + * @apiExample Example usage: + * // continued from above + * // ----------------------------------------------------------------------- + * // Step 7: Exchange partial signatures (communication round 3) + * // The partial signature of each signer is exchanged with the other + * // participants. Simulated here by copying. + * // ----------------------------------------------------------------------- + * for (let i = 0; i < publicData.pubKeys.length; i++) { + * publicData.partialSignatures[i] = + * signerPrivateData[i].session.partialSignature + * } + * + * // ----------------------------------------------------------------------- + * // Step 8: Verify individual partial signatures + * // Every participant should verify the partial signatures received by the + * // other participants. + * // ----------------------------------------------------------------------- + * for (let i = 0; i < publicData.pubKeys.length; i++) { + * bchjs.Schnorr.partialSignatureVerify( + * signerSession, + * publicData.partialSignatures[i], + * publicData.nonceCombined, + * i, + * publicData.pubKeys[i], + * publicData.nonces[i] + * ) + * } + */ + partialSignatureVerify( + session, + partialSignature, + nonceCombined, + idx, + pubKey, + nonce + ) { + return schnorr.muSig.partialSigVerify( + session, + partialSignature, + nonceCombined, + idx, + pubKey, + nonce + ) + } + + /** + * @api Schnorr.partialSignaturesCombine() partialSignaturesCombine() - Combines multiple partial signatures into a Schnorr signature. + * @apiName partialSignaturesCombine + * @apiGroup Schnorr + * @apiDescription + * Combines multiple partial signatures into a Schnorr signature (s, R) that can be verified against the combined public key P. + * + * @apiExample Example usage: + * // continued from above + * // ----------------------------------------------------------------------- + * // Step 9: Combine partial signatures + * // Finally, the partial signatures can be combined into the full signature + * // (s, R) that can be verified against combined public key P. + * // ----------------------------------------------------------------------- + * publicData.signature = bchjs.Schnorr.partialSignaturesCombine( + * publicData.nonceCombined, + * publicData.partialSignatures + * ) + * + * // ----------------------------------------------------------------------- + * // Step 10: Verify signature + * // The resulting signature can now be verified as a normal Schnorr + * // signature (s, R) over the message m and public key P. + * // ----------------------------------------------------------------------- + * bchjs.Schnorr.verify( + * publicData.pubKeyCombined, + * publicData.message, + * publicData.signature + * ) + */ + partialSignaturesCombine(nonceCombined, partialSignatures) { + return schnorr.muSig.partialSigCombine(nonceCombined, partialSignatures) + } + + bufferToInt(buffer) { + return schnorr.convert.bufferToInt(buffer) + } + + intToBuffer(bigInteger) { + return schnorr.convert.intToBuffer(bigInteger) + } + + hash(buffer) { + return schnorr.convert.hash(buffer) + } + + pointToBuffer(point) { + return schnorr.convert.pointToBuffer(point) + } + + pubKeyToPoint(publicKey) { + return schnorr.convert.pubKeyToPoint(publicKey) + } +} + +module.exports = Schnorr diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..eff1fc1 --- /dev/null +++ b/src/script.js @@ -0,0 +1,243 @@ +const Bitcoin = require("bitcoincashjs-lib") +const opcodes = require("bitcoincash-ops") + +class Script { + constructor() { + this.opcodes = opcodes + this.nullData = Bitcoin.script.nullData + this.multisig = { + input: { + encode: signatures => { + const sigs = [] + signatures.forEach(sig => { + sigs.push(sig) + }) + return Bitcoin.script.multisig.input.encode(sigs) + }, + decode: Bitcoin.script.multisig.input.decode, + check: Bitcoin.script.multisig.input.check + }, + output: { + encode: (m, pubKeys) => { + const pks = [] + pubKeys.forEach(pubKey => { + pks.push(pubKey) + }) + return Bitcoin.script.multisig.output.encode(m, pks) + }, + decode: Bitcoin.script.multisig.output.decode, + check: Bitcoin.script.multisig.output.check + } + } + this.pubKey = Bitcoin.script.pubKey + this.pubKeyHash = Bitcoin.script.pubKeyHash + this.scriptHash = Bitcoin.script.scriptHash + } + + /** + * @api Script.classifyInput() classifyInput() + * @apiName classifyInput + * @apiGroup Script + * @apiDescription + * Classify transaction input. + * + * @apiExample Example usage: + * let pubkeyInput = "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441"; + * bchjs.Script.classifyInput(bchjs.Script.fromASM(pubkeyInput)); + * // pubkey + * + * let pubkeyhashInput = "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441 02969479fa9bea3082697dce683ac05b13ae63016b41d5ca1a450ad40f6c543751"; + * bchjs.Script.classifyInput(bchjs.Script.fromASM(pubkeyhashInput)); + * // pubkeyhash + * + * let multisigInput = "OP_0 3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01"; + * bchjs.Script.classifyInput(bchjs.Script.fromASM(multisigInput)); + * // multisig + * + * let scripthashInput = "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae"; + * bchjs.Script.classifyInput(bchjs.Script.fromASM(scripthashInput)); + * // scripthash + */ + classifyInput(script) { + return Bitcoin.script.classifyInput(script) + } + + /** + * @api Script.classifyOutput() classifyOutput() + * @apiName classifyOutput + * @apiGroup Script + * @apiDescription + * Classify transaction output. + * + * @apiExample Example usage: + * let nullDataOutput = "OP_RETURN 424348466f7245766572796f6e65"; + * bchjs.Script.classifyOutput(bchjs.Script.fromASM(nullDataOutput)); + * // nulldata + * + * let pubkeyOutput = "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 OP_CHECKSIG"; + * bchjs.Script.classifyOutput(bchjs.Script.fromASM(pubkeyOutput)); + * // pubkey + * + * let pubkeyhashOutput = "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG"; + * bchjs.Script.classifyOutput(bchjs.Script.fromASM(pubkeyhashOutput)); + * // pubkeyhash + * + * let multisigOutput = "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG"; + * bchjs.Script.classifyOutput(bchjs.Script.fromASM(multisigOutput)); + * // multisig + * + * let scripthashOutput = "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL"; + * bchjs.Script.classifyOutput(bchjs.Script.fromASM(scripthashOutput)); + * // scripthash + */ + classifyOutput(script) { + return Bitcoin.script.classifyOutput(script) + } + + /** + * @api Script.decode() decode() + * @apiName decode + * @apiGroup Script + * @apiDescription + * Decode a Script buffer. + * + * @apiExample Example usage: + * // decode P2PKH scriptSig buffer + * let scriptSigBuffer = Buffer.from("483045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a6012102fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb", 'hex'); + * bchjs.Script.decode(scriptSigBuffer); + * // [ , ] + * + * // decode P2PKH scriptPubKey buffer + * let scriptPubKeyBuffer = Buffer.from("76a91424e9c07804d0ee7e5bda934e0a3ae8710fc007dd88ac", 'hex'); + * bchjs.Script.decode(scriptPubKeyBuffer); + * // [ 118, + * // 169, + * // , + * // 136, + * // 172 ] + */ + decode(scriptBuffer) { + return Bitcoin.script.decompile(scriptBuffer) + } + + /** + * @api Script.encode() encode() + * @apiName encode + * @apiGroup Script + * @apiDescription + * Encode a Script buffer with minimal push data. This function is used for + * Script files like CashScript. However, it will mangle the OP_RETURN of + * an SLP token transaction and will burn the tokens as a result. Use encode2() + * instead for that. + * + * @apiExample Example usage: + * // encode P2PKH scriptSig to buffer + * let scriptSig = [ + * Buffer.from('3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601', 'hex'), + * Buffer.from('02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb', 'hex') + * ] + * bchjs.Script.encode(scriptSig); + * // + * + * // encode P2PKH scriptPubKey to buffer + * let scriptPubKey = [ + * 118, + * 169, + * Buffer.from('24e9c07804d0ee7e5bda934e0a3ae8710fc007dd', 'hex'), + * 136, + * 172 + * ]; + * bchjs.Script.encode(scriptPubKey); + * // + */ + encode(scriptChunks) { + const arr = [] + scriptChunks.forEach(chunk => { + arr.push(chunk) + }) + return Bitcoin.script.compile(arr) + } + + /** + * @api Script.encode2() encode2() + * @apiName encode2 + * @apiGroup Script + * @apiDescription + * Encode a Script buffer without minimal push data. This should be used if + * encode() does not produce the desired results. This should be used for compiling + * SLP OP_RETURNs. + * + * @apiExample Example usage: + * // encode P2PKH scriptSig to buffer + * let scriptSig = [ + * Buffer.from('3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601', 'hex'), + * Buffer.from('02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb', 'hex') + * ] + * bchjs.Script.encode2(scriptSig); + * // + * + * // encode P2PKH scriptPubKey to buffer + * let scriptPubKey = [ + * 118, + * 169, + * Buffer.from('24e9c07804d0ee7e5bda934e0a3ae8710fc007dd', 'hex'), + * 136, + * 172 + * ]; + * bchjs.Script.encode2(scriptPubKey); + * // + */ + encode2(scriptChunks) { + const arr = [] + scriptChunks.forEach(chunk => { + arr.push(chunk) + }) + return Bitcoin.script.compile2(arr) + } + + /** + * @api Script.toASM() toASM() + * @apiName toASM + * @apiGroup Script + * @apiDescription + * Script buffer to ASM. + * + * @apiExample Example usage: + * // P2PKH scriptSig + * let scriptSigBuffer = Buffer.from('483045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a6012102fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb', 'hex'); + * bchjs.Script.toASM(scriptSigBuffer); + * // 3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601 02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb + * + * // P2PKH scriptPubKey + * let scriptBuffer = Buffer.from("76a914bee4182d9fbc8931a728410a0cd3e0f340f2995a88ac", 'hex'); + * bchjs.Script.toASM(scriptBuffer); + * // OP_DUP OP_HASH160 bee4182d9fbc8931a728410a0cd3e0f340f2995a OP_EQUALVERIFY OP_CHECKSIG + */ + toASM(buffer) { + return Bitcoin.script.toASM(buffer) + } + + /** + * @api Script.fromASM() fromASM() + * @apiName fromASM + * @apiGroup Script + * @apiDescription + * Script ASM to buffer. + * + * @apiExample Example usage: + * // P2PKH scriptSig + * let scriptSigASM = "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601 02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb"; + * bchjs.Script.fromASM(scriptSigASM); + * // + * + * // P2PKH scriptPubKey + * let scriptPubKeyASM = "OP_DUP OP_HASH160 bee4182d9fbc8931a728410a0cd3e0f340f2995a OP_EQUALVERIFY OP_CHECKSIG"; + * bchjs.Script.fromASM(scriptPubKeyASM); + * // + */ + fromASM(asm) { + return Bitcoin.script.fromASM(asm) + } +} + +module.exports = Script diff --git a/src/slp/address.js b/src/slp/address.js new file mode 100644 index 0000000..7dab706 --- /dev/null +++ b/src/slp/address.js @@ -0,0 +1,658 @@ +const BCHJSAddress = require("../address") +// const bchAddress = new BCHJSAddress() +let bchAddress + +const bchaddrjs = require("bchaddrjs-slp") + +class Address extends BCHJSAddress { + constructor(config) { + super(config) + + this.restURL = config.restURL + + bchAddress = new BCHJSAddress(config) + } + + /** + * @api SLP.Address.toSLPAddress() toSLPAddress() - Converting to slpAddress format. + * @apiName toSLPAddress + * @apiGroup SLP + * @apiDescription Converting legacy or cashaddr to slpAddress format. + * + * @apiExample Example usage: + * // mainnet legacy + * bchjs.SLP.Address.toSLPAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN') + * // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzd + * + * // mainnet legacy return no prefix + * bchjs.SLP.Address.toSLPAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN', false) + * // qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // mainnet cashaddr + * bchjs.SLP.Address.toSLPAddress('bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp + * + * // mainnet slpaddr no prefix + * bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp + * + * // tesnet legacy + * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx') + * // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv + * + * // testnet legacy return no prefix + * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx', false) + * // qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv + * + * // tesnet cashaddr + * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx') + * // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv + * + * // testnet cashaddr no prefix + * bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx', false) + * // qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv + */ + toSLPAddress(address, prefix = true, regtest = false) { + this._ensureValidAddress(address) + const slpAddress = bchaddrjs.toSlpAddress(address) + if (prefix) return slpAddress + return slpAddress.split(":")[1] + } + + /** + * @api SLP.Address.toCashAddress() toCashAddress() - Converting to cashAddress format. + * @apiName toCashAddress + * @apiGroup SLP + * @apiDescription Converting legacy or slpaddr to cashAddress format. + * + * @apiExample Example usage: + * // mainnet legacy + * bchjs.SLP.Address.toCashAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN') + * // bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // mainnet legacy return no prefix + * bchjs.SLP.Address.toCashAddress('1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN', false) + * // qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // mainnet slpaddr + * bchjs.SLP.Address.toCashAddress('simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp') + * // bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // mainnet slpaddr no prefix + * bchjs.SLP.Address.toCashAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp') + * // qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl + * + * // tesnet legacy + * bchjs.SLP.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx') + * // bchtest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + * + * // testnet legacy return no prefix + * bchjs.SLP.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx', false) + * // qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + * + * // tesnet cashaddr + * bchjs.SLP.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx') + * // bchtest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + * + * // testnet cashaddr no prefix + * bchjs.SLP.Address.toCashAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx', false) + * // qzq9je6pntpva3wf6scr7mlnycr54sjgeqxgrr9ku3 + */ + toCashAddress(address, prefix = true, regtest = false) { + this._ensureValidAddress(address) + const cashAddress = bchaddrjs.toCashAddress(address) + if (prefix) return cashAddress + return cashAddress.split(":")[1] + } + + /** + * @api SLP.Address.toLegacyAddress() toLegacyAddress() - Converting to legacy address format. + * @apiName toLegacyAddress + * @apiGroup SLP + * @apiDescription Converting cashaddr or slpaddr to legacy address format. + * + * @apiExample Example usage: + * + * // mainnet cashaddr w/ prefix + * bchjs.SLP.Address.toLegacyAddress('bitcoincash:qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // mainnet cashaddr w/ no prefix + * bchjs.SLP.Address.toLegacyAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // mainnet slpaddr w/ prefix + * bchjs.SLP.Address.toLegacyAddress('simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // mainnet slpaddr w/ no prefix + * bchjs.SLP.Address.toLegacyAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp') + * // 1HiaTupadqQN66Tvgt7QSE5Wg13BUy25eN + * + * // testnet cashaddr w/ prefix + * bchjs.SLP.Address.toLegacyAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + * + * // testnet cashaddr w/ no prefix + * bchjs.SLP.Address.toLegacyAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + * + * // testnet slpaddr w/ prefix + * bchjs.SLP.Address.toLegacyAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + * + * // testnet slpaddr w/ no prefix + * bchjs.SLP.Address.toLegacyAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // mqc1tmwY2368LLGktnePzEyPAsgADxbksi + */ + toLegacyAddress(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.toLegacyAddress(cashAddr) + } + + isLegacyAddress(address) { + this._ensureValidAddress(address) + return bchAddress.isLegacyAddress(address) + } + + isCashAddress(address) { + this._ensureValidAddress(address) + if (bchaddrjs.isSlpAddress(address)) return false + + return bchAddress.isCashAddress(address) + } + + /** + * @api SLP.Address.isSLPAddress() isSLPAddress() - Detect if slpAddr encoded address. + * @apiName isSLPAddress + * @apiGroup SLP + * @apiDescription Detect if slpAddr encoded address. + * + * @apiExample Example usage: + * + * // mainnet slpaddr + * bchjs.SLP.Address.isSLPAddress('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // mainnet w/ no slpaddr prefix + * bchjs.SLP.Address.isSLPAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // mainnet legacy + * bchjs.SLP.Address.isSLPAddress('18HEMuar5ZhXDFep1gEiY1eoPPcBLxfDxj') + * // false + * + * // testnet w/ slpaddr prefix + * bchjs.SLP.Address.isSLPAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // testnet w/ no slpaddr prefix + * bchjs.SLP.Address.isSLPAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // testnet legacy + * bchjs.SLP.Address.isSLPAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + isSLPAddress(address) { + this._ensureValidAddress(address) + return bchaddrjs.isSlpAddress(address) + } + + /** + * @api SLP.Address.isMainnetAddress() isMainnetAddress() - Detect if mainnet address. + * @apiName isMainnetAddress + * @apiGroup SLP + * @apiDescription Detect if mainnet address. + * + * @apiExample Example usage: + * + * // mainnet cashaddr + * bchjs.SLP.Address.isMainnetAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet cashaddr w/ no prefix + * bchjs.SLP.Address.isMainnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet slpaddr + * bchjs.SLP.Address.isMainnetAddress('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // mainnet slpaddr w/ no prefix + * bchjs.SLP.Address.isMainnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // mainnet legacy + * bchjs.SLP.Address.isMainnetAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // true + * + * // testnet cashaddr + * bchjs.SLP.Address.isMainnetAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet w/ no cashaddr prefix + * bchjs.SLP.Address.isMainnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // testnet slpaddr + * bchjs.SLP.Address.isMainnetAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // false + * + * // testnet w/ no slpaddr prefix + * bchjs.SLP.Address.isMainnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // false + * + * // testnet legacy + * bchjs.SLP.Address.isMainnetAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + isMainnetAddress(address) { + this._ensureValidAddress(address) + const cashaddr = bchaddrjs.toCashAddress(address) + return bchAddress.isMainnetAddress(cashaddr) + } + + /** + * @api SLP.Address.isTestnetAddress() isTestnetAddress() - Detect if testnet address. + * @apiName isTestnetAddress + * @apiGroup SLP + * @apiDescription Detect if testnet address. + * + * @apiExample Example usage: + * // cashaddr mainnet + * bchjs.SLP.Address.isTestnetAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * //false + * + * // w/ no cashaddr prefix + * bchjs.SLP.Address.isTestnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // slpaddr mainnet + * bchjs.SLP.Address.isTestnetAddress('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * //false + * + * // w/ no slpaddr prefix + * bchjs.SLP.Address.isTestnetAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // false + * + * // legacy mainnet + * bchjs.SLP.Address.isTestnetAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // false + * + * // cashaddr testnet + * bchjs.SLP.Address.isTestnetAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet w/ no cashaddr prefix + * bchjs.SLP.Address.isTestnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // slpaddr testnet + * bchjs.SLP.Address.isTestnetAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // testnet w/ no slpaddr prefix + * bchjs.SLP.Address.isTestnetAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // testnet legacy + * bchjs.SLP.Address.isTestnetAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + */ + isTestnetAddress(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.isTestnetAddress(cashAddr) + } + /** + * @api SLP.Address.isP2PKHAddress() isP2PKHAddress() - Detect if p2pkh address. + * @apiName isP2PKHAddress + * @apiGroup SLP + * @apiDescription Detect if p2pkh address. + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.SLP.Address.isP2PKHAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet w/ no cashaddr prefix + * bchjs.SLP.Address.isP2PKHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // true + * + * // mainnet slpaddr + * bchjs.SLP.Address.isP2PKHAddress('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // mainnet w/ no slpaddr prefix + * bchjs.SLP.Address.isP2PKHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // true + * + * // legacy + * bchjs.SLP.Address.isP2PKHAddress('14krEkSaKoTkbFT9iUCfUYARo4EXA8co6M') + * // true + * + * // cashaddr testnet + * bchjs.SLP.Address.isP2PKHAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // testnet w/ no cashaddr prefix + * bchjs.SLP.Address.isP2PKHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // true + * + * // slpaddr testnet + * bchjs.SLP.Address.isP2PKHAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // testnet w/ no slpaddr prefix + * bchjs.SLP.Address.isP2PKHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // true + * + * // legacy testnet + * bchjs.SLP.Address.isP2PKHAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // true + */ + isP2PKHAddress(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.isP2PKHAddress(cashAddr) + } + /** + * @api SLP.Address.isP2SHAddress() isP2SHAddress() - Detect if p2sh address. + * @apiName isP2SHAddress + * @apiGroup SLP + * @apiDescription Detect if p2sh address. + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.SLP.Address.isP2SHAddress('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // mainnet cashaddr w/ no prefix + * bchjs.SLP.Address.isP2SHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // false + * + * // mainnet slpaddr + * bchjs.SLP.Address.isP2SHAddress('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // false + * + * // mainnet slpaddr w/ no prefix + * bchjs.SLP.Address.isP2SHAddress('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // false + * + * // mainnet legacy + * bchjs.SLP.Address.isP2SHAddress('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // false + * + * // cashaddr testnet + * bchjs.SLP.Address.isP2SHAddress('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // cashaddr testnet w/ no prefix + * bchjs.SLP.Address.isP2SHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // false + * + * // slpaddr testnet + * bchjs.SLP.Address.isP2SHAddress('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // false + * + * // slpaddr testnet w/ no prefix + * bchjs.SLP.Address.isP2SHAddress('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // false + * + * // legacy testnet + * bchjs.SLP.Address.isP2SHAddress('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // false + */ + isP2SHAddress(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.isP2SHAddress(cashAddr) + } + /** + * @api SLP.Address.detectAddressFormat() detectAddressFormat() - Detect address format. + * @apiName detectAddressFormat + * @apiGroup SLP + * @apiDescription Detect address format. + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.SLP.Address.detectAddressFormat('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // cashaddr + * + * // mainnet cashaddr w/ no prefix + * bchjs.SLP.Address.detectAddressFormat('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // cashaddr + * + * // mainnet slpaddr + * bchjs.SLP.Address.detectAddressFormat('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // slpaddr + * + * // mainnet slpaddr w/ no prefix + * bchjs.SLP.Address.detectAddressFormat('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // slpaddr + * + * // mainnet legacy + * bchjs.SLP.Address.detectAddressFormat('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // legacy + * + * // cashaddr testnet + * bchjs.SLP.Address.detectAddressFormat('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // cashaddr + * + * // cashaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressFormat('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // cashaddr + * + * // slpaddr testnet + * bchjs.SLP.Address.detectAddressFormat('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // slpaddr + * + * // slpaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressFormat('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // slpaddr + * + * // legacy testnet + * bchjs.SLP.Address.detectAddressFormat('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // legacy + */ + detectAddressFormat(address) { + this._ensureValidAddress(address) + if (bchaddrjs.isSlpAddress(address)) return "slpaddr" + + return bchAddress.detectAddressFormat(address) + } + /** + * @api SLP.Address.detectAddressNetwork() detectAddressNetwork() - Detect address network. + * @apiName detectAddressNetwork + * @apiGroup SLP + * @apiDescription Detect address network. + * + * @apiExample Example usage: + * // mainnet cashaddr + * bchjs.SLP.Address.detectAddressNetwork('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // mainnet + * + * // mainnet cashaddr w/ no prefix + * bchjs.SLP.Address.detectAddressNetwork('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s') + * // mainnet + * + * // mainnet slpaddr + * bchjs.SLP.Address.detectAddressNetwork('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // mainnet + * + * // mainnet slpaddr w/ no prefix + * bchjs.SLP.Address.detectAddressNetwork('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w') + * // mainnet + * + * // mainnet legacy + * bchjs.SLP.Address.detectAddressNetwork('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74') + * // mainnet + * + * // cashaddr testnet + * bchjs.SLP.Address.detectAddressNetwork('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // testnet + * + * // cashaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressNetwork('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy') + * // testnet + * + * // slpaddr testnet + * bchjs.SLP.Address.detectAddressNetwork('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // testnet + * + * // slpaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressNetwork('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse') + * // testnet + * + * // legacy testnet + * bchjs.SLP.Address.detectAddressNetwork('mqc1tmwY2368LLGktnePzEyPAsgADxbksi') + * // testnet + */ + detectAddressNetwork(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.detectAddressNetwork(cashAddr) + } + /** + * @api SLP.Address.detectAddressType() detectAddressType() - Detect address type. + * @apiName detectAddressType + * @apiGroup SLP + * @apiDescription Detect address type. + * + * @apiExample Example usage: + * // mainet cashaddr + * bchjs.SLP.Address.detectAddressType('bitcoincash:qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s'); + * // p2pkh + * + * // mainet cashaddr w/ no prefix + * bchjs.SLP.Address.detectAddressType('qqfx3wcg8ts09mt5l3zey06wenapyfqq2qrcyj5x0s'); + * // p2pkh + * + * // mainet slpaddr + * bchjs.SLP.Address.detectAddressType('simpleledger:qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w'); + * // p2pkh + * + * // mainet slpaddr w/ no prefix + * bchjs.SLP.Address.detectAddressType('qqfx3wcg8ts09mt5l3zey06wenapyfqq2q0r0fpx3w'); + * // p2pkh + * + * // mainet legacy + * bchjs.SLP.Address.detectAddressType('1NoYQso5UF6XqC4NbjKAp2EnjJ59yLNn74'); + * // p2pkh + * + * // cashaddr testnet + * bchjs.SLP.Address.detectAddressType('bchtest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy'); + * // p2pkh + * + * // cashaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressType('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggt9t0a6zy'); + * // p2pkh + * + * // slpaddr testnet + * bchjs.SLP.Address.detectAddressType('slptest:qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse'); + * // p2pkh + * + * // slpaddr testnet w/ no prefix + * bchjs.SLP.Address.detectAddressType('qph2v4mkxjgdqgmlyjx6njmey0ftrxlnggs3v58dse'); + * // p2pkh + * + * // legacy testnet + * bchjs.SLP.Address.detectAddressType('mqc1tmwY2368LLGktnePzEyPAsgADxbksi'); + * // p2pkh + */ + detectAddressType(address) { + this._ensureValidAddress(address) + const cashAddr = bchaddrjs.toCashAddress(address) + return bchAddress.detectAddressType(cashAddr) + } + /* + async details(address) { + let tmpBITBOX + let network + if (typeof address === "string") + network = this.detectAddressNetwork(address) + else network = this.detectAddressNetwork(address[0]) + + if (network === "mainnet") + tmpBITBOX = new BITBOX({ restURL: "https://rest.bitcoin.com/v2/" }) + else tmpBITBOX = new BITBOX({ restURL: "https://trest.bitcoin.com/v2/" }) + + if (typeof address === "string") { + const cashAddr = bchaddrjs.toCashAddress(address) + return tmpBITBOX.Address.details(cashAddr) + } + address = address.map(address => bchaddrjs.toCashAddress(address)) + return tmpBITBOX.Address.details(address) + } + + async utxo(address) { + let tmpBITBOX + let network + if (typeof address === "string") + network = this.detectAddressNetwork(address) + else network = this.detectAddressNetwork(address[0]) + + if (network === "mainnet") + tmpBITBOX = new BITBOX({ restURL: "https://rest.bitcoin.com/v2/" }) + else tmpBITBOX = new BITBOX({ restURL: "https://trest.bitcoin.com/v2/" }) + + if (typeof address === "string") { + const cashAddr = bchaddrjs.toCashAddress(address) + return tmpBITBOX.Address.utxo(cashAddr) + } + address = address.map(address => bchaddrjs.toCashAddress(address)) + return tmpBITBOX.Address.utxo(address) + } + + async unconfirmed(address) { + let tmpBITBOX + let network + if (typeof address === "string") + network = this.detectAddressNetwork(address) + else network = this.detectAddressNetwork(address[0]) + + if (network === "mainnet") + tmpBITBOX = new BITBOX({ restURL: "https://rest.bitcoin.com/v2/" }) + else tmpBITBOX = new BITBOX({ restURL: "https://trest.bitcoin.com/v2/" }) + + if (typeof address === "string") { + const cashAddr = bchaddrjs.toCashAddress(address) + return tmpBITBOX.Address.unconfirmed(cashAddr) + } + address = address.map(address => bchaddrjs.toCashAddress(address)) + return tmpBITBOX.Address.unconfirmed(address) + } + + async transactions(address) { + let tmpBITBOX + let network + if (typeof address === "string") + network = this.detectAddressNetwork(address) + else network = this.detectAddressNetwork(address[0]) + + if (network === "mainnet") + tmpBITBOX = new BITBOX({ restURL: "https://rest.bitcoin.com/v2/" }) + else tmpBITBOX = new BITBOX({ restURL: "https://trest.bitcoin.com/v2/" }) + + if (typeof address === "string") { + const cashAddr = bchaddrjs.toCashAddress(address) + return tmpBITBOX.Address.transactions(cashAddr) + } + address = address.map(address => bchaddrjs.toCashAddress(address)) + return tmpBITBOX.Address.transactions(address) + } +*/ + _ensureValidAddress(address) { + try { + bchaddrjs.toCashAddress(address) + } catch (err) { + throw new Error( + `Invalid BCH address. Double check your address is valid: ${address}` + ) + } + } +} + +module.exports = Address diff --git a/src/slp/ecpair.js b/src/slp/ecpair.js new file mode 100644 index 0000000..0c87b14 --- /dev/null +++ b/src/slp/ecpair.js @@ -0,0 +1,34 @@ +//const BCHJS = require("../bch-js") +//const bchjs = new BCHJS() + +const BCHJSECPair = require("../ecpair") + +const bchaddrjs = require("bchaddrjs-slp") + +class ECPair extends BCHJSECPair { + /* + constructor(restURL) { + super(restURL) + this.restURL = restURL + } + */ + /** + * @api SLP.ECPair.toSLPAddress() toSLPAddress() - Get slp address of ECPair. + * @apiName toSLPAddress + * @apiGroup SLP + * @apiDescription Get slp address of ECPair. + * + * @apiExample Example usage: + * // create ecpair from wif + * let ecpair = bchjs.SLP.ECPair.fromWIF('cUCSrdhu7mCzx4sWqL6irqzprkofxPmLHYgkSnG2WaWVqJDXtWRS') + * // to slp address + * bchjs.SLP.ECPair.toSLPAddress(ecpair); + * // slptest:qq835u5srlcqwrtwt6xm4efwan30fxg9hcqag6fk03 + */ + static toSLPAddress(ecpair) { + const slpAddress = bchaddrjs.toSlpAddress(this.toCashAddress(ecpair)) + return slpAddress + } +} + +module.exports = ECPair diff --git a/src/slp/nft1.js b/src/slp/nft1.js new file mode 100644 index 0000000..631ec3a --- /dev/null +++ b/src/slp/nft1.js @@ -0,0 +1,411 @@ +/* + This library wraps the slp-mdm library to generate the OP_RETURN for NFT1 tokens. + + NFT Group tokens (Parents) are generated and minted. They are like amorphous + NFTs; like stem cells that haven't specialized yet. + + NFT 'Children' are the 'real' NFTs. They are created by burning an NFT Group + (Parent) token. +*/ + +const Address = require("./address") + +const BigNumber = require("bignumber.js") +const slpMdm = require("slp-mdm") + +// const addy = new Address() +let addy +const TransactionBuilder = require("../transaction-builder") + +class TokenType1 { + constructor(config) { + this.restURL = config.restURL + + addy = new Address(config) + + // Instantiate the transaction builder. + TransactionBuilder.setAddress(addy) + } + + /** + * @api SLP.NFT1.newNFTGroupOpReturn() newNFTGroupOpReturn() + * @apiName newNFTGroupOpReturn + * @apiGroup SLP NFT1 + * @apiDescription Generate the OP_RETURN value needed to create an SLP + * NFT Group token. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const configObj = { + * name: "SLP Test Token", + * ticker: "SLPTEST", + * documentUrl: "https://FullStack.cash", + * initialQty: 1 + * } + * + * const result = await bchjs.SLP.NFT1.newNFTGroupOpReturn( + * configObj + * ) + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/tree/master/applications/slp/nft + * + */ + newNFTGroupOpReturn(configObj) { + try { + // TODO: Add input validation. + + // Prevent error if user fails to add the document hash. + if (!configObj.documentHash) configObj.documentHash = "" + + // If mint baton is not specified, then replace it with null. + if (!configObj.mintBatonVout) configObj.mintBatonVout = null + + const script = slpMdm.NFT1.Group.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + 0, + configObj.mintBatonVout, + new slpMdm.BN(configObj.initialQty) + ) + + return script + } catch (err) { + console.log(`Error in generateNFTParentOpReturn()`) + throw err + } + } + + // Mint additional NFT Group 'Parent' tokens. + /** + * @api SLP.NFT1.mintNFTGroupOpReturn() mintNFTGroupOpReturn() + * @apiName mintNFTGroupOpReturn + * @apiGroup SLP NFT1 + * @apiDescription Generate the OP_RETURN value needed to create an SLP Mint + * transaction for an NFT Group token. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the minting baton. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.utxoType === "minting-baton" && // UTXO is not a minting baton. + * utxo.tokenType === 129 // UTXO is for NFT Group + * ) + * return true; + * }); + * + * // Generate the SLP OP_RETURN + * const slpData = bchjs.SLP.NFT1.mintNFTGroupOpReturn( + * tokenUtxos, + * 1 // Mint 1 new token. + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/tree/master/applications/slp/nft + */ + mintNFTGroupOpReturn(tokenUtxos, mintQty, destroyBaton = false) { + try { + // Throw error if input is not an array. + if (!Array.isArray(tokenUtxos)) + throw new Error(`tokenUtxos must be an array.`) + + // 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") + mintBatonUtxo = tokenUtxos[i] + } + + // Throw an error if the minting baton could not be found. + if (!mintBatonUtxo) + throw new Error(`Minting baton could not be found in tokenUtxos array.`) + + const tokenId = mintBatonUtxo.tokenId + + if (!tokenId) + throw new Error(`tokenId property not found in mint-baton UTXO.`) + + // Signal that the baton should be passed or detroyed. + let batonVout = 2 + if (destroyBaton) batonVout = null + + const script = slpMdm.NFT1.Group.mint( + tokenId, + batonVout, + new slpMdm.BN(mintQty) + ) + + return script + } catch (err) { + // console.log(`Error in generateMintOpReturn()`) + throw err + } + } + + /** + * @api SLP.NFT1.generateNFTChildGenesisOpReturn() generateNFTChildGenesisOpReturn() + * @apiName generateNFTChildGenesisOpReturn + * @apiGroup SLP NFT1 + * @apiDescription Generate the OP_RETURN value needed to create an SLP + * NFT Child token. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const configObj = { + * name: "NFT Child", + * ticker: "NFTC", + * documentUrl: "https://FullStack.cash", + * } + * + * const result = await bchjs.SLP.NFT1.generateNFTChildGenesisOpReturn( + * configObj + * ) + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/tree/master/applications/slp/nft + * + */ + generateNFTChildGenesisOpReturn(configObj) { + try { + // TODO: Add input validation. + + // Prevent error if user fails to add the document hash. + if (!configObj.documentHash) configObj.documentHash = "" + + // If mint baton is not specified, then replace it with null. + if (!configObj.mintBatonVout) configObj.mintBatonVout = null + + const script = slpMdm.NFT1.Child.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + 0, + configObj.mintBatonVout, + new slpMdm.BN("1") + ) + + return script + } catch (err) { + console.log(`Error in generateNFTChildGenesisOpReturn()`) + throw err + } + } + + /** + * @api SLP.NFT1.generateNFTChildSendOpReturn() generateNFTChildSendOpReturn() + * @apiName generateNFTChildSendOpReturn + * @apiGroup SLP NFT1 + * @apiDescription Generate the OP_RETURN value needed to send an SLP NFT + * Child token to another address. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the token UTXOs that match the user-provided token ID. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.tokenType === "token" && // UTXO is not a minting baton. + * utxo.tokenType === 65 // UTXO is for an NFT Child + * ) + * return true; + * }); + * + * // Generate the SEND OP_RETURN + * const slpData = bchjs.SLP.NFT1.generateNFTChildSendOpReturn( + * tokenUtxos, + * TOKENQTY + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/tree/master/applications/slp/nft + */ + generateNFTChildSendOpReturn(tokenUtxos, sendQty) { + try { + // TODO: Add input validation. + + const tokenId = tokenUtxos[0].tokenId + + // Calculate the total amount of tokens owned by the wallet. + let totalTokens = 0 + for (let i = 0; i < tokenUtxos.length; i++) + totalTokens += tokenUtxos[i].tokenQty + + const change = totalTokens - sendQty + + let script + let outputs = 1 + + // The normal case, when there is token change to return to sender. + if (change > 0) { + outputs = 2 + + // Convert to integer string. + const sendStr = Math.floor(sendQty).toString() + const changeStr = Math.floor(change).toString() + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.NFT1.Child.send(tokenId, [ + new slpMdm.BN(sendStr), + new slpMdm.BN(changeStr) + ]) + // + + // Corner case, when there is no token change to send back. + } else { + // Convert to integer string. + const sendStr = Math.floor(sendQty).toString() + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.NFT1.Child.send(tokenId, [new slpMdm.BN(sendStr)]) + } + + return { script, outputs } + } catch (err) { + console.log(`Error in generateNFTChildSendOpReturn()`) + throw err + } + } + + /** + * @api SLP.NFT1.generateNFTGroupSendOpReturn() generateNFTGroupSendOpReturn() + * @apiName generateNFTGroupSendOpReturn + * @apiGroup SLP NFT1 + * @apiDescription Generate the OP_RETURN value needed to send an SLP NFT + * Group token to another address. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the token UTXOs that match the user-provided token ID. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.tokenType === "token" && // UTXO is not a minting baton. + * utxo.tokenType === 129 // UTXO is for an NFT Group + * ) + * return true; + * }); + * + * // Generate the SEND OP_RETURN + * const slpData = bchjs.SLP.NFT1.generateNFTGroupSendOpReturn( + * tokenUtxos, + * TOKENQTY + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/tree/master/applications/slp/nft + */ + generateNFTGroupSendOpReturn(tokenUtxos, sendQty) { + try { + // TODO: Add input validation. + + const tokenId = tokenUtxos[0].tokenId + + // Calculate the total amount of tokens owned by the wallet. + let totalTokens = 0 + for (let i = 0; i < tokenUtxos.length; i++) + totalTokens += tokenUtxos[i].tokenQty + + const change = totalTokens - sendQty + + let script + let outputs = 1 + + // The normal case, when there is token change to return to sender. + if (change > 0) { + outputs = 2 + + // Convert to integer string. + const sendStr = Math.floor(sendQty).toString() + const changeStr = Math.floor(change).toString() + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.NFT1.Group.send(tokenId, [ + new slpMdm.BN(sendStr), + new slpMdm.BN(changeStr) + ]) + // + + // Corner case, when there is no token change to send back. + } else { + // Convert to integer string. + const sendStr = Math.floor(sendQty).toString() + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.NFT1.Group.send(tokenId, [new slpMdm.BN(sendStr)]) + } + + return { script, outputs } + } catch (err) { + console.log(`Error in generateNFTGroupSendOpReturn()`) + throw err + } + } +} + +module.exports = TokenType1 diff --git a/src/slp/slp.js b/src/slp/slp.js new file mode 100644 index 0000000..b91c366 --- /dev/null +++ b/src/slp/slp.js @@ -0,0 +1,40 @@ +/* + This is the parent library for the SLP class. It was originally forked from slp-sdk. + + TODO: Create an SLP fee calculator like slpjs: + https://github.com/simpleledger/slpjs/blob/master/lib/slp.ts#L921 +*/ + +// imports +// require deps +//const BCHJS = require("../bch-js") +const Address = require("./address") +const ECPair = require("./ecpair") +// const HDNode = require("./hdnode") +const TokenType1 = require("./tokentype1") +const NFT1 = require("./nft1") +const Utils = require("./utils") + +// SLP is a superset of BITBOX +class SLP { + constructor(config) { + const tmp = {} + if (!config || !config.restURL) { + tmp.restURL = `https://api.fullstack.cash/v3/` + } else { + tmp.restURL = config.restURL + tmp.apiToken = config.apiToken + } + + this.restURL = tmp.restURL + this.apiToken = tmp.apiToken + + this.Address = new Address(tmp) + this.ECPair = ECPair + this.TokenType1 = new TokenType1(tmp) + this.NFT1 = new NFT1(this.restURL) + this.Utils = new Utils(tmp) + } +} + +module.exports = SLP diff --git a/src/slp/tokentype1.js b/src/slp/tokentype1.js new file mode 100644 index 0000000..a1fd3fd --- /dev/null +++ b/src/slp/tokentype1.js @@ -0,0 +1,435 @@ +//const BCHJS = require("../bch-js") +//const bchjs = new BCHJS() + +const Address = require("./address") +const Script = require("../script") + +const BigNumber = require("bignumber.js") +const slpMdm = require("slp-mdm") +const axios = require("axios") + +// const addy = new Address() +let addy +const TransactionBuilder = require("../transaction-builder") + +let _this // local global + +class TokenType1 { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + addy = new Address(config) + this.Script = new Script() + + this.axios = axios + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + // Instantiate the transaction builder. + TransactionBuilder.setAddress(addy) + + _this = this + } + + /** + * @api SLP.TokenType1.generateSendOpReturn() generateSendOpReturn() + * @apiName generateSendOpReturn + * @apiGroup SLP TokenType1 + * @apiDescription Generate the OP_RETURN value needed to create an SLP Send transaction. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the token UTXOs that match the user-provided token ID. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.tokenType === "token" // UTXO is not a minting baton. + * ) + * return true; + * }); + * + * // Generate the SEND OP_RETURN + * const slpData = bchjs.SLP.TokenType1.generateSendOpReturn( + * tokenUtxos, + * TOKENQTY + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/send-token/send-token.js + */ + generateSendOpReturn(tokenUtxos, sendQty) { + try { + const tokenId = tokenUtxos[0].tokenId + const decimals = tokenUtxos[0].decimals + + // Calculate the total amount of tokens owned by the wallet. + let totalTokens = 0 + for (let i = 0; i < tokenUtxos.length; i++) + totalTokens += tokenUtxos[i].tokenQty + + const change = totalTokens - sendQty + // console.log(`change: ${change}`) + + let script + let outputs = 1 + + // The normal case, when there is token change to return to sender. + if (change > 0) { + outputs = 2 + + // Convert the send quantity to the format expected by slp-mdm. + let baseQty = new BigNumber(sendQty).times(10 ** decimals) + baseQty = baseQty.absoluteValue() + baseQty = Math.floor(baseQty) + baseQty = baseQty.toString() + // console.log(`baseQty: `, baseQty) + + // Convert the change quantity to the format expected by slp-mdm. + let baseChange = new BigNumber(change).times(10 ** decimals) + baseChange = baseChange.absoluteValue() + baseChange = Math.floor(baseChange) + baseChange = baseChange.toString() + // console.log(`baseChange: `, baseChange) + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.TokenType1.send(tokenId, [ + new slpMdm.BN(baseQty), + new slpMdm.BN(baseChange) + ]) + // + + // Corner case, when there is no token change to send back. + } else { + let baseQty = new BigNumber(sendQty).times(10 ** decimals) + baseQty = baseQty.absoluteValue() + baseQty = Math.floor(baseQty) + baseQty = baseQty.toString() + // console.log(`baseQty: `, baseQty) + + // console.log(`baseQty: ${baseQty.toString()}`) + + // Generate the OP_RETURN as a Buffer. + script = slpMdm.TokenType1.send(tokenId, [new slpMdm.BN(baseQty)]) + } + + return { script, outputs } + } catch (err) { + console.log(`Error in generateSendOpReturn()`) + throw err + } + } + + /** + * @api SLP.TokenType1.generateBurnOpReturn() generateBurnOpReturn() + * @apiName generateBurnOpReturn + * @apiGroup SLP TokenType1 + * @apiDescription Generate the OP_RETURN value needed to create a SLP Send + * transaction that burns tokens. + * This is a slight variation of generateSendOpReturn(). It generates a SLP + * SEND transaction designed to burn a select quantity of tokens. + * + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the token UTXOs that match the user-provided token ID. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.tokenType === "token" // UTXO is not a minting baton. + * ) + * return true; + * }); + * + * // Generate the SEND OP_RETURN + * const slpData = bchjs.SLP.TokenType1.generateBurnOpReturn( + * tokenUtxos, + * 10 // Burn 10 tokens + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/burn-tokens/burn-tokens.js + * + */ + generateBurnOpReturn(tokenUtxos, burnQty) { + try { + const tokenId = tokenUtxos[0].tokenId + const decimals = tokenUtxos[0].decimals + + // Calculate the total amount of tokens owned by the wallet. + let totalTokens = 0 + for (let i = 0; i < tokenUtxos.length; i++) + totalTokens += tokenUtxos[i].tokenQty + + const remainder = totalTokens - burnQty + + let baseQty = new BigNumber(remainder).times(10 ** decimals) + baseQty = baseQty.absoluteValue() + baseQty = Math.floor(baseQty) + baseQty = baseQty.toString() + + // console.log(`baseQty: ${baseQty.toString()}`) + + // Generate the OP_RETURN as a Buffer. + const script = slpMdm.TokenType1.send(tokenId, [new slpMdm.BN(baseQty)]) + + return script + } catch (err) { + console.log(`Error in generateBurnOpReturn()`) + throw err + } + } + + /** + * @api SLP.TokenType1.generateGenesisOpReturn() generateGenesisOpReturn() + * @apiName generateGenesisOpReturn + * @apiGroup SLP TokenType1 + * @apiDescription Generate the OP_RETURN value needed to create a new SLP token class. + * + * Expects a config object as input, see the example for properties.: + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const configObj = { + * name: "SLP Test Token", + * ticker: "SLPTEST", + * documentUrl: "https://FullStack.cash", + * documentHash: "", + * decimals: 8, + * initialQty: 10 + * } + * + * const result = await bchjs.SLP.TokenType1.generateGenesisOpReturn( + * configObj + * ) + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/create-token/create-token.js + * + */ + generateGenesisOpReturn(configObj) { + try { + // TODO: Add input validation. + + let baseQty = new BigNumber(configObj.initialQty).times( + 10 ** configObj.decimals + ) + baseQty = baseQty.absoluteValue() + baseQty = Math.floor(baseQty) + baseQty = baseQty.toString() + + // Prevent error if user fails to add the document hash. + if (!configObj.documentHash) configObj.documentHash = "" + + // If mint baton is not specified, then replace it with null. + if (!configObj.mintBatonVout) configObj.mintBatonVout = null + + const script = slpMdm.TokenType1.genesis( + configObj.ticker, + configObj.name, + configObj.documentUrl, + configObj.documentHash, + configObj.decimals, + configObj.mintBatonVout, + new slpMdm.BN(baseQty) + ) + + return script + } catch (err) { + console.log(`Error in generateGenesisOpReturn()`) + throw err + } + } + + // Expects tokenUtxos to be an array of UTXOs. Must contain a UTXO with the + // minting baton. + // mintQty is the number of new coins to mint. + // destroyBaton is an option Boolean. If true, will destroy the baton. By + // default it is false and will pass the baton. + /** + * @api SLP.TokenType1.generateMintOpReturn() generateMintOpReturn() + * @apiName generateMintOpReturn + * @apiGroup SLP TokenType1 + * @apiDescription Generate the OP_RETURN value needed to create an SLP Mint transaction. + * It's assumed all elements in the tokenUtxos array belong to the same token. + * + * Returns a Buffer representing a transaction output, ready to be added to + * the Transaction Builder. + * + * @apiExample Example usage: + * + * const addr = "bitcoincash:qq6xz6wwcy78uh79vgjvfyahj4arq269w5an8pcjak" + * const utxos = await bchjs.Blockbook.utxos(addr) + * + * // Identify the SLP token UTXOs. + * let tokenUtxos = await bchjs.SLP.Utils.tokenUtxoDetails(utxos); + * + * // Filter out the minting baton. + * tokenUtxos = tokenUtxos.filter((utxo, index) => { + * if ( + * utxo && // UTXO is associated with a token. + * utxo.tokenId === TOKENID && // UTXO matches the token ID. + * utxo.utxoType === "minting-baton" // UTXO is not a minting baton. + * ) + * return true; + * }); + * + * // Generate the SLP OP_RETURN + * const slpData = bchjs.SLP.TokenType1.generateMintOpReturn( + * tokenUtxos, + * 100 // Mint 100 new tokens. + * ); + * + * ... + * // Add OP_RETURN as first output. + * transactionBuilder.addOutput(slpData, 0); + * + * // See additional code here: + * // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/applications/slp/mint-token/mint-token.js + */ + generateMintOpReturn(tokenUtxos, mintQty, destroyBaton = false) { + try { + // Throw error if input is not an array. + if (!Array.isArray(tokenUtxos)) + throw new Error(`tokenUtxos must be an array.`) + + // 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") + mintBatonUtxo = tokenUtxos[i] + } + + // Throw an error if the minting baton could not be found. + if (!mintBatonUtxo) + throw new Error(`Minting baton could not be found in tokenUtxos array.`) + + const tokenId = mintBatonUtxo.tokenId + const decimals = mintBatonUtxo.decimals + + if (!tokenId) + throw new Error(`tokenId property not found in mint-baton UTXO.`) + if (!decimals) + throw new Error(`decimals property not found in mint-baton UTXO.`) + + let baseQty = new BigNumber(mintQty).times(10 ** decimals) + baseQty = baseQty.absoluteValue() + baseQty = Math.floor(baseQty) + baseQty = baseQty.toString() + + // Signal that the baton should be passed or detroyed. + let batonVout = 2 + if (destroyBaton) batonVout = null + + const script = slpMdm.TokenType1.mint( + tokenId, + batonVout, + new slpMdm.BN(baseQty) + ) + + return script + } catch (err) { + // console.log(`Error in generateMintOpReturn()`) + throw err + } + } + + /** + * @api SLP.TokenType1.getHexOpReturn() getHexOpReturn() + * @apiName getHexOpReturn + * @apiGroup SLP TokenType1 + * @apiDescription Get hex representation of an SLP OP_RETURN + * This command returns a hex encoded OP_RETURN for SLP Send (Token Type 1) + * transactions. Rather than computing it directly, it calls bch-api to do + * the heavy lifting. This is easier and lighter weight for web apps. + * + * @apiExample Example usage: + * + * const tokenUtxos = [{ + * tokenId: "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + * decimals: 0, + * tokenQty: 2 + * }] + * + * const sendQty = 1.5 + * + * const result = await bchjs.SLP.TokenType1.getHexOpReturn(tokenUtxos, sendQty) + * + * // result: + * { + * "script": "6a04534c500001010453454e44200a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1080000000000000001080000000000000000", + * "outputs": 2 + * } + */ + async getHexOpReturn(tokenUtxos, sendQty) { + try { + // TODO: Add input filtering. + + const data = { + tokenUtxos, + sendQty + } + + const result = await _this.axios.post( + `${this.restURL}slp/generatesendopreturn`, + data, + _this.axiosOptions + ) + + const slpSendObj = result.data + + // const script = _this.Buffer.from(slpSendObj.script) + // + // slpSendObj.script = script + // return slpSendObj + + return slpSendObj + } catch (err) { + console.log(err) + throw err + } + } +} + +module.exports = TokenType1 diff --git a/src/slp/utils.js b/src/slp/utils.js new file mode 100644 index 0000000..da0b7d6 --- /dev/null +++ b/src/slp/utils.js @@ -0,0 +1,1106 @@ +const axios = require("axios") +const slpParser = require("slp-parser") + +const Script = require("../script") +const scriptLib = new Script() + +const BigNumber = require("bignumber.js") + +let _this + +class Utils { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + this.slpParser = slpParser + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api SLP.Utils.list() list() + * @apiName list + * @apiGroup SLP Utils + * @apiDescription List all tokens or list single token by id. + * + * @apiExample Example usage: + * + * // List all tokens + * + * (async () => { + * try { + * let list = await bchjs.SLP.Utils.list(); + * console.log(list); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { decimals: 5, + * timestamp: '2019-04-20 05:03', + * versionType: 1, + * documentUri: 'developer.bitcoin.com', + * symbol: 'MYSTERY', + * name: 'Mystery', + * containsBaton: true, + * id: + * '10528f22fc20422f7c1075a87ed7270c0a17bc17ea79c6e2f426c6cc14bb25f2', + * documentHash: + * '1010101010101010101010101010101010101010101010101010101010101010', + * initialTokenQty: 500, + * blockCreated: 579041, + * blockLastActiveSend: null, + * blockLastActiveMint: null, + * txnsSinceGenesis: 1, + * validAddresses: 1, + * totalMinted: 500, + * totalBurned: 0, + * circulatingSupply: 500, + * mintingBatonStatus: 'ALIVE' }, + * { decimals: 8, + * timestamp: '2019-04-20 04:54', + * versionType: 1, + * documentUri: 'developer.bitcoin.com', + * symbol: 'ENIGMA', + * name: 'Enigma', + * containsBaton: true, + * id: + * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa', + * documentHash: + * '1010101010101010101010101010101010101010101010101010101010101010', + * initialTokenQty: 1234, + * blockCreated: 579040, + * blockLastActiveSend: null, + * blockLastActiveMint: 579040, + * txnsSinceGenesis: 2, + * validAddresses: 2, + * totalMinted: 1334, + * totalBurned: 0, + * circulatingSupply: 1334, + * mintingBatonStatus: 'ALIVE' } + * ] + * + * // List single token + * + * (async () => { + * try { + * let list = await bchjs.SLP.Utils.list( + * "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313" + * ); + * console.log(list); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * { decimals: 8, + * timestamp: '2019-04-20 04:54', + * versionType: 1, + * documentUri: 'developer.bitcoin.com', + * symbol: 'ENIGMA', + * name: 'Enigma', + * containsBaton: true, + * id: + * '113c55921fe29919ff84e53a6d5af39ed9d983a1c3b3000f27125688489935fa', + * documentHash: + * '1010101010101010101010101010101010101010101010101010101010101010', + * initialTokenQty: 1234, + * blockCreated: 579040, + * blockLastActiveSend: null, + * blockLastActiveMint: 579040, + * txnsSinceGenesis: 2, + * validAddresses: 2, + * totalMinted: 1334, + * totalBurned: 0, + * circulatingSupply: 1334, + * mintingBatonStatus: 'ALIVE' } + * + * // List multiple tokens by tokenIds + * + * (async () => { + * try { + * let list = await bchjs.SLP.Utils.list([ + * "fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f", + * "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0" + * ]); + * console.log(list); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { decimals: 0, + * timestamp: '2019-02-18 14:47', + * versionType: 1, + * documentUri: 'https://simpleledger.cash', + * symbol: 'SLP', + * name: 'Official SLP Token', + * containsBaton: true, + * id: + * 'fa6c74c52450fc164e17402a46645ce494a8a8e93b1383fa27460086931ef59f', + * documentHash: null, + * initialTokenQty: 18446744073709552000, + * blockCreated: 570305, + * blockLastActiveSend: 580275, + * blockLastActiveMint: 575914, + * txnsSinceGenesis: 4537, + * validAddresses: 164, + * totalMinted: 19414628793626410000, + * totalBurned: 18446568350267302000, + * circulatingSupply: 968060443359109600, + * mintingBatonStatus: 'ALIVE' }, + * { decimals: 8, + * timestamp: '2019-02-14 03:11', + * versionType: 1, + * documentUri: 'psfoundation.cash', + * symbol: 'PSF', + * name: 'Permissionless Software Foundation', + * containsBaton: true, + * id: + * '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0', + * documentHash: null, + * initialTokenQty: 19882.09163133, + * blockCreated: 569658, + * blockLastActiveSend: 580153, + * blockLastActiveMint: null, + * txnsSinceGenesis: 51, + * validAddresses: 9, + * totalMinted: 19882.09163133, + * totalBurned: 0.0534241, + * circulatingSupply: 19882.03820723, + * mintingBatonStatus: 'ALIVE' } ] + */ + async list(id) { + let path + let method + if (!id) { + method = "get" + path = `${this.restURL}slp/list` + } else if (typeof id === "string") { + method = "get" + path = `${this.restURL}slp/list/${id}` + } else if (typeof id === "object") { + method = "post" + path = `${this.restURL}slp/list` + } + + //console.log(`path: ${path}`) + + try { + let response + if (method === "get") { + response = await axios.get(path, _this.axiosOptions) + } else { + response = await axios.post( + path, + { + tokenIds: id + }, + _this.axiosOptions + ) + } + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.balancesForAddress() balancesForAddress() + * @apiName balancesForAddress + * @apiGroup SLP Utils + * @apiDescription Return all balances for an address or array of addresses. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * let balances = await bchjs.SLP.Utils.balancesForAddress('simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk'); + * console.log(balances); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // [ { tokenId: + * // '968ff0cc4c93864001e03e9524e351250b94ec56150fa4897f65b0b6477d44d4', + * // balance: '9980', + * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', + * // decimalCount: 9 }, + * // { tokenId: + * // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * // balance: '617', + * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', + * // decimalCount: 8 }, + * // { tokenId: + * // 'b96304d12f1bbc2196df582516410e55a9b34e13c7b4585bf5c1770af30d034f', + * // balance: '1', + * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', + * // decimalCount: 0 }, + * // { tokenId: + * // 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37', + * // balance: '776', + * // slpAddress: 'simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk', + * // decimalCount: 7 } ] + * + * // balances for Cash Address + * (async () => { + * try { + * let balances = await bchjs.SLP.Utils.balancesForAddress('bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y'); + * console.log(balances); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // [ { tokenId: + * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5', + * // balance: '507', + * // decimalCount: 2 } ] + * + * // balances for Legacy Address + * (async () => { + * try { + * let balances = await bchjs.SLP.Utils.balancesForAddress('1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS'); + * console.log(balances); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // [ { tokenId: + * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5', + * // balance: '507', + * // decimalCount: 2 } ] + * + * Note: Balances for multiple addresses can be retrieves by passing in an + * array of addresses. + */ + // Retrieve token balances for a given address. + async balancesForAddress(address) { + try { + // Single address. + if (typeof address === "string") { + const path = `${this.restURL}slp/balancesForAddress/${address}` + + const response = await axios.get(path, _this.axiosOptions) + return response.data + + // Array of addresses. + } else if (Array.isArray(address)) { + const path = `${this.restURL}slp/balancesForAddress` + + // Dev note: must use axios.post for unit test stubbing. + const response = await axios.post( + path, + { + 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 + throw error + } + } + + /** + * @api SLP.Utils.balancesForToken() balancesForToken() + * @apiName balancesForToken + * @apiGroup SLP Utils + * @apiDescription List all balances for tokenId. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * let balances = await bchjs.SLP.Utils.balancesForToken( + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ) + * console.log(balances) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * [ + * { + * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + * tokenBalance: 20, + * slpAddress: 'simpleledger:qp4g0q97tq53pasnxk2rs570c6573qvylunsf5gy9e' + * }, + * { + * tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + * tokenBalance: 335.55, + * slpAddress: 'simpleledger:qqcraw7q0ys3kg4z6f2zd267fhg2093c5c0spfk03f' + * } + * ] + * + */ + // Retrieve token balances for a given tokenId. + async balancesForToken(tokenId) { + const path = `${this.restURL}slp/balancesForToken/${tokenId}` + + try { + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.balance() balance() + * @apiName balance + * @apiGroup SLP Utils + * @apiDescription Return single balance for an address by token id. + * + * @apiExample Example usage: + * + * // single balance for SLP Address + * (async () => { + * try { + * let balance = await bchjs.SLP.Utils.balance( + * "simpleledger:qr5agtachyxvrwxu76vzszan5pnvuzy8duhv4lxrsk", + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ); + * console.log(balance); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // { tokenId: + * // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * // balance: '617', + * // decimalCount: 8 } + * + * // single balance for Cash Address + * (async () => { + * try { + * let balance = await bchjs.SLP.Utils.balance( + * "bitcoincash:qr5agtachyxvrwxu76vzszan5pnvuzy8dumh7ynrwg", + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ); + * console.log(balance); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // { tokenId: + * // 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * // balance: '617', + * // decimalCount: 8 } + * + * // single balance for Legacy Address + * (async () => { + * try { + * let balance = await bchjs.SLP.Utils.balance( + * "1DwQqpWc8pzaRydCmiJsPdoqCzmjSQQbp8", + * "467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5" + * ); + * console.log(balance); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * // { tokenId: + * // '467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5', + * // balance: '1234', + * // decimalCount: 2 } + */ + // Retrieve a balance for a specific address and token ID + async balance(address, tokenId) { + const path = `${this.restURL}slp/balance/${address}/${tokenId}` + + try { + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.validateTxid() validateTxid() + * @apiName validateTxid + * @apiGroup SLP Utils + * @apiDescription Validate that txid is an SLP transaction. + * + * @apiExample Example usage: + * + * // validate single SLP txid + * (async () => { + * try { + * let validated = await bchjs.SLP.Utils.validateTxid( + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ); + * console.log(validated); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { txid: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * valid: true } ] + * + * // validate multiple SLP txids + * (async () => { + * try { + * let validated = await bchjs.SLP.Utils.validateTxid([ + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + * "00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476" + * ]); + * console.log(validated); + * } catch (error) { + * console.error(error); + * } + * })(); + * + * // returns + * [ { txid: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * valid: true }, + * { txid: + * '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476', + * valid: true } ] + */ + async validateTxid(txid) { + const path = `${this.restURL}slp/validateTxid` + + // console.log(`txid: ${JSON.stringify(txid, null, 2)}`) + + // Handle a single TXID or an array of TXIDs. + let txids + if (typeof txid === "string") txids = [txid] + else txids = txid + + try { + const response = await axios.post( + path, + { + txids: txids + }, + _this.axiosOptions + ) + // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`) + + const validatedTxids = response.data + + // Handle any null values + for (let i = 0; i < validatedTxids.length; i++) { + if (validatedTxids[i] === null) { + validatedTxids[i] = { + txid: txids[i], + valid: false + } + } + } + + return validatedTxids + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.tokenStats() tokenStats() + * @apiName tokenStats + * @apiGroup SLP Utils + * @apiDescription Stats for token by tokenId. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * let stats = await bchjs.SLP.Utils.tokenStats( + * "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + * ) + * console.log(stats) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * { tokenId: + * 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', + * documentUri: '', + * symbol: 'NAKAMOTO', + * name: 'NAKAMOTO', + * decimals: 8, + * txnsSinceGenesis: 367, + * validUtxos: 248, + * validAddresses: 195, + * circulatingSupply: 20995990, + * totalBurned: 4010, + * totalMinted: 21000000, + * satoshisLockedUp: 135408 + * } + */ + async tokenStats(tokenId) { + const path = `${this.restURL}slp/tokenStats/${tokenId}` + + try { + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.transactions() transactions() + * @apiName transactions + * @apiGroup SLP Utils + * @apiDescription SLP Transactions by tokenId and address. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * let transactions = await bchjs.SLP.Utils.transactions( + * "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", + * "qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqlsy4gusz" + * ) + * console.log(transactions) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * [ + * { + * "txid": "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f", + * "tokenDetails": { + * "valid": true, + * "detail": { + * "decimals": null, + * "tokenIdHex": "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", + * "timestamp": null, + * "transactionType": "SEND", + * "versionType": 1, + * "documentUri": null, + * "documentSha256Hex": null, + * "symbol": null, + * "name": null, + * "batonVout": null, + * "containsBaton": null, + * "genesisOrMintQuantity": null, + * "sendOutputs": [ + * { + * "$numberDecimal": "0" + * }, + * { + * "$numberDecimal": "25" + * }, + * { + * "$numberDecimal": "77" + * } + * ] + * }, + * "invalidReason": null, + * "schema_version": 30 + * } + * } + * ] + */ + // Retrieve token transactions for a given tokenId and address. + async transactions(tokenId, address) { + const path = `${this.restURL}slp/transactions/${tokenId}/${address}` + + try { + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.burnTotal() burnTotal() + * @apiName burnTotal + * @apiGroup SLP Utils + * @apiDescription List input, output and burn total for slp transaction. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const burnTotal = await bchjs.SLP.Utils.burnTotal( + * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de" + * ) + * console.log(burnTotal) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * { + * transactionId: 'c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de', + * inputTotal: 100000100, + * outputTotal: 100000000, + * burnTotal: 100 + * } + */ + async burnTotal(transactionId) { + const path = `${this.restURL}slp/burnTotal/${transactionId}` + + try { + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.txDetails() txDetails() + * @apiName txDetails + * @apiGroup SLP Utils + * @apiDescription Transaction details on a token transfer. + * There is no bulk method for this endpoint. Can only get one tx at a time. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const details = await bchjs.SLP.Utils.txDetails( + * "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de" + * ) + * console.log(details) + * } catch (error) { + * console.error(error) + * } + * })() + * + */ + async txDetails(txid) { + try { + if ( + !txid || + txid === "" || + typeof txid !== "string" || + txid.length !== 64 + ) + throw new Error(`txid string must be included.`) + + // console.log(`this.restURL: ${this.restURL}`) + const path = `${this.restURL}slp/txDetails/${txid}` + + const response = await axios.get(path, _this.axiosOptions) + return response.data + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.decodeOpReturn() decodeOpReturn() + * @apiName decodeOpReturn + * @apiGroup SLP Utils + * @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data. + * + * Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser + * library maintained by JT Freeman. Outputs have slightly different format. + * + * Throws an error if given a non-SLP txid. + * + * In a future version of bch-js, this method will replace the origonal + * decodeOpReturn() method. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const txid = + * "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" + * + * const data = await slp.Utils.decodeOpReturn2(txid) + * + * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * { + * "tokenType": 1, + * "txType": "SEND", + * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + * "amounts": [ + * "100000000", + * "99883300000000" + * ] + * } + */ + // Reimplementation of decodeOpReturn() using slp-parser. + async decodeOpReturn(txid) { + try { + // Validate the txid input. + if (!txid || txid === "" || typeof txid !== "string") + throw new Error(`txid string must be included.`) + + // Retrieve the transaction object from the full node. + const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true` + const response = await axios.get(path, _this.axiosOptions) + const txDetails = response.data + // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) + + // SLP spec expects OP_RETURN to be the first output of the transaction. + const opReturn = txDetails.vout[0].scriptPubKey.hex + // console.log(`opReturn hex: ${opReturn}`) + + const parsedData = _this.slpParser.parseSLP(Buffer.from(opReturn, "hex")) + // console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`) + + // Convert Buffer data to hex strings or utf8 strings. + let tokenData = {} + if (parsedData.transactionType === "SEND") { + tokenData = { + tokenType: parsedData.tokenType, + txType: parsedData.transactionType, + tokenId: parsedData.data.tokenId.toString("hex"), + amounts: parsedData.data.amounts + } + } else if (parsedData.transactionType === "GENESIS") { + tokenData = { + tokenType: parsedData.tokenType, + txType: parsedData.transactionType, + ticker: parsedData.data.ticker.toString(), + name: parsedData.data.name.toString(), + tokenId: txid, + documentUri: parsedData.data.documentUri.toString(), + documentHash: parsedData.data.documentHash.toString(), + decimals: parsedData.data.decimals, + mintBatonVout: parsedData.data.mintBatonVout, + qty: parsedData.data.qty + } + } else if (parsedData.transactionType === "MINT") { + tokenData = { + tokenType: parsedData.tokenType, + txType: parsedData.transactionType, + tokenId: parsedData.data.tokenId.toString("hex"), + mintBatonVout: parsedData.data.mintBatonVout, + qty: parsedData.data.qty + } + } + // console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`) + + return tokenData + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } + + /** + * @api SLP.Utils.tokenUtxoDetails() tokenUtxoDetails() + * @apiName tokenUtxoDetails + * @apiGroup SLP Utils + * @apiDescription Hydrate a UTXO with SLP token metadata. + * + * Expects an array of UTXO objects as input. Returns an array of equal size. + * Returns UTXO data hydrated with token information. + * If the + * UTXO does not belong to a SLP transaction, it will return an `isValid` property + * set to false. + * If the UTXO is part of an SLP transaction, it will return the UTXO object + * with additional SLP information attached. An `isValid` property will be included. + * If its value is true, the UTXO is a valid SLP UTXO. If the value is null, + * then SLPDB has not yet processed that txid and validity has not been confirmed. + * + * @apiExample Example usage: + * + * (async () => { + * try { + * const utxos = await bchjs.Blockbook.utxos(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`) + * + * const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + * + * console.log(`utxoInfo: ${JSON.stringify(isSLPUtxo,null,2)}`) + * } catch (error) { + * console.error(error) + * } + * })() + * + * // returns + * { + * "txid": "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + * "vout": 1, + * "amount": 0.00000546, + * "satoshis": 546, + * "height": 596089, + * "confirmations": 748, + * "utxoType": "token", + * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + * "tokenTicker": "TOK-CH", + * "tokenName": "TokyoCash", + * "tokenDocumentUrl": "", + * "tokenDocumentHash": "", + * "decimals": 8, + * "tokenQty": 2, + * "isValid": true, + * "tokenType": 1 + * } + */ + + // CT 1/11/20: Refactored to comply with this GitHub Issue: + // https://github.com/Bitcoin-com/slp-sdk/issues/84 + + // CT 5/31/20: Refactored to use slp-parse library. + async tokenUtxoDetails(utxos) { + try { + // Throw error if input is not an array. + if (!Array.isArray(utxos)) throw new Error(`Input must be an array.`) + + // Loop through each element in the array and validate the input before + // further processing. + for (let i = 0; i < utxos.length; i++) { + const utxo = utxos[i] + + if (!utxo.satoshis) { + // If Electrumx, convert the value to satoshis. + if (utxo.value) { + utxo.satoshis = utxo.value + } + // If there is neither a satoshis or value property, throw an error. + else { + throw new Error( + `utxo ${i} does not have a satoshis or value property.` + ) + } + } + + if (!utxo.txid) { + // If Electrumx, convert the tx_hash property to txid. + if (utxo.tx_hash) { + utxo.txid = utxo.tx_hash + } + // If there is neither a txid or tx_hash property, throw an error. + else { + throw new Error( + `utxo ${i} does not have a txid or tx_hash property.` + ) + } + } + + if (!Number.isInteger(utxo.vout)) { + if (Number.isInteger(utxo.tx_pos)) { + utxo.vout = utxo.tx_pos + } else { + throw new Error( + `utxo ${i} does not have a vout or tx_pos property.` + ) + } + } + } + + // Output Array + const outAry = [] + + // Loop through each utxo + for (let i = 0; i < utxos.length; i++) { + const utxo = utxos[i] + + // Get raw transaction data from the full node and attempt to decode + // the OP_RETURN data. + // If there is no OP_RETURN, mark the UTXO as false. + let slpData = false + try { + slpData = await this.decodeOpReturn(utxo.txid) + // console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`) + } catch (err) { + // console.log(`error: `, err) + // An error will be thrown if the txid is not SLP. + // Mark as false and continue the loop. + // outAry.push(false) + utxo.isValid = false + outAry.push(utxo) + continue + } + + const txType = slpData.txType.toLowerCase() + + // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + // If there is an OP_RETURN, attempt to decode it. + // Handle Genesis SLP transactions. + if (txType === "genesis") { + if ( + utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. + utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. + ) { + // Can safely be marked as false. + // outAry[i] = false + utxo.isValid = false + outAry[i] = utxo + } + + // If this is a valid SLP UTXO, then return the decoded OP_RETURN data. + else { + // Minting Baton + if (utxo.vout === slpData.mintBatonVout) { + utxo.utxoType = "minting-baton" + } + // Tokens + else { + utxo.utxoType = "token" + utxo.tokenQty = slpData.qty / Math.pow(10, slpData.decimals) + } + + utxo.tokenId = utxo.txid + utxo.tokenTicker = slpData.ticker + utxo.tokenName = slpData.name + utxo.tokenDocumentUrl = slpData.documentUri + utxo.tokenDocumentHash = slpData.documentHash + utxo.decimals = slpData.decimals + utxo.tokenType = slpData.tokenType + + // something + outAry[i] = utxo + } + } + + // Handle Mint SLP transactions. + if (txType === "mint") { + if ( + utxo.vout !== slpData.mintBatonVout && // UTXO is not a mint baton output. + utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens. + ) { + // Can safely be marked as false. + // outAry[i] = false + utxo.isValid = false + outAry[i] = utxo + } + + // If UTXO passes validation, then return formatted token data. + else { + const genesisData = await this.decodeOpReturn(slpData.tokenId) + // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) + + // Minting Baton + if (utxo.vout === slpData.mintBatonVout) { + utxo.utxoType = "minting-baton" + } + // Tokens + else { + utxo.utxoType = "token" + utxo.tokenQty = slpData.qty / Math.pow(10, genesisData.decimals) + } + + // Hydrate the UTXO object with information about the SLP token. + utxo.transactionType = "mint" + utxo.tokenId = slpData.tokenId + utxo.tokenType = slpData.tokenType + + utxo.tokenTicker = genesisData.ticker + utxo.tokenName = genesisData.name + utxo.tokenDocumentUrl = genesisData.documentUri + utxo.tokenDocumentHash = genesisData.documentHash + utxo.decimals = genesisData.decimals + + utxo.mintBatonVout = slpData.mintBatonVout + + outAry[i] = utxo + } + } + + // Handle Send SLP transactions. + if (txType === "send") { + // Filter out any vouts that match. + // const voutMatch = slpData.spendData.filter(x => utxo.vout === x.vout) + // console.log(`voutMatch: ${JSON.stringify(voutMatch, null, 2)}`) + + // Figure out what token quantity is represented by this utxo. + const tokenQty = slpData.amounts[utxo.vout - 1] + // console.log(`tokenQty: `, tokenQty) + + if (!tokenQty) { + // outAry[i] = false + utxo.isValid = false + outAry[i] = utxo + } + + // If UTXO passes validation, then return formatted token data. + else { + const genesisData = await this.decodeOpReturn(slpData.tokenId) + // console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`) + + // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + // Hydrate the UTXO object with information about the SLP token. + utxo.utxoType = "token" + utxo.transactionType = "send" + utxo.tokenId = slpData.tokenId + utxo.tokenTicker = genesisData.ticker + utxo.tokenName = genesisData.name + utxo.tokenDocumentUrl = genesisData.documentUri + utxo.tokenDocumentHash = genesisData.documentHash + utxo.decimals = genesisData.decimals + utxo.tokenType = slpData.tokenType + + // Calculate the real token quantity. + utxo.tokenQty = tokenQty / Math.pow(10, genesisData.decimals) + + // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + outAry[i] = utxo + } + } + + // Finally, validate the SLP txid with SLPDB. + if (outAry[i].tokenType) { + const isValid = await this.validateTxid(utxo.txid) + // console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`) + + outAry[i].isValid = isValid[0].valid + } + } + + return outAry + } catch (error) { + if (error.response && error.response.data) throw error.response.data + throw error + } + } +} + +module.exports = Utils diff --git a/src/socket.js b/src/socket.js new file mode 100644 index 0000000..7151088 --- /dev/null +++ b/src/socket.js @@ -0,0 +1,29 @@ +const io = require("socket.io-client") + +class Socket { + constructor(config = {}) { + if (typeof config === "string") { + // TODO remove this check in v2.0 + this.socket = io(`${config}`) + } else { + if (config.restURL) { + this.socket = io(`${config.restURL}`) + } else { + const restURL = "https://rest.bitcoin.com" + this.socket = io(`${restURL}`) + } + + if (config.callback) config.callback() + } + } + + listen(endpoint, cb) { + this.socket.emit(endpoint) + + if (endpoint === "blocks") this.socket.on("blocks", msg => cb(msg)) + else if (endpoint === "transactions") + this.socket.on("transactions", msg => cb(msg)) + } +} + +module.exports = Socket diff --git a/src/transaction-builder.js b/src/transaction-builder.js new file mode 100644 index 0000000..dbd2f16 --- /dev/null +++ b/src/transaction-builder.js @@ -0,0 +1,164 @@ +const Bitcoin = require("bitcoincashjs-lib") +const coininfo = require("coininfo") +const bip66 = require("bip66") +const bip68 = require("bc-bip68") + +class TransactionBuilder { + static setAddress(address) { + TransactionBuilder._address = address + } + + constructor(network = "mainnet") { + let bitcoincash + if (network === "bitcoincash" || network === "mainnet") + bitcoincash = coininfo.bitcoincash.main + else bitcoincash = coininfo.bitcoincash.test + + const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() + this.transaction = new Bitcoin.TransactionBuilder(bitcoincashBitcoinJSLib) + this.DEFAULT_SEQUENCE = 0xffffffff + this.hashTypes = { + SIGHASH_ALL: 0x01, + SIGHASH_NONE: 0x02, + SIGHASH_SINGLE: 0x03, + SIGHASH_ANYONECANPAY: 0x80, + SIGHASH_BITCOINCASH_BIP143: 0x40, + ADVANCED_TRANSACTION_MARKER: 0x00, + ADVANCED_TRANSACTION_FLAG: 0x01 + } + this.signatureAlgorithms = { + ECDSA: Bitcoin.ECSignature.ECDSA, + SCHNORR: Bitcoin.ECSignature.SCHNORR + } + this.bip66 = bip66 + this.bip68 = bip68 + this.p2shInput = false + this.tx + } + + /** + * @api Transaction-Builder.addInput() addInput() - Add input to transaction. + * @apiName AddInput + * @apiGroup TransactionBuilder + * @apiDescription Add input to transaction. + * + * @apiExample Example usage: + * // txid of vout + * let txid = 'f7890915febe580920df2681d2bac0909ae89bd0cc1d3ed763e5eeba7f337f0e'; + * // add input with txid and index of vout + * transactionBuilder.addInput(txid, 0); + */ + addInput(txHash, vout, sequence = this.DEFAULT_SEQUENCE, prevOutScript) { + this.transaction.addInput(txHash, vout, sequence, prevOutScript) + } + + addInputScript(vout, script) { + this.tx = this.transaction.buildIncomplete() + this.tx.setInputScript(vout, script) + this.p2shInput = true + } + + addInputScripts(scripts) { + this.tx = this.transaction.buildIncomplete() + scripts.forEach(script => { + this.tx.setInputScript(script.vout, script.script) + }) + this.p2shInput = true + } + /** + * @api Transaction-Builder.addOutput() addOutput() - Add output to transaction. + * @apiName AddOutput + * @apiGroup TransactionBuilder + * @apiDescription Add output to transaction. + * + * @apiExample Example usage: + * let originalAmount = 100000; + * let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); + * // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + * let sendAmount = originalAmount - byteCount; + * // add output w/ address and amount to send + * transactionBuilder.addOutput('bitcoincash:qpuax2tarq33f86wccwlx8ge7tad2wgvqgjqlwshpw', sendAmount); + */ + addOutput(scriptPubKey, amount) { + try { + this.transaction.addOutput( + TransactionBuilder._address.toLegacyAddress(scriptPubKey), + amount + ) + } catch (error) { + this.transaction.addOutput(scriptPubKey, amount) + } + } + /** + * @api Transaction-Builder.setLockTime() setLockTime() - Set locktime. + * @apiName SetLockTime + * @apiGroup TransactionBuilder + * @apiDescription Set locktime. + * + * @apiExample Example usage: + * let originalAmount = 100000; + * let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); + * // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + * let sendAmount = originalAmount - byteCount; + * // add output w/ address and amount to send + * transactionBuilder.addOutput('bitcoincash:qpuax2tarq33f86wccwlx8ge7tad2wgvqgjqlwshpw', sendAmount); + * transactionBuilder.setLockTime(50000) + */ + setLockTime(locktime) { + this.transaction.setLockTime(locktime) + } + /** + * @api Transaction-Builder.sign() sign() - Sign transaction.. + * @apiName Sign. + * @apiGroup TransactionBuilder + * @apiDescription Sign transaction. It creates the unlocking script needed to spend an input. Each input has its own script and thus 'sign' must be called for each input even if the keyPair is the same.. + * + * @apiExample Example usage: + * let originalAmount = 100000; + * // node of address which is going to spend utxo + * let hdnode = bchjs.HDNode.fromXPriv("xprvA3eaDg64MwDr72PVGJ7CkvshNAzCDRz7rn98sYrZVAtDSWCAmNGQhEQeCLDcnmcpSkfjhHevXmu4ZL8ZcT9D4vEbG8LpiToZETrHZttw9Yw"); + * // keypair + * let keyPair = bchjs.HDNode.toKeyPair(hdnode); + * // empty redeemScript variable + * let redeemScript; + * // sign w/ keyPair + * transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount, transactionBuilder.signatureAlgorithms.SCHNORR); + */ + sign( + vin, + keyPair, + redeemScript, + hashType = this.hashTypes.SIGHASH_ALL, + value, + signatureAlgorithm + ) { + let witnessScript + + this.transaction.sign( + vin, + keyPair, + redeemScript, + hashType, + value, + witnessScript, + signatureAlgorithm + ) + } + /** + * @api Transaction-Builder.build() build() - Build transaction. + * @apiName Build. + * @apiGroup TransactionBuilder + * @apiDescription Build transaction. + * + * @apiExample Example usage: + * // build tx + * let tx = bchjs.transactionBuilder.build(); + */ + build() { + if (this.p2shInput === true) return this.tx + + return this.transaction.build() + } +} + +module.exports = TransactionBuilder diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..e56b6e4 --- /dev/null +++ b/src/util.js @@ -0,0 +1,100 @@ +const axios = require("axios") + +let _this + +class Util { + constructor(config) { + this.restURL = config.restURL + this.apiToken = config.apiToken + + // Add JWT token to the authorization header. + this.axiosOptions = { + headers: { + authorization: `Token ${this.apiToken}` + } + } + + _this = this + } + + /** + * @api util.validateAddress() validateAddress() - Get information about the given bitcoin address. + * @apiName Validate Address. + * @apiGroup Util + * @apiDescription Return information about the given bitcoin address. + * + * @apiExample Example usage: + * (async () => { + * try { + * let validateAddress = await bchjs.Util.validateAddress("bitcoincash:qzc86hrdufhcwlyzk7k82x77kfs2myekn57nv9cw5f"); + * console.log(validateAddress); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // { isvalid: true, + * // address: '17fshh33qUze2yifiJ2sXgijSMzJ2KNEwu', + * // scriptPubKey: '76a914492ae280d70af33acf0ae7cd329b961e65e9cbd888ac', + * // ismine: true, + * // iswatchonly: false, + * // isscript: false, + * // pubkey: '0312eeb9ae5f14c3cf43cece11134af860c2ef7d775060e3a578ceec888acada31', + * // iscompressed: true, + * // account: 'Test' } + * + * (async () => { + * try { + * let validateAddress = await bchjs.Util.validateAddress(["bitcoincash:qzc86hrdufhcwlyzk7k82x77kfs2myekn57nv9cw5f"]); + * console.log(validateAddress); + * } catch(error) { + * console.error(error) + * } + * })() + * + * // [{ isvalid: true, + * // address: '17fshh33qUze2yifiJ2sXgijSMzJ2KNEwu', + * // scriptPubKey: '76a914492ae280d70af33acf0ae7cd329b961e65e9cbd888ac', + * // ismine: true, + * // iswatchonly: false, + * // isscript: false, + * // pubkey: '0312eeb9ae5f14c3cf43cece11134af860c2ef7d775060e3a578ceec888acada31', + * // iscompressed: true, + * // account: 'Test' }] + */ + async validateAddress(address) { + try { + // Single block + if (typeof address === "string") { + const response = await axios.get( + `${this.restURL}util/validateAddress/${address}`, + _this.axiosOptions + ) + return response.data + + // Array of blocks. + } else if (Array.isArray(address)) { + const options = { + method: "POST", + url: `${this.restURL}util/validateAddress`, + data: { + addresses: address + }, + headers: { + authorization: `Token ${this.apiToken}` + } + } + const response = await axios(options) + + return response.data + } + + throw new Error(`Input 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 = Util diff --git a/src/wallet.js b/src/wallet.js new file mode 100644 index 0000000..43fc232 --- /dev/null +++ b/src/wallet.js @@ -0,0 +1,77 @@ +// import BCHWalletBridge from "bch-wallet-bridge.js" +class Wallet { + constructor(walletProvider) { + // this.bchWalletBridge = new BCHWalletBridge(walletProvider) + } + + setWalletProvider(walletProvider) { + this.bchWalletBridge.walletProvider = walletProvider + } + + getAddress(changeType, index, dAppId) { + return this.bchWalletBridge.getAddress(changeType, index, dAppId) + } + + getAddressIndex(changeType, dAppId) { + return this.bchWalletBridge.getAddressIndex(changeType, dAppId) + } + + getAddresses(changeType, startIndex, size, dAppId) { + return this.bchWalletBridge.getAddresses( + changeType, + startIndex, + size, + dAppId + ) + } + + getRedeemScript(p2shAddress, dAppId) { + return this.bchWalletBridge.getRedeemScript(p2shAddress, dAppId) + } + + getRedeemScripts(dAppId) { + return this.bchWalletBridge.getRedeemScripts(dAppId) + } + + addRedeemScript(redeemScript, dAppId) { + return this.bchWalletBridge.addRedeemScript(redeemScript, dAppId) + } + + getUtxos(dAppId) { + return this.bchWalletBridge.getUtxos(dAppId) + } + + getBalance(dAppId) { + return this.bchWalletBridge.getBalance(dAppId) + } + + sign(address, dataToSign) { + return this.bchWalletBridge.sign(address, dataToSign) + } + + buildTransaction(outputs, dAppId) { + return this.bchWalletBridge.buildTransaction(outputs, dAppId) + } + + getProtocolVersion() { + return this.bchWalletBridge.getProtocolVersion() + } + + getNetwork() { + return this.bchWalletBridge.getNetwork() + } + + getFeePerByte() { + return this.bchWalletBridge.getFeePerByte() + } + + getDefaultDAppId() { + return this.bchWalletBridge.getDefaultDAppId() + } + + setDefaultDAppId(dAppId) { + return this.bchWalletBridge.setDefaultDAppId(dAppId) + } +} + +module.exports = Wallet diff --git a/test/e2e/bch-js-e2e-tests.js b/test/e2e/bch-js-e2e-tests.js new file mode 100644 index 0000000..63f4a00 --- /dev/null +++ b/test/e2e/bch-js-e2e-tests.js @@ -0,0 +1,18 @@ +/* + A Mocha test file for running end-to-end (e2e) tests. +*/ + +//const mocha = require("mocha") +const assert = require("chai").assert + +const sendToken = require("./send-token/send-token") + +describe("#end-to-end tests", () => { + describe("#send-tokens", () => { + it("SLPDB should update balances in less than 10 seconds", async () => { + const result = await sendToken.sendTokenTest() + + assert(result, true, "True expected if test passed successfully.") + }) + }) +}) diff --git a/test/e2e/ipfs/ipfs-e2e.js b/test/e2e/ipfs/ipfs-e2e.js new file mode 100644 index 0000000..7111a5e --- /dev/null +++ b/test/e2e/ipfs/ipfs-e2e.js @@ -0,0 +1,24 @@ +/* + An end-to-end test for testing the functionality of uploading a file to + IPFS is working. +*/ + +process.env.IPFS_API = `http://localhost:5001` +// process.env.IPFS_API = `https://ipfs-api.fullstack.cash` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#IPFS`, () => { + it("should upload a file to the server", async () => { + const path = `${__dirname}/ipfs-e2e.js` + + const fileModel = await bchjs.IPFS.createFileModel(path) + console.log(`fileModel: ${JSON.stringify(fileModel, null, 2)}`) + + const fileId = fileModel.file._id + + const fileObj = await bchjs.IPFS.uploadFile(path, fileId) + console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) + }).timeout(30000) +}) diff --git a/test/e2e/rate-limits/anonymous-rate-limits.js b/test/e2e/rate-limits/anonymous-rate-limits.js new file mode 100644 index 0000000..50643c7 --- /dev/null +++ b/test/e2e/rate-limits/anonymous-rate-limits.js @@ -0,0 +1,41 @@ +/* + Mocha test that verifies that the bch-api server is enforcing its + anonymous access rules + + To Run Test: + - Update the RESTURL for bch-api you want to test against. +*/ + +const assert = require("chai").assert + +const RESTURL = `https://api.fullstack.cash/v3/` +// const RESTURL = `http://localhost:3000/v3/` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ restURL: RESTURL }) + +describe("#anonymous rate limits", () => { + it("should allow an anonymous call to a full node endpoint", async () => { + const result = await bchjs.Control.getNetworkInfo() + + assert.property(result, "version") + }).timeout(5000) + + it("should allow an anonymous call to an indexer", async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + const result = await bchjs.Blockbook.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "balance") + }).timeout(5000) + + it("should throw error when rate limit exceeded", async () => { + try { + for (let i = 0; i < 5; i++) await bchjs.Control.getNetworkInfo() + + assert.equal(true, false, "unexpected result") + } catch (err) { + assert.include(err.error, "Too many requests") + } + }).timeout(5000) +}) diff --git a/test/e2e/rate-limits/free-rate-limits.js b/test/e2e/rate-limits/free-rate-limits.js new file mode 100644 index 0000000..4a4b4dd --- /dev/null +++ b/test/e2e/rate-limits/free-rate-limits.js @@ -0,0 +1,90 @@ +/* + Mocha test that verifies that the bch-api server is enforcing its + FREE access rules + + To Run Test: + - Update the restURL for bch-api you want to test against. + - Update the JWT_TOKEN value with a current free-level JWT token. +*/ + +const assert = require("chai").assert + +const JWT_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2YyMDIyMWMxMDAxMmFkOTQwMSIsImVtYWlsIjoiZGVtb0BkZW1vLmNvbSIsImFwaUxldmVsIjoxMCwicmF0ZUxpbWl0IjozLCJpYXQiOjE1OTA1Mjk2NzgsImV4cCI6MTU5MzEyMTY3OH0.4q5MFDCcVx0FusivJPiEIj3w8nhFG7ZV5IhJS7faUlc" + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ + restURL: `https://api.fullstack.cash/v3/`, + // restURL: `http://localhost:3000/v3/`, + apiToken: JWT_TOKEN +}) + +describe("#free rate limits", () => { + it("should allow an free call to an indexer", async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + const result = await bchjs.Blockbook.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "balance") + }).timeout(5000) + + // it("should throw error when rate limit exceeded 3 RPM for indexer endpoints", async () => { + // const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + // + // try { + // for (let i = 0; i < 5; i++) await bchjs.Blockbook.balance(addr) + // + // assert.equal(true, false, "unexpected result") + // } catch (err) { + // // console.log(`err: `, err) + // assert.include(err.error, "Too many requests") + // } + // }).timeout(10000) + + it("should allow up to 10 RPM to indexer, then throw error", async () => { + try { + const addr = "bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0" + + for (let i = 0; i < 15; i++) { + const result = await await bchjs.Blockbook.balance(addr) + + if (i === 5) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "address", "more than 3 calls allowed") + } + } + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.error, + "currently 10 requests", + "more than 10 not allowed" + ) + assert.include(err.error, 10) + assert.notInclude(err.error, 3) + } + }).timeout(20000) + + it("should allow up to 10 RPM to full node, then throw error", async () => { + try { + for (let i = 0; i < 15; i++) { + const result = await bchjs.Control.getNetworkInfo() + + if (i === 5) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "version", "more than 3 calls allowed") + } + } + } catch (err) { + // console.log(`validating after 10th call`) + // console.log(`err: `, err) + assert.include( + err.error, + "currently 10 requests", + "more than 10 not allowed" + ) + assert.include(err.error, 10) + assert.notInclude(err.error, 3) + } + }).timeout(20000) +}) diff --git a/test/e2e/rate-limits/full-node-rate-limits.js b/test/e2e/rate-limits/full-node-rate-limits.js new file mode 100644 index 0000000..99c62b8 --- /dev/null +++ b/test/e2e/rate-limits/full-node-rate-limits.js @@ -0,0 +1,70 @@ +/* + Mocha test that verifies that the bch-api server is enforcing its + full-node access rules + + To Run Test: + - Update the restURL for bch-api you want to test against. + - Update the JWT_TOKEN value with a current full-node-level JWT token. +*/ + +const assert = require("chai").assert + +const JWT_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYmY4MjA1YTYwODliMjliYTlhZjc1OSIsImlhdCI6MTU3MjgzMTgzOSwiZXhwIjoxNTc1NDIzODM5fQ.sFFS4AF04U2aUwmKkBIfnMQIUoygzV9UMVzzQuwctpY" + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ + restURL: `https://api.bchjs.cash/v3/`, + // restURL: `http://localhost:3000/v3/`, + apiToken: JWT_TOKEN +}) + +describe("#full node rate limits", () => { + it("should allow an free call to an indexer", async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + const result = await bchjs.Blockbook.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "balance") + }).timeout(5000) + + it("should throw error when rate limit exceeded 3 RPM for indexer endpoints", async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + try { + for (let i = 0; i < 5; i++) await bchjs.Blockbook.balance(addr) + + assert.equal(true, false, "unexpected result") + } catch (err) { + // console.log(`err: `, err) + assert.include(err.error, "Too many requests") + } + }).timeout(10000) + + it("should allow more than 10 RPM to full node", async () => { + for (let i = 0; i < 20; i++) { + const result = await bchjs.Control.getNetworkInfo() + + if (i === 5) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "version", "more than 3 calls allowed") + } + + if (i === 15) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "version", "more than 10 calls allowed") + } + } + }).timeout(10000) + + it("should throw error for more than 100 RPM to fullnode", async () => { + try { + for (let i = 0; i < 100; i++) await bchjs.Control.getNetworkInfo() + } catch (err) { + // console.log(`validating after 10th call`) + // console.log(`err: `, err) + assert.include(err.error, "Too many requests") + assert.include(err.error, 100) + } + }).timeout(30000) +}) diff --git a/test/e2e/rate-limits/indexer-rate-limits.js b/test/e2e/rate-limits/indexer-rate-limits.js new file mode 100644 index 0000000..8207f29 --- /dev/null +++ b/test/e2e/rate-limits/indexer-rate-limits.js @@ -0,0 +1,56 @@ +/* + Mocha test that verifies that the bch-api server is enforcing its + indexer access rules + + To Run Test: + - Update the restURL for bch-api you want to test against. + - Update the JWT_TOKEN value with a current indexer-level JWT token. +*/ + +const assert = require("chai").assert + +const JWT_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTU5MDQzMjIxNywiZXhwIjoxNTkzMDI0MjE3fQ.R0Euhutd5FHCxLlSzjq1TYsGjI6iua_AguWIeLK2xCU" + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ + restURL: `https://api.fullstack.cash/v3/`, + // restURL: `http://localhost:3000/v3/`, + apiToken: JWT_TOKEN +}) + +describe("#full node rate limits", () => { + it("should allow more than 10 RPM to full node", async () => { + for (let i = 0; i < 20; i++) { + const result = await bchjs.Control.getNetworkInfo() + + if (i === 5) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "version", "more than 3 calls allowed") + } + + if (i === 15) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "version", "more than 10 calls allowed") + } + } + }).timeout(30000) + + it("should allow more than 10 RPM to an indexer", async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + for (let i = 0; i < 20; i++) { + const result = await bchjs.Blockbook.balance(addr) + + if (i === 5) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "balance", "more than 3 calls allowed") + } + + if (i === 15) { + // console.log(`validating 5th call: ${i}`) + assert.property(result, "balance", "more than 10 calls allowed") + } + } + }).timeout(30000) +}) diff --git a/test/e2e/send-raw-transaction-bulk/package.json b/test/e2e/send-raw-transaction-bulk/package.json new file mode 100644 index 0000000..6f2b009 --- /dev/null +++ b/test/e2e/send-raw-transaction-bulk/package.json @@ -0,0 +1,28 @@ +{ + "name": "sendrawtransaction", + "version": "1.0.0", + "description": "e2e test - Send two transactions in parallel.", + "main": "sendrawtransaction.js", + "scripts": { + "test": "echo no tests yet", + "start": "node sendrawtransaction.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Bitcoin-com/bitbox-javascript-sdk.git" + }, + "keywords": [ + "bitbox", + "bch", + "bitcoin", + "bitcoin cash", + "bitcoin.com", + "javascript" + ], + "author": "Chris Troutner ", + "license": "MIT", + "bugs": { + "url": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/issues" + }, + "homepage": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/blob/master/README.md" +} diff --git a/test/e2e/send-raw-transaction-bulk/sendrawtransaction.js b/test/e2e/send-raw-transaction-bulk/sendrawtransaction.js new file mode 100644 index 0000000..6f45ecc --- /dev/null +++ b/test/e2e/send-raw-transaction-bulk/sendrawtransaction.js @@ -0,0 +1,281 @@ +/* + This is an end-to-end test adapted from the send-bch example. It's purpose + is to test the sendRawTransaction endpoint. Since the single call is tested + all the time, this test focuses on testing the bulk endpoint by sending + two transactions at once. + + Instructions: + - Ensure the address in the wallet.json file has some tBCH. + - Ensure the address in the wallet.json file has two UTXOs between 0.1 and + 0.001 tBCH + - This test will generate two transactions from two UTXOs and broadcast them + using the sendrawtransaction bulk endpoint. +*/ + +// Replace the address below with the address you want to send the BCH to. +const RECV_ADDR1 = `bchtest:qzfn2mly05t6fjsh5kjj0dqq0jjtct27ng089dgg05` +const RECV_ADDR2 = `bchtest:qz6yw0kqgfkknfy6jh2jvlfnkzmre3lt2u0pgcckdk` +const SATOSHIS_TO_SEND = 1000 + +// Instantiate BITBOX. +const bitboxLib = "../../../lib/BITBOX" +const BITBOXSDK = require(bitboxLib) +const BITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) +//const BITBOX = new BITBOXSDK({ restURL: "http://localhost:3000/v2/" }) + +const util = require("util") + +// Open the wallet generated with create-wallet. +try { + var walletInfo = require(`./wallet.json`) +} catch (err) { + console.log( + `Could not open wallet.json. Generate a wallet with create-wallet first.` + ) + process.exit(0) +} + +const SEND_ADDR = walletInfo.cashAddress +const SEND_MNEMONIC = walletInfo.mnemonic + +async function testSend() { + try { + const hex1 = await buildTx1(RECV_ADDR1) + const hex2 = await buildTx2(RECV_ADDR2) + + console.log(`hex1: ${hex1}\n\n`) + console.log(`hex2: ${hex2}\n\n`) + + const broadcast = await BITBOX.RawTransactions.sendRawTransaction([ + hex1, + hex2 + ]) + console.log(`Transaction IDs: ${JSON.stringify(broadcast, null, 2)}`) + console.log(`Should return an array of TXID strings.`) + } catch (err) { + console.log(`Error in testSend: `, err) + } +} +testSend() + +// Build a TX hex with the largest UTXO. +async function buildTx1(recAddr) { + try { + // Get the balance of the sending address. + const balance = await getBCHBalance(SEND_ADDR, false) + console.log(`balance: ${JSON.stringify(balance, null, 2)}`) + console.log(`Balance of sending address ${SEND_ADDR} is ${balance} BCH.`) + + // Exit if the balance is zero. + if (balance <= 0.0) { + console.log(`Balance of sending address is zero. Exiting.`) + process.exit(0) + } + + const SEND_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(SEND_ADDR) + const RECV_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(recAddr) + console.log(`Sender Legacy Address: ${SEND_ADDR_LEGACY}`) + console.log(`Receiver Legacy Address: ${RECV_ADDR_LEGACY}`) + + const balance2 = await getBCHBalance(recAddr, false) + //console.log(`Balance of recieving address ${recAddr} is ${balance2} BCH.`) + + const u = await BITBOX.Address.utxo(SEND_ADDR) + //console.log(`u: ${JSON.stringify(u, null, 2)}`) + const utxo = findBiggestUtxo(u.utxos) + console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + // instance of transaction builder + const transactionBuilder = new BITBOX.TransactionBuilder("testnet") + + const satoshisToSend = SATOSHIS_TO_SEND + const originalAmount = utxo.satoshis + const vout = utxo.vout + const txid = utxo.txid + + // add input with txid and index of vout + transactionBuilder.addInput(txid, vout) + + // get byte count to calculate fee. paying 1.2 sat/byte + const byteCount = BITBOX.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + console.log(`byteCount: ${byteCount}`) + const satoshisPerByte = 1.0 + const txFee = Math.floor(satoshisPerByte * byteCount) + console.log(`txFee: ${txFee}`) + + // amount to send back to the sending address. + // It's the original amount - 1 sat/byte for tx size + const remainder = originalAmount - satoshisToSend - txFee + + // add output w/ address and amount to send + transactionBuilder.addOutput(recAddr, satoshisToSend) + transactionBuilder.addOutput(SEND_ADDR, remainder) + + // Generate a change address from a Mnemonic of a private key. + const change = changeAddrFromMnemonic(SEND_MNEMONIC) + + // Generate a keypair from the change address. + const keyPair = BITBOX.HDNode.toKeyPair(change) + + // Sign the transaction with the HD node. + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + + return hex + } catch (err) { + console.log(`Error in buildTx().`) + throw err + } +} + +// Build a TX hex with the SECOND largest UTXO. +async function buildTx2(recAddr) { + try { + // Get the balance of the sending address. + const balance = await getBCHBalance(SEND_ADDR, false) + console.log(`balance: ${JSON.stringify(balance, null, 2)}`) + console.log(`Balance of sending address ${SEND_ADDR} is ${balance} BCH.`) + + // Exit if the balance is zero. + if (balance <= 0.0) { + console.log(`Balance of sending address is zero. Exiting.`) + process.exit(0) + } + + const SEND_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(SEND_ADDR) + const RECV_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(recAddr) + console.log(`Sender Legacy Address: ${SEND_ADDR_LEGACY}`) + console.log(`Receiver Legacy Address: ${RECV_ADDR_LEGACY}`) + + const balance2 = await getBCHBalance(recAddr, false) + //console.log(`Balance of recieving address ${recAddr} is ${balance2} BCH.`) + + const u = await BITBOX.Address.utxo(SEND_ADDR) + //console.log(`u: ${JSON.stringify(u, null, 2)}`) + const utxo = findNextBiggestUtxo(u.utxos) + console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + // instance of transaction builder + const transactionBuilder = new BITBOX.TransactionBuilder("testnet") + + const satoshisToSend = SATOSHIS_TO_SEND + const originalAmount = utxo.satoshis + const vout = utxo.vout + const txid = utxo.txid + + // add input with txid and index of vout + transactionBuilder.addInput(txid, vout) + + // get byte count to calculate fee. paying 1.2 sat/byte + const byteCount = BITBOX.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + console.log(`byteCount: ${byteCount}`) + const satoshisPerByte = 1.0 + const txFee = Math.floor(satoshisPerByte * byteCount) + console.log(`txFee: ${txFee}`) + + // amount to send back to the sending address. + // It's the original amount - 1 sat/byte for tx size + const remainder = originalAmount - satoshisToSend - txFee + + // add output w/ address and amount to send + transactionBuilder.addOutput(recAddr, satoshisToSend) + transactionBuilder.addOutput(SEND_ADDR, remainder) + + // Generate a change address from a Mnemonic of a private key. + const change = changeAddrFromMnemonic(SEND_MNEMONIC) + + // Generate a keypair from the change address. + const keyPair = BITBOX.HDNode.toKeyPair(change) + + // Sign the transaction with the HD node. + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + + return hex + } catch (err) { + console.log(`Error in buildTx().`) + throw err + } +} + +// Generate a change address from a Mnemonic of a private key. +function changeAddrFromMnemonic(mnemonic) { + // root seed buffer + const rootSeed = BITBOX.Mnemonic.toSeed(mnemonic) + + // master HDNode + const masterHDNode = BITBOX.HDNode.fromSeed(rootSeed, "testnet") + + // HDNode of BIP44 account + const account = BITBOX.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") + + // derive the first external change address HDNode which is going to spend utxo + const change = BITBOX.HDNode.derivePath(account, "0/0") + + return change +} + +// Get the balance in BCH of a BCH address. +async function getBCHBalance(addr, verbose) { + try { + const result = await BITBOX.Address.details(addr) + + if (verbose) console.log(result) + + const bchBalance = result + + return bchBalance.balance + } catch (err) { + console.error(`Error in getBCHBalance: `, err) + console.log(`addr: ${addr}`) + throw err + } +} + +// Returns the utxo with the biggest balance from an array of utxos. +function findBiggestUtxo(utxos) { + // Sort the utxos by the amount of satoshis, largest first. + utxos.sort(function(a, b) { + return b.satoshis - a.satoshis + }) + + return utxos[0] +} + +// Returns the utxo with the 2nd biggest balance from an array of utxos. +function findNextBiggestUtxo(utxos) { + // Sort the utxos by the amount of satoshis, largest first. + utxos.sort(function(a, b) { + return b.satoshis - a.satoshis + }) + + return utxos[1] +} diff --git a/test/e2e/send-raw-transaction-single/package.json b/test/e2e/send-raw-transaction-single/package.json new file mode 100644 index 0000000..6f2b009 --- /dev/null +++ b/test/e2e/send-raw-transaction-single/package.json @@ -0,0 +1,28 @@ +{ + "name": "sendrawtransaction", + "version": "1.0.0", + "description": "e2e test - Send two transactions in parallel.", + "main": "sendrawtransaction.js", + "scripts": { + "test": "echo no tests yet", + "start": "node sendrawtransaction.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Bitcoin-com/bitbox-javascript-sdk.git" + }, + "keywords": [ + "bitbox", + "bch", + "bitcoin", + "bitcoin cash", + "bitcoin.com", + "javascript" + ], + "author": "Chris Troutner ", + "license": "MIT", + "bugs": { + "url": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/issues" + }, + "homepage": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/blob/master/README.md" +} diff --git a/test/e2e/send-raw-transaction-single/sendrawtransaction.js b/test/e2e/send-raw-transaction-single/sendrawtransaction.js new file mode 100644 index 0000000..372baee --- /dev/null +++ b/test/e2e/send-raw-transaction-single/sendrawtransaction.js @@ -0,0 +1,184 @@ +/* + This is an end-to-end test adapted from the send-bch example. It's purpose + is to test the sendRawTransaction endpoint. + + This version tests the single send call. + + Instructions: + - Ensure the address in the wallet.json file has some tBCH. + - Ensure the address in the wallet.json file has a UTXOs between 0.1 and + 0.001 tBCH + - This test will generate a single transaction from a UTXOs and broadcast it + using the sendrawtransaction single GET endpoint. +*/ + +// Replace the address below with the address you want to send the BCH to. +const RECV_ADDR1 = `bchtest:qzfn2mly05t6fjsh5kjj0dqq0jjtct27ng089dgg05` +const SATOSHIS_TO_SEND = 1000 + +// Instantiate BITBOX. +const bitboxLib = "../../../lib/BITBOX" +const BITBOXSDK = require(bitboxLib) +const BITBOX = new BITBOXSDK({ restURL: "https://trest.bitcoin.com/v2/" }) +//const BITBOX = new BITBOXSDK({ restURL: "http://localhost:3000/v2/" }) + +const util = require("util") + +// Open the wallet generated with create-wallet. +try { + var walletInfo = require(`./wallet.json`) +} catch (err) { + console.log( + `Could not open wallet.json. Generate a wallet with create-wallet first.` + ) + process.exit(0) +} + +const SEND_ADDR = walletInfo.cashAddress +const SEND_MNEMONIC = walletInfo.mnemonic + +async function testSend() { + try { + const hex1 = await buildTx1(RECV_ADDR1) + //const hex2 = await buildTx2(RECV_ADDR2) + + console.log(`hex1: ${hex1}\n\n`) + //console.log(`hex2: ${hex2}\n\n`) + + const broadcast = await BITBOX.RawTransactions.sendRawTransaction(hex1) + + console.log(`Transaction IDs: ${JSON.stringify(broadcast, null, 2)}`) + console.log(`Should return a TXID string.`) + } catch (err) { + console.log(`Error in testSend: `, err) + } +} +testSend() + +// Build a TX hex with the largest UTXO. +async function buildTx1(recAddr) { + try { + // Get the balance of the sending address. + const balance = await getBCHBalance(SEND_ADDR, false) + console.log(`balance: ${JSON.stringify(balance, null, 2)}`) + console.log(`Balance of sending address ${SEND_ADDR} is ${balance} BCH.`) + + // Exit if the balance is zero. + if (balance <= 0.0) { + console.log(`Balance of sending address is zero. Exiting.`) + process.exit(0) + } + + const SEND_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(SEND_ADDR) + const RECV_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(recAddr) + console.log(`Sender Legacy Address: ${SEND_ADDR_LEGACY}`) + console.log(`Receiver Legacy Address: ${RECV_ADDR_LEGACY}`) + + const balance2 = await getBCHBalance(recAddr, false) + //console.log(`Balance of recieving address ${recAddr} is ${balance2} BCH.`) + + const u = await BITBOX.Address.utxo(SEND_ADDR) + //console.log(`u: ${JSON.stringify(u, null, 2)}`) + const utxo = findBiggestUtxo(u.utxos) + console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) + + // instance of transaction builder + const transactionBuilder = new BITBOX.TransactionBuilder("testnet") + + const satoshisToSend = SATOSHIS_TO_SEND + const originalAmount = utxo.satoshis + const vout = utxo.vout + const txid = utxo.txid + + // add input with txid and index of vout + transactionBuilder.addInput(txid, vout) + + // get byte count to calculate fee. paying 1.2 sat/byte + const byteCount = BITBOX.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + console.log(`byteCount: ${byteCount}`) + const satoshisPerByte = 1.0 + const txFee = Math.floor(satoshisPerByte * byteCount) + console.log(`txFee: ${txFee}`) + + // amount to send back to the sending address. + // It's the original amount - 1 sat/byte for tx size + const remainder = originalAmount - satoshisToSend - txFee + + // add output w/ address and amount to send + transactionBuilder.addOutput(recAddr, satoshisToSend) + transactionBuilder.addOutput(SEND_ADDR, remainder) + + // Generate a change address from a Mnemonic of a private key. + const change = changeAddrFromMnemonic(SEND_MNEMONIC) + + // Generate a keypair from the change address. + const keyPair = BITBOX.HDNode.toKeyPair(change) + + // Sign the transaction with the HD node. + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + + return hex + } catch (err) { + console.log(`Error in buildTx().`) + throw err + } +} + +// Generate a change address from a Mnemonic of a private key. +function changeAddrFromMnemonic(mnemonic) { + // root seed buffer + const rootSeed = BITBOX.Mnemonic.toSeed(mnemonic) + + // master HDNode + const masterHDNode = BITBOX.HDNode.fromSeed(rootSeed, "testnet") + + // HDNode of BIP44 account + const account = BITBOX.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") + + // derive the first external change address HDNode which is going to spend utxo + const change = BITBOX.HDNode.derivePath(account, "0/0") + + return change +} + +// Get the balance in BCH of a BCH address. +async function getBCHBalance(addr, verbose) { + try { + const result = await BITBOX.Address.details(addr) + + if (verbose) console.log(result) + + const bchBalance = result + + return bchBalance.balance + } catch (err) { + console.error(`Error in getBCHBalance: `, err) + console.log(`addr: ${addr}`) + throw err + } +} + +// Returns the utxo with the biggest balance from an array of utxos. +function findBiggestUtxo(utxos) { + // Sort the utxos by the amount of satoshis, largest first. + utxos.sort(function(a, b) { + return b.satoshis - a.satoshis + }) + + return utxos[0] +} diff --git a/test/e2e/send-token/send-token.js b/test/e2e/send-token/send-token.js new file mode 100644 index 0000000..a40c094 --- /dev/null +++ b/test/e2e/send-token/send-token.js @@ -0,0 +1,100 @@ +/* + This is an end-to-end test which verified the happy-path of sending an SLP + token. It's really a test of the speed of SLPDB to process new token + transactions. + + This program expects two wallets. Wallet 1 must have a small amount of BCH + and an inventory of SLP test tokens. Wallet 2 is the reieving wallet. +*/ + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 1 +} + +// const SLPSDK = require("../../../lib/SLP") +// const slpsdk = new SLPSDK() + +const WALLET1 = `../wallet1.json` +const WALLET2 = `../wallet2.json` + +const lib = require("../util/e2e-util") + +// The main test function. +// Sends a token and reports on how long it takes to show up in SLPDB production. +async function sendTokenTest() { + try { + // Open the sending wallet. + const sendWallet = await lib.openWallet(WALLET1) + //console.log(`sendWallet: ${JSON.stringify(walletInfo, null, 2)}`) + + // Open the recieving wallet. + const recvWallet = await lib.openWallet(WALLET2) + //console.log(`recvWallet: ${JSON.stringify(walletInfo, null, 2)}`) + + // Get the balance of the recieving wallet. + // const testTokens = recvWallet.tokenBalance.filter( + // x => testTokenId === x.tokenId + // ) + // const startBalance = testTokens[0].balance + const startBalance = await lib.getTestTokenBalance(recvWallet) + console.log(`Starting balance: ${startBalance} test tokens.`) + let newBalance = startBalance + + // Send a token to the recieving wallet. + await lib.sendToken(sendWallet, recvWallet) + console.log(`Sent test token.`) + + // Track the time until the balance for the recieving wallet has been updated. + const startTime = new Date() + const waitTime = 10000 // time in milliseconds + + // Loop with a definite exit point, so we don't loop forever. + for (let i = 0; i < 50; i++) { + await sleep(waitTime) // Wait for a while before checking + + console.log(`Checking token balance...`) + newBalance = await lib.getTestTokenBalance(recvWallet) + + // Break out of the loop once a new balance is detected. + if (newBalance > startBalance) break + + // Provide high-level warnings. + const secondsPassed = (i * waitTime) / 1000 + if (secondsPassed > 60 * 10) { + console.log(`More than 10 minutes passed.`) + return false // Fail the test. + } else if (secondsPassed > 60 * 5) { + console.log(`More than 5 minutes passed.`) + } else if (secondsPassed > 60) { + console.log(`More than 1 minute passed.`) + } + } + + // Calculate the amount of time that has passed. + const endTime = new Date() + let deltaTime = (endTime.getTime() - startTime.getTime()) / 60000 + deltaTime = lib.threeDecimals(deltaTime) + console.log(`SLPDB updated token balance in ${deltaTime} minutes.`) + + // Consolidate the SLP UTXOs on the recieve wallet. + await lib.sendToken(recvWallet, recvWallet) + + return deltaTime // Return the time in minutes it took for SLPDB to update. + } catch (err) { + console.log(`Error in e2e/send-token.js/sendTokenTest(): `, err) + return false + } +} + +// Promise based sleep function. +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +module.exports = { + sendTokenTest +} diff --git a/test/e2e/util/e2e-util.js b/test/e2e/util/e2e-util.js new file mode 100644 index 0000000..56fac06 --- /dev/null +++ b/test/e2e/util/e2e-util.js @@ -0,0 +1,119 @@ +/* + This is a utility library for common SLP and BCH actions needed by the e2e + tests. +*/ + +module.exports = { + openWallet, + sendToken, + getTestTokenBalance, + threeDecimals +} + +const SLPSDK = require("../../../lib/SLP") +const slpsdk = new SLPSDK() + +const testTokenId = + "cc1b2084a9c43bb5a633df7f38201adde5c5f5cef2fed945d12f8dcd4e505c67" + +// Open a wallet and return an object with its address, BCH balance, and SLP +// token balance. +async function openWallet(filename) { + try { + walletInfo = require(filename) + + //const walletBalance = await getBalance(walletInfo) + // const walletBalance = await slpsdk.Utils.balancesForAddress( + // walletInfo.slpAddress + // ) + // // console.log(`walletBalance: ${JSON.stringify(walletBalance, null, 2)}`) + // walletInfo.tokenBalance = walletBalance + + return walletInfo + } catch (err) { + console.log( + `Could not open ${filename}. Generate a wallet with create-wallet first.`, + err + ) + process.exit(0) + } +} + +// Send a token from wallet1 to wallet2. +async function sendToken(wallet1, wallet2) { + try { + const mnemonic = wallet1.mnemonic + + // root seed buffer + const rootSeed = slpsdk.Mnemonic.toSeed(mnemonic) + + // master HDNode + const masterHDNode = slpsdk.HDNode.fromSeed(rootSeed) + + // HDNode of BIP44 account + const account = slpsdk.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") + + const change = slpsdk.HDNode.derivePath(account, "0/0") + + // get the cash address + //const cashAddress = slpsdk.HDNode.toCashAddress(change) + //const slpAddress = slpsdk.HDNode.toSLPAddress(change) + + const fundingAddress = wallet1.slpAddress + const fundingWif = slpsdk.HDNode.toWIF(change) // <-- compressed WIF format + const tokenReceiverAddress = wallet2.slpAddress + const bchChangeReceiverAddress = wallet1.cashAddress + + // Create a config object for minting + const sendConfig = { + fundingAddress, + fundingWif, + tokenReceiverAddress, + bchChangeReceiverAddress, + tokenId: + "cc1b2084a9c43bb5a633df7f38201adde5c5f5cef2fed945d12f8dcd4e505c67", + amount: 1 + } + + //console.log(`createConfig: ${util.inspect(createConfig)}`) + + // Generate, sign, and broadcast a hex-encoded transaction for sending + // the tokens. + const sendTxId = await slpsdk.TokenType1.send(sendConfig) + + //console.log(`sendTxId: ${sendTxId}`) + } catch (err) { + console.log(`Error in e2e-util.js/sendToken()`) + throw err + } +} + +// Returns just the test token balance for a wallet. +async function getTestTokenBalance(walletData) { + try { + const tokenBalance = await slpsdk.Util.balancesForAddress( + walletData.slpAddress + ) + // console.log(`tokenBalance: ${JSON.stringify(tokenBalance, null, 2)}`) + + const testTokens = tokenBalance.filter(x => testTokenId === x.tokenId) + + return testTokens[0].balance + } catch (err) { + console.log(`Error in e2e-util.js/getTestTokenBalance()`) + throw err + } +} + +// Round a number to three decimal places. +function threeDecimals(inNum) { + try { + let tempNum = inNum * 1000 + tempNum = Math.round(tempNum) + tempNum = tempNum / 1000 + return tempNum + } catch (err) { + console.log(`Error in e2e-util.js/threeDecimals()`) + throw err + } +} diff --git a/test/e2e/wallet1.json b/test/e2e/wallet1.json new file mode 100644 index 0000000..f49c8bf --- /dev/null +++ b/test/e2e/wallet1.json @@ -0,0 +1,6 @@ +{ + "mnemonic": "capital cushion ostrich later educate rubber thank resist alter hollow way dragon", + "cashAddress": "bitcoincash:qrjnvnvsukcvc59a7v28pzmtue6aes4qgy5zmp72sr", + "slpAddress": "simpleledger:qrjnvnvsukcvc59a7v28pzmtue6aes4qgyces6t2wa", + "legacyAddress": "1Mtxny6hMUifjE9hNFoVbUYPS3wDkroLzH" +} \ No newline at end of file diff --git a/test/e2e/wallet2.json b/test/e2e/wallet2.json new file mode 100644 index 0000000..08eadd2 --- /dev/null +++ b/test/e2e/wallet2.json @@ -0,0 +1,6 @@ +{ + "mnemonic": "obscure volume zone abstract humor wisdom panther economy upset nation choose latin", + "cashAddress": "bitcoincash:qpqv8cqlgkzzh2ed372g9l5vxur7f39y6vq7hac00m", + "slpAddress": "simpleledger:qpqv8cqlgkzzh2ed372g9l5vxur7f39y6vv9uxd039", + "legacyAddress": "16uStwkG1nGC1HB2tSWGqLQN2bDjXeeeRk" +} \ No newline at end of file diff --git a/test/integration/blockbook.js b/test/integration/blockbook.js new file mode 100644 index 0000000..7b20a58 --- /dev/null +++ b/test/integration/blockbook.js @@ -0,0 +1,170 @@ +/* + Integration tests for the Blockbook library. + +*/ + +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +//const axios = require("axios") + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 3 +} + +describe(`#Blockbook`, () => { + describe(`#Balance`, () => { + it(`should GET balance for a single address`, async () => { + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + const result = await bchjs.Blockbook.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAnyKeys(result, [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result.txids) + }) + + it(`should POST request balances for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Blockbook.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result[0].txids) + }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Blockbook.balance(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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Blockbook.balance(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + const addr = "bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7" + + const result = await bchjs.Blockbook.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should POST utxo details for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7", + "bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr" + ] + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isArray(result[0]) + assert.hasAnyKeys(result[0][0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Blockbook.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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr") + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/blockchain.js b/test/integration/blockchain.js new file mode 100644 index 0000000..4ea3761 --- /dev/null +++ b/test/integration/blockchain.js @@ -0,0 +1,331 @@ +/* + Integration tests for the bchjs. Only covers calls made to + rest.bitcoin.com. + + TODO + - getMempoolEntry() only works on TXs in the mempool, so it needs to be part + of an e2e test to be properly tested. +*/ + +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(`#blockchain`, () => { + describe(`#getBestBlockHash`, () => { + it(`should GET best block hash`, async () => { + const result = await bchjs.Blockchain.getBestBlockHash() + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal(result.length, 64, "Specific hash length") + }) + }) + + describe("#getBlockHeader", () => { + it(`should GET block header for a single hash`, async () => { + const hash = + "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" + + const result = await bchjs.Blockchain.getBlockHeader(hash) + + assert.hasAllKeys(result, [ + "hash", + "confirmations", + "height", + "version", + "versionHex", + "merkleroot", + "time", + "mediantime", + "nonce", + "bits", + "difficulty", + "chainwork", + "previousblockhash", + "nextblockhash", + "nTx" + ]) + }) + + it(`should GET block headers for an array of hashes`, async () => { + const hash = [ + "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201", + "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" + ] + + const result = await bchjs.Blockchain.getBlockHeader(hash) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "hash", + "confirmations", + "height", + "version", + "versionHex", + "merkleroot", + "time", + "mediantime", + "nonce", + "bits", + "difficulty", + "chainwork", + "previousblockhash", + "nextblockhash", + "nTx" + ]) + }) + + it(`should throw an error for improper input`, async () => { + try { + const hash = 12345 + + await bchjs.Blockchain.getBlockHeader(hash) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input hash must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) { + data.push( + "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201" + ) + } + + const result = await bchjs.Blockchain.getBlockHeader(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#getMempoolEntry", () => { + /* + // To run this test, the txid must be unconfirmed. + const txid = + "defea04c38ee00cf73ad402984714ed22dc0dd99b2ae5cb50d791d94343ba79b" + + it(`should GET single mempool entry`, async () => { + const result = await bchjs.Blockchain.getMempoolEntry(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "size", + "fee", + "modifiedfee", + "time", + "height", + "startingpriority", + "currentpriority", + "descendantcount", + "descendantsize", + "descendantfees", + "ancestorcount", + "ancestorsize", + "ancestorfees", + "depends" + ]) + }) + + it(`should get an array of mempool entries`, async () => { + const result = await bchjs.Blockchain.getMempoolEntry([txid, txid]) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "size", + "fee", + "modifiedfee", + "time", + "height", + "startingpriority", + "currentpriority", + "descendantcount", + "descendantsize", + "descendantfees", + "ancestorcount", + "ancestorsize", + "ancestorfees", + "depends" + ]) + }) + */ + + it(`should throw an error if txid is not in mempool`, async () => { + try { + const txid = + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + + await bchjs.Blockchain.getMempoolEntry(txid) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: ${util.inspect(err)}`) + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, `Transaction not in mempool`) + } + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.getMempoolEntry(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + }) + + describe(`#getTxOutProof`, () => { + it(`should get single tx out proof`, async () => { + const txid = + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + + const result = await bchjs.Blockchain.getTxOutProof(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isString(result) + }) + + it(`should get an array of tx out proofs`, async () => { + const txid = [ + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7", + "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" + ] + + const result = await bchjs.Blockchain.getTxOutProof(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.getTxOutProof(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + }) + + describe(`#verifyTxOutProof`, () => { + const mockTxOutProof = + "0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01" + + it(`should verify a single proof`, async () => { + const result = await bchjs.Blockchain.verifyTxOutProof(mockTxOutProof) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + assert.equal( + result[0], + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + ) + }) + + it(`should verify an array of proofs`, async () => { + const proofs = [mockTxOutProof, mockTxOutProof] + const result = await bchjs.Blockchain.verifyTxOutProof(proofs) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + assert.equal( + result[0], + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7" + ) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.verifyTxOutProof(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) data.push(mockTxOutProof) + + const result = await bchjs.Blockchain.verifyTxOutProof(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#getTxOut", () => { + it("should get information on an unspent tx", async () => { + const result = await bchjs.Blockchain.getTxOut( + "62a3ea958a463a372bc0caf2c374a7f60be9c624be63a0db8db78f05809df6d8", + 0, + true + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "bestblock", + "confirmations", + "value", + "scriptPubKey", + "coinbase" + ]) + }) + + it("should get information on a spent tx", async () => { + const result = await bchjs.Blockchain.getTxOut( + "87380e52d151856b23173d6d8a3db01b984c6b50f77ea045a5a1cf4f54497871", + 0, + true + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result, null) + }) + }) +}) diff --git a/test/integration/control.js b/test/integration/control.js new file mode 100644 index 0000000..5e4fd98 --- /dev/null +++ b/test/integration/control.js @@ -0,0 +1,19 @@ +/* + Integration tests for bchjs control library. +*/ + +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#control`, () => { + describe(`#getNetworkInfo`, () => { + it("should get info on the full node", async () => { + const result = await bchjs.Control.getNetworkInfo() + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "version") + }) + }) +}) diff --git a/test/integration/electrumx.js b/test/integration/electrumx.js new file mode 100644 index 0000000..e7f9915 --- /dev/null +++ b/test/integration/electrumx.js @@ -0,0 +1,181 @@ +const chai = require("chai") +const assert = chai.assert +const sinon = require("sinon") + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#ElectrumX`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "tx_pos") + assert.property(result.utxos[0], "value") + }) + + it(`should POST request for UTXOs for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "tx_pos") + assert.property(result.utxos[0].utxos[0], "value") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Electrumx.utxo(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#balance`, () => { + it(`should GET balance for a single address`, async () => { + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balance") + assert.property(result.balance, "confirmed") + assert.property(result.balance, "unconfirmed") + }) + + it(`should POST request for balances for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balances") + assert.isArray(result.balances) + + assert.property(result.balances[0], "address") + assert.property(result.balances[0], "balance") + assert.property(result.balances[0].balance, "confirmed") + assert.property(result.balances[0].balance, "unconfirmed") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Electrumx.balance(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#transactions`, () => { + it(`should GET transaction history for a single address`, async () => { + const addr = "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + assert.property(result.transactions[0], "height") + assert.property(result.transactions[0], "tx_hash") + }) + + it(`should POST request for transaction history for an array of addresses`, async () => { + const addr = [ + "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + + assert.property(result.transactions[0], "address") + assert.property(result.transactions[0], "transactions") + assert.isArray(result.transactions[0].transactions) + assert.property(result.transactions[0].transactions[0], "height") + assert.property(result.transactions[0].transactions[0], "tx_hash") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0") + + const result = await bchjs.Electrumx.transactions(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/encryption.js b/test/integration/encryption.js new file mode 100644 index 0000000..edf1747 --- /dev/null +++ b/test/integration/encryption.js @@ -0,0 +1,31 @@ +const assert = require("chai").assert + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe("#Encryption", () => { + describe("#getPubKey", () => { + it("should get a public key", async () => { + const addr = "bitcoincash:qpf8jv9hmqcda0502gjp7nm3g24y5h5s4unutghsxq" + + const result = await bchjs.encryption.getPubKey(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + assert.property(result, "publicKey") + }) + + it("should report when public key can not be found", async () => { + const addr = "bitcoincash:qrgqqkky28jdkv3w0ctrah0mz3jcsnsklc34gtukrh" + + const result = await bchjs.encryption.getPubKey(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, false) + assert.property(result, "publicKey") + assert.equal(result.publicKey, "not found") + }) + }) +}) diff --git a/test/integration/ninsight.js b/test/integration/ninsight.js new file mode 100644 index 0000000..098e972 --- /dev/null +++ b/test/integration/ninsight.js @@ -0,0 +1,37 @@ +const chai = require("chai") +const assert = chai.assert +const sinon = require("sinon") + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#Ninsight`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Ninsight.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "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") + }) + }) +}) diff --git a/test/integration/openbazaar.js b/test/integration/openbazaar.js new file mode 100644 index 0000000..1cac45d --- /dev/null +++ b/test/integration/openbazaar.js @@ -0,0 +1,117 @@ +/* + tests for OpenBazaar library. +*/ + +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe(`#OpenBazaar`, () => { + describe(`#Balance`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.balance(addr) + // assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + + it(`should GET balance for a single address`, async () => { + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.OpenBazaar.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "page", + "totalPages", + "itemsOnPage", + "addrStr", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxApperances", + "txApperances", + "transactions" + ]) + assert.isArray(result.transactions) + }) + }) + + describe(`#utxo`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.utxo(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + + it(`should GET utxos for a single address`, async () => { + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.OpenBazaar.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "txid", + "vout", + "amount", + "height", + "confirmations", + "satoshis" + ]) + }) + }) + + describe(`#tx`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.tx(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input txid must be a string`) + } + }) + + it(`should GET transactions for a single address`, async () => { + const addr = + "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7" + + const result = await bchjs.OpenBazaar.tx(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "txid", + "version", + "vin", + "vout", + "blockhash", + "blockheight", + "confirmations", + "blocktime", + "valueOut", + "valueIn", + "fees", + "hex", + "time" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + }) +}) diff --git a/test/integration/price.js b/test/integration/price.js new file mode 100644 index 0000000..f6e325d --- /dev/null +++ b/test/integration/price.js @@ -0,0 +1,14 @@ +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe("#price", () => { + describe("#current", () => { + describe("#single currency", () => { + it("should get current price for single currency", async () => { + const result = await bchjs.Price.current("usd") + assert.notEqual(0, result) + }) + }) + }) +}) diff --git a/test/integration/rawtransaction.js b/test/integration/rawtransaction.js new file mode 100644 index 0000000..d391d7c --- /dev/null +++ b/test/integration/rawtransaction.js @@ -0,0 +1,327 @@ +/* + 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", () => { + describe("#decodeRawTransaction", () => { + it("should decode tx for a single hex", async () => { + const hex = + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + + const result = await bchjs.RawTransactions.decodeRawTransaction(hex) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + + it("should decode an array of tx hexes", async () => { + const hexes = [ + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000", + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + ] + + const result = await bchjs.RawTransactions.decodeRawTransaction(hexes) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout" + ]) + assert.isArray(result[0].vin) + assert.isArray(result[0].vout) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.decodeRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) { + data.push( + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + ) + } + + const result = await bchjs.RawTransactions.decodeRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#getRawTransaction", () => { + it("should decode a single txid, with concise output", async () => { + const txid = + "23213453b4642a73b4fc30d3112d72549ca153a8707255b14373b59e43558de1" + const verbose = false + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isString(result) + }) + + it("should decode a single txid, with verbose output", async () => { + const txid = + "23213453b4642a73b4fc30d3112d72549ca153a8707255b14373b59e43558de1" + const verbose = true + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "hex", + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "confirmations", + "time", + "blocktime" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + + it("should decode an array of txids, with a concise output", async () => { + const txid = [ + "23213453b4642a73b4fc30d3112d72549ca153a8707255b14373b59e43558de1", + "b25d24fbb42d84812ed2cb55797f10fdec41afc7906ab563d1ec8c8676a2037f" + ] + const verbose = false + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + }) + + it("should decode an array of txids, with a verbose output", async () => { + const txid = [ + "23213453b4642a73b4fc30d3112d72549ca153a8707255b14373b59e43558de1", + "b25d24fbb42d84812ed2cb55797f10fdec41afc7906ab563d1ec8c8676a2037f" + ] + const verbose = true + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "hex", + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "confirmations", + "time", + "blocktime" + ]) + assert.isArray(result[0].vin) + assert.isArray(result[0].vout) + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const dataMock = + "23213453b4642a73b4fc30d3112d72549ca153a8707255b14373b59e43558de1" + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.RawTransactions.getRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#decodeScript", () => { + it("should decode script for a single hex", async () => { + const hex = + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" + + const result = await bchjs.RawTransactions.decodeScript(hex) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["asm", "type", "p2sh"]) + }) + + // CT 2/20/19 - Waiting for this PR to be merged complete the test: + // https://github.com/Bitcoin-com/rest.bitcoin.com/pull/312 + /* + it("should decode an array of tx hexes", async () => { + const hexes = [ + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16", + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" + ] + + const result = await bchjs.RawTransactions.decodeScript(hexes) + console.log(`result ${JSON.stringify(result, null, 2)}`) + }) +*/ + /* + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.decodeRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) +*/ + }) + + /* + 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") + } + }) + + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.sendRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input hex must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const dataMock = + "01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.RawTransactions.sendRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/slp.js b/test/integration/slp.js new file mode 100644 index 0000000..a641a21 --- /dev/null +++ b/test/integration/slp.js @@ -0,0 +1,529 @@ +/* + Integration tests for the bchjs covering SLP tokens. +*/ + +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: 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. + await sleep(1000) + }) + + describe("#util", () => { + describe("#list", () => { + it(`should get information on the Spice token`, async () => { + const tokenId = `4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf` + + const result = await bchjs.SLP.Utils.list(tokenId) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "decimals", + "timestamp", + "timestamp_unix", + "versionType", + "documentUri", + "symbol", + "name", + "containsBaton", + "id", + "documentHash", + "initialTokenQty", + "blockCreated", + "blockLastActiveSend", + "blockLastActiveMint", + "txnsSinceGenesis", + "validAddress", + "totalMinted", + "totalBurned", + "circulatingSupply", + "mintingBatonStatus" + ]) + }) + }) + + // describe("#decodeOpReturn", () => { + // it("should decode the OP_RETURN for a SEND txid", async () => { + // const txid = + // "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "tokenId", + // "spendData" + // ]) + // }) + // + // it("should decode the OP_RETURN for a GENESIS txid", async () => { + // const txid = + // "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "ticker", + // "name", + // "documentUrl", + // "documentHash", + // "decimals", + // "mintBatonVout", + // "initialQty", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should decode the OP_RETURN for a MINT txid", async () => { + // const txid = + // "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" + // + // const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "tokenType", + // "transactionType", + // "tokenId", + // "mintBatonVout", + // "batonStillExists", + // "quantity", + // "tokensSentTo", + // "batonHolder" + // ]) + // }) + // + // it("should throw an error for a non-SLP transaction", async () => { + // try { + // const txid = + // "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" + // + // await bchjs.SLP.Utils.decodeOpReturn(txid) + // + // assert.equal(true, false, "Unexpected result") + // } catch (err) { + // // console.log(`err: `, err) + // assert.include(err.message, "Not an OP_RETURN") + // } + // }) + // }) + + describe("#decodeOpReturn", () => { + it("should decode the OP_RETURN for a SEND txid", async () => { + const txid = + "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" + + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"]) + assert.equal( + result.tokenId, + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + ) + + // Verify outputs + assert.equal(result.amounts.length, 2) + assert.equal(result.amounts[0], "100000000") + assert.equal(result.amounts[1], "99883300000000") + }) + + it("should decode the OP_RETURN for a GENESIS txid", async () => { + const txid = + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "tokenType", + "txType", + "tokenId", + "ticker", + "name", + "documentUri", + "documentHash", + "decimals", + "mintBatonVout", + "qty" + ]) + assert.equal( + result.tokenId, + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + ) + assert.equal(result.txType, "GENESIS") + assert.equal(result.ticker, "TOK-CH") + assert.equal(result.name, "TokyoCash") + }) + + it("should decode the OP_RETURN for a MINT txid", async () => { + const txid = + "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" + + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "tokenType", + "txType", + "tokenId", + "mintBatonVout", + "qty" + ]) + }) + + it("should throw an error for a non-SLP transaction", async () => { + try { + const txid = + "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" + + await bchjs.SLP.Utils.decodeOpReturn(txid) + + assert.equal(true, false, "Unexpected result") + } catch (err) { + // console.log(`err: `, err) + assert.include(err.message, "scriptpubkey not op_return") + } + }) + + // Note: This TX is interpreted as valid by the original decodeOpReturn(). + // Fixing this issue and related issues was the reason for creating the + // decodeOpReturn2() method using the slp-parser library. + it("should throw error for invalid SLP transaction", async () => { + try { + const txid = + "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415" + + await bchjs.SLP.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + } catch (err) { + // console.log(`err: `, err) + assert.include(err.message, "amount string size not 8 bytes") + } + }) + }) + + describe("#tokenUtxoDetails", () => { + // // This captures an important corner-case. When an SLP token is created, the + // // change UTXO will contain the same SLP txid, but it is not an SLP UTXO. + it("should return details on minting baton from genesis transaction", async () => { + const utxos = [ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 3, + amount: 0.00002015, + satoshis: 2015, + height: 594892, + confirmations: 5 + }, + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 2, + amount: 0.00000546, + satoshis: 546, + height: 594892, + confirmations: 5 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.equal(data[0].isValid, false, "Change UTXO marked as false.") + + assert.property(data[1], "txid") + assert.property(data[1], "vout") + assert.property(data[1], "amount") + assert.property(data[1], "satoshis") + assert.property(data[1], "height") + assert.property(data[1], "confirmations") + assert.property(data[1], "utxoType") + assert.property(data[1], "tokenId") + assert.property(data[1], "tokenTicker") + assert.property(data[1], "tokenName") + assert.property(data[1], "tokenDocumentUrl") + assert.property(data[1], "tokenDocumentHash") + assert.property(data[1], "decimals") + assert.property(data[1], "isValid") + assert.equal(data[1].isValid, true) + }) + + it("should return details for a MINT token utxo", async () => { + // Mock the call to REST API + + const utxos = [ + { + txid: + "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 600297, + confirmations: 76 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.property(data[0], "txid") + assert.property(data[0], "vout") + assert.property(data[0], "amount") + assert.property(data[0], "satoshis") + assert.property(data[0], "height") + assert.property(data[0], "confirmations") + assert.property(data[0], "utxoType") + assert.property(data[0], "transactionType") + assert.property(data[0], "tokenId") + assert.property(data[0], "tokenTicker") + assert.property(data[0], "tokenName") + assert.property(data[0], "tokenDocumentUrl") + assert.property(data[0], "tokenDocumentHash") + assert.property(data[0], "decimals") + assert.property(data[0], "mintBatonVout") + assert.property(data[0], "tokenQty") + assert.property(data[0], "isValid") + assert.equal(data[0].isValid, true) + }) + + it("should return details for a simple SEND SLP token utxo", async () => { + const utxos = [ + { + txid: + "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 596089, + confirmations: 748 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.property(data[0], "txid") + assert.property(data[0], "vout") + assert.property(data[0], "amount") + assert.property(data[0], "satoshis") + assert.property(data[0], "height") + assert.property(data[0], "confirmations") + assert.property(data[0], "utxoType") + assert.property(data[0], "tokenId") + assert.property(data[0], "tokenTicker") + assert.property(data[0], "tokenName") + assert.property(data[0], "tokenDocumentUrl") + assert.property(data[0], "tokenDocumentHash") + assert.property(data[0], "decimals") + assert.property(data[0], "tokenQty") + assert.property(data[0], "isValid") + assert.equal(data[0].isValid, true) + }) + + it("should handle BCH and SLP utxos in the same TX", async () => { + const utxos = [ + { + txid: + "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + vout: 3, + value: "6816", + height: 606848, + confirmations: 13, + satoshis: 6816 + }, + { + txid: + "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + vout: 2, + value: "546", + height: 606848, + confirmations: 13, + satoshis: 546 + } + ] + + const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.equal(result.length, 2) + assert.equal(result[0].isValid, false) + assert.equal(result[1].isValid, true) + }) + + it("should handle problematic utxos", async () => { + const utxos = [ + { + txid: + "0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959", + vout: 0, + amount: 0.00018362, + satoshis: 18362, + height: 613483, + confirmations: 124 + }, + { + txid: + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 612075, + confirmations: 1532 + } + ] + + const result = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.equal(result.length, 2) + assert.equal(result[0].isValid, false) + assert.equal(result[1].isValid, true) + }) + + it("should return false for BCH-only UTXOs", async () => { + const utxos = [ + { + txid: + "a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b", + vout: 0, + amount: 0.00001, + satoshis: 1000, + confirmations: 0, + ts: 1578424704 + }, + { + txid: + "53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44", + vout: 0, + amount: 0.00001, + satoshis: 1000, + confirmations: 0, + ts: 1578424634 + } + ] + + const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert.isArray(data) + assert.equal(data[0].isValid, false) + assert.equal(data[1].isValid, false) + }) + }) + + describe("#balancesForAddress", () => { + it(`should throw an error if input is not a string or array of strings`, async () => { + try { + const address = 1234 + + await bchjs.SLP.Utils.balancesForAddress(address) + + assert.equal(true, false, "Uh oh. Code path should not end here.") + } catch (err) { + //console.log(`Error: `, err) + assert.include( + err.message, + `Input address must be a string or array of strings` + ) + } + }) + + it(`should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9`, async () => { + const balances = await bchjs.SLP.Utils.balancesForAddress( + "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9" + ) + // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert.isArray(balances) + assert.hasAllKeys(balances[0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + + it(`should fetch balances for multiple addresses`, async () => { + const addresses = [ + "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + "simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57" + ] + + const balances = await bchjs.SLP.Utils.balancesForAddress(addresses) + // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert.isArray(balances) + assert.isArray(balances[0]) + assert.hasAllKeys(balances[0][0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + }) + }) + + describe("#tokentype1", () => { + describe("#getHexOpReturn", () => { + it("should return OP_RETURN object ", async () => { + const tokenUtxos = [ + { + tokenId: + "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + decimals: 0, + tokenQty: 2 + } + ] + const sendQty = 1.5 + + const result = await bchjs.SLP.TokenType1.getHexOpReturn( + tokenUtxos, + sendQty + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "script") + assert.isString(result.script) + + assert.property(result, "outputs") + assert.isNumber(result.outputs) + }) + }) + }) +}) + +// Promise-based sleep function +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} diff --git a/test/integration/test-free-tier.sh b/test/integration/test-free-tier.sh new file mode 100755 index 0000000..fa2aea2 --- /dev/null +++ b/test/integration/test-free-tier.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +export RESTURL=https://free-main.fullstack.cash/v3/ +#export RESTURL=http://localhost:3000/v3/ + +cd test/integration/ +mocha --timeout 30000 blockchain.js control.js electrumx.js ninsight.js openbazaar.js price.js rawtransaction.js slp.js util.js diff --git a/test/integration/testnet/blockbook.js b/test/integration/testnet/blockbook.js new file mode 100644 index 0000000..d286f6e --- /dev/null +++ b/test/integration/testnet/blockbook.js @@ -0,0 +1,178 @@ +/* + Integration tests for the Blockbook library. + +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` +// if (process.env.RESTURL) RESTURL = process.env.RESTURL + +const BCHJS = require("../../../src/bch-js") +// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v3/` }) +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) +// const bchjs = new BCHJS({ restURL: RESTURL }) +//const axios = require("axios") + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 3 +} + +describe(`#Blockbook`, () => { + describe(`#Balance`, () => { + it(`should GET balance for a single address`, async () => { + const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + + const result = await bchjs.Blockbook.balance(addr) + // console.log(`result: ${util.inspect(result)}`) + + assert.hasAnyKeys(result, [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result.txids) + }) + + it(`should POST request balances for an array of addresses`, async () => { + const addr = [ + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2", + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + ] + + const result = await bchjs.Blockbook.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result[0].txids) + }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Blockbook.balance(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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Blockbook.balance(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + + const result = await bchjs.Blockbook.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should POST utxo details for an array of addresses`, async () => { + const addr = [ + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2", + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + ] + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isArray(result[0]) + assert.hasAnyKeys(result[0][0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Blockbook.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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/testnet/blockchain.js b/test/integration/testnet/blockchain.js new file mode 100644 index 0000000..961f252 --- /dev/null +++ b/test/integration/testnet/blockchain.js @@ -0,0 +1,308 @@ +/* + Integration tests for the bchjs. Only covers calls made to + rest.bitcoin.com. + + TODO + - getMempoolEntry() only works on TXs in the mempool, so it needs to be part + of an e2e test to be properly tested. +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` +// if (process.env.RESTURL) RESTURL = process.env.RESTURL + +const BCHJS = require("../../../src/bch-js") +// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v3/` }) +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 3 +} + +describe(`#blockchain`, () => { + describe(`#getBestBlockHash`, () => { + it(`should GET best block hash`, async () => { + const result = await bchjs.Blockchain.getBestBlockHash() + //console.log(`result: ${util.inspect(result)}`) + + assert.isString(result) + assert.equal(result.length, 64, "Specific hash length") + }) + }) + + describe("#getBlockHeader", () => { + it(`should GET block header for a single hash`, async () => { + const hash = + "000000000000c57178ace90210289e6b5383134c5b5306e1cdd8395176e10aaf" + + const result = await bchjs.Blockchain.getBlockHeader(hash) + + assert.hasAllKeys(result, [ + "hash", + "confirmations", + "height", + "version", + "versionHex", + "merkleroot", + "time", + "mediantime", + "nonce", + "bits", + "difficulty", + "chainwork", + "previousblockhash", + "nextblockhash", + "nTx" + ]) + }) + + it(`should GET block headers for an array of hashes`, async () => { + const hash = [ + "000000000000c57178ace90210289e6b5383134c5b5306e1cdd8395176e10aaf", + "00000000000b7db4cbae48d852fcbef32f728014582094ad613fe12af6600ff2" + ] + + const result = await bchjs.Blockchain.getBlockHeader(hash) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "hash", + "confirmations", + "height", + "version", + "versionHex", + "merkleroot", + "time", + "mediantime", + "nonce", + "bits", + "difficulty", + "chainwork", + "previousblockhash", + "nextblockhash", + "nTx" + ]) + }) + + it(`should throw an error for improper input`, async () => { + try { + const hash = 12345 + + await bchjs.Blockchain.getBlockHeader(hash) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input hash must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) { + data.push( + "00000000000b7db4cbae48d852fcbef32f728014582094ad613fe12af6600ff2" + ) + } + + const result = await bchjs.Blockchain.getBlockHeader(data) + + // console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#getMempoolEntry", () => { + /* + // To run this test, the txid must be unconfirmed. + const txid = + "defea04c38ee00cf73ad402984714ed22dc0dd99b2ae5cb50d791d94343ba79b" + + it(`should GET single mempool entry`, async () => { + const result = await bchjs.Blockchain.getMempoolEntry(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "size", + "fee", + "modifiedfee", + "time", + "height", + "startingpriority", + "currentpriority", + "descendantcount", + "descendantsize", + "descendantfees", + "ancestorcount", + "ancestorsize", + "ancestorfees", + "depends" + ]) + }) + + it(`should get an array of mempool entries`, async () => { + const result = await bchjs.Blockchain.getMempoolEntry([txid, txid]) + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "size", + "fee", + "modifiedfee", + "time", + "height", + "startingpriority", + "currentpriority", + "descendantcount", + "descendantsize", + "descendantfees", + "ancestorcount", + "ancestorsize", + "ancestorfees", + "depends" + ]) + }) + */ + + it(`should throw an error if txid is not in mempool`, async () => { + try { + const txid = + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + + await bchjs.Blockchain.getMempoolEntry(txid) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: ${util.inspect(err)}`) + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, `Transaction not in mempool`) + } + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.getMempoolEntry(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + }) + + describe(`#getTxOutProof`, () => { + it(`should get single tx out proof`, async () => { + const txid = + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + + const result = await bchjs.Blockchain.getTxOutProof(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isString(result) + }) + + it(`should get an array of tx out proofs`, async () => { + const txid = [ + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04", + "fc4f696c0ebb3d0994b3975f57d85be75ef752b9fd52c17e361ec3be2fa3e752" + ] + + const result = await bchjs.Blockchain.getTxOutProof(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.getTxOutProof(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + }) + + describe(`#verifyTxOutProof`, () => { + const mockTxOutProof = + "00000020ac86ce8f2bda235c0dc135d18f6a777c44b121e8f41db8a51ca65000000000000cd4de3c49337712f3a1092c47c3bf73ec2f9b1cfd289ee991ede0b6eab4df229d5b8c5dffff001d82ce005b0700000003ec55d9142eac2c1d2229e5af24898b2590c111b62e2787fa1998d3d713fd432fd557124ed758fa156a6cdc6d317d5575aec2b86dfb159c62ecb4743f28bef1cb52e7a32fbec31e367ec152fdb952f75ee75bd8575f97b394093dbb0e6c694ffc0135" + + it(`should verify a single proof`, async () => { + const result = await bchjs.Blockchain.verifyTxOutProof(mockTxOutProof) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + assert.equal( + result[0], + "fc4f696c0ebb3d0994b3975f57d85be75ef752b9fd52c17e361ec3be2fa3e752" + ) + }) + + it(`should verify an array of proofs`, async () => { + const proofs = [mockTxOutProof, mockTxOutProof] + const result = await bchjs.Blockchain.verifyTxOutProof(proofs) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + assert.equal( + result[0], + "fc4f696c0ebb3d0994b3975f57d85be75ef752b9fd52c17e361ec3be2fa3e752" + ) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const txid = 12345 + + await bchjs.Blockchain.verifyTxOutProof(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.include( + err.message, + `Input must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) data.push(mockTxOutProof) + + const result = await bchjs.Blockchain.verifyTxOutProof(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/testnet/control.js b/test/integration/testnet/control.js new file mode 100644 index 0000000..e1e36e9 --- /dev/null +++ b/test/integration/testnet/control.js @@ -0,0 +1,24 @@ +/* + Integration tests for bchjs control library. +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +describe(`#control`, () => { + describe(`#getNetworkInfo`, () => { + it("should get info on the full node", async () => { + const result = await bchjs.Control.getNetworkInfo() + console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "version") + }) + }) +}) diff --git a/test/integration/testnet/electrumx.js b/test/integration/testnet/electrumx.js new file mode 100644 index 0000000..aad0b39 --- /dev/null +++ b/test/integration/testnet/electrumx.js @@ -0,0 +1,185 @@ +const chai = require("chai") +const assert = chai.assert +const sinon = require("sinon") + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +describe(`#ElectrumX`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "tx_pos") + assert.property(result.utxos[0], "value") + }) + + it(`should POST utxo details for an array of addresses`, async () => { + const addr = [ + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2", + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + ] + + const result = await bchjs.Electrumx.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "tx_pos") + assert.property(result.utxos[0].utxos[0], "value") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Electrumx.utxo(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#balance`, () => { + it(`should GET balance for a single address`, async () => { + const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balance") + assert.property(result.balance, "confirmed") + assert.property(result.balance, "unconfirmed") + }) + + it(`should POST request for balances for an array of addresses`, async () => { + const addr = [ + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2", + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + ] + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balances") + assert.isArray(result.balances) + + assert.property(result.balances[0], "address") + assert.property(result.balances[0], "balance") + assert.property(result.balances[0].balance, "confirmed") + assert.property(result.balances[0].balance, "unconfirmed") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Electrumx.balance(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe(`#transactions`, () => { + it(`should GET transaction history for a single address`, async () => { + const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + assert.property(result.transactions[0], "height") + assert.property(result.transactions[0], "tx_hash") + }) + + it(`should POST request for transaction history for an array of addresses`, async () => { + const addr = [ + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2", + "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + ] + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + + assert.property(result.transactions[0], "address") + assert.property(result.transactions[0], "transactions") + assert.isArray(result.transactions[0].transactions) + assert.property(result.transactions[0].transactions[0], "height") + assert.property(result.transactions[0].transactions[0], "tx_hash") + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2") + + const result = await bchjs.Electrumx.transactions(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/testnet/openbazaar.js b/test/integration/testnet/openbazaar.js new file mode 100644 index 0000000..9d02775 --- /dev/null +++ b/test/integration/testnet/openbazaar.js @@ -0,0 +1,124 @@ +/* + tests for OpenBazaar library. +*/ + +// Force testnet call to OB1 servers. +process.env.NETWORK = "testnet" + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` + +const BCHJS = require("../../../src/bch-js") +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +describe(`#OpenBazaar`, () => { + describe(`#Balance`, () => { + // it(`should GET balance for a single address`, async () => { + // const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + // + // const result = await bchjs.OpenBazaar.balance(addr) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAllKeys(result, [ + // "page", + // "totalPages", + // "itemsOnPage", + // "addrStr", + // "balance", + // "totalReceived", + // "totalSent", + // "unconfirmedBalance", + // "unconfirmedTxApperances", + // "txApperances", + // "transactions" + // ]) + // assert.isArray(result.transactions) + // }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.balance(addr) + // assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + }) + + describe(`#utxo`, () => { + // it(`should GET utxos for a single address`, async () => { + // const addr = "bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2" + // + // const result = await bchjs.OpenBazaar.utxo(addr) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.hasAllKeys(result[0], [ + // "txid", + // "vout", + // "amount", + // "height", + // "confirmations", + // "satoshis" + // ]) + // }) + + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.utxo(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + }) + + describe(`#tx`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.tx(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input txid must be a string`) + } + }) + + // it(`should GET transactions for a single address`, async () => { + // const addr = + // "ed4692f50a4553527dd26cd8674ca06a0ab2d366f3135ca3668310467ead3cbf" + // + // const result = await bchjs.OpenBazaar.tx(addr) + // // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.hasAnyKeys(result, [ + // "txid", + // "version", + // "vin", + // "vout", + // "blockhash", + // "blockheight", + // "confirmations", + // "blocktime", + // "valueOut", + // "valueIn", + // "fees", + // "hex" + // ]) + // assert.isArray(result.vin) + // assert.isArray(result.vout) + // }) + }) +}) diff --git a/test/integration/testnet/rawtransaction.js b/test/integration/testnet/rawtransaction.js new file mode 100644 index 0000000..97a2c9f --- /dev/null +++ b/test/integration/testnet/rawtransaction.js @@ -0,0 +1,334 @@ +/* + Integration tests for the bchjs. Only covers calls made to + rest.bitcoin.com. + + TODO +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` +// if (process.env.RESTURL) RESTURL = process.env.RESTURL + +const BCHJS = require("../../../src/bch-js") +// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v3/` }) +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 3 +} + +describe("#rawtransaction", () => { + describe("#decodeRawTransaction", () => { + it("should decode tx for a single hex", async () => { + const hex = + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + + const result = await bchjs.RawTransactions.decodeRawTransaction(hex) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + + it("should decode an array of tx hexes", async () => { + const hexes = [ + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000", + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + ] + + const result = await bchjs.RawTransactions.decodeRawTransaction(hexes) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout" + ]) + assert.isArray(result[0].vin) + assert.isArray(result[0].vout) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.decodeRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const data = [] + for (let i = 0; i < 25; i++) { + data.push( + "0200000001b9b598d7d6d72fc486b2b3a3c03c79b5bade6ec9a77ced850515ab5e64edcc21010000006b483045022100a7b1b08956abb8d6f322aa709d8583c8ea492ba0585f1a6f4f9983520af74a5a0220411aee4a9a54effab617b0508c504c31681b15f9b187179b4874257badd4139041210360cfc66fdacb650bc4c83b4e351805181ee696b7d5ab4667c57b2786f51c413dffffffff0210270000000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac786e9800000000001976a914eb4b180def88e3f5625b2d8ae2c098ff7d85f66488ac00000000" + ) + } + + const result = await bchjs.RawTransactions.decodeRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#getRawTransaction", () => { + it("should decode a single txid, with concise output", async () => { + const txid = + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + const verbose = false + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isString(result) + }) + + it("should decode a single txid, with verbose output", async () => { + const txid = + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + const verbose = true + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "hex", + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "confirmations", + "time", + "blocktime" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + + it("should decode an array of txids, with a concise output", async () => { + const txid = [ + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04", + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + ] + const verbose = false + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isString(result[0]) + }) + + it("should decode an array of txids, with a verbose output", async () => { + const txid = [ + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04", + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + ] + const verbose = true + + const result = await bchjs.RawTransactions.getRawTransaction( + txid, + verbose + ) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "hex", + "txid", + "hash", + "size", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "confirmations", + "time", + "blocktime" + ]) + assert.isArray(result[0].vin) + assert.isArray(result[0].vout) + }) + + it(`should throw error on array size limit`, async () => { + try { + const dataMock = + "1f121fb6a33f48cd426dde06aa20ce589dd97becabb835a8d33071cbf40c7d04" + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.RawTransactions.getRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) + + describe("#decodeScript", () => { + it("should decode script for a single hex", async () => { + const hex = + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" + + const result = await bchjs.RawTransactions.decodeScript(hex) + //console.log(`result ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["asm", "type", "p2sh"]) + }) + + // CT 2/20/19 - Waiting for this PR to be merged complete the test: + // https://github.com/Bitcoin-com/rest.bitcoin.com/pull/312 + /* + it("should decode an array of tx hexes", async () => { + const hexes = [ + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16", + "4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" + ] + + const result = await bchjs.RawTransactions.decodeScript(hexes) + console.log(`result ${JSON.stringify(result, null, 2)}`) + }) +*/ + /* + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.decodeRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) +*/ + }) + + /* + 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") + } + }) + + it(`should throw an error for improper single input`, async () => { + try { + const addr = 12345 + + await bchjs.RawTransactions.sendRawTransaction(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input hex must be a string or array of strings` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const dataMock = + "01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.RawTransactions.sendRawTransaction(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/testnet/slp.js b/test/integration/testnet/slp.js new file mode 100644 index 0000000..088e178 --- /dev/null +++ b/test/integration/testnet/slp.js @@ -0,0 +1,147 @@ +/* + Integration tests for the bchjs covering SLP tokens. +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` +// if (process.env.RESTURL) RESTURL = process.env.RESTURL + +const BCHJS = require("../../../src/bch-js") +// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v3/` }) +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 1 +} + +describe(`#SLP`, () => { + beforeEach(async () => { + // Introduce a delay so that the BVT doesn't trip the rate limits. + await sleep(1000) + }) + + describe("#util", () => { + it(`should get information on the Oasis token`, async () => { + const tokenId = `a371e9934c7695d08a5eb7f31d3bceb4f3644860cc67520cda1e149423b9ec39` + + const result = await bchjs.SLP.Utils.list(tokenId) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "decimals", + "timestamp", + "timestamp_unix", + "versionType", + "documentUri", + "symbol", + "name", + "containsBaton", + "id", + "documentHash", + "initialTokenQty", + "blockCreated", + "blockLastActiveSend", + "blockLastActiveMint", + "txnsSinceGenesis", + "validAddress", + "totalMinted", + "totalBurned", + "circulatingSupply", + "mintingBatonStatus" + ]) + }) + }) + + describe("#decodeOpReturn", () => { + it("should decode the OP_RETURN for a SEND txid", async () => { + const txid = + "ad28116e0818339342dddfc5f58ca8a5379ceb9679b4e4cbd72f4de905415ec1" + + const result = await bchjs.SLP.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["amounts", "tokenType", "tokenId", "txType"]) + }) + }) + + describe("#balancesForAddress", () => { + it(`should throw an error if input is not a string or array of strings`, async () => { + try { + const address = 1234 + + await bchjs.SLP.Utils.balancesForAddress(address) + + assert.equal(true, false, "Uh oh. Code path should not end here.") + } catch (err) { + //console.log(`Error: `, err) + assert.include( + err.message, + `Input address must be a string or array of strings` + ) + } + }) + + it(`should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9`, async () => { + // Mock the call to rest.bitcoin.com + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.balancesForAddress }) + } + + const balances = await bchjs.SLP.Utils.balancesForAddress( + "slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9" + ) + // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert.isArray(balances) + assert.hasAllKeys(balances[0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + + it(`should fetch balances for multiple addresses`, async () => { + const addresses = [ + "slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9", + "slptest:qr5uy4h8ysyhwkp9s245scckdnc7teyjqc5ft2z43h" + ] + + // Mock the call to rest.bitcoin.com + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "post") + .resolves({ data: mockData.balancesForAddresses }) + } + + const balances = await bchjs.SLP.Utils.balancesForAddress(addresses) + // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert.isArray(balances) + assert.isArray(balances[0]) + assert.hasAllKeys(balances[0][0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + }) +}) + +// Promise-based sleep function +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)) +} diff --git a/test/integration/testnet/test-free-tier.sh b/test/integration/testnet/test-free-tier.sh new file mode 100755 index 0000000..2658fbf --- /dev/null +++ b/test/integration/testnet/test-free-tier.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +export RESTURL=https://free-test.fullstack.cash/v3/ +export NETWORK=testnet +#export RESTURL=http://localhost:3000/v3/ + +cd test/integration/testnet/ +mocha --timeout 30000 blockchain.js control.js electrumx.js openbazaar.js rawtransaction.js slp.js util.js diff --git a/test/integration/testnet/util.js b/test/integration/testnet/util.js new file mode 100644 index 0000000..e3096e9 --- /dev/null +++ b/test/integration/testnet/util.js @@ -0,0 +1,116 @@ +/* + Integration tests for the bchjs. Only covers calls made to + rest.bitcoin.com. +*/ + +const chai = require("chai") +const assert = chai.assert + +const RESTURL = process.env.RESTURL + ? process.env.RESTURL + : `https://tapi.fullstack.cash/v3/` +// if (process.env.RESTURL) RESTURL = process.env.RESTURL + +const BCHJS = require("../../../src/bch-js") +// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v3/` }) +const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) + +// Inspect utility used for debugging. +const util = require("util") +util.inspect.defaultOptions = { + showHidden: true, + colors: true, + depth: 3 +} + +describe(`#util`, () => { + describe(`#validateAddress`, () => { + it(`should return false for testnet addr on mainnet`, async () => { + const address = `bitcoincash:qp4k8fjtgunhdr7yq30ha4peu` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["isvalid"]) + assert.equal(result.isvalid, false) + }) + + it(`should return false for bad address`, async () => { + const address = `bchtest:qqqk4y6lsl5da64sg53xezmplyu5kmpyz2ysaa5y` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["isvalid"]) + assert.equal(result.isvalid, false) + }) + + it(`should validate valid address`, async () => { + const address = `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "isvalid", + "address", + "scriptPubKey", + //"ismine", + //"iswatchonly", + "isscript" + ]) + assert.equal(result.isvalid, true) + }) + + it(`should validate an array of addresses`, async () => { + const address = [ + `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y`, + `bchtest:pq6k9969f6v6sg7a75jkru4n7wn9sknv5cztcp0dnh` + ] + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "isvalid", + "address", + "scriptPubKey", + //"ismine", + //"iswatchonly", + "isscript" + ]) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const address = 15432 + + await bchjs.Util.validateAddress(address) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const dataMock = `bchtest:pq6k9969f6v6sg7a75jkru4n7wn9sknv5cztcp0dnh` + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.Util.validateAddress(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/integration/util.js b/test/integration/util.js new file mode 100644 index 0000000..16de2f6 --- /dev/null +++ b/test/integration/util.js @@ -0,0 +1,109 @@ +/* + Integration tests for the bchjs. Only covers calls made to + rest.bitcoin.com. +*/ + +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(`#util`, () => { + describe(`#validateAddress`, () => { + it(`should return false for testnet addr on mainnet`, async () => { + const address = `bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["isvalid"]) + assert.equal(result.isvalid, false) + }) + + it(`should return false for bad address`, async () => { + const address = `bitcoincash:qp4k8fjtgunhdr7yq30ha4peu` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["isvalid"]) + assert.equal(result.isvalid, false) + }) + + it(`should validate valid address`, async () => { + const address = `bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z` + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAnyKeys(result, [ + "isvalid", + "address", + "scriptPubKey", + //"ismine", + //"iswatchonly", + "isscript" + ]) + assert.equal(result.isvalid, true) + }) + + it(`should validate an array of addresses`, async () => { + const address = [ + `bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z`, + `bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z` + ] + + const result = await bchjs.Util.validateAddress(address) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "isvalid", + "address", + "scriptPubKey", + //"ismine", + //"iswatchonly", + "isscript" + ]) + }) + + it(`should throw an error for improper single input`, async () => { + try { + const address = 15432 + + await bchjs.Util.validateAddress(address) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include( + err.message, + `Input must be a string or array of strings.` + ) + } + }) + + it(`should throw error on array size rate limit`, async () => { + try { + const dataMock = `bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z` + const data = [] + for (let i = 0; i < 25; i++) data.push(dataMock) + + const result = await bchjs.Util.validateAddress(data) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + }) +}) diff --git a/test/unit/address.js b/test/unit/address.js new file mode 100644 index 0000000..1a48a5c --- /dev/null +++ b/test/unit/address.js @@ -0,0 +1,905 @@ +const fixtures = require("./fixtures/address.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const Bitcoin = require("bitcoincashjs-lib") + +function flatten(arrays) { + return [].concat.apply([], arrays) +} + +const XPUBS = flatten([fixtures.mainnetXPub, fixtures.testnetXPub]) + +const LEGACY_ADDRESSES = flatten([ + fixtures.legacyMainnetP2PKH, + fixtures.legacyMainnetP2SH, + fixtures.legacyTestnetP2PKH +]) + +const mainnet_xpubs = [] +fixtures.mainnetXPub.forEach((f, i) => { + mainnet_xpubs.push(f.xpub) +}) +const MAINNET_ADDRESSES = flatten([ + mainnet_xpubs, + fixtures.legacyMainnetP2PKH, + fixtures.legacyMainnetP2SH, + fixtures.cashaddrMainnetP2PKH +]) + +const testnet_xpubs = [] +fixtures.testnetXPub.forEach((f, i) => { + testnet_xpubs.push(f.xpub) +}) +const TESTNET_ADDRESSES = flatten([ + testnet_xpubs, + fixtures.legacyTestnetP2PKH, + fixtures.cashaddrTestnetP2PKH +]) + +const CASHADDR_ADDRESSES = flatten([ + fixtures.cashaddrMainnetP2PKH, + fixtures.cashaddrMainnetP2SH, + fixtures.cashaddrTestnetP2PKH +]) + +const CASHADDR_ADDRESSES_NO_PREFIX = CASHADDR_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) + +const REGTEST_ADDRESSES = fixtures.cashaddrRegTestP2PKH + +const REGTEST_ADDRESSES_NO_PREFIX = REGTEST_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) + +const HASH160_HASHES = flatten([ + fixtures.hash160MainnetP2PKH, + fixtures.hash160MainnetP2SH, + fixtures.hash160TestnetP2PKH +]) + +const P2PKH_ADDRESSES = flatten([ + fixtures.legacyMainnetP2PKH, + fixtures.legacyTestnetP2PKH, + fixtures.cashaddrMainnetP2PKH, + fixtures.cashaddrTestnetP2PKH, + fixtures.cashaddrRegTestP2PKH +]) + +const P2SH_ADDRESSES = flatten([ + fixtures.legacyMainnetP2SH, + fixtures.cashaddrMainnetP2SH +]) + +describe("#addressConversion", () => { + describe("#toLegacyAddress", () => { + it("should translate legacy address format to itself correctly", () => { + assert.deepEqual( + LEGACY_ADDRESSES.map(address => bchjs.Address.toLegacyAddress(address)), + LEGACY_ADDRESSES + ) + }) + + it("should convert cashaddr address to legacy base58Check", () => { + assert.deepEqual( + CASHADDR_ADDRESSES.map(address => + bchjs.Address.toLegacyAddress(address) + ), + LEGACY_ADDRESSES + ) + }) + + it("should convert cashaddr regtest address to legacy base58Check", () => { + assert.deepEqual( + REGTEST_ADDRESSES.map(address => + bchjs.Address.toLegacyAddress(address) + ), + fixtures.legacyTestnetP2PKH + ) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.toLegacyAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.toLegacyAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + + describe("#toCashAddress", () => { + it("should convert legacy base58Check address to cashaddr", () => { + assert.deepEqual( + LEGACY_ADDRESSES.map(address => + bchjs.Address.toCashAddress(address, true) + ), + CASHADDR_ADDRESSES + ) + }) + + it("should convert legacy base58Check address to regtest cashaddr", () => { + assert.deepEqual( + fixtures.legacyTestnetP2PKH.map(address => + bchjs.Address.toCashAddress(address, true, true) + ), + REGTEST_ADDRESSES + ) + }) + + it("should translate cashaddr address format to itself correctly", () => { + assert.deepEqual( + CASHADDR_ADDRESSES.map(address => + bchjs.Address.toCashAddress(address, true) + ), + CASHADDR_ADDRESSES + ) + }) + + it("should translate regtest cashaddr address format to itself correctly", () => { + assert.deepEqual( + REGTEST_ADDRESSES.map(address => + bchjs.Address.toCashAddress(address, true, true) + ), + REGTEST_ADDRESSES + ) + }) + + it("should translate no-prefix cashaddr address format to itself correctly", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.toCashAddress(address, true) + ), + CASHADDR_ADDRESSES + ) + }) + + it("should translate no-prefix regtest cashaddr address format to itself correctly", () => { + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.toCashAddress(address, true, true) + ), + REGTEST_ADDRESSES + ) + }) + + it("should translate cashaddr address format to itself of no-prefix correctly", () => { + CASHADDR_ADDRESSES.forEach(address => { + const noPrefix = bchjs.Address.toCashAddress(address, false) + assert.equal(address.split(":")[1], noPrefix) + }) + }) + + it("should translate regtest cashaddr address format to itself of no-prefix correctly", () => { + REGTEST_ADDRESSES.forEach(address => { + const noPrefix = bchjs.Address.toCashAddress(address, false, true) + assert.equal(address.split(":")[1], noPrefix) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.BitcoinCash.Address.toCashAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.BitcoinCash.Address.toCashAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + describe("#toHash160", () => { + it("should convert legacy base58check address to hash160", () => { + assert.deepEqual( + LEGACY_ADDRESSES.map(address => bchjs.Address.toHash160(address)), + HASH160_HASHES + ) + }) + + it("should convert cashaddr address to hash160", () => { + assert.deepEqual( + CASHADDR_ADDRESSES.map(address => bchjs.Address.toHash160(address)), + HASH160_HASHES + ) + }) + + it("should convert regtest cashaddr address to hash160", () => { + assert.deepEqual( + REGTEST_ADDRESSES.map(address => bchjs.Address.toHash160(address)), + fixtures.hash160TestnetP2PKH + ) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.toHash160() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.toHash160("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + describe("#fromHash160", () => { + it("should convert hash160 to mainnet P2PKH legacy base58check address", () => { + assert.deepEqual( + fixtures.hash160MainnetP2PKH.map(hash160 => + bchjs.Address.hash160ToLegacy(hash160) + ), + fixtures.legacyMainnetP2PKH + ) + }) + + it("should convert hash160 to mainnet P2SH legacy base58check address", () => { + assert.deepEqual( + fixtures.hash160MainnetP2SH.map(hash160 => + bchjs.Address.hash160ToLegacy( + hash160, + Bitcoin.networks.bitcoin.scriptHash + ) + ), + fixtures.legacyMainnetP2SH + ) + }) + + it("should convert hash160 to testnet P2PKH legacy base58check address", () => { + assert.deepEqual( + fixtures.hash160TestnetP2PKH.map(hash160 => + bchjs.Address.hash160ToLegacy( + hash160, + Bitcoin.networks.testnet.pubKeyHash + ) + ), + fixtures.legacyTestnetP2PKH + ) + }) + + it("should convert hash160 to mainnet P2PKH cash address", () => { + assert.deepEqual( + fixtures.hash160MainnetP2PKH.map(hash160 => + bchjs.Address.hash160ToCash(hash160) + ), + fixtures.cashaddrMainnetP2PKH + ) + }) + + it("should convert hash160 to mainnet P2SH cash address", () => { + assert.deepEqual( + fixtures.hash160MainnetP2SH.map(hash160 => + bchjs.Address.hash160ToCash( + hash160, + Bitcoin.networks.bitcoin.scriptHash + ) + ), + fixtures.cashaddrMainnetP2SH + ) + }) + + it("should convert hash160 to testnet P2PKH cash address", () => { + assert.deepEqual( + fixtures.hash160TestnetP2PKH.map(hash160 => + bchjs.Address.hash160ToCash( + hash160, + Bitcoin.networks.testnet.pubKeyHash + ) + ), + fixtures.cashaddrTestnetP2PKH + ) + }) + + it("should convert hash160 to regtest P2PKH cash address", () => { + assert.deepEqual( + fixtures.hash160TestnetP2PKH.map(hash160 => + bchjs.Address.hash160ToCash( + hash160, + Bitcoin.networks.testnet.pubKeyHash, + true + ) + ), + REGTEST_ADDRESSES + ) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.hash160ToLegacy() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.hash160ToLegacy("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.hash160ToCash() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.hash160ToCash("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) +}) + +describe("address format detection", () => { + describe("#isLegacyAddress", () => { + describe("is legacy", () => { + LEGACY_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy base58Check address`, () => { + const isBase58Check = bchjs.Address.isLegacyAddress(address) + assert.equal(isBase58Check, true) + }) + }) + }) + describe("is not legacy", () => { + CASHADDR_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isBase58Check = bchjs.Address.isLegacyAddress(address) + assert.equal(isBase58Check, false) + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isBase58Check = bchjs.Address.isLegacyAddress(address) + assert.equal(isBase58Check, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isLegacyAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isLegacyAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + + describe("#isCashAddress", () => { + describe("is cashaddr", () => { + CASHADDR_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cashaddr address`, () => { + const isCashaddr = bchjs.Address.isCashAddress(address) + assert.equal(isCashaddr, true) + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cashaddr address`, () => { + const isCashaddr = bchjs.Address.isCashAddress(address) + assert.equal(isCashaddr, true) + }) + }) + }) + + describe("is not cashaddr", () => { + LEGACY_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a cashaddr address`, () => { + const isCashaddr = bchjs.Address.isCashAddress(address) + assert.equal(isCashaddr, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isCashAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isCashAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + describe("#isHash160", () => { + describe("is hash160", () => { + HASH160_HASHES.forEach(address => { + it(`should detect ${address} is a hash160 hash`, () => { + const isHash160 = bchjs.Address.isHash160(address) + assert.equal(isHash160, true) + }) + }) + }) + describe("is not hash160", () => { + LEGACY_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a hash160 hash`, () => { + const isHash160 = bchjs.Address.isHash160(address) + assert.equal(isHash160, false) + }) + }) + + CASHADDR_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a hash160 hash`, () => { + const isHash160 = bchjs.Address.isHash160(address) + assert.equal(isHash160, false) + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isHash160 = bchjs.Address.isHash160(address) + assert.equal(isHash160, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isHash160() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isHash160("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) +}) + +describe("network detection", () => { + describe("#isMainnetAddress", () => { + describe("is mainnet", () => { + MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnet = bchjs.Address.isMainnetAddress(address) + assert.equal(isMainnet, true) + }) + }) + }) + + describe("is not mainnet", () => { + TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a mainnet address`, () => { + const isMainnet = bchjs.Address.isMainnetAddress(address) + assert.equal(isMainnet, false) + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a mainnet address`, () => { + const isMainnet = bchjs.Address.isMainnetAddress(address) + assert.equal(isMainnet, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isMainnetAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isMainnetAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + + describe("#isTestnetAddress", () => { + describe("is testnet", () => { + TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = bchjs.Address.isTestnetAddress(address) + assert.equal(isTestnet, true) + }) + }) + }) + + describe("is not testnet", () => { + MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a testnet address`, () => { + const isTestnet = bchjs.Address.isTestnetAddress(address) + assert.equal(isTestnet, false) + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a testnet address`, () => { + const isTestnet = bchjs.Address.isTestnetAddress(address) + assert.equal(isTestnet, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isTestnetAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isTestnetAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + + describe("#isRegTestAddress", () => { + describe("is testnet", () => { + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is a regtest address`, () => { + const isRegTest = bchjs.Address.isRegTestAddress(address) + assert.equal(isRegTest, true) + }) + }) + }) + + describe("is not testnet", () => { + MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a regtest address`, () => { + const isRegTest = bchjs.Address.isRegTestAddress(address) + assert.equal(isRegTest, false) + }) + }) + + TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a regtest address`, () => { + const isRegTest = bchjs.Address.isRegTestAddress(address) + assert.equal(isRegTest, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isRegTestAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isRegTestAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) +}) + +describe("address type detection", () => { + describe("#isP2PKHAddress", () => { + describe("is P2PKH", () => { + P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2PKH address`, () => { + const isP2PKH = bchjs.Address.isP2PKHAddress(address) + assert.equal(isP2PKH, true) + }) + }) + }) + + describe("is not P2PKH", () => { + P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a P2PKH address`, () => { + const isP2PKH = bchjs.Address.isP2PKHAddress(address) + assert.equal(isP2PKH, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isP2PKHAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isP2PKHAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) + + describe("#isP2SHAddress", () => { + describe("is P2SH", () => { + P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2SH address`, () => { + const isP2SH = bchjs.Address.isP2SHAddress(address) + assert.equal(isP2SH, true) + }) + }) + }) + + describe("is not P2SH", () => { + P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a P2SH address`, () => { + const isP2SH = bchjs.Address.isP2SHAddress(address) + assert.equal(isP2SH, false) + }) + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.isP2SHAddress() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.isP2SHAddress("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) + }) +}) + +describe("cashaddr prefix detection", () => { + it("should return the same result for detectAddressFormat", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressFormat(address) + ), + CASHADDR_ADDRESSES.map(address => + bchjs.Address.detectAddressFormat(address) + ) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressFormat(address) + ), + REGTEST_ADDRESSES.map(address => + bchjs.Address.detectAddressFormat(address) + ) + ) + }) + it("should return the same result for detectAddressNetwork", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressNetwork(address) + ), + CASHADDR_ADDRESSES.map(address => + bchjs.Address.detectAddressNetwork(address) + ) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressNetwork(address) + ), + REGTEST_ADDRESSES.map(address => + bchjs.Address.detectAddressNetwork(address) + ) + ) + }) + it("should return the same result for detectAddressType", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressType(address) + ), + CASHADDR_ADDRESSES.map(address => + bchjs.Address.detectAddressType(address) + ) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.detectAddressType(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.detectAddressType(address)) + ) + }) + it("should return the same result for toLegacyAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.toLegacyAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.toLegacyAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.toLegacyAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.toLegacyAddress(address)) + ) + }) + it("should return the same result for isLegacyAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isLegacyAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isLegacyAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isLegacyAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isLegacyAddress(address)) + ) + }) + it("should return the same result for isCashAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isCashAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isCashAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isCashAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isCashAddress(address)) + ) + }) + it("should return the same result for isMainnetAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isMainnetAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isMainnetAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isMainnetAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isMainnetAddress(address)) + ) + }) + it("should return the same result for isTestnetAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isTestnetAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isTestnetAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isTestnetAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isTestnetAddress(address)) + ) + }) + it("should return the same result for isP2PKHAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isP2PKHAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isP2PKHAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isP2PKHAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isP2PKHAddress(address)) + ) + }) + it("should return the same result for isP2SHAddress", () => { + assert.deepEqual( + CASHADDR_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isP2SHAddress(address) + ), + CASHADDR_ADDRESSES.map(address => bchjs.Address.isP2SHAddress(address)) + ) + assert.deepEqual( + REGTEST_ADDRESSES_NO_PREFIX.map(address => + bchjs.Address.isP2SHAddress(address) + ), + REGTEST_ADDRESSES.map(address => bchjs.Address.isP2SHAddress(address)) + ) + }) +}) + +describe("#detectAddressFormat", () => { + LEGACY_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy base58Check address`, () => { + const isBase58Check = bchjs.Address.detectAddressFormat(address) + assert.equal(isBase58Check, "legacy") + }) + }) + + CASHADDR_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy cashaddr address`, () => { + const isCashaddr = bchjs.Address.detectAddressFormat(address) + assert.equal(isCashaddr, "cashaddr") + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy cashaddr address`, () => { + const isCashaddr = bchjs.Address.detectAddressFormat(address) + assert.equal(isCashaddr, "cashaddr") + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.detectAddressFormat() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.detectAddressFormat("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) +}) + +describe("#detectAddressNetwork", () => { + MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnet = bchjs.Address.detectAddressNetwork(address) + assert.equal(isMainnet, "mainnet") + }) + }) + + TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = bchjs.Address.detectAddressNetwork(address) + assert.equal(isTestnet, "testnet") + }) + }) + + REGTEST_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = bchjs.Address.detectAddressNetwork(address) + assert.equal(isTestnet, "regtest") + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.detectAddressNetwork() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.detectAddressNetwork("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) +}) + +describe("#detectAddressType", () => { + P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2PKH address`, () => { + const isP2PKH = bchjs.Address.detectAddressType(address) + assert.equal(isP2PKH, "p2pkh") + }) + }) + + P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2SH address`, () => { + const isP2SH = bchjs.Address.detectAddressType(address) + assert.equal(isP2SH, "p2sh") + }) + }) + + describe("errors", () => { + it("should fail when called with an invalid address", () => { + assert.throws(() => { + bchjs.Address.detectAddressType() + }, bchjs.BitcoinCash.InvalidAddressError) + assert.throws(() => { + bchjs.Address.detectAddressType("some invalid address") + }, bchjs.BitcoinCash.InvalidAddressError) + }) + }) +}) + +describe("#fromXPub", () => { + XPUBS.forEach((xpub, i) => { + xpub.addresses.forEach((address, j) => { + it(`generate public external change address ${j} for ${xpub.xpub}`, () => { + assert.equal(bchjs.Address.fromXPub(xpub.xpub, `0/${j}`), address) + }) + }) + }) +}) + +describe("#fromOutputScript", () => { + const script = bchjs.Script.encode([ + Buffer.from("BOX", "ascii"), + bchjs.Script.opcodes.OP_CAT, + Buffer.from("BITBOX", "ascii"), + bchjs.Script.opcodes.OP_EQUAL + ]) + + // hash160 script buffer + const p2sh_hash160 = bchjs.Crypto.hash160(script) + + // encode hash160 as P2SH output + const scriptPubKey = bchjs.Script.scriptHash.output.encode(p2sh_hash160) + const p2shAddress = bchjs.Address.fromOutputScript(scriptPubKey) + fixtures.p2shMainnet.forEach((address, i) => { + it(`generate address from output script`, () => { + assert.equal(p2shAddress, address) + }) + }) +}) diff --git a/test/unit/bitbox-shim.js b/test/unit/bitbox-shim.js new file mode 100644 index 0000000..7d9b74e --- /dev/null +++ b/test/unit/bitbox-shim.js @@ -0,0 +1,25 @@ +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") + +// Used for debugging and iterrogating JS objects. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +describe(`#BitboxShim`, () => { + it("should create the shim library", () => { + const BitboxShim = BCHJS.BitboxShim() + const bitbox = new BitboxShim() + + //console.log(`bitbox.Address: ${util.inspect(bitbox.Address)}`) + + assert.hasAnyKeys(bitbox.Address, [ + // "details", + // "utxo", + // "unconfirmed", + // "transactions", + // "toLegacyAddress" + "restURL" + ]) + }) +}) diff --git a/test/unit/bitcoin-cash.js b/test/unit/bitcoin-cash.js new file mode 100644 index 0000000..e64901b --- /dev/null +++ b/test/unit/bitcoin-cash.js @@ -0,0 +1,314 @@ +const fixtures = require("./fixtures/bitcoincash.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +// TODO +// 1. generate testnet p2sh +// 2. generate cashaddr mainnet p2sh +// 3. generate cashaddr testnet p2sh +// 4. create bchjs fromBase58 method +// * confirm xpub cannot generate WIF +// * confirm xpriv can generate WIF +// 5. create fromXPriv method w/ tests and docs +// 1. mainnet +// * confirm xpriv generates address +// * confirm xpriv generates WIF +// 2. testnet +// * confirm xpriv generates address +// * confirm xpriv generates WIF +// 6. More error test cases. + +describe("#BitcoinCash", () => { + describe("price conversion", () => { + describe("#toBitcoinCash", () => { + fixtures.conversion.toBCH.satoshis.forEach(satoshi => { + it(`should convert ${satoshi[0]} Satoshis to ${ + satoshi[1] + } $BCH`, () => { + assert.equal(bchjs.BitcoinCash.toBitcoinCash(satoshi[0]), satoshi[1]) + }) + }) + + fixtures.conversion.toBCH.strings.forEach(satoshi => { + it(`should convert "${satoshi[0]}" Satoshis as a string to ${ + satoshi[1] + } $BCH`, () => { + assert.equal(bchjs.BitcoinCash.toBitcoinCash(satoshi[0]), satoshi[1]) + }) + }) + + fixtures.conversion.toBCH.not.forEach(bch => { + it(`converts ${bch[0]} to Bitcoin Cash, not to ${ + bch[1] + } Satoshi`, () => { + assert.notEqual(bchjs.BitcoinCash.toBitcoinCash(bch[0]), bch[1]) + }) + }) + + fixtures.conversion.toBCH.rounding.forEach(satoshi => { + it(`rounding ${satoshi[0]} to ${satoshi[1]} $BCH`, () => { + assert.equal(bchjs.BitcoinCash.toBitcoinCash(satoshi[0]), satoshi[1]) + }) + }) + }) + + describe("#toSatoshi", () => { + fixtures.conversion.toSatoshi.bch.forEach(bch => { + it(`should convert ${bch[0]} $BCH to ${bch[1]} Satoshis`, () => { + assert.equal(bchjs.BitcoinCash.toSatoshi(bch[0]), bch[1]) + }) + }) + + fixtures.conversion.toSatoshi.strings.forEach(bch => { + it(`should convert "${bch[0]}" $BCH as a string to ${ + bch[1] + } Satoshis`, () => { + assert.equal(bchjs.BitcoinCash.toSatoshi(bch[0]), bch[1]) + }) + }) + + fixtures.conversion.toSatoshi.not.forEach(satoshi => { + it(`converts ${satoshi[0]} to Satoshi, not to ${ + satoshi[1] + } Bitcoin Cash`, () => { + assert.notEqual(bchjs.BitcoinCash.toSatoshi(satoshi[0]), satoshi[1]) + }) + }) + + fixtures.conversion.toSatoshi.rounding.forEach(bch => { + it(`rounding ${bch[0]} to ${bch[1]} Satoshi`, () => { + assert.equal(bchjs.BitcoinCash.toSatoshi(bch[0]), bch[1]) + }) + }) + }) + + describe("#satsToBits", () => { + fixtures.conversion.satsToBits.bch.forEach(bch => { + it(`should convert ${bch[0]} BCH to ${bch[1]} bits`, () => { + assert.equal( + bchjs.BitcoinCash.satsToBits(bchjs.BitcoinCash.toSatoshi(bch[0])), + bch[1] + ) + }) + }) + + fixtures.conversion.satsToBits.strings.forEach(bch => { + it(`should convert "${bch[0]}" BCH as a string to ${ + bch[1] + } bits`, () => { + assert.equal( + bchjs.BitcoinCash.satsToBits(bchjs.BitcoinCash.toSatoshi(bch[0])), + bch[1] + ) + }) + }) + }) + // + // describe('#satsFromBits', () => { + // fixtures.conversion.satsFromBits.bch.forEach((bch) => { + // it(`should convert ${bch[1]} bits to ${bch[0]} satoshis`, () => { + // assert.equal(bchjs.BitcoinCash.satsFromBits(bch[1]), bch[0]); + // }); + // }); + // + // fixtures.conversion.satsFromBits.strings.forEach((bch) => { + // it(`should convert "${bch[1]}" bits as a string to ${bch[0]} satoshis`, () => { + // assert.equal(bchjs.BitcoinCash.satsFromBits(bch[1]), bch[0]); + // }); + // }); + // }); + }) + + describe("sign and verify messages", () => { + describe("#signMessageWithPrivKey", () => { + fixtures.signatures.sign.forEach(sign => { + it(`should sign a message w/ ${sign.network} ${sign.privateKeyWIF}`, () => { + const privateKeyWIF = sign.privateKeyWIF + const message = sign.message + const signature = bchjs.BitcoinCash.signMessageWithPrivKey( + privateKeyWIF, + message + ) + assert.equal(signature, sign.signature) + }) + }) + }) + + describe("#verifyMessage", () => { + fixtures.signatures.verify.forEach(sign => { + it(`should verify a valid signed message from ${sign.network} cashaddr address ${sign.address}`, () => { + assert.equal( + bchjs.BitcoinCash.verifyMessage( + sign.address, + sign.signature, + sign.message + ), + true + ) + }) + }) + + fixtures.signatures.verify.forEach(sign => { + const legacyAddress = bchjs.Address.toLegacyAddress(sign.address) + it(`should verify a valid signed message from ${sign.network} legacy address ${legacyAddress}`, () => { + assert.equal( + bchjs.BitcoinCash.verifyMessage( + legacyAddress, + sign.signature, + sign.message + ), + true + ) + }) + }) + + fixtures.signatures.verify.forEach(sign => { + const legacyAddress = bchjs.Address.toLegacyAddress(sign.address) + it(`should not verify an invalid signed message from ${sign.network} cashaddr address ${sign.address}`, () => { + assert.equal( + bchjs.BitcoinCash.verifyMessage( + sign.address, + sign.signature, + "nope" + ), + false + ) + }) + }) + }) + }) + + describe("encode and decode to base58Check", () => { + describe("#encodeBase58Check", () => { + fixtures.encodeBase58Check.forEach((base58Check, i) => { + it(`encode ${base58Check.hex} as base58Check ${base58Check.base58Check}`, () => { + assert.equal( + bchjs.BitcoinCash.encodeBase58Check(base58Check.hex), + base58Check.base58Check + ) + }) + }) + }) + + describe("#decodeBase58Check", () => { + fixtures.encodeBase58Check.forEach((base58Check, i) => { + it(`decode ${base58Check.base58Check} as ${base58Check.hex}`, () => { + assert.equal( + bchjs.BitcoinCash.decodeBase58Check(base58Check.base58Check), + base58Check.hex + ) + }) + }) + }) + }) + + describe("encode and decode BIP21 urls", () => { + describe("#encodeBIP21", () => { + fixtures.bip21.valid.forEach((bip21, i) => { + it(`encode ${bip21.address} as url`, () => { + const url = bchjs.BitcoinCash.encodeBIP21( + bip21.address, + bip21.options + ) + assert.equal(url, bip21.url) + }) + }) + fixtures.bip21.valid_regtest.forEach((bip21, i) => { + it(`encode ${bip21.address} as url`, () => { + const url = bchjs.BitcoinCash.encodeBIP21( + bip21.address, + bip21.options, + true + ) + assert.equal(url, bip21.url) + }) + }) + }) + + describe("#decodeBIP21", () => { + fixtures.bip21.valid.forEach((bip21, i) => { + it(`decodes ${bip21.url}`, () => { + const decoded = bchjs.BitcoinCash.decodeBIP21(bip21.url) + assert.equal(decoded.options.amount, bip21.options.amount) + assert.equal(decoded.options.label, bip21.options.label) + assert.equal( + bchjs.Address.toCashAddress(decoded.address), + bchjs.Address.toCashAddress(bip21.address) + ) + }) + }) + // fixtures.bip21.valid_regtest.forEach((bip21, i) => { + // it(`decodes ${bip21.url}`, () => { + // const decoded = bchjs.BitcoinCash.decodeBIP21(bip21.url) + // assert.equal(decoded.options.amount, bip21.options.amount) + // assert.equal(decoded.options.label, bip21.options.label) + // assert.equal( + // bchjs.Address.toCashAddress(decoded.address, true, true), + // bchjs.Address.toCashAddress(bip21.address, true, true) + // ) + // }) + // }) + }) + }) + + describe("#getByteCount", () => { + fixtures.getByteCount.forEach(fixture => { + it(`get byte count`, () => { + const byteCount = bchjs.BitcoinCash.getByteCount( + fixture.inputs, + fixture.outputs + ) + assert.equal(byteCount, fixture.byteCount) + }) + }) + }) + + describe("#bip38", () => { + describe("#encryptBIP38", () => { + fixtures.bip38.encrypt.mainnet.forEach(fixture => { + it(`BIP 38 encrypt wif ${fixture.wif} with password ${fixture.password} on mainnet`, () => { + const encryptedKey = bchjs.BitcoinCash.encryptBIP38( + fixture.wif, + fixture.password + ) + assert.equal(encryptedKey, fixture.encryptedKey) + }) + }) + + fixtures.bip38.encrypt.testnet.forEach(fixture => { + it(`BIP 38 encrypt wif ${fixture.wif} with password ${fixture.password} on testnet`, () => { + const encryptedKey = bchjs.BitcoinCash.encryptBIP38( + fixture.wif, + fixture.password + ) + assert.equal(encryptedKey, fixture.encryptedKey) + }) + }) + }) + + describe("#decryptBIP38", () => { + fixtures.bip38.decrypt.mainnet.forEach(fixture => { + it(`BIP 38 decrypt encrypted key ${fixture.encryptedKey} on mainnet`, () => { + const wif = bchjs.BitcoinCash.decryptBIP38( + fixture.encryptedKey, + fixture.password, + "mainnet" + ) + assert.equal(wif, fixture.wif) + }) + }) + + fixtures.bip38.decrypt.testnet.forEach(fixture => { + it(`BIP 38 decrypt encrypted key ${fixture.encryptedKey} on testnet`, () => { + const wif = bchjs.BitcoinCash.decryptBIP38( + fixture.encryptedKey, + fixture.password, + "testnet" + ) + assert.equal(wif, fixture.wif) + }) + }) + }) + }) +}) diff --git a/test/unit/blockbook.js b/test/unit/blockbook.js new file mode 100644 index 0000000..9866c81 --- /dev/null +++ b/test/unit/blockbook.js @@ -0,0 +1,275 @@ +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const axios = require("axios") +const sinon = require("sinon") + +const mockData = require("./fixtures/blockbook-mock") + +describe(`#Blockbook`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe(`#Balance`, () => { + it(`should GET balance for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.balance }) + + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + const result = await bchjs.Blockbook.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAnyKeys(result, [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result.txids) + }) + + it(`should POST request balances for an array of addresses`, async () => { + // Stub the network call. + sandbox + .stub(axios, "post") + .resolves({ data: [mockData.balance, mockData.balance] }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Blockbook.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "page", + "totalPages", + "itemsOnPage", + "address", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxs", + "txs", + "txids" + ]) + assert.isArray(result[0].txids) + }) + + it(`should throw an error for improper input`, async () => { + try { + // Stub the network call. + sandbox + .stub(axios, "post") + .throws(`Input address must be a string or array of strings`) + + const addr = 12345 + + await bchjs.Blockbook.balance(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 throw error on array size rate limit`, async () => { + try { + // Stub the network call. + sandbox.stub(axios, "post").throws(`Array too large`) + + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Blockbook.balance(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + */ + }) + + describe(`#utxo`, () => { + it(`should GET utxos for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.utxo }) + + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should POST utxo details for an array of addresses`, async () => { + // Stub the network call. + sandbox.stub(axios, "post").resolves({ data: mockData.utxos }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.isArray(result[0]) + assert.hasAnyKeys(result[0][0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + it(`should throw an error for improper input`, async () => { + try { + // Stub the network call. + sandbox + .stub(axios, "post") + .throws(`Input address must be a string or array of strings`) + + const addr = 12345 + + await bchjs.Blockbook.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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Blockbook.utxo(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + */ + }) + + describe(`#tx`, () => { + it(`should GET tx details for a single txid`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.utxo }) + + const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + + const result = await bchjs.Blockbook.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAnyKeys(result[0], [ + "txid", + "vout", + "value", + "height", + "confirmations" + ]) + }) + + // it(`should POST utxo details for an array of addresses`, async () => { + // // Stub the network call. + // sandbox.stub(axios, "post").resolves({ data: mockData.utxos }) + // + // const addr = [ + // "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + // "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + // ] + // + // const result = await bchjs.Blockbook.utxo(addr) + // //console.log(`result: ${JSON.stringify(result, null, 2)}`) + // + // assert.isArray(result) + // assert.isArray(result[0]) + // assert.hasAnyKeys(result[0][0], [ + // "txid", + // "vout", + // "value", + // "height", + // "confirmations" + // ]) + // }) + + // it(`should throw an error for improper input`, async () => { + // try { + // // Stub the network call. + // sandbox + // .stub(axios, "post") + // .throws(`Input address must be a string or array of strings`) + // + // const addr = 12345 + // + // await bchjs.Blockbook.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 throw error on array size rate limit`, async () => { + try { + const addr = [] + for (let i = 0; i < 25; i++) + addr.push("bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf") + + const result = await bchjs.Blockbook.utxo(addr) + + console.log(`result: ${util.inspect(result)}`) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + assert.hasAnyKeys(err, ["error"]) + assert.include(err.error, "Array too large") + } + }) + */ + }) +}) diff --git a/test/unit/blockchain.js b/test/unit/blockchain.js new file mode 100644 index 0000000..e088ffe --- /dev/null +++ b/test/unit/blockchain.js @@ -0,0 +1,494 @@ +const assert = require("assert") +const assert2 = require("chai").assert +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const sinon = require("sinon") + +const mockData = require("./fixtures/blockchain-mock") + +describe("#Blockchain", () => { + describe("#getBestBlockHash", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get best block hash", done => { + const resolved = new Promise(r => + r({ + data: + "0000000000000000005f1f550d3d8b142b684277016ebd00fa29c668606ae52d" + }) + ) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBestBlockHash() + .then(result => { + const hash = + "0000000000000000005f1f550d3d8b142b684277016ebd00fa29c668606ae52d" + assert.equal(hash, result) + }) + .then(done, done) + }) + }) + + describe("#getBlock", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + hash: "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", + confirmations: 526807, + size: 216, + height: 1000, + version: 1, + versionHex: "00000001", + merkleroot: + "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33", + tx: ["fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33"], + time: 1232346882, + mediantime: 1232344831, + nonce: 2595206198, + bits: "1d00ffff", + difficulty: 1, + chainwork: + "000000000000000000000000000000000000000000000000000003e903e903e9", + previousblockhash: + "0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d", + nextblockhash: + "00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6" + } + + it("should get block by hash", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBlock( + "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getBlockchainInfo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + chain: "main", + blocks: 527810, + headers: 527810, + bestblockhash: + "000000000000000001d127592d091d4c45062504663c9acab27a1b16c028e3c0", + difficulty: 576023394804.6666, + mediantime: 1524878499, + verificationprogress: 0.9999990106793685, + chainwork: + "00000000000000000000000000000000000000000096da5b040913fa09249b4e", + pruned: false, + softforks: [ + { id: "bip34", version: 2, reject: [Object] }, + { id: "bip66", version: 3, reject: [Object] }, + { id: "bip65", version: 4, reject: [Object] } + ], + bip9_softforks: { + csv: { + status: "active", + startTime: 1462060800, + timeout: 1493596800, + since: 419328 + } + } + } + + it("should get blockchain info", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBlockchainInfo() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getBlockCount", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = 527810 + + it("should get block count", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBlockCount() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getBlockHash", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = + "000000000000000001d127592d091d4c45062504663c9acab27a1b16c028e3c0" + + it("should get block hash by height", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBlockHash(527810) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getBlockHeader", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + hash: "000000000000000001d127592d091d4c45062504663c9acab27a1b16c028e3c0", + confirmations: 1, + height: 527810, + version: 536870912, + versionHex: "20000000", + merkleroot: + "9298432bbebe4638456aa19cb7ef91639da87668a285d88d0ecd6080424d223b", + time: 1524881438, + mediantime: 1524878499, + nonce: 3326843941, + bits: "1801e8a5", + difficulty: 576023394804.6666, + chainwork: + "00000000000000000000000000000000000000000096da5b040913fa09249b4e", + previousblockhash: + "000000000000000000b33251708bc7a7b4540e61880d8c376e8e2db6a19a4789" + } + + it("should get block header by hash", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getBlockHeader( + "000000000000000001d127592d091d4c45062504663c9acab27a1b16c028e3c0", + true + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getDifficulty", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = "577528469277.1339" + + it("should get difficulty", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getDifficulty() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMempoolAncestors", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = "Transaction not in mempool" + + it("should get mempool ancestors", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getMempoolAncestors( + "daf58932cb91619304dd4cbd03c7202e89ad7d6cbd6e2209e5f64ce3b6ed7c88", + true + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMempoolDescendants", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + result: "Transaction not in mempool" + } + + it("should get mempool descendants", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getMempoolDescendants( + "daf58932cb91619304dd4cbd03c7202e89ad7d6cbd6e2209e5f64ce3b6ed7c88", + true + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMempoolEntry", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + result: "Transaction not in mempool" + } + + it("should get mempool entry", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getMempoolEntry( + "daf58932cb91619304dd4cbd03c7202e89ad7d6cbd6e2209e5f64ce3b6ed7c88" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMempoolInfo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + result: { + size: 317, + bytes: 208583, + usage: 554944, + maxmempool: 300000000, + mempoolminfee: 0 + } + } + + it("should get mempool info", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getMempoolInfo() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getRawMempool", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + result: { + transactions: [ + { + txid: + "ab36d68dd0a618592fe34e4a898e8beeeb4049133547dbb16f9338384084af96", + size: 191, + fee: 0.00047703, + modifiedfee: 0.00047703, + time: 1524883317, + height: 527811, + startingpriority: 5287822727.272727, + currentpriority: 5287822727.272727, + descendantcount: 1, + descendantsize: 191, + descendantfees: 47703, + ancestorcount: 1, + ancestorsize: 191, + ancestorfees: 47703, + depends: [] + } + ] + } + } + + it("should get mempool info", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.getRawMempool() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getTxOut", () => { + // TODO finish this test + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + // const data = { + // result: {} + // } + + it("should throw an error for improper txid.", async () => { + try { + await bchjs.Blockchain.getTxOut("badtxid") + } catch (err) { + assert2.include(err.message, "txid needs to be a proper transaction ID") + } + }) + + it("should throw an error if no vout value is provided.", async () => { + try { + await bchjs.Blockchain.getTxOut( + "daf58932cb91619304dd4cbd03c7202e89ad7d6cbd6e2209e5f64ce3b6ed7c88" + ) + } catch (err) { + assert2.include(err.message, "n must be an integer") + } + }) + + it("should throw an error if include_mempool is not a boolean", async () => { + try { + await bchjs.Blockchain.getTxOut( + "daf58932cb91619304dd4cbd03c7202e89ad7d6cbd6e2209e5f64ce3b6ed7c88", + 0, + "bad value" + ) + } catch (err) { + assert2.include( + err.message, + "include_mempool input must be of type boolean" + ) + } + }) + + it("should get information on an unspent tx", async () => { + sandbox.stub(axios, "post").resolves({ data: mockData.txOutUnspent }) + + const result = await bchjs.Blockchain.getTxOut( + "62a3ea958a463a372bc0caf2c374a7f60be9c624be63a0db8db78f05809df6d8", + 0, + true + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.hasAllKeys(result, [ + "bestblock", + "confirmations", + "value", + "scriptPubKey", + "coinbase" + ]) + }) + + it("should get information on a spent tx", async () => { + sandbox.stub(axios, "post").resolves({ data: null }) + + const result = await bchjs.Blockchain.getTxOut( + "87380e52d151856b23173d6d8a3db01b984c6b50f77ea045a5a1cf4f54497871", + 0, + true + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.equal(result, null) + }) + }) + + describe("#preciousBlock", () => { + // TODO finish this test + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = { + result: {} + } + + it("should get TODO", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.preciousBlock() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#pruneBlockchain", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = "Cannot prune blocks because node is not in prune mode." + + it("should prune blockchain", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "post").returns(resolved) + + bchjs.Blockchain.pruneBlockchain(507) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#verifyChain", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = true + + it("should verify blockchain", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.verifyChain(3, 6) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#verifyTxOutProof", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + const data = "proof must be hexadecimal string (not '')" + + it("should verify utxo proof", done => { + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Blockchain.verifyTxOutProof("3") + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) +}) diff --git a/test/unit/control.js b/test/unit/control.js new file mode 100644 index 0000000..2ec8219 --- /dev/null +++ b/test/unit/control.js @@ -0,0 +1,64 @@ +const assert = require("assert") +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const sinon = require("sinon") + +describe("#Control", () => { + describe("#getNetworkInfo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get info", done => { + const data = { + version: 170000, + protocolversion: 70015, + blocks: 527813, + timeoffset: 0, + connections: 21, + proxy: "", + difficulty: 581086703759.5878, + testnet: false, + paytxfee: 0, + relayfee: 0.00001, + errors: "" + } + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Control.getNetworkInfo() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMemoryInfo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get memory info", done => { + const data = { + locked: { + used: 0, + free: 65536, + total: 65536, + locked: 65536, + chunks_used: 0, + chunks_free: 1 + } + } + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Control.getMemoryInfo() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) +}) diff --git a/test/unit/crypto.js b/test/unit/crypto.js new file mode 100644 index 0000000..73f6d23 --- /dev/null +++ b/test/unit/crypto.js @@ -0,0 +1,100 @@ +const fixtures = require("./fixtures/crypto.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const Buffer = require("safe-buffer").Buffer + +describe("#Crypto", () => { + describe("#sha256", () => { + fixtures.sha256.forEach(fixture => { + it(`should create SHA256Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { + const data = Buffer.from(fixture.hex, "hex") + const sha256Hash = bchjs.Crypto.sha256(data).toString("hex") + assert.equal(sha256Hash, fixture.hash) + }) + + it(`should create 64 character SHA256Hash hex encoded`, () => { + const data = Buffer.from(fixture.hex, "hex") + const sha256Hash = bchjs.Crypto.sha256(data).toString("hex") + assert.equal(sha256Hash.length, 64) + }) + }) + }) + + describe("#ripemd160", () => { + fixtures.ripemd160.forEach(fixture => { + it(`should create RIPEMD160Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { + const data = Buffer.from(fixture.hex, "hex") + const ripemd160 = bchjs.Crypto.ripemd160(data).toString("hex") + assert.equal(ripemd160, fixture.hash) + }) + + it(`should create 64 character RIPEMD160Hash hex encoded`, () => { + const data = Buffer.from(fixture.hex, "hex") + const ripemd160 = bchjs.Crypto.ripemd160(data).toString("hex") + assert.equal(ripemd160.length, 40) + }) + }) + }) + + describe("#hash256", () => { + fixtures.hash256.forEach(fixture => { + it(`should create double SHA256 Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { + const data = Buffer.from(fixture.hex, "hex") + const hash256 = bchjs.Crypto.hash256(data).toString("hex") + assert.equal(hash256, fixture.hash) + }) + + it(`should create 64 character SHA256 Hash hex encoded`, () => { + const data = Buffer.from(fixture.hex, "hex") + const hash256 = bchjs.Crypto.hash256(data).toString("hex") + assert.equal(hash256.length, 64) + }) + }) + }) + + describe("#hash160", () => { + fixtures.hash160.forEach(fixture => { + it(`should create RIPEMD160(SHA256()) hex encoded ${fixture.hash} from ${fixture.hex}`, () => { + const data = Buffer.from(fixture.hex, "hex") + const hash160 = bchjs.Crypto.hash160(data).toString("hex") + assert.equal(hash160, fixture.hash) + }) + + it(`should create 64 character SHA256Hash hex encoded`, () => { + const data = Buffer.from(fixture.hex, "hex") + const hash160 = bchjs.Crypto.hash160(data).toString("hex") + assert.equal(hash160.length, 40) + }) + }) + }) + + describe("#randomBytes", () => { + for (let i = 0; i < 6; i++) { + it("should return 16 bytes of entropy hex encoded", () => { + const entropy = bchjs.Crypto.randomBytes(16) + assert.equal(Buffer.byteLength(entropy), 16) + }) + + it("should return 20 bytes of entropy hex encoded", () => { + const entropy = bchjs.Crypto.randomBytes(20) + assert.equal(Buffer.byteLength(entropy), 20) + }) + + it("should return 24 bytes of entropy hex encoded", () => { + const entropy = bchjs.Crypto.randomBytes(24) + assert.equal(Buffer.byteLength(entropy), 24) + }) + + it("should return 28 bytes of entropy hex encoded", () => { + const entropy = bchjs.Crypto.randomBytes(28) + assert.equal(Buffer.byteLength(entropy), 28) + }) + + it("should return 32 bytes of entropy hex encoded", () => { + const entropy = bchjs.Crypto.randomBytes(32) + assert.equal(Buffer.byteLength(entropy), 32) + }) + } + }) +}) diff --git a/test/unit/ecpairs.js b/test/unit/ecpairs.js new file mode 100644 index 0000000..d3dbb6f --- /dev/null +++ b/test/unit/ecpairs.js @@ -0,0 +1,146 @@ +const assert = require("assert") + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +const Buffer = require("safe-buffer").Buffer + +const fixtures = require("./fixtures/ecpair.json") + +describe("#ECPair", () => { + describe("#fromWIF", () => { + fixtures.fromWIF.forEach(fixture => { + it(`should create ECPair from WIF ${fixture.privateKeyWIF}`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + assert.equal(typeof ecpair, "object") + }) + + it(`should get ${fixture.legacy} legacy address`, () => { + const legacy = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + assert.equal(bchjs.HDNode.toLegacyAddress(legacy), fixture.legacy) + }) + + it(`should get ${fixture.cashAddr} cash address`, () => { + const cashAddr = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + assert.equal(bchjs.HDNode.toCashAddress(cashAddr), fixture.cashAddr) + }) + + it(`should get ${fixture.regtestAddr} cash address`, () => { + const cashAddr = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + assert.equal( + bchjs.HDNode.toCashAddress(cashAddr, true), + fixture.regtestAddr + ) + }) + }) + }) + + describe("#toWIF", () => { + fixtures.toWIF.forEach(fixture => { + it(`should get WIF ${fixture.privateKeyWIF} from ECPair`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + const wif = bchjs.ECPair.toWIF(ecpair) + assert.equal(wif, fixture.privateKeyWIF) + }) + }) + }) + + describe("#fromPublicKey", () => { + fixtures.fromPublicKey.forEach(fixture => { + it(`should create ECPair from public key buffer`, () => { + const ecpair = bchjs.ECPair.fromPublicKey( + Buffer.from(fixture.pubkeyHex, "hex") + ) + assert.equal(typeof ecpair, "object") + }) + + it(`should get ${fixture.legacy} legacy address`, () => { + const ecpair = bchjs.ECPair.fromPublicKey( + Buffer.from(fixture.pubkeyHex, "hex") + ) + assert.equal(bchjs.HDNode.toLegacyAddress(ecpair), fixture.legacy) + }) + + it(`should get ${fixture.cashAddr} cash address`, () => { + const ecpair = bchjs.ECPair.fromPublicKey( + Buffer.from(fixture.pubkeyHex, "hex") + ) + assert.equal(bchjs.HDNode.toCashAddress(ecpair), fixture.cashAddr) + }) + + it(`should get ${fixture.regtestAddr} cash address`, () => { + const ecpair = bchjs.ECPair.fromPublicKey( + Buffer.from(fixture.pubkeyHex, "hex") + ) + assert.equal( + bchjs.HDNode.toCashAddress(ecpair, true), + fixture.regtestAddr + ) + }) + }) + }) + + describe("#toPublicKey", () => { + fixtures.toPublicKey.forEach(fixture => { + it(`should create a public key buffer from an ECPair`, () => { + const ecpair = bchjs.ECPair.fromPublicKey( + Buffer.from(fixture.pubkeyHex, "hex") + ) + const pubkeyBuffer = bchjs.ECPair.toPublicKey(ecpair) + assert.equal(typeof pubkeyBuffer, "object") + }) + }) + }) + + describe("#toLegacyAddress", () => { + fixtures.toLegacyAddress.forEach(fixture => { + it(`should create legacy address ${fixture.legacy} from an ECPair`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + const legacyAddress = bchjs.ECPair.toLegacyAddress(ecpair) + assert.equal(legacyAddress, fixture.legacy) + }) + }) + }) + + describe("#toCashAddress", () => { + fixtures.toCashAddress.forEach(fixture => { + it(`should create cash address ${fixture.cashAddr} from an ECPair`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + const cashAddr = bchjs.ECPair.toCashAddress(ecpair) + assert.equal(cashAddr, fixture.cashAddr) + }) + }) + + fixtures.toCashAddress.forEach(fixture => { + it(`should create regtest cash address ${fixture.regtestAddr} from an ECPair`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + const regtestAddr = bchjs.ECPair.toCashAddress(ecpair, true) + assert.equal(regtestAddr, fixture.regtestAddr) + }) + }) + }) + + describe("#sign", () => { + fixtures.sign.forEach(fixture => { + it(`should sign 32 byte hash buffer`, () => { + const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) + const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), "hex") + const signatureBuf = bchjs.ECPair.sign(ecpair, buf) + assert.equal(typeof signatureBuf, "object") + }) + }) + }) + + describe("#verify", () => { + fixtures.verify.forEach(fixture => { + it(`should verify signed 32 byte hash buffer`, () => { + const ecpair1 = bchjs.ECPair.fromWIF(fixture.privateKeyWIF1) + //const ecpair2 = bchjs.ECPair.fromWIF(fixture.privateKeyWIF2) + const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), "hex") + const signature = bchjs.ECPair.sign(ecpair1, buf) + const verify = bchjs.ECPair.verify(ecpair1, buf, signature) + assert.equal(verify, true) + }) + }) + }) +}) diff --git a/test/unit/electrumx.js b/test/unit/electrumx.js new file mode 100644 index 0000000..50fdd3e --- /dev/null +++ b/test/unit/electrumx.js @@ -0,0 +1,199 @@ +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/electrumx-mock") + +describe(`#ElectrumX`, () => { + 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.Electrumx.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, "get").resolves({ data: mockData.utxo }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "height") + assert.property(result.utxos[0], "tx_hash") + assert.property(result.utxos[0], "tx_pos") + assert.property(result.utxos[0], "value") + }) + + it(`should POST utxo details for an array of addresses`, async () => { + // Stub the network call. + sandbox.stub(axios, "post").resolves({ data: mockData.utxos }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.utxo(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "utxos") + assert.isArray(result.utxos) + + assert.property(result.utxos[0], "utxos") + assert.isArray(result.utxos[0].utxos) + assert.property(result.utxos[0], "address") + + assert.property(result.utxos[0].utxos[0], "height") + assert.property(result.utxos[0].utxos[0], "tx_hash") + assert.property(result.utxos[0].utxos[0], "tx_pos") + assert.property(result.utxos[0].utxos[0], "value") + }) + }) + + describe(`#balance`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.balance(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 balance for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.balance }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balance") + assert.property(result.balance, "confirmed") + assert.property(result.balance, "unconfirmed") + }) + + it(`should POST balance for an array of addresses`, async () => { + // Stub the network call. + sandbox.stub(axios, "post").resolves({ data: mockData.balances }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.balance(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "balances") + assert.isArray(result.balances) + + assert.property(result.balances[0], "address") + assert.property(result.balances[0], "balance") + assert.property(result.balances[0].balance, "confirmed") + assert.property(result.balances[0].balance, "unconfirmed") + }) + }) + + describe(`#transactions`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.Electrumx.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 GET transactions for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.transaction }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + assert.property(result.transactions[0], "height") + assert.property(result.transactions[0], "tx_hash") + }) + + it(`should POST transaction history for an array of addresses`, async () => { + // Stub the network call. + sandbox.stub(axios, "post").resolves({ data: mockData.transactions }) + + const addr = [ + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + ] + + const result = await bchjs.Electrumx.transactions(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + + assert.property(result, "transactions") + assert.isArray(result.transactions) + + assert.property(result.transactions[0], "address") + assert.property(result.transactions[0], "transactions") + assert.isArray(result.transactions[0].transactions) + assert.property(result.transactions[0].transactions[0], "height") + assert.property(result.transactions[0].transactions[0], "tx_hash") + }) + }) +}) diff --git a/test/unit/encryption.js b/test/unit/encryption.js new file mode 100644 index 0000000..9242348 --- /dev/null +++ b/test/unit/encryption.js @@ -0,0 +1,68 @@ +const assert = require("chai").assert +const sinon = require("sinon") + +const BCHJS = require("../../src/bch-js") +// const bchjs = new BCHJS() +let bchjs + +const mockData = require("./fixtures/encryption-mock") + +describe("#Encryption", () => { + let sandbox + + beforeEach(() => { + bchjs = new BCHJS() + sandbox = sinon.createSandbox() + }) + + afterEach(() => sandbox.restore()) + + describe("#getPubKey", () => { + it("should throw error if BCH address is not provided.", async () => { + try { + await bchjs.encryption.getPubKey() + + assert.equal(true, false, "Unexpected result!") + } catch (err) { + // console.log(`err: `, err) + assert.include( + err.message, + "Input must be a valid Bitcoin Cash address" + ) + } + }) + + it("should report when public key can not be found", async () => { + // Stub the network call. + sandbox + .stub(bchjs.encryption.axios, "get") + .resolves({ data: mockData.failureMock }) + + const addr = "bitcoincash:qpxqr2pmcverj4vukgjqssvk2zju8tp9xsgz2nqagx" + + const result = await bchjs.encryption.getPubKey(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, false) + assert.property(result, "publicKey") + assert.equal(result.publicKey, "not found") + }) + + it("should get a public key", async () => { + // Stub the network call. + sandbox + .stub(bchjs.encryption.axios, "get") + .resolves({ data: mockData.successMock }) + + const addr = "bitcoincash:qpf8jv9hmqcda0502gjp7nm3g24y5h5s4unutghsxq" + + const result = await bchjs.encryption.getPubKey(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "success") + assert.equal(result.success, true) + assert.property(result, "publicKey") + }) + }) +}) diff --git a/test/unit/fixtures/address.json b/test/unit/fixtures/address.json new file mode 100644 index 0000000..02c1112 --- /dev/null +++ b/test/unit/fixtures/address.json @@ -0,0 +1,187 @@ +{ + "legacyMainnetP2PKH": [ + "18xHZ8g2feo4ceejGpvzHkvXT79fi2ZdTG", + "1K7Qb1dWkiYwPrZhLws3uUKhxKEU7dRnbQ", + "1Au7gWXS2zAgFSdWwT5QnTNeasye16kxoG", + "174uecjfRgh1XkVBYFzZymkV1JwmFQ91s9", + "1KLXUPMdq4vaU3VoQzSvLUx2mub5qzFkTc", + "12AqNsGTyL6RDR8SDEtW9EyfYruFNZ9Sjs", + "1P3Lq89iP27S76ZMKonty4mpJMDHp3a9NE", + "14A8F8jHiipwnKLd2JEWqPvbGiS6FA7VoG", + "1KgRZzxLUvZqL8EuufmdxqSjh3tgURwD6d", + "1GMApiWJoTfQ21jbtVX3Qz8YtnGUuZAJtS" + ], + "legacyMainnetP2SH": [ + "3DA6RBcFgLwLTpnF6BRAee8w6a9H6JQLCm", + "3AbtU1JSaiQijyGT21stxpBRZj1hixWcGB", + "3KfgmLeczB525pV2tJLQ6RM5qFMLaB2Kn1", + "3Bsr5dvAJ2Q8CpHxJSZkNdgD12Tkb9TaR7", + "3J78iYD4i4ht8Btp8pyRx71jg5yrRTkQaM", + "3EGxduodZm6a9FLN1jdVxN8pT5orCmFdUK", + "3DWvzGWYJCY19Gc4LcN8noHGpxdjRbJeYe", + "3Aw9ePtWrH8EEJoF5HW8swW9VXxpF4dP54", + "3QTQE5rt7TZg7hXZ4DsTQ6mUUZCbFY2RiL", + "3EoWFm3GcTzDFBQ9KGVevhH1nuSxa14eFM" + ], + "legacyTestnetP2PKH": [ + "mhTg9sgNgvAGfmJs192oUzQWqAXHH5nqLE", + "mmpjQ24UyGGfJ39k44prH6W2A7y1qVaQti", + "muEnSveRwu8cEJSvXzDTJZQWrr7y9i331i", + "n3jZn7rb3Bjepap4TWo8pyqcwuxAw439HW", + "mqHUen3SUNjQkAPjMxZfSnuyd8cmcbL6fq", + "n4g3swxSebC9cDHXTjYQcx3tRAe4EPrZrZ", + "n2yNUahmfBFLLzrpcXpMh8Votw7zbKVHtX", + "mqYM6bu8Vw6xFTTPhbtxWPPmFN8BwpVQeE", + "mrVbyCMyB3vhDya4rYSxdM8qivEGfn7JSP", + "mnqGaf1SbPFh1tdXdeXd7pWhJUY2ite6SY" + ], + "cashaddrMainnetP2PKH": [ + "bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh", + "bitcoincash:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsn9h5vuvr", + "bitcoincash:qpkfg4kck99wksyss6nvaqtafeahfnyrpsj0ed372t", + "bitcoincash:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvy0c2jvx7", + "bitcoincash:qryj8x4s7vfsc864jm0xaak9qfe8qgk245y9ska57l", + "bitcoincash:qqxdgs9x9rx4v7g4ea5kxyprj2k9wnv695578lwmt8", + "bitcoincash:qrcugfh4prxzl8qzqazyth7ytuc6k4qqcg50etp609", + "bitcoincash:qq32y2h0ljtyyv4xkk07gtyrclv2xwq5sq3pqrzjes", + "bitcoincash:qrxwdlhfx5f4xsfnl0g887e5ccs5puy8sgj0z0md6k", + "bitcoincash:qz595e7jwa3q4vw26whn6zcz6x074669lgnnvjkz5m" + ], + "cashaddrMainnetP2SH": [ + "bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug", + "bitcoincash:ppsup5akyvql5w46q9eszd9fxpx970acpyqkw79vq2", + "bitcoincash:prznrkhnf6zqsap6l664ayzu2xue67ue4gv686sjyu", + "bitcoincash:pphmm80pznl6pnkmzakz3ahafydmvhwzcslea4v5mz", + "bitcoincash:pz6pr25g6mulp0kes9xnmsda0u4rf442ase2un89pl", + "bitcoincash:pz9qe6ys8x0l98zegf0a9rae4zenk95yp5dv5xjnk5", + "bitcoincash:pzqmjwk8929jtqt7ac7szjuawtswmxhtu5xxm64upf", + "bitcoincash:ppjk2e4nz47n50hpg8z6nt5hfjeajy4cyyp0p2tvnp", + "bitcoincash:prum0t7tqczlyaw2d30tmpj3je9q2xlzwcqk9wx83u", + "bitcoincash:pz8a837lttkvjksg0jjmmulqvfkgpqrcdgufy8ns5s" + ], + "cashaddrTestnetP2PKH": [ + "bchtest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u22nzt946", + "bchtest:qpzj67wmlsq8uttddddapjjawyusureca59ug9cak8", + "bchtest:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5m6kyejfd", + "bchtest:qrem2cg43ksmvlampheur8gfgdhhk57mygy26y7f2e", + "bchtest:qp4jf3n740kffkladul96xnq5dtrflg4x5w5rfy22r", + "bchtest:qrlqxnzuu6hvvg9qer0m093ywkjnu7wm7ypj7unqzc", + "bchtest:qr44nfr9egxt7h7lxdktf9kajds9fwdxxcved7827j", + "bchtest:qpklfd26u70ta4j7p4twxv2k5apx95p0qqgm8cf2sd", + "bchtest:qpuxsqzgq3j0a58tnmwpgcrwwyrkenalevaeknklfg", + "bchtest:qpgrlg0yqmdf4y8q980e57tx6v7vrvds2ug89n8w8q" + ], + "cashaddrRegTestP2PKH": [ + "bchreg:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2usk9rgkku", + "bchreg:qpzj67wmlsq8uttddddapjjawyusureca5lq7ymw4p", + "bchreg:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5pxq96p2t", + "bchreg:qrem2cg43ksmvlampheur8gfgdhhk57myg7kv9a6fl", + "bchreg:qp4jf3n740kffkladul96xnq5dtrflg4x55g4g8ef9", + "bchreg:qrlqxnzuu6hvvg9qer0m093ywkjnu7wm7ymwgasnp7", + "bchreg:qr44nfr9egxt7h7lxdktf9kajds9fwdxxck9mlyea5", + "bchreg:qpklfd26u70ta4j7p4twxv2k5apx95p0qqj83e2ent", + "bchreg:qpuxsqzgq3j0a58tnmwpgcrwwyrkenalev89qj4v2w", + "bchreg:qpgrlg0yqmdf4y8q980e57tx6v7vrvds2ujmnjyayx" + ], + "hash160MainnetP2PKH": [ + "573d93b475be4f1925f3b74ed951201b0147eac1", + "c6a872e524a03b7d400c425d7b974a35c78f4634", + "6c9456d8b14aeb409086a6ce817d4e7b74cc830c", + "428df38e23fc879a25819427995c3e6355b12d33", + "c9239ab0f3130c1f5596de6ef6c502727022caad", + "0cd440a628cd567915cf6963102392ac574d9a2d", + "f1c426f508cc2f9c02074445dfc45f31ab5400c2", + "22a22aeffc964232a6b59fe42c83c7d8a3381480", + "cce6fee93513534133fbd073fb34c62140f08782", + "a85a67d277620ab1cad3af3d0b02d19feaeb45fa" + ], + "hash160MainnetP2SH": [ + "7dc85da64d1d93ef01ef62e0221c02f512e3942f", + "61c0d3b62301fa3aba01730134a9304c5f3fb809", + "c531daf34e8408743afeb55e905c51b99d7b99aa", + "6fbd9de114ffa0cedb176c28f6fd491bb65dc2c4", + "b411aa88d6f9f0bed9814d3dc1bd7f2a34d6aaec", + "8a0ce890399ff29c59425fd28fb9a8b33b16840d", + "81b93ac72a8b25817eee3d014b9d72e0ed9aebe5", + "656566b3157d3a3ee141c5a9ae974cb3d912b821", + "f9b7afcb0605f275ca6c5ebd8651964a051be276", + "8fd3c7df5aecc95a087ca5bdf3e0626c8080786a" + ], + "hash160TestnetP2PKH": [ + "155187a3283b08b30519db50bc23bbba9f4b6657", + "452d79dbfc007e2d6d6b5bd0ca5d71390e0f38ed", + "9682e2bc8fdb7468fb020430ff3aee4aabba69cd", + "f3b561158da1b67fbb0df3c19d09436f7b53db22", + "6b24c67eabec94dbfd6f3e5d1a60a35634fd1535", + "fe034c5ce6aec620a0c8dfb7962475a53e79dbf1", + "eb59a465ca0cbf5fdf336cb496dd936054b9a636", + "6df4b55ae79ebed65e0d56e33156a74262d02f00", + "786800480464fed0eb9edc14606e71076ccfbfcb", + "503fa1e406da9a90e029df9a7966d33cc1b1b057" + ], + "mainnetXPub": [ + { + "xpub": "xpub6CVcpZVmPNjuniVYu1mLnLjDBfxWpx7LS25uxwRm5BLbXMCJmRaQgAxuqZDoYDeidJUh5QUatLJPWpeCkEK648hExyKFezqxJz4CMfEoYAc", + "addresses": [ + "bitcoincash:qp3c2e0ehq5lqgnf2v8kqk0qemkatnknmga3mwqkms", + "bitcoincash:qznkqj05zsx8uszyjfdyvfn55ecwltfsrq93jhwchv", + "bitcoincash:qpkvr2gs84g6nltzcs9d9ef7qerh7qqenca2zddlpz", + "bitcoincash:qpuchd6als5yc3fs55aktwylr20u0antyu5f99jt8k", + "bitcoincash:qq670p9knuarh75ae7er4c246w909nd0z5gx6h60vv" + ] + }, + { + "xpub": "xpub6CVcpZVmPNjuqYFnvSYoD9hE89PKmsWewFWGKeaCcVNSfDTgQgMuhm8Q2zreBsNQVMSJrBmQ3C95fi3SiRXYRQckQJMZuPXTJm9TcQejW13", + "addresses": [ + "bitcoincash:qqhkcs5fnchh99p68mcszcz68cecef5jfyeugnyysl", + "bitcoincash:qqxj4w8lgctdhpvl9um9w7tvgmc9se3ttcfkdfcv5e", + "bitcoincash:qp30m7fu27vljfqvqq26kwn8f4zp4g7wngqdzn40xh", + "bitcoincash:qrvtu7nw8x5343c9jgc5a58zw5vw3susful9x9yhrv", + "bitcoincash:qrjcyuxunxzt2w35ney2hzrvgynrcqg5qqj9366m6a" + ] + }, + { + "xpub": "xpub6CVcpZVmPNjuu1zjt9joxDzDrP9UNs6GVS5K1jSopnq76RBZswLsxy75qvJTVcSS6V9Y4Df6L7GDsbns99WrLrfmBNwocEdQm5CXFGExAgq", + "addresses": [ + "bitcoincash:qp3d5t96fdvjkurpt2h0t8nw5u4hr294q5q4dw6g9r", + "bitcoincash:qpusevag7l73543hs2lw69aqx4kzlsxamyg785mnsa", + "bitcoincash:qp48wzx62956wqux569mhm7dzyykya35aghd0y6qxf", + "bitcoincash:qqxd6u4lap7s9zcm8jdkd44uakyghcw8hqawurftc6", + "bitcoincash:qrn4gf9ufqp3ze4refp6yqh60s47crcwksqp20txs7" + ] + } + ], + "testnetXPub": [ + { + "xpub": "tpubDCrnMSKwDMAbxg82yqDt97peMvftCXk3EfBb9WgZh27mPbHGkysU3TW7qX5AwydmnVQfaGeNhUR6okQ3dS5AJTP9gEP7jk2Wcj6Xntc6gNh", + "addresses": [ + "bchtest:qrth8470sc9scek9u0jj2d0349t62gxzdstw2jukl8", + "bchtest:qpm56zc5re0nhms96r7p985aajthp0vxvg6e4ux3kc", + "bchtest:qqtu3tf6yyd73ejhk3a2ylqynpl3mzzhwuzt299jfd", + "bchtest:qzd7dvlnfukggjqsf5ju0qqwwltakfumjsck33js6m", + "bchtest:qq322ataqeas4n0pdn4gz2sdereh5ae43ylk4qdvus" + ] + }, + { + "xpub": "tpubDCrnMSKwDMAbzuN7eQDcFh9c6BsUvHHiL7j1AE9f9mE2ertgK6DwAZ6xmqtM3G5ifPkVynnjhMMMS87R1x4DTPrCbp4VjBttqMc4KmQEMRv", + "addresses": [ + "bchtest:qq2lnfskh8herq7hhj067uzfmhg2cf6arvc8eld50k", + "bchtest:qrjqt3vw8us9nc4d3h8j5fn6cxa0h5485ydd5zhq8y", + "bchtest:qze485w97jjhpucgk9dmh7h4fx56z8tmvgv4c3esmm", + "bchtest:qp6dvdwfu02z6d8cp6uqkpnjde66m89t9cexm00fmd", + "bchtest:qz266xueu99z6mh8af66gwnsv48jxva6aslmju2wat" + ] + }, + { + "xpub": "tpubDCrnMSKwDMAc4zKRDfxDqmzzxwLd85MFYF3cHzmdGDchnS7Sz2UiHD6HqaJR5Lefq6BhApPvGsdp3smjE1KRNkWHZ4wWogt7UYrgVznGfs1", + "addresses": [ + "bchtest:qqzkac72c0dygl92ru74zmwkkhdq7sc845n43ulmlw", + "bchtest:qzsm0ymr035a5eg0r8k4hxvyaxqyd0e8msd7f79v2z", + "bchtest:qqg6yea05gkqyxwdce6k5sjun8k9drgzn54d63cq5n", + "bchtest:qrfumvznl5ck903vq3sp3gfsrjt22tqa85lxzjv4p9", + "bchtest:qrd88na4869phkr0h44kzfakwc52w2jwmqjl6vlynn" + ] + } + ], + "p2shMainnet": ["bitcoincash:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqncnufkrl"] +} diff --git a/test/unit/fixtures/bitcoincash.json b/test/unit/fixtures/bitcoincash.json new file mode 100644 index 0000000..d53c6f3 --- /dev/null +++ b/test/unit/fixtures/bitcoincash.json @@ -0,0 +1,456 @@ +{ + "conversion": { + "toBCH": { + "satoshis": [ + [100000000, 1], + [123456789012345, 1234567.89012345], + [314159265359, 3141.59265359], + [9876765, 0.09876765], + [507, 0.00000507] + ], + "strings": [ + ["100000000", 1], + ["123456789012345", "1234567.89012345"], + ["314159265359", "3141.59265359"], + ["9876765", "0.09876765"], + ["507", "0.00000507"] + ], + "not": [ + [100000000, 10000000000000000], + [12345, 1234500000000], + [314159, 31415900000000], + [9876765, 987676500000000], + [507, 50700000000] + ], + "rounding": [ + [1, 0.00000001], + [42, 0.00000042], + [507, 0.00000507], + [1234, 0.00001234], + [65432, 0.00065432] + ] + }, + "toSatoshi": { + "bch": [ + [1, 100000000], + [1234567.89012345, 123456789012345], + [3141.59265359, 314159265359], + [0.09876765, 9876765], + [0.00000507, 507] + ], + "strings": [ + ["1", "100000000"], + ["1234567.89012345", "123456789012345"], + ["3141.59265359", "314159265359"], + ["0.09876765", "9876765"], + ["0.00000507", "507"] + ], + "not": [ + [10000000000000000, 100000000], + [1234500000000, 12345], + [31415900000000, 314159], + [987676500000000, 9876765], + [50700000000, 507] + ], + "rounding": [ + [0.00000001, 1], + [0.00000042, 42], + [0.00000507, 507], + [0.00001234, 1234], + [0.00065432, 65432] + ] + }, + "satsToBits": { + "bch": [ + [42.423234, 42423234], + [1, 1000000], + [3.14, 3140000], + [9876, 9876000000], + [0.000123, 123] + ], + "strings": [ + ["42.423234", "42423234"], + ["1", "1000000"], + ["3.14", "3140000"], + ["9876", "9876000000"], + ["0.000123", "123"] + ] + }, + "satsFromBits": { + "bch": [ + [4242324, 42423.2340], + [100000, 1000.0000], + [314000, 3140.0000], + [987600000, 9876000.0000], + [12.3, 0.1230] + ], + "strings": [ + ["4242324", "42423.2340"], + ["100000", "1000.0000"], + ["314000", "3140.0000"], + ["987600000", "9876000.0000"], + ["12.3", "0.1230"] + ] + } + }, + "signatures": { + "sign": [ + { + "network": "mainnet", + "privateKeyWIF": "Ky54Y7sxkGovqTi5ogipFUToqsz4NCepLn3bitzHyiF3JaYULMbP", + "message": "#BCHForEveryone", + "signature": "IMx84IyWWbygyT5MnN1RWykh9B4lHMxght69emBCqmGfSGaYozaXeVZNzmKVGMxjSqqGZZ0aGPf08GtahLAjWXk=" + }, + { + "network": "mainnet", + "privateKeyWIF": "KzkgRUDjAofdLvwHFGEyhtiULPYpKDozgfkz5yWR1S39isWHWPrz", + "message": "BCHForEveryone", + "signature": "H8X9FFIbyD4zAC/Hr57m5qTA2IDgbYTbdBpm/FEIhJVYMsG5xiuZxPrt2OA0zS7Uon1fwGLvLr0rdi+aD+JSl4w=" + }, + { + "network": "mainnet", + "privateKeyWIF": "Kyb6tWZUfppAyQMRX9b1yAKahFDn8Xr2dY26tk7nM9s5cLvvFuqS", + "message": "BCHForEveryone", + "signature": "IFRV4fhdbJdKIABCkKQWezJTjUqw1aEZZc0k+pX5mQseG5e7UyGKG6r6REU1hSwahA0NHfOeNP2dZGzo9uYv+VU=" + }, + { + "network": "mainnet", + "privateKeyWIF": "KzoVcYVG1dqVPa8AASR9tjXQHqs8KDx9Ex3WrwaEYPPxJ76gTZC2", + "message": "BCHForEveryone", + "signature": "IAaBJA09iOlyfQkCsWSlFC82YdE3leIKOCLfiJA0FTaDM30xvQrVb38L5sF6eD2zUUPpc0wMbxeqC7Rj8lRroHk=" + }, + { + "network": "mainnet", + "privateKeyWIF": "KwEYU8cuczmym3KXUcVqmQS2R4zcKgzQ8ba2R54LHuSsY2wKXXkF", + "message": "BCHForEveryone", + "signature": "H+enm4Bb938z433Q7TBeFIMGNXrV8B1ui4SuLgkKqdYRDQgdaT1OHt5sk54CrjTQHGYOhpQW7DrRJLsIJffjJqs=" + }, + { + "network": "testnet", + "privateKeyWIF": "cN4Uy1GrEG2nLEiwB4pcGe7DuVDhX3aRoc6EMbLTYUGiunEgY5aZ", + "message": "#BCHForEveryone", + "signature": "H58Em7Eehv1pEIBHf3qmQTrGHbk7RaShpUIWZcYyjq4FHiKaVrzk4ZB2pThQe/+JmzM9tG/om9R90/LyN2YCVV8=" + }, + { + "network": "testnet", + "privateKeyWIF": "cVsp354pynjPN7Z6aeiHHkwEiwb6YpxymmEAYGGKxDumHxF6JHuQ", + "message": "#BCHForEveryone", + "signature": "IIFdTtIjnRzxQ07Yx8PKRjCNvDKjgiyujDzWUkFiSMV7Z8EM84DYIvFYslL00N4QDec6YFoSSmHAYi9IbJ8MRoQ=" + }, + { + "network": "testnet", + "privateKeyWIF": "cSbWcRrNdTwSafDci88gVMPrrHpTV7ew3aqnTEDrKd8EsU6cg3zL", + "message": "#BCHForEveryone", + "signature": "INB4Mv4YGwFZZl72ZW4Z+iNWcWgfl4mVhkrA9do+47MZJ+x5NcOylfzK++8BkzaQA08DeTsGw3zpOzbwJ/B+uZE=" + }, + { + "network": "testnet", + "privateKeyWIF": "cTUU8gWrGvxArHUzRjibHfCDGpbhfUsYQxbnS5hurtQPUDfFuouo", + "message": "#BCHForEveryone", + "signature": "IIoQBpSKymzH2Tg75bPXcZSxPyHPJU3Q/AcxwURmKWrDdWhSsKlv0dVXL0js28JD505Js8r/mkbe6MPV5PdVbCk=" + }, + { + "network": "testnet", + "privateKeyWIF": "cS8T5umEVPMFpjvMViqPqnTBN5Zqs1HNCxKpVPoNbFCGaUxRzMz4", + "message": "#BCHForEveryone", + "signature": "H1gOF88XX/tfH7pa50CfRXoD6h8pzi+Ceg3SG9E4UsJnat90Nw4nxbLJBTMDNTHbTfCD1Upe1IlbYLZEaVsIXrY=" + } + ], + "verify": [ + { + "network": "mainnet", + "address": "bitcoincash:qzq53zlu48a00h0a8ay8gq3wnkvfxc50cv6fj5qm9d", + "message": "#BCHForEveryone", + "signature": "IMx84IyWWbygyT5MnN1RWykh9B4lHMxght69emBCqmGfSGaYozaXeVZNzmKVGMxjSqqGZZ0aGPf08GtahLAjWXk=" + }, + { + "network": "mainnet", + "address": "bitcoincash:qrpuxwzerdfm5plxzxekya3js9wn2f4wa5lcgnat7c", + "message": "BCHForEveryone", + "signature": "H8X9FFIbyD4zAC/Hr57m5qTA2IDgbYTbdBpm/FEIhJVYMsG5xiuZxPrt2OA0zS7Uon1fwGLvLr0rdi+aD+JSl4w=" + }, + { + "network": "mainnet", + "address": "bitcoincash:qpq7zz6ax7lun9ks92g5qwye206xkenc655y4x7rfn", + "message": "BCHForEveryone", + "signature": "IFRV4fhdbJdKIABCkKQWezJTjUqw1aEZZc0k+pX5mQseG5e7UyGKG6r6REU1hSwahA0NHfOeNP2dZGzo9uYv+VU=" + }, + { + "network": "mainnet", + "address": "bitcoincash:qp22q7vt79qp724zvk0qkj59ec4h9ghx9yscnl3mpw", + "message": "BCHForEveryone", + "signature": "IAaBJA09iOlyfQkCsWSlFC82YdE3leIKOCLfiJA0FTaDM30xvQrVb38L5sF6eD2zUUPpc0wMbxeqC7Rj8lRroHk=" + }, + { + "network": "mainnet", + "address": "bitcoincash:qzul5ttllyky9tcdmc458tw0fymtc2mkvc75542prl", + "message": "BCHForEveryone", + "signature": "H+enm4Bb938z433Q7TBeFIMGNXrV8B1ui4SuLgkKqdYRDQgdaT1OHt5sk54CrjTQHGYOhpQW7DrRJLsIJffjJqs=" + }, + { + "network": "testnet", + "address": "bchtest:qrhxjj4zxr342rpetm5hgmlxgrkkf9yhxspquuzpvf", + "message": "#BCHForEveryone", + "signature": "H58Em7Eehv1pEIBHf3qmQTrGHbk7RaShpUIWZcYyjq4FHiKaVrzk4ZB2pThQe/+JmzM9tG/om9R90/LyN2YCVV8=" + }, + { + "network": "testnet", + "address": "bchtest:qr8qhm92wf070lsd9xegmhnx8lt74tx0svc9yg3g0m", + "message": "#BCHForEveryone", + "signature": "IIFdTtIjnRzxQ07Yx8PKRjCNvDKjgiyujDzWUkFiSMV7Z8EM84DYIvFYslL00N4QDec6YFoSSmHAYi9IbJ8MRoQ=" + }, + { + "network": "testnet", + "address": "bchtest:qpgke00rhhl2w4a9jxzrdalaw0vz383j7uah86pv7f", + "message": "#BCHForEveryone", + "signature": "INB4Mv4YGwFZZl72ZW4Z+iNWcWgfl4mVhkrA9do+47MZJ+x5NcOylfzK++8BkzaQA08DeTsGw3zpOzbwJ/B+uZE=" + }, + { + "network": "testnet", + "address": "bchtest:qpnvzdzxshnvz8k28jrql4qr8qg6mc6mzc498c68c0", + "message": "#BCHForEveryone", + "signature": "IIoQBpSKymzH2Tg75bPXcZSxPyHPJU3Q/AcxwURmKWrDdWhSsKlv0dVXL0js28JD505Js8r/mkbe6MPV5PdVbCk=" + }, + { + "network": "testnet", + "address": "bchtest:qqs5j90clt5v2rw824xnlta4q2exv76uvsfrrmar7z", + "message": "#BCHForEveryone", + "signature": "H1gOF88XX/tfH7pa50CfRXoD6h8pzi+Ceg3SG9E4UsJnat90Nw4nxbLJBTMDNTHbTfCD1Upe1IlbYLZEaVsIXrY=" + } + ] + }, + "encodeBase58Check": [ + { + "base58Check": "1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar", + "hex": "0079bd35d306f648350818470c9f18903df6e06902a026f2a7" + }, + { + "base58Check": "1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK", + "hex": "006da742680accf2282df5fade8e9b7a01a517e779289b52cc" + }, + { + "base58Check": "1K6ncAmMEyQrKUYosZRD9swyZNXECu2aKs", + "hex": "00c68a6a07ccdaf1669cfd8d244d80ff36b713551c6208f672" + }, + { + "base58Check": "1L2FG9hH3bwchhxHaCs5cg1QNbhmbaeAs6", + "hex": "00d0a6b5e3dd43d0fb895b3b3df565bb8266c5ab00a25dbeb5" + }, + { + "base58Check": "1Ly4gqPddveYHMNkfjoXHanVszXpD3duKg", + "hex": "00db04c2e6f104997cb04c956bf25da6078e559d303127f08b" + } + ], + "bip21": { + "invalid": [ + { + "address": "bitcoincash:qpk6wsngptx0y2pd7hadar5m0gq629l80y2tcp5ktd", + "url": "satoshi:qpk6wsngptx0y2pd7hadar5m0gq629l80y2tcp5ktd" + } + ], + "valid": [ + { + "address": "1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar", + "options": { + "amount": 12.5, + "label": "coinbase donation", + "message": "and ya don't stop" + }, + "url": "bitcoincash:qpum6dwnqmmysdggrprse8ccjq7ldcrfqgmmtgcmny?amount=12.5&label=coinbase%20donation&message=and%20ya%20don%27t%20stop" + }, + { + "address": "1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK", + "options": { + "label": "Foobar" + }, + "url": "bitcoincash:qpk6wsngptx0y2pd7hadar5m0gq629l80y2tcp5ktd?label=Foobar" + }, + { + "address": "bitcoincash:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s", + "options": { + "amount": 1, + "label": "test" + }, + "url": "bitcoincash:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqrt2qkz5s?amount=1&label=test" + }, + { + "address": "bitcoincash:qzc5fxdm6lnwzgeju4jaemngtxkkkgt78ucn6vkjz5", + "options": { + "amount": 3, + "label": "hhhhhhh" + }, + "url": "bitcoincash:qzc5fxdm6lnwzgeju4jaemngtxkkkgt78ucn6vkjz5?amount=3&label=hhhhhhh" + }, + { + "address": "qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03", + "options": { + "amount": 23, + "label": "no prefix" + }, + "url": "bitcoincash:qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qw0e54u03?amount=23&label=no%20prefix" + }, + { + "address": "qphgz6ut3uu9xu8sly2zj7n5jkg5eyes5vjv3juf0x", + "options": { + "amount": 20.3, + "label": "Foobar" + }, + "url": "bitcoincash:qphgz6ut3uu9xu8sly2zj7n5jkg5eyes5vjv3juf0x?amount=20.3&label=Foobar" + } + ], + "valid_regtest": [ + { + "address": "1C6hRmfzvWst5WA7bFRCVAqHt5gE2g7Qar", + "options": { + "amount": 12.5, + "label": "coinbase donation", + "message": "and ya don't stop" + }, + "url": "bchreg:qpum6dwnqmmysdggrprse8ccjq7ldcrfqg94ewelh7?amount=12.5&label=coinbase%20donation&message=and%20ya%20don%27t%20stop" + }, + { + "address": "1Azo2JBz2JswboeY9xSMcp14BAfhjnD9SK", + "options": { + "label": "Foobar" + }, + "url": "bchreg:qpk6wsngptx0y2pd7hadar5m0gq629l80y59284j0h?label=Foobar" + }, + { + "address": "bchreg:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqa9cxhxs2", + "options": { + "amount": 1, + "label": "test" + }, + "url": "bchreg:qrdsfshx7yzfjl9sfj2khuja5crcu4vaxqa9cxhxs2?amount=1&label=test" + }, + { + "address": "bchreg:qzc5fxdm6lnwzgeju4jaemngtxkkkgt78uxag2hkxw", + "options": { + "amount": 3, + "label": "hhhhhhh" + }, + "url": "bchreg:qzc5fxdm6lnwzgeju4jaemngtxkkkgt78uxag2hkxw?amount=3&label=hhhhhhh" + }, + { + "address": "qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qsptj5ctt", + "options": { + "amount": 23, + "label": "no prefix" + }, + "url": "bchreg:qzw6tfrh8p0jh834uf9rhg77pjg5rgnt3qsptj5ctt?amount=23&label=no%20prefix" + }, + { + "address": "qphgz6ut3uu9xu8sly2zj7n5jkg5eyes5vvzr5adtu", + "options": { + "amount": 20.3, + "label": "Foobar" + }, + "url": "bchreg:qphgz6ut3uu9xu8sly2zj7n5jkg5eyes5vvzr5adtu?amount=20.3&label=Foobar" + } + ] + }, + "getByteCount": [ + { + "byteCount": 190, + "inputs": { + "P2PKH":1 + }, + "outputs": { + "P2SH":1 + } + }, + { + "byteCount": 2750, + "inputs": { + "MULTISIG-P2SH:2-4":4, + "P2PKH":10 + }, + "outputs": { + "P2PKH":23 + } + }, + { + "byteCount": 565, + "inputs": { + "MULTISIG-P2SH:3-5":2 + }, + "outputs": { + "P2PKH":2 + } + }, + { + "byteCount": 16506, + "inputs": { + "P2PKH":111 + }, + "outputs": { + "P2PKH":2 + } + }, + { + "byteCount": 1780, + "inputs": { + "P2PKH":10, + "MULTISIG-P2SH:1-2":1 + }, + "outputs": { + "P2PKH":2, + "P2SH":1 + } + } + ], + "bip38": { + "encrypt": { + "mainnet": [ + { + "wif": "L1XHKhaBAfkr2FJQn3pTfCMxz652WYfmvKj8xDCHCEDV9tWGcbYj", + "password": "1EBPIyj55eR8bVUov9", + "encryptedKey": "6PYWWnBNfNpSqEJZKcfwbrYgTTdb9PNiGjQJ8r9V6cvsZNKLfcZD8YefQc" + }, + { + "wif": "L1phBREbhL4vb1uHHHCAse8bdGE5c7ic2PFjRxMawLzQCsiFVbvu", + "password": "9GKVkabAHBMyAf", + "encryptedKey": "6PYU2fDHRVF2194gKDGkbFbeu4mFgkWtVvg2RPd2Sp6KmZx3RCHFpgBB2G" + } + ], + "testnet": [ + { + "wif": "cSx7KzdH9EcvDEireu2WYpGnXdFYpta7sJUNt5kVCJgA7kcAU8Gm", + "password": "1EBPIyj55eR8bVUov9", + "encryptedKey": "6PYUAPLwLSEjWSAfoe9NTSPkMZXnJA8j8EFJtKaeSnP18RCouutBrS2735" + }, + { + "wif": "cRgunCa2z1gCN6nNapTwKLdo58FdgTeAiJSaXx6RZeWSabQHkQKG", + "password": "9GKVkabAHBMyAf", + "encryptedKey": "6PYTNQJ1dYhLg6X1Xqm62ceuyfxCUYvV4LfhFfzzBaTuV4cFhgS5Xe8t1Y" + } + ] + }, + "decrypt": { + "mainnet": [ + { + "wif": "L1XHKhaBAfkr2FJQn3pTfCMxz652WYfmvKj8xDCHCEDV9tWGcbYj", + "password": "1EBPIyj55eR8bVUov9", + "encryptedKey": "6PYWWnBNfNpSqEJZKcfwbrYgTTdb9PNiGjQJ8r9V6cvsZNKLfcZD8YefQc" + }, + { + "wif": "L1phBREbhL4vb1uHHHCAse8bdGE5c7ic2PFjRxMawLzQCsiFVbvu", + "password": "9GKVkabAHBMyAf", + "encryptedKey": "6PYU2fDHRVF2194gKDGkbFbeu4mFgkWtVvg2RPd2Sp6KmZx3RCHFpgBB2G" + } + ], + "testnet": [ + { + "wif": "cSx7KzdH9EcvDEireu2WYpGnXdFYpta7sJUNt5kVCJgA7kcAU8Gm", + "password": "1EBPIyj55eR8bVUov9", + "encryptedKey": "6PYUAPLwLSEjWSAfoe9NTSPkMZXnJA8j8EFJtKaeSnP18RCouutBrS2735" + }, + { + "wif": "cRgunCa2z1gCN6nNapTwKLdo58FdgTeAiJSaXx6RZeWSabQHkQKG", + "password": "9GKVkabAHBMyAf", + "encryptedKey": "6PYTNQJ1dYhLg6X1Xqm62ceuyfxCUYvV4LfhFfzzBaTuV4cFhgS5Xe8t1Y" + } + ] + } + } +} diff --git a/test/unit/fixtures/bitcore-mock.js b/test/unit/fixtures/bitcore-mock.js new file mode 100644 index 0000000..33d9cb8 --- /dev/null +++ b/test/unit/fixtures/bitcore-mock.js @@ -0,0 +1,45 @@ +/* + Unit test mocks for bitcore endpoints. +*/ + +const balance = { confirmed: 230000, unconfirmed: 0, balance: 230000 } + +const utxo = [ + { + _id: "5cecdd39a9f1235e2a3d409a", + chain: "BCH", + network: "mainnet", + coinbase: false, + mintIndex: 0, + spentTxid: "", + mintTxid: + "27ec8512c1a9ee9e9ae9b98eb60375f1d2bd60e2e76a1eff5a45afdbc517cf9c", + mintHeight: 560430, + spentHeight: -2, + address: "qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + script: "76a914db6ea94fa26b7272dc5e1487c35f258391e0f38788ac", + value: 100000, + confirmations: -1 + }, + { + _id: "5cecdd1ca9f1235e2a3b6349", + chain: "BCH", + network: "mainnet", + coinbase: false, + mintIndex: 0, + spentTxid: "", + mintTxid: + "6e1ae1bf7db6de799ec1c05ab2816ac65549bd80141567af088e6f291385b07d", + mintHeight: 560039, + spentHeight: -2, + address: "qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + script: "76a914db6ea94fa26b7272dc5e1487c35f258391e0f38788ac", + value: 130000, + confirmations: -1 + } +] + +module.exports = { + balance, + utxo +} diff --git a/test/unit/fixtures/block-mock.js b/test/unit/fixtures/block-mock.js new file mode 100644 index 0000000..844140f --- /dev/null +++ b/test/unit/fixtures/block-mock.js @@ -0,0 +1,29 @@ +/* + Mock data used for unit testing. +*/ + +module.exports = { + details: { + hash: "000000001c6aeec19265e9cc3ded8ba5ef5e63fae7747f30bf9c02c7bc8883f0", + size: 216, + height: 507, + version: 1, + merkleroot: + "a85fa3d831ab6b0305e7ff88d2d4941e25a810d4461635df51490653822071a8", + tx: ["a85fa3d831ab6b0305e7ff88d2d4941e25a810d4461635df51490653822071a8"], + time: 1231973656, + nonce: 330467862, + bits: "1d00ffff", + difficulty: 1, + chainwork: + "000000000000000000000000000000000000000000000000000001fc01fc01fc", + confirmations: 585104, + previousblockhash: + "00000000a99525c043fd7e323414b60add43c254c44860094048f9c01e9a5fdd", + nextblockhash: + "000000000d550f4161f2702165fdd782ec72ff9c541f864ebb8256b662b7e51a", + reward: 50, + isMainChain: true, + poolInfo: {} + } +} diff --git a/test/unit/fixtures/blockbook-mock.js b/test/unit/fixtures/blockbook-mock.js new file mode 100644 index 0000000..5048a31 --- /dev/null +++ b/test/unit/fixtures/blockbook-mock.js @@ -0,0 +1,64 @@ +/* + Unit test mocks for Blockbook endpoints. +*/ + +const balance = { + page: 1, + totalPages: 1, + itemsOnPage: 1000, + address: "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf", + balance: "230000", + totalReceived: "6194528752", + totalSent: "6194298752", + unconfirmedBalance: "0", + unconfirmedTxs: 0, + txs: 3, + txids: [ + "27ec8512c1a9ee9e9ae9b98eb60375f1d2bd60e2e76a1eff5a45afdbc517cf9c", + "6e1ae1bf7db6de799ec1c05ab2816ac65549bd80141567af088e6f291385b07d", + "5d95f4e6047901fc1502a9758bbdb14ce73f24662b1784d13e0217b3d37bb7a2" + ] +} + +const utxo = [ + { + txid: "27ec8512c1a9ee9e9ae9b98eb60375f1d2bd60e2e76a1eff5a45afdbc517cf9c", + vout: 0, + value: "100000", + height: 560430, + confirmations: 29321 + }, + { + txid: "6e1ae1bf7db6de799ec1c05ab2816ac65549bd80141567af088e6f291385b07d", + vout: 0, + value: "130000", + height: 560039, + confirmations: 29712 + } +] + +const utxos = [ + [ + { + txid: "27ec8512c1a9ee9e9ae9b98eb60375f1d2bd60e2e76a1eff5a45afdbc517cf9c", + vout: 0, + value: "100000", + height: 560430, + confirmations: 29868 + }, + { + txid: "6e1ae1bf7db6de799ec1c05ab2816ac65549bd80141567af088e6f291385b07d", + vout: 0, + value: "130000", + height: 560039, + confirmations: 30259 + } + ], + [] +] + +module.exports = { + balance, + utxo, + utxos +} diff --git a/test/unit/fixtures/blockchain-mock.js b/test/unit/fixtures/blockchain-mock.js new file mode 100644 index 0000000..17b7c43 --- /dev/null +++ b/test/unit/fixtures/blockchain-mock.js @@ -0,0 +1,51 @@ +/* + Mock data used for unit testing. +*/ + +module.exports = { + bestBlockHash: + "0000000000000000008e1f65f875703872544aa888c7ca6587f055f8f5fbd4bf", + + blockHeader: { + hash: "000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201", + confirmations: 85727, + height: 500000, + version: 536870912, + versionHex: "20000000", + merkleroot: + "4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091", + time: 1509343584, + mediantime: 1509336533, + nonce: 3604508752, + bits: "1809b91a", + difficulty: 113081236211.4533, + chainwork: + "0000000000000000000000000000000000000000007ae48aca46e3b449ad9714", + previousblockhash: + "0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523", + nextblockhash: + "00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3" + }, + + txOutProof: + "0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01", + + verifiedProof: + "03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7", + + txOutUnspent: { + bestblock: + "000000000000000000b441e02f5b1b9f5b3def961047afcc6f2f5636c952705e", + confirmations: 2, + value: 0.00006, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 d19fae66b685f5c3633c0db0600313918347225f OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914d19fae66b685f5c3633c0db0600313918347225f88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qrgeltnxk6zltsmr8sxmqcqrzwgcx3eztusrwgf0x3"] + }, + coinbase: false + } +} diff --git a/test/unit/fixtures/blockchain.json b/test/unit/fixtures/blockchain.json new file mode 100644 index 0000000..74d0813 --- /dev/null +++ b/test/unit/fixtures/blockchain.json @@ -0,0 +1,7 @@ +{ + "getBestBlockHash": [ + { "data": + { "result": "0000000000000000005f1f550d3d8b142b684277016ebd00fa29c668606ae52d"} + } + ] +} diff --git a/test/unit/fixtures/crypto.json b/test/unit/fixtures/crypto.json new file mode 100644 index 0000000..11316bb --- /dev/null +++ b/test/unit/fixtures/crypto.json @@ -0,0 +1,90 @@ +{ + "sha256": [ + { + "hash": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061", + "hex": "0101010101010101" + }, + { + "hash": "75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab", + "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" + }, + { + "hash": "978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de", + "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" + }, + { + "hash": "243e5ec9798d6ac435b30661528d8d83745543f517b8adac421a76fbe7f08105", + "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" + }, + { + "hash": "1304a815512047082dba9a9414ff0db643dbbf9eeb68527195f79cc26b021e10", + "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" + } + ], + "ripemd160": [ + { + "hash": "5825701b4b9767fd35063b286dca3582853e0630", + "hex": "0101010101010101" + }, + { + "hash": "8874ef888a9bcbd83b87d06ff7bc213c51497362", + "hex": "75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab" + }, + { + "hash": "5f956a88863051ea5215d8970ced8e218eb615cf", + "hex": "978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de" + }, + { + "hash": "1fc790f399d3064cdf917c6b22bb0ef534fe35c9", + "hex": "243e5ec9798d6ac435b30661528d8d83745543f517b8adac421a76fbe7f08105" + }, + { + "hash": "abd7091d8c4bd1e2c074f125870427f3dc0c0ce9", + "hex": "1304a815512047082dba9a9414ff0db643dbbf9eeb68527195f79cc26b021e10" + } + ], + "hash256": [ + { + "hash": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", + "hex": "0101010101010101" + }, + { + "hash": "7ad2a74bd59698714a2991a82b71736f3542b2828b6ac24de427c440da89d01a", + "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" + }, + { + "hash": "688f1d029ed54c34d0320b838bf6fc64f62f38a6e930a0af5bdb4e27d1a684cd", + "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" + }, + { + "hash": "46f3c4ddfa908cf8a3cda8ce3a676d98fec149d97db834a6e8f071790d839c52", + "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" + }, + { + "hash": "72cfe7b6b9402a4463dfc8bc1c08080578b1b82a2e3e632062e2cce2c9327c1f", + "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" + } + ], + "hash160": [ + { + "hash": "abaf1119f83e384210fe8e222eac76e2f0da39dc", + "hex": "0101010101010101" + }, + { + "hash": "8874ef888a9bcbd83b87d06ff7bc213c51497362", + "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" + }, + { + "hash": "5f956a88863051ea5215d8970ced8e218eb615cf", + "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" + }, + { + "hash": "1fc790f399d3064cdf917c6b22bb0ef534fe35c9", + "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" + }, + { + "hash": "abd7091d8c4bd1e2c074f125870427f3dc0c0ce9", + "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" + } + ] +} diff --git a/test/unit/fixtures/ecpair.json b/test/unit/fixtures/ecpair.json new file mode 100644 index 0000000..ec74811 --- /dev/null +++ b/test/unit/fixtures/ecpair.json @@ -0,0 +1,198 @@ +{ + "fromWIF": [ + { + "privateKeyWIF": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "legacy": "1P23VhvFLH18qP7YRCPDWPwA9TCJjmp9B5", + "cashAddr": "bitcoincash:qrcc23q0w7wgld690kduxvhetu0am523c5ufq5pes4", + "regtestAddr": "bchreg:qrcc23q0w7wgld690kduxvhetu0am523c5z8jjqa50" + }, + { + "privateKeyWIF": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "legacy": "14tqru6iL3KX2m4nKg36tfLFe13J1d1uwu", + "cashAddr": "bitcoincash:qq4tvcxq5z35pkfj65a2elyy8xpn0r55r59r5he7nz", + "regtestAddr": "bchreg:qq4tvcxq5z35pkfj65a2elyy8xpn0r55r5mdx3c6hc" + }, + { + "privateKeyWIF": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe", + "legacy": "135cNoFyxcwJ88BQsJd7c6qKfSBgRK7cCT", + "cashAddr": "bitcoincash:qqtv7uskar0nd8hyflesg7wq4uuqwdmtyg4hsx9kge", + "regtestAddr": "bchreg:qqtv7uskar0nd8hyflesg7wq4uuqwdmtygtezqyjvr" + }, + { + "privateKeyWIF": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW", + "legacy": "1NbRysVu1cy8A8mjrLh4z649HqG6DrX9Z", + "cashAddr": "bitcoincash:qqzp270mhkglttfaysmt985aykwjhndw0ulmgr5g5d", + "regtestAddr": "bchreg:qqzp270mhkglttfaysmt985aykwjhndw0up4694vsh" + }, + { + "privateKeyWIF": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "legacy": "1FoZGWj8rgrimR7W383LNSLUfdPCjKAXLK", + "cashAddr": "bitcoincash:qz39l492msv9zz838t8ltx2w4zmycnqhwck4fnnyym", + "regtestAddr": "bchreg:qz39l492msv9zz838t8ltx2w4zmycnqhwcgmm4jqqp" + } + ], + "toWIF": [ + { + "privateKeyWIF": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz" + }, + { + "privateKeyWIF": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP" + }, + { + "privateKeyWIF": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe" + }, + { + "privateKeyWIF": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW" + }, + { + "privateKeyWIF": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV" + } + ], + "fromPublicKey": [ + { + "pubkeyHex": "02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb", + "legacy": "1SeP7kmdWTwjFmWiRMpqtSkDpW39zrVLK", + "cashAddr": "bitcoincash:qqzdnxncgm3v247u5v0gqnglr2vpdhe0hu4wl0rmwt", + "regtestAddr": "bchreg:qqzdnxncgm3v247u5v0gqnglr2vpdhe0hutqdfzl23" + }, + { + "pubkeyHex": "02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b", + "legacy": "1GpugKfjEycPRhk8A8ALPh1zAJmTQGdJPp", + "cashAddr": "bitcoincash:qzkej6g2zr9c9k83chyqh5pllzv6pkw62ckf2m82ks", + "regtestAddr": "bchreg:qzkej6g2zr9c9k83chyqh5pllzv6pkw62cg8caxwj2" + }, + { + "pubkeyHex": "02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b", + "legacy": "1GpugKfjEycPRhk8A8ALPh1zAJmTQGdJPp", + "cashAddr": "bitcoincash:qzkej6g2zr9c9k83chyqh5pllzv6pkw62ckf2m82ks", + "regtestAddr": "bchreg:qzkej6g2zr9c9k83chyqh5pllzv6pkw62cg8caxwj2" + }, + { + "pubkeyHex": "033c071f19057f11140f80308031fa58b6e7d8f9e889252805cae6a29c1248f843", + "legacy": "15NQKRj7rAVsFL5rWMmhkAgx2K1dYoHoDW", + "cashAddr": "bitcoincash:qqh7evl4w7h6lsu7uvakvadm0z5hj535lqk098l670", + "regtestAddr": "bchreg:qqh7evl4w7h6lsu7uvakvadm0z5hj535lqgphp7764" + }, + { + "pubkeyHex": "024933d7cc962415fda8249d6ee6820009fb6f0cf592385d91f8d9f39de507b7e1", + "legacy": "14sq5dL1TeFdQJpj3vVMQvjrdmQoqVoM6m", + "cashAddr": "bitcoincash:qq4g2nmk64wt7a2mx85cl07n45nkh2p7lcytfgmqdl", + "regtestAddr": "bchreg:qq4g2nmk64wt7a2mx85cl07n45nkh2p7lc69mw6yf9" + } + ], + "toPublicKey": [ + { + "pubkeyHex": "02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + }, + { + "pubkeyHex": "02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b" + }, + { + "pubkeyHex": "02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b" + }, + { + "pubkeyHex": "033c071f19057f11140f80308031fa58b6e7d8f9e889252805cae6a29c1248f843" + }, + { + "pubkeyHex": "024933d7cc962415fda8249d6ee6820009fb6f0cf592385d91f8d9f39de507b7e1" + } + ], + "toLegacyAddress": [ + { + "privateKeyWIF": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "legacy": "1P23VhvFLH18qP7YRCPDWPwA9TCJjmp9B5" + }, + { + "privateKeyWIF": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "legacy": "14tqru6iL3KX2m4nKg36tfLFe13J1d1uwu" + }, + { + "privateKeyWIF": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe", + "legacy": "135cNoFyxcwJ88BQsJd7c6qKfSBgRK7cCT" + }, + { + "privateKeyWIF": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW", + "legacy": "1NbRysVu1cy8A8mjrLh4z649HqG6DrX9Z" + }, + { + "privateKeyWIF": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "legacy": "1FoZGWj8rgrimR7W383LNSLUfdPCjKAXLK" + } + ], + "toCashAddress": [ + { + "privateKeyWIF": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "cashAddr": "bitcoincash:qrcc23q0w7wgld690kduxvhetu0am523c5ufq5pes4", + "regtestAddr": "bchreg:qrcc23q0w7wgld690kduxvhetu0am523c5z8jjqa50" + }, + { + "privateKeyWIF": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "cashAddr": "bitcoincash:qq4tvcxq5z35pkfj65a2elyy8xpn0r55r59r5he7nz", + "regtestAddr": "bchreg:qq4tvcxq5z35pkfj65a2elyy8xpn0r55r5mdx3c6hc" + }, + { + "privateKeyWIF": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe", + "cashAddr": "bitcoincash:qqtv7uskar0nd8hyflesg7wq4uuqwdmtyg4hsx9kge", + "regtestAddr": "bchreg:qqtv7uskar0nd8hyflesg7wq4uuqwdmtygtezqyjvr" + }, + { + "privateKeyWIF": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW", + "cashAddr": "bitcoincash:qqzp270mhkglttfaysmt985aykwjhndw0ulmgr5g5d", + "regtestAddr": "bchreg:qqzp270mhkglttfaysmt985aykwjhndw0up4694vsh" + }, + { + "privateKeyWIF": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "cashAddr": "bitcoincash:qz39l492msv9zz838t8ltx2w4zmycnqhwck4fnnyym", + "regtestAddr": "bchreg:qz39l492msv9zz838t8ltx2w4zmycnqhwcgmm4jqqp" + } + ], + "sign": [ + { + "privateKeyWIF": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "data": "EARTH" + }, + { + "privateKeyWIF": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "data": "foobar" + }, + { + "privateKeyWIF": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe", + "data": "12334567890" + }, + { + "privateKeyWIF": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW", + "data": "Be excellent to each other" + }, + { + "privateKeyWIF": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "data": "satoshi" + } + ], + "verify": [ + { + "privateKeyWIF1": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "privateKeyWIF2": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "data": "EARTH" + }, + { + "privateKeyWIF1": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "privateKeyWIF2": "L5PKiCuwwgmVN1GQNg8CdR6Rmg7tg1Npw7GyL5ULRPZ5EVPhtSrz", + "data": "foobar" + }, + { + "privateKeyWIF1": "L57L3Xtu3j1Z2B2RuQzc59JLQXJQ86TH8To1hM348URbC2iCZqLe", + "privateKeyWIF2": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "data": "12334567890" + }, + { + "privateKeyWIF1": "KzTm29RBrSgUbMyeJL9sCNUnc7LQEuDeT6Upq1iUwx78qWRdRzyW", + "privateKeyWIF2": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "data": "Be excellent to each other" + }, + { + "privateKeyWIF1": "L4BwXDmjzEyzKHbAfGruhieUDPs8KTx7DMgqPk4aF9GefzgqPENV", + "privateKeyWIF2": "L1SD3NYLMcoqpUzn2hwJ7c1Vb4pPiqMToSMyGMTcbcvRBDh9U9SP", + "data": "satoshi" + } + ] +} diff --git a/test/unit/fixtures/electrumx-mock.js b/test/unit/fixtures/electrumx-mock.js new file mode 100644 index 0000000..8e951c7 --- /dev/null +++ b/test/unit/fixtures/electrumx-mock.js @@ -0,0 +1,129 @@ +/* + Mocking data for unit tests for the ElectrumX library. +*/ + +const utxo = { + success: true, + utxos: [ + { + height: 602405, + tx_hash: + "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + tx_pos: 0, + value: 1000 + } + ] +} + +const utxos = { + success: true, + utxos: [ + { + utxos: [ + { + height: 604392, + tx_hash: + "7774e449c5a3065144cefbc4c0c21e6b69c987f095856778ef9f45ddd8ae1a41", + tx_pos: 0, + value: 1000 + }, + { + height: 630834, + tx_hash: + "4fe60a51e0d8f5134bfd8e5f872d6e502d7f01b28a6afebb27f4438a4f638d53", + tx_pos: 0, + value: 6000 + } + ], + address: "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + }, + { + utxos: [], + address: "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + } + ] +} + +const balance = { + success: true, + balance: { + confirmed: 1000, + unconfirmed: 0 + } +} + +const balances = { + success: true, + balances: [ + { + balance: { + confirmed: 7000, + unconfirmed: 0 + }, + address: "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + }, + { + balance: { + confirmed: 0, + unconfirmed: 0 + }, + address: "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + } + ] +} + +const transaction = { + success: true, + transactions: [ + { + height: 560430, + tx_hash: + "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + }, + { + height: 560534, + tx_hash: + "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + } + ] +} + +const transactions = { + success: true, + transactions: [ + { + transactions: [ + { + height: 631219, + tx_hash: + "ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f" + } + ], + address: "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" + }, + { + transactions: [ + { + height: 560430, + tx_hash: + "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851" + }, + { + height: 560534, + tx_hash: + "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280" + } + ], + address: "bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v" + } + ] +} + +module.exports = { + utxo, + utxos, + balance, + balances, + transaction, + transactions +} diff --git a/test/unit/fixtures/encryption-mock.js b/test/unit/fixtures/encryption-mock.js new file mode 100644 index 0000000..d4d19da --- /dev/null +++ b/test/unit/fixtures/encryption-mock.js @@ -0,0 +1,19 @@ +/* + Mock data used for unit tests against the Encryption library. +*/ + +const successMock = { + success: true, + publicKey: + "03fcc37586d93af1e146238217989924e0ab1f011c34e1a23aec529354d5b28eb4" +} + +const failureMock = { + success: false, + publicKey: "not found" +} + +module.exports = { + successMock, + failureMock +} diff --git a/test/unit/fixtures/hdnode.json b/test/unit/fixtures/hdnode.json new file mode 100644 index 0000000..b6f05bc --- /dev/null +++ b/test/unit/fixtures/hdnode.json @@ -0,0 +1,529 @@ +{ + "toXPub": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub661MyMwAqRbcGedwRJEC3DjCdtR3aQChzBzrGuLs1ou9ynjKoMBivmYEmQP3Acfg1azWxBmzSmQi7rLPMFbqtus8LZQngeVkSSqU63UabpZ" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub661MyMwAqRbcEchwVMh1yv3z9qDniozsh9niTRpwh12zG1FvGxTjRbMXMLYx2AszH2eQDSxNnq1TUzcGZFWPZt9qLRracrs3Hc2vZNmJHyj" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub661MyMwAqRbcGSmyx3bRW9NhjR3F1Agh9c1d4Hbu8SfdoiC23LXoA2n6T6sZQZybp8ZyjpxN9we9LXwKraVARdwsVMuL8BYFYC3TP3FX2Jx" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub661MyMwAqRbcGkg9gnqwWnxq5KsvMkFsGrjoSYUZKmS19rwiUXGYzZ8wRYXXwxrTpNFN9tiqrB6nDEFEGmaQfz9jfBqio3YsBW8asrh5d2r" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub661MyMwAqRbcFVDUv6gAfL63Z55XDyT1mNePiUKktmVcL426ZHWz5GRssSZ5PHCrKQgPKui7rRGiiNMSfvt3vJ6V4NqbSsNqgoEeimsyHrJ" + } + ], + "toXPriv": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpriv": "xprv9s21ZrQH143K4AZUKGhBg5nU5raZAwUrcy5FUWwFTUNB6zQBFosUNyDkv8EmFd1V4ASGt7d8sJ8sBem1DB8asw38K4cccN6azVCssnyfb4t" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpriv": "xprv9s21ZrQH143K28dUPLA1cn7FboPJKMH2Kvs7f3RL8fW1PCvmjR9Uso33W5M1PvysLBcnhhj9DvW5CUviWa6iswxv9QBYnmcsNKjonHwEZpj" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpriv": "xprv9s21ZrQH143K3xhWr24R91RyBPCkbhxqnP62FuCHa78evursVoDYcETcbprrDaiaoxhqieMCzM48THvTGM3b9hTb4FKHUoJrrMsX1cWw2G1" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpriv": "xprv9s21ZrQH143K4GbgamJw9f26XJ3RxHY1udpCeA4wmRu2H4cZvyxJSkpTaHReJUj5sjRWAfm8WkNChP29GdmkZcwjvWxVrWTe7vmXsx3XZHv" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpriv": "xprv9s21ZrQH143K3191p59AJC9K13F2pWjAQ9inv5v9LRxdTFgx1kCjXU7Q2ACdAm7P432XP8MjAoTHW9dDxjbFpfRqv7cu3hkn9u5ZtBQs3Vi" + } + ], + "toKeyPair": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe" + } + ], + "toPublicKey": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe" + } + ], + "derive": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub68gU3eZPcTu8CCNt5rky8yvMxD4VAmUujkprpEAQj7MtYX7JziPNfGjvmtnmdk6HKD2XUdSTHq3cTk1QafQsgUcUsHcPdCJ8pud8dvDo2Vb", + "xpriv": "xprv9uh7e92Vn6LpyiJQyqDxmqydQBDzmJm4NXuG1qkoAmpufinATB587URSvepj7Leu2Qg6EZwwrmYZqD64M3C7izCWaaGkzMPwknFgbmjLAfP" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub698V4vD7nYSXEeVd8a4txiscubcaQBUSB9ZjhRSdbzCXmD81UG6P3AidHwudaUatfuX5nsB7iwUnn6M4Q21pX1nkB5G4X1zb4dCxNm7DG54", + "xpriv": "xprv9v98fQgDxAtE2ARA2YXtbavtMZn5zikaove8u3323efYtQnrvin8VNQ9SeNJkJNxRhGGG3Eavc8edbGiiS6nr6MSdNCexgv8uYp5Ee6ctmH" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub69hicEP7JPwB9241jVA1ZGBhRfyhUXrxF25gpfN532wL8H6weGE7kwjLoyHvHFQPoeGk7MVsAq7129eFQBHhhc4Zpa64RvLpL8u6uMV6ywG", + "xpriv": "xprv9viNCirDU2NsvXyYdTd1C8Exse9D5596soA62GxTUhQMFUmo6iusD9QrxiLPcLF2TUrxPPZLife9ubNCxpa378FLrUbGb8wqN7vtN1zZ8Jg" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub69eRZ9j2Qu22pbQDzPNNcRJHZZu5wwMpJCsaHWbGUs28fjf9rCxN2UhcnrgbRpbaK9m1hj6qXnUHUb8C4pHFsyqhqhLMXdi2wRmUWXpJRpE", + "xpriv": "xprv9vf59eC8aXTjc7KktMqNFHMZ1Y4bYUdxvywyV8BevXV9nwL1Jfe7UgP8wYxkmGAL1qC9dRUS67HCMagx8pH566LkVFcen7DJDoCCtEbGB5g" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub69f9C5sYoihLrxJX4ixujbortnFkuXXW7xY5A5G5X6Ho4MhWzzKRjkgDbt1zRVeSD5tj91yFQ4p8qg6oCjr1HwFsMpSZBzUjm5nUo1eRwwA", + "xpriv": "xprv9vfnnaLeyM93eUE3xhRuNTs8LkRGW4oekjcUMgrTxkkpBZNNTT1BBxMjkd6yNH5trjDw3wnYoXUZLCUs6xhs9sUfgNi1ufBkLJoCFXWsL3R" + } + ], + "deriveHardened": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub68gU3eZXx8S6MyhU1WTL8TyeaNYQ8yqutgPernats3dwvpqDMYv6VJbEi5Eqa6cPYe58wTWJjzSbbTMmVVpJxmwAfLs6BQoAxS1YpggMCrV", + "xpriv": "xprv9uh7e92e7kso9VczuUvKmL2v2LhujX84XTU44QBHJi6y42W4p1bqwWGkrqdmPoJHAKNSXA399Unq9DRbJ5KkHLFUsSa7NMGjE2r3A98nd97" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub698V4vDG8CyVRafuqQjpVoqLr6NFUUwJFC4sgfj27RtP7Y2EGw1hwS3EUTzvDrDFZYMp5riwMJrhWKUv8HuNS2M5PVjzkDjFxbuboVNA39h", + "xpriv": "xprv9v98fQgNHqRCD6bSjPCp8ftcJ4Xm52DSsy9GtHKQZ6MQEjh5jPhTPdikdE1MW1A8RGjv77qqqNYnGZHdDYEjMXgNU6mxHkLGLaePduedXWm" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub69hicEPFe4U9M3iKudDvQUcJEiuTXnjVpP3r7QfFMtXmwvBqFdq654QBjFkdb6K3RXinFySyQxPZTsyP7Nu56xqUMfiksCLaaU4o7j2TN5e", + "xpriv": "xprv9viNCirMogur8Zdrobgv3LfZgh4y8L1eTA8FK2FdoYzo57rgi6WqXG5hszQufNumyFzLsiXYbZRWj35qcDQHQNP4BFtriHD6Z3J9KtkqSwY" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub69eRZ9jAkZZ128YH5uj3qkat4iUu7vogszErxHPdpFSbKhtQMkhSaGGr7nqQnV7pn2M9TR4bhfFMuPv8t3m9aAzrXLTucyfYKubKHsVV7a1", + "xpriv": "xprv9vf59eCGvBzhoeToytC3Uce9WgeQiU5qWmKG9tz2FuucSuZFpDPC2TxNGY1gAqAmWyyq1Z9DKhyXiEgDYVt4Q3eptWdbASYm82ohkDyBjQo" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub69f9C5sh9PEK1QpikAfEcqvJtn9SoPFDieM91SPDczSb5zBBn5MmZ4wRCsKyeDJBHnavxzqZ2hwDBDdVnyBZuppPVXxH3LUrXsHB6Lx9nLe", + "xpriv": "xprv9vfnnaLoK1g1nvkFe98EFhyaLkJxPvXNMRRYD3yc4eucDBr3EY3X1GcwMbfUHssRHAYnvHonMbQP5Dm9s56N4Moj32fwCSC6kGU6SbHTm3D" + } + ], + "derivePath": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub68gU3eZPcTu8CCNt5rky8yvMxD4VAmUujkprpEAQj7MtYX7JziPNfGjvmtnmdk6HKD2XUdSTHq3cTk1QafQsgUcUsHcPdCJ8pud8dvDo2Vb", + "xpriv": "xprv9uh7e92Vn6LpyiJQyqDxmqydQBDzmJm4NXuG1qkoAmpufinATB587URSvepj7Leu2Qg6EZwwrmYZqD64M3C7izCWaaGkzMPwknFgbmjLAfP" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub698V4vD7nYSXEeVd8a4txiscubcaQBUSB9ZjhRSdbzCXmD81UG6P3AidHwudaUatfuX5nsB7iwUnn6M4Q21pX1nkB5G4X1zb4dCxNm7DG54", + "xpriv": "xprv9v98fQgDxAtE2ARA2YXtbavtMZn5zikaove8u3323efYtQnrvin8VNQ9SeNJkJNxRhGGG3Eavc8edbGiiS6nr6MSdNCexgv8uYp5Ee6ctmH" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub69hicEP7JPwB9241jVA1ZGBhRfyhUXrxF25gpfN532wL8H6weGE7kwjLoyHvHFQPoeGk7MVsAq7129eFQBHhhc4Zpa64RvLpL8u6uMV6ywG", + "xpriv": "xprv9viNCirDU2NsvXyYdTd1C8Exse9D5596soA62GxTUhQMFUmo6iusD9QrxiLPcLF2TUrxPPZLife9ubNCxpa378FLrUbGb8wqN7vtN1zZ8Jg" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub69eRZ9j2Qu22pbQDzPNNcRJHZZu5wwMpJCsaHWbGUs28fjf9rCxN2UhcnrgbRpbaK9m1hj6qXnUHUb8C4pHFsyqhqhLMXdi2wRmUWXpJRpE", + "xpriv": "xprv9vf59eC8aXTjc7KktMqNFHMZ1Y4bYUdxvywyV8BevXV9nwL1Jfe7UgP8wYxkmGAL1qC9dRUS67HCMagx8pH566LkVFcen7DJDoCCtEbGB5g" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub69f9C5sYoihLrxJX4ixujbortnFkuXXW7xY5A5G5X6Ho4MhWzzKRjkgDbt1zRVeSD5tj91yFQ4p8qg6oCjr1HwFsMpSZBzUjm5nUo1eRwwA", + "xpriv": "xprv9vfnnaLeyM93eUE3xhRuNTs8LkRGW4oekjcUMgrTxkkpBZNNTT1BBxMjkd6yNH5trjDw3wnYoXUZLCUs6xhs9sUfgNi1ufBkLJoCFXWsL3R" + } + ], + "deriveHardenedPath": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub68gU3eZXx8S6MyhU1WTL8TyeaNYQ8yqutgPernats3dwvpqDMYv6VJbEi5Eqa6cPYe58wTWJjzSbbTMmVVpJxmwAfLs6BQoAxS1YpggMCrV", + "xpriv": "xprv9uh7e92e7kso9VczuUvKmL2v2LhujX84XTU44QBHJi6y42W4p1bqwWGkrqdmPoJHAKNSXA399Unq9DRbJ5KkHLFUsSa7NMGjE2r3A98nd97" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub698V4vDG8CyVRafuqQjpVoqLr6NFUUwJFC4sgfj27RtP7Y2EGw1hwS3EUTzvDrDFZYMp5riwMJrhWKUv8HuNS2M5PVjzkDjFxbuboVNA39h", + "xpriv": "xprv9v98fQgNHqRCD6bSjPCp8ftcJ4Xm52DSsy9GtHKQZ6MQEjh5jPhTPdikdE1MW1A8RGjv77qqqNYnGZHdDYEjMXgNU6mxHkLGLaePduedXWm" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub69hicEPFe4U9M3iKudDvQUcJEiuTXnjVpP3r7QfFMtXmwvBqFdq654QBjFkdb6K3RXinFySyQxPZTsyP7Nu56xqUMfiksCLaaU4o7j2TN5e", + "xpriv": "xprv9viNCirMogur8Zdrobgv3LfZgh4y8L1eTA8FK2FdoYzo57rgi6WqXG5hszQufNumyFzLsiXYbZRWj35qcDQHQNP4BFtriHD6Z3J9KtkqSwY" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub69eRZ9jAkZZ128YH5uj3qkat4iUu7vogszErxHPdpFSbKhtQMkhSaGGr7nqQnV7pn2M9TR4bhfFMuPv8t3m9aAzrXLTucyfYKubKHsVV7a1", + "xpriv": "xprv9vf59eCGvBzhoeToytC3Uce9WgeQiU5qWmKG9tz2FuucSuZFpDPC2TxNGY1gAqAmWyyq1Z9DKhyXiEgDYVt4Q3eptWdbASYm82ohkDyBjQo" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub69f9C5sh9PEK1QpikAfEcqvJtn9SoPFDieM91SPDczSb5zBBn5MmZ4wRCsKyeDJBHnavxzqZ2hwDBDdVnyBZuppPVXxH3LUrXsHB6Lx9nLe", + "xpriv": "xprv9vfnnaLoK1g1nvkFe98EFhyaLkJxPvXNMRRYD3yc4eucDBr3EY3X1GcwMbfUHssRHAYnvHonMbQP5Dm9s56N4Moj32fwCSC6kGU6SbHTm3D" + } + ], + "deriveBIP44": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "xpub": "xpub6CUjAgu7fDFsW63QGEe8T6XMfjh8PA2dHP7GQBwpRQSniWEY5VmA1CjQJ93s3BqT9VkML8qytP6rgJSf5jvP3bqzN7CTiwTxPo2ZpEuhQ6N", + "xpriv": "xprv9yVNmBNDpqhaHbxwAD785xad7hrdyhJmvABfboYCs4uoqhuPXxSuTQQvSrRUXpN8it1sZvhKpxQ7pbHJb5aQ8Vghc51SCpWijdHZmWGMKp1" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "xpub": "xpub6CTS16pXaVuu7Uj6D7AcMhomMJ45DqaKCdUY8TYmkAuwDCaarsBhLRzHrVcLooPgTPnRF1mQSbUmfPWg3bVu2tWHuB7zzNbsDGbUHWZ6FCZ", + "xpriv": "xprv9yU5bbHdk8Mbtzed75dbzZs2oGDapNrTqQYwL59ABqNxLQFSKKsSndfp1DkQNF1JiCDZfN3JLGdUtn5tBZ9V9i3uhjX1ymBVdsCt3kqAtXb" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "xpub": "xpub6CjWtuhSaU64a1vhqxT81EbcTw5GksMTgtNwU1uXGayxyP2DW12V6MvTfrvWXrCDp55z6d87iSwGfxENcgDuu3QpuzFzWXtfbcoBLzDySQo", + "xpriv": "xprv9ykAVQAYk6XmMXrEjvv7e6esuuEnMQdcKfTLfdVuiFSz6ah4xTiEYZbypbXxvNzY5ZN3wkrBWnSAGAvsiZVDXuyB6qyxprJfDb6yxYj1ZhP" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "xpub": "xpub6CFoRnxxMJa9gbVBNMh3VS9Zb6mkaHiWzFGEm3gkzmQ7H8b45nGj2fffGRR5Requ4wAwhEQjkArSaqv7HHvLK3y9wJcJRY6x1PuVWcKvXpn", + "xpriv": "xprv9yGT2HS4Ww1rU7QiGLA38JCq34wGApzfd2LdxfH9SRs8QLFuYExUUsMBR8UShsuRfYr3X57gmbtdz9BsC4gxEkESSdBxdmiuLULfVq4A8Vw" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "xpub": "xpub6CmhKTZY8gxFWcZRLyUUncSuXmWiifSSEdXxY7GbSA5oigKpxEjTCosSVaFFMMnq3ymUuLNaoe8b4SQUYmXPqYX8CUwMh1N9Sge7RBuvEsB", + "xpriv": "xprv9ynLux2eJKPxJ8UxEwwURUWAyjgEKCiasQcMjiryspYpqszgQhRCf1YxeHWjM9v9rpLZW1DUEQRFNH9XmXTXL3zmxEvFV6JUe2N2yYVdpJK" + } + ], + "toLegacyAddress": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "address": "1HFrMyKqduYaStXuwwKJo4U6LYd8ygFfJP" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "address": "16PcBknwp8vdT8RH3iV9rFW8Us8JKws2jp" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "address": "18gWYq4bf8hiS5hPDurPrxEs3wikdzskXH" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "address": "14HyYKA6dgn1DVTLnvEFfh1jNFBATyragw" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "address": "1AFWhEY61yrTgsW1bbAfNM6LnrTZ77oMc6" + } + ], + "toCashAddress": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "address": "bitcoincash:qze9zznaqzdne2awnlnygt0p74dkc3e4svm9ty58nx", + "regtestAddress": "bchreg:qze9zznaqzdne2awnlnygt0p74dkc3e4sv9tez4rhu" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "address": "bitcoincash:qqa37fkpl3g90ydgas3t3ev0xes24du2aqfdxaz85s", + "regtestAddress": "bchreg:qqa37fkpl3g90ydgas3t3ev0xes24du2aqhr5mrrs2" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "address": "bitcoincash:qp2yr38pjeqdtsqw9pfwjs6ng3ya4lqlpu46wkzsh5", + "regtestAddress": "bchreg:qp2yr38pjeqdtsqw9pfwjs6ng3ya4lqlput5usr5nw" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "address": "bitcoincash:qqjpuwkhepkgxylfg8tq59sqm8zwf32dxqdchcnge7", + "regtestAddress": "bchreg:qqjpuwkhepkgxylfg8tq59sqm8zwf32dxqnk97jvay" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "address": "bitcoincash:qpjh00scq3ratsytmttle5cml6yyk9stvc5lfd6pck", + "regtestAddress": "bchreg:qpjh00scq3ratsytmttle5cml6yyk9stvc23mtm9uv" + } + ], + "toWIF": [ + { + "privateKeyWIF": "L3nMAFHGojwdTajZAjEfSsqyP8TmQgqpxmhKNdwpLiNw4EBuhUcg", + "xpriv": "xprv9ysdmq3FsEdR2byz2KdNuGNd1dY181zpe5jBpydatj49DaxVnrTGNkM4S28WgBzNSFQA5SSnT34XcqcoS6opVGebrhXAef3rGpXPPb7yHPi" + }, + { + "privateKeyWIF": "KxWMGJMHMhB4f8aTwpmqags7gNvcrc7gs76SJHaXbAc4Me2DKP3L", + "xpriv": "xprv9ysdmq3FsEdQyzSTsENJdmQPvXWDArRWG118RzwPTGoYmwUDGoeHoBjr1hxgGrLP9XpEXg9roQhSBFrwjb934bnu8yQzdHi6MmcPUXniGrP" + }, + { + "privateKeyWIF": "KzYUskkRuJrhDYLgP1JAywmUx3MPh2UZNQa299iZgGS9cNKytzy4", + "xpriv": "xprv9ysdmq3FsEdR71JPPhVFP2DwwQCUksByZA7kg6y5qTjDvkX8GQH1WwJDc2epCeCL416qnVHJYNQvaFjkrM1CkGwaHy1w9oVAUAe4V6cYiyq" + }, + { + "privateKeyWIF": "L54oL29DdWDiPtHDW1kr3fzTWdm2f34V1tWhrFxnJoVafadwx6aS", + "xpriv": "xprv9ysdmq3FsEdR8Zka7LWxKXaW2bsS3fjmusdBq5GnnDVMSAZ2LqhsoydPr6gD4CS9Ww8vWfnKtGe1rTqbw5NBvuqJwhebogWGbmkGhV9xa69" + }, + { + "privateKeyWIF": "L5UtYZibxTiHYefBQNvTRHYp75V3EeoqzRdv8KwqzYBtvzewaUME", + "xpriv": "xprv9ysdmq3FsEdRD3iBKzw3f8HwpR1TbZNbySfxvXFZ6ZnmiMQGPfcnfCuDoLcnGT27p8DTvsqVubj8tv6yQp5siqUoPmT4WRLpjZXauriLnZB" + } + ], + "fromSeed": [ + "rare dinosaur army fat spend average design order ritual town brave spike", + "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe" + ], + "fromXPriv": [ + { + "xpriv": "xprv9ysdmq3FsEdR2byz2KdNuGNd1dY181zpe5jBpydatj49DaxVnrTGNkM4S28WgBzNSFQA5SSnT34XcqcoS6opVGebrhXAef3rGpXPPb7yHPi", + "legacy": "1CtdPZHebT5QTdknNvEBjGSgACHbriUmLV", + "cashaddress": "bitcoincash:qzpxe7f7zc3rwckjmtled2wfssvv5gkvw52e35pc9q", + "regtestaddress": "bchreg:qzpxe7f7zc3rwckjmtled2wfssvv5gkvw55hrjqup6", + "xpub": "xpub6CrzBLa9hcBiF64T8MAPGQKMZfNVXUig1JendN3CT4b86PHeLPmWvYfYHK2NNYrEBj1GwfWqWF1xYjn6aD1Y7RYDNajKSKbSi1CW2hzeDA5", + "privateKeyWIF": "L3nMAFHGojwdTajZAjEfSsqyP8TmQgqpxmhKNdwpLiNw4EBuhUcg" + }, + { + "xpriv": "xprv9ysdmq3FsEdQyzSTsENJdmQPvXWDArRWG118RzwPTGoYmwUDGoeHoBjr1hxgGrLP9XpEXg9roQhSBFrwjb934bnu8yQzdHi6MmcPUXniGrP", + "legacy": "1J1nGBW8rWeV1VEETii5nKhKHT7qshuQYw", + "cashaddress": "bitcoincash:qzafl67pkxgmmzfp5jj4937jv6rlfs7zxy6gurw0xh", + "regtestaddress": "bchreg:qzafl67pkxgmmzfp5jj4937jv6rlfs7zxyyxw90tzd", + "xpub": "xpub6CrzBLa9hcBiCUWvyFuJzuM8UZLhaK9MdDvjEPM11cLXejoMpLxYLz4KrxjRV9cd44EacgeaWU4E3GT6HfkDwjEup3Hw9inf9opWxxCZUa6", + "privateKeyWIF": "KxWMGJMHMhB4f8aTwpmqags7gNvcrc7gs76SJHaXbAc4Me2DKP3L" + }, + { + "xpriv": "xprv9ysdmq3FsEdR71JPPhVFP2DwwQCUksByZA7kg6y5qTjDvkX8GQH1WwJDc2epCeCL416qnVHJYNQvaFjkrM1CkGwaHy1w9oVAUAe4V6cYiyq", + "legacy": "1CrwnAN3p4Y5h7511F49VgwUvbEq314BM9", + "cashaddress": "bitcoincash:qzppkls7al33d0w2dd07776d0fu2jwvtxsnw8nyum4", + "regtestaddress": "bchreg:qzppkls7al33d0w2dd07776d0fu2jwvtxsdq449cl0", + "xpub": "xpub6CrzBLa9hcBiKVNrVj2FkAAgVS2yAKupvP3MUVNhPoGCoYrGowbG4jchTLccFRbah2Kw7VNbP11G6mXYAkur47EZJcsXkvwTqCekjrpBYaS", + "privateKeyWIF": "KzYUskkRuJrhDYLgP1JAywmUx3MPh2UZNQa299iZgGS9cNKytzy4" + }, + { + "xpriv": "xprv9ysdmq3FsEdR8Zka7LWxKXaW2bsS3fjmusdBq5GnnDVMSAZ2LqhsoydPr6gD4CS9Ww8vWfnKtGe1rTqbw5NBvuqJwhebogWGbmkGhV9xa69", + "legacy": "1HbccrsbZjyPewX6vaHBFyuhE2NtP3BJYp", + "cashaddress": "bitcoincash:qzmqmc6vq4hrdwtjrluwk9uvyj6nt4h4aqe24qxnqr", + "regtestaddress": "bchreg:qzmqmc6vq4hrdwtjrluwk9uvyj6nt4h4aq8y8x8hye", + "xpub": "xpub6CrzBLa9hcBiM3q3DN3xgfXEadhvT8TdH6YndTgQLZ2LJxtAtP28MmwshM34EtFhPDLorNocunk1E2KBvBbsGVJ5NorsvxW1yDWxsuLZF8Y", + "privateKeyWIF": "L54oL29DdWDiPtHDW1kr3fzTWdm2f34V1tWhrFxnJoVafadwx6aS" + }, + { + "xpriv": "xprv9ysdmq3FsEdRD3iBKzw3f8HwpR1TbZNbySfxvXFZ6ZnmiMQGPfcnfCuDoLcnGT27p8DTvsqVubj8tv6yQp5siqUoPmT4WRLpjZXauriLnZB", + "legacy": "1CmRFZjxL6auhVooc9sd3d8oKwL8mKwfFQ", + "cashaddress": "bitcoincash:qzqslmsyprdfzwu3hk4sa237c7lpm44kesvkl2vtnq", + "regtestaddress": "bchreg:qzqslmsyprdfzwu3hk4sa237c7lpm44kesjcdvd0h6", + "xpub": "xpub6CrzBLa9hcBiRXneS2U42GEgNSqx126TLfbZiufAeuKkb9jQwCw3D1Dhec4bNzwm9JkunejrxWcviV5NmoQTkb5eMGqJrVby2L7p6Mtee75", + "privateKeyWIF": "L5UtYZibxTiHYefBQNvTRHYp75V3EeoqzRdv8KwqzYBtvzewaUME" + } + ], + "fromXPub": [ + { + "legacy": "1CtdPZHebT5QTdknNvEBjGSgACHbriUmLV", + "cashaddress": "bitcoincash:qzpxe7f7zc3rwckjmtled2wfssvv5gkvw52e35pc9q", + "regtestaddress": "bchreg:qzpxe7f7zc3rwckjmtled2wfssvv5gkvw55hrjqup6", + "xpub": "xpub6CrzBLa9hcBiF64T8MAPGQKMZfNVXUig1JendN3CT4b86PHeLPmWvYfYHK2NNYrEBj1GwfWqWF1xYjn6aD1Y7RYDNajKSKbSi1CW2hzeDA5" + }, + { + "legacy": "1J1nGBW8rWeV1VEETii5nKhKHT7qshuQYw", + "cashaddress": "bitcoincash:qzafl67pkxgmmzfp5jj4937jv6rlfs7zxy6gurw0xh", + "regtestaddress": "bchreg:qzafl67pkxgmmzfp5jj4937jv6rlfs7zxyyxw90tzd", + "xpub": "xpub6CrzBLa9hcBiCUWvyFuJzuM8UZLhaK9MdDvjEPM11cLXejoMpLxYLz4KrxjRV9cd44EacgeaWU4E3GT6HfkDwjEup3Hw9inf9opWxxCZUa6" + }, + { + "legacy": "1CrwnAN3p4Y5h7511F49VgwUvbEq314BM9", + "cashaddress": "bitcoincash:qzppkls7al33d0w2dd07776d0fu2jwvtxsnw8nyum4", + "regtestaddress": "bchreg:qzppkls7al33d0w2dd07776d0fu2jwvtxsdq449cl0", + "xpub": "xpub6CrzBLa9hcBiKVNrVj2FkAAgVS2yAKupvP3MUVNhPoGCoYrGowbG4jchTLccFRbah2Kw7VNbP11G6mXYAkur47EZJcsXkvwTqCekjrpBYaS" + }, + { + "legacy": "1HbccrsbZjyPewX6vaHBFyuhE2NtP3BJYp", + "cashaddress": "bitcoincash:qzmqmc6vq4hrdwtjrluwk9uvyj6nt4h4aqe24qxnqr", + "regtestaddress": "bchreg:qzmqmc6vq4hrdwtjrluwk9uvyj6nt4h4aq8y8x8hye", + "xpub": "xpub6CrzBLa9hcBiM3q3DN3xgfXEadhvT8TdH6YndTgQLZ2LJxtAtP28MmwshM34EtFhPDLorNocunk1E2KBvBbsGVJ5NorsvxW1yDWxsuLZF8Y" + }, + { + "legacy": "1CmRFZjxL6auhVooc9sd3d8oKwL8mKwfFQ", + "cashaddress": "bitcoincash:qzqslmsyprdfzwu3hk4sa237c7lpm44kesvkl2vtnq", + "regtestaddress": "bchreg:qzqslmsyprdfzwu3hk4sa237c7lpm44kesjcdvd0h6", + "xpub": "xpub6CrzBLa9hcBiRXneS2U42GEgNSqx126TLfbZiufAeuKkb9jQwCw3D1Dhec4bNzwm9JkunejrxWcviV5NmoQTkb5eMGqJrVby2L7p6Mtee75" + } + ], + "accounts": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "externals": [ + "bitcoincash:qr2xjcqfmpl8thetzssxm32s2fnpvsfplvm37gh72u", + "bitcoincash:qzxuj7cxg4t8vwd2q9ktl354zxga3csgqg7rmxtuv2", + "bitcoincash:qp9uznqurj0kpu2zd25cg2nth3tht9cj5u9v7wwnyc", + "bitcoincash:qq6g7j7m6sr22rpun5m4jz9v57698nfevc640rhqjm", + "bitcoincash:qzxwx6z6hkr9eu3sekn5cvl7d0p26t42ns6kxn4004" + ] + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "externals": [ + "bitcoincash:qr92mpxs2p4swnn5segxnvx654gj9k5f3udnpmdp6e", + "bitcoincash:qqfgdzjzyhzvlfkv6g06pshq8ddm6mdc9q8l4n5d87", + "bitcoincash:qpnk7c29fst9c339cv2cd2d5qh5t6y6ypu7ma9nysd", + "bitcoincash:qz49d0m0sdvucs9z7gg0p34jzc8qk9qlpc4ggzkc0v", + "bitcoincash:qqap24wa7uu9nskcxfsc2t4mtx6sf5sh6ye43grqne" + ] + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "externals": [ + "bitcoincash:qqf38us2wju8n4kakdd9pkm20h4pd7tt3u4tm6wval", + "bitcoincash:qp57af8qltwk90kkj8gm60k0m6dkgcuzc57kraaswg", + "bitcoincash:qzxfrmcjkr4l2zdy05ghxcqh2z2jr62n8qpdskq6u9", + "bitcoincash:qpj5yyzafyek7hw0pawmys0xqkm4pgf2h5u3v0ndpw", + "bitcoincash:qr84wwrp5yvre58qq6mk323y0pmmzmdgxs9vukesmj" + ] + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "externals": [ + "bitcoincash:qzzd8g4l4rum2txra2jkw5mqe29e8ac6vsjrkruedp", + "bitcoincash:qq0su63j07y7vcts6etld9q3c57jhav4pc867t4927", + "bitcoincash:qrfxkf0dgqrzwl0dkkt5tde77l758lzepyelpt5qkr", + "bitcoincash:qq2hwe79eugk3fpx7n6f5nfqkeh4eygq7v9ay8dxrf", + "bitcoincash:qprcxg6tkm0hq3773a2v66ue379fdh49mcdgg7fscr" + ] + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "externals": [ + "bitcoincash:qqqqwccu8c2j2twsh3qp6wdyfnzdvxa2qqqdqst9eq", + "bitcoincash:qr53v0r6djuu0jnpkphtl53kgptvv6x67yt7djky59", + "bitcoincash:qzm4awn03edhskc6rtzytud35x84njlkls63ur3ffa", + "bitcoincash:qqsyhnfcxv4g4e8sn3xlkx99urrzfqlw7u8y5eukdz", + "bitcoincash:qqrlf0qszjwrrsh0d8cz3anvusu6q37xvynms533gt" + ] + } + ], + "sign": [ + { + "privateKeyWIF": "tprv8gSiosqMhzpP3XaTadTEcFe57vcwxWbxNPvcwesb262wbzcFzKtqH8KAZApTBDJipJuAu3Dqzj3MZmLTz1mSeXP7Kn9uZeNXFpHBDQHr4hW", + "data": "EARTH" + }, + { + "privateKeyWIF": "xprv9yFjArUhvYMNZGr63oZK2TptuZDLMfBSz2c5mnFwSCmQeCMNTgJyZwkhjAumNgfsDsRJEn7pYmLQM7o8u5P2dWk6tG6TndWoVBPQi3DvmpY", + "data": "foobar" + }, + { + "privateKeyWIF": "tprv8gSiosqMhzpPMPbutrLEoyEea5DetcC5oorjaqY5qUFVwdPves2vkdYxR3PpXj67hBC6EUH9DTCDzReR6UWAjgF69bsrJTudWqNHe5LbNTU", + "data": "12334567890" + }, + { + "privateKeyWIF": "xprv9yFjArUhvYMNciVULCdXz7D6xAevAHgWXmsioewMAiuaAzBgkyVqxkqDcyg8KTeJedKRyxaNWLYSwAUts41vGoWCkWiEtcrRMo7y1gCfCZe", + "data": "Be excellent to each other" + }, + { + "privateKeyWIF": "xprv9yFjArUhvYMNmSjZ8k77PA6B6wsJUAq7sm6cKuZhkcVe85jkdpwbWgcMmXq4SDZZ7dg9GaoJCCaFSnRSVhFTQh2LJdH3pzMwaTQANk78PFA", + "data": "satoshi" + } + ], + "verify": [ + { + "privateKeyWIF1": "xprv9yFjArUhvYMNZGr63oZK2TptuZDLMfBSz2c5mnFwSCmQeCMNTgJyZwkhjAumNgfsDsRJEn7pYmLQM7o8u5P2dWk6tG6TndWoVBPQi3DvmpY", + "privateKeyWIF2": "xprv9yFjArUhvYMNmSjZ8k77PA6B6wsJUAq7sm6cKuZhkcVe85jkdpwbWgcMmXq4SDZZ7dg9GaoJCCaFSnRSVhFTQh2LJdH3pzMwaTQANk78PFA", + "data": "EARTH" + }, + { + "privateKeyWIF1": "tprv8gSiosqMhzpPMPbutrLEoyEea5DetcC5oorjaqY5qUFVwdPves2vkdYxR3PpXj67hBC6EUH9DTCDzReR6UWAjgF69bsrJTudWqNHe5LbNTU", + "privateKeyWIF2": "tprv8gSiosqMhzpP3XaTadTEcFe57vcwxWbxNPvcwesb262wbzcFzKtqH8KAZApTBDJipJuAu3Dqzj3MZmLTz1mSeXP7Kn9uZeNXFpHBDQHr4hW", + "data": "foobar" + }, + { + "privateKeyWIF1": "xprv9yFjArUhvYMNmSjZ8k77PA6B6wsJUAq7sm6cKuZhkcVe85jkdpwbWgcMmXq4SDZZ7dg9GaoJCCaFSnRSVhFTQh2LJdH3pzMwaTQANk78PFA", + "privateKeyWIF2": "xprv9yFjArUhvYMNZGr63oZK2TptuZDLMfBSz2c5mnFwSCmQeCMNTgJyZwkhjAumNgfsDsRJEn7pYmLQM7o8u5P2dWk6tG6TndWoVBPQi3DvmpY", + "data": "12334567890" + }, + { + "privateKeyWIF1": "xprv9yFjArUhvYMNu32qRztSu86QozxDQjcUqFQ2YKxEaZ3Ww8m5CkfXXJMQxNLGQysS8uDnf7NGyboab2Rn4YskRh97jxFXNPgpqxVinM3vMki", + "privateKeyWIF2": "xprv9yFjArUhvYMNZGr63oZK2TptuZDLMfBSz2c5mnFwSCmQeCMNTgJyZwkhjAumNgfsDsRJEn7pYmLQM7o8u5P2dWk6tG6TndWoVBPQi3DvmpY", + "data": "Be excellent to each other" + }, + { + "privateKeyWIF1": "xprv9yFjArUhvYMNyhQ3AsdJ6r8NH4NWwyXDg91twPN3hqdWZw6GJSz2JDLxfhJf46PCbs2K73239jzvNxRXqg62aV9M5ytN4kuSpi6JyGxm7eZ", + "privateKeyWIF2": "xprv9yFjArUhvYMNu32qRztSu86QozxDQjcUqFQ2YKxEaZ3Ww8m5CkfXXJMQxNLGQysS8uDnf7NGyboab2Rn4YskRh97jxFXNPgpqxVinM3vMki", + "data": "satoshi" + } + ], + "isPublic": [ + { + "xpriv": "xprv9ys4cvcoU8RoxqkZ7Fgt33te4LPHgcsKwyoZYVorkzp9uonWxWgP9wiSQhPeBUqVHbdAyov4Yi55RywBkDfZKdJFRqA51Anz6v72zGaMGZp", + "xpub": "xpub6CrR2S9hJVz7BKq2DHDtQBqNcNDn65bBKCjALtDUKLM8nc7fW3zdhk2vFwgAKyFx1VNfQrXTZyKz9sAHZp8rHUEq6n8aWLSaDHxBcsHUPr8" + }, + { + "xpriv": "xprv9ys4cvcoU8RoqvzxGj886r4Ey3w1WfVNYH8sMnVPVzyQtaPPM6Q8pHm3D9WPWvEupGEgcJ1xLaGaZDcvKfoAurE2AzHRRRup5FuHzDr8n15", + "xpub": "xpub6CrR2S9hJVz74R5RNkf8TyzyX5mVv8DDuW4UAAu14LWPmNiXtdiPN65X4RFeCDjdgMQsFYBGVFV3gtscV3Caa1jkZMccuMSUchy6193GqoA" + }, + { + "xpriv": "xprv9ys4cvcoU8RoskNxLPn5ia9NLq5o2KS2H5aPXAp8yrXZCtDETASDjp1dbj4fDKHFzJD3T7xZgZRUeD1wi498xLtJcEEaqQty7xhQ8eYP2jv", + "xpub": "xpub6CrR2S9hJVz76ETRSRK65i66trvHRn9seJVzKZDkYC4Y5gYNzhkUHcL7SzB5fZ8Dcbv19bzMqKR3W3XRKfMoJ8Us8QVkhAowV3KTVjibTjj" + }, + { + "xpriv": "tprv8gHe7RzeBHKRs7RNjmrcwaBeupsSMj6pJ5Fc9VyoWgHSQNQS2MuSv66VunfCVDGDuDUPj1T5CyXkmUyEZULcntcfc5TdQfpNWni9UU8QTkC", + "xpub": "tpubDCygFr2tKf16kaTAdRXDLyqmUrPNX4HisNrPS226vx5qErfCekj36aiN5x2EDBBefiewYnLHKnRyUMLHN1X2A7212XEzoQqnTbdi5xGSu3X" + }, + { + "xpriv": "tprv8gHe7RzeBHKRz5dTMj3kbdpZUhSoEbx9dKgeTMf1Krcv4Vad4ESDpChSb2Dd1te4URYFp47aa7Ps6HYgz7UEPs9r438n9iST7LEJyDsLjXB", + "xpub": "tpubDCygFr2tKf16sYfFFNiM13Ug3ixjPw94CdHRjshJk8RJtyqPgdFozhKJm8snjzRrAqSho3f3K4AT9XeP6xYN1sx58dPRfBsJ5FxTB5weFLL" + } + ], + "isPrivate": [ + { + "xpriv": "xprv9ys4cvcoU8RoxqkZ7Fgt33te4LPHgcsKwyoZYVorkzp9uonWxWgP9wiSQhPeBUqVHbdAyov4Yi55RywBkDfZKdJFRqA51Anz6v72zGaMGZp", + "xpub": "xpub6CrR2S9hJVz7BKq2DHDtQBqNcNDn65bBKCjALtDUKLM8nc7fW3zdhk2vFwgAKyFx1VNfQrXTZyKz9sAHZp8rHUEq6n8aWLSaDHxBcsHUPr8" + }, + { + "xpriv": "xprv9ys4cvcoU8RoqvzxGj886r4Ey3w1WfVNYH8sMnVPVzyQtaPPM6Q8pHm3D9WPWvEupGEgcJ1xLaGaZDcvKfoAurE2AzHRRRup5FuHzDr8n15", + "xpub": "xpub6CrR2S9hJVz74R5RNkf8TyzyX5mVv8DDuW4UAAu14LWPmNiXtdiPN65X4RFeCDjdgMQsFYBGVFV3gtscV3Caa1jkZMccuMSUchy6193GqoA" + }, + { + "xpriv": "xprv9ys4cvcoU8RoskNxLPn5ia9NLq5o2KS2H5aPXAp8yrXZCtDETASDjp1dbj4fDKHFzJD3T7xZgZRUeD1wi498xLtJcEEaqQty7xhQ8eYP2jv", + "xpub": "xpub6CrR2S9hJVz76ETRSRK65i66trvHRn9seJVzKZDkYC4Y5gYNzhkUHcL7SzB5fZ8Dcbv19bzMqKR3W3XRKfMoJ8Us8QVkhAowV3KTVjibTjj" + }, + { + "xpriv": "tprv8gHe7RzeBHKRs7RNjmrcwaBeupsSMj6pJ5Fc9VyoWgHSQNQS2MuSv66VunfCVDGDuDUPj1T5CyXkmUyEZULcntcfc5TdQfpNWni9UU8QTkC", + "xpub": "tpubDCygFr2tKf16kaTAdRXDLyqmUrPNX4HisNrPS226vx5qErfCekj36aiN5x2EDBBefiewYnLHKnRyUMLHN1X2A7212XEzoQqnTbdi5xGSu3X" + }, + { + "xpriv": "tprv8gHe7RzeBHKRz5dTMj3kbdpZUhSoEbx9dKgeTMf1Krcv4Vad4ESDpChSb2Dd1te4URYFp47aa7Ps6HYgz7UEPs9r438n9iST7LEJyDsLjXB", + "xpub": "tpubDCygFr2tKf16sYfFFNiM13Ug3ixjPw94CdHRjshJk8RJtyqPgdFozhKJm8snjzRrAqSho3f3K4AT9XeP6xYN1sx58dPRfBsJ5FxTB5weFLL" + } + ], + "toIdentifier": [ + { + "xpriv": "tprv8ggxJ8SG5EdqakzVUeLa9Gr7sqCdEcJPUNDmtdJscNxfmxoXvU36ZguiUWukJVEWEixAUr8pJabJkCt33wzxFQA587gqN51Lxdxx97zAzuG" + }, + { + "xpriv": "xprv9ys4cvcoU8RoqvzxGj886r4Ey3w1WfVNYH8sMnVPVzyQtaPPM6Q8pHm3D9WPWvEupGEgcJ1xLaGaZDcvKfoAurE2AzHRRRup5FuHzDr8n15" + }, + { + "xpriv": "xprv9ys4cvcoU8RoskNxLPn5ia9NLq5o2KS2H5aPXAp8yrXZCtDETASDjp1dbj4fDKHFzJD3T7xZgZRUeD1wi498xLtJcEEaqQty7xhQ8eYP2jv" + }, + { + "xpriv": "tprv8gHe7RzeBHKRs7RNjmrcwaBeupsSMj6pJ5Fc9VyoWgHSQNQS2MuSv66VunfCVDGDuDUPj1T5CyXkmUyEZULcntcfc5TdQfpNWni9UU8QTkC" + }, + { + "xpriv": "tprv8gHe7RzeBHKRz5dTMj3kbdpZUhSoEbx9dKgeTMf1Krcv4Vad4ESDpChSb2Dd1te4URYFp47aa7Ps6HYgz7UEPs9r438n9iST7LEJyDsLjXB" + } + ] +} diff --git a/test/unit/fixtures/ipfs-mock.js b/test/unit/fixtures/ipfs-mock.js new file mode 100644 index 0000000..25f628a --- /dev/null +++ b/test/unit/fixtures/ipfs-mock.js @@ -0,0 +1,127 @@ +/* + Mock data for IPFS unit tests. +*/ + +const uploadData = { + successful: [ + { + source: "Local", + id: "uppy-ipfs/js-1e-application/octet-stream", + name: "ipfs.js", + extension: "js", + meta: { + test: "avatar", + name: "ipfs.js", + type: "application/octet-stream", + fileModelId: "5ec562319bfacc745e8d8a52" + }, + type: "application/octet-stream", + progress: { + uploadStarted: 1589596706524, + uploadComplete: true, + percentage: 100, + bytesUploaded: 2374, + bytesTotal: 2374 + }, + size: null, + isRemote: false, + remote: "", + tus: { + uploadUrl: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33" + }, + response: { + uploadURL: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33" + }, + uploadURL: + "http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33", + isPaused: false + } + ], + failed: [], + uploadID: "cka90u4m300000fi0cldg6lrf" +} + +const paymentInfo = { + success: true, + hostingCostBCH: 0.00004201, + hostingCostUSD: 0.01, + file: { + payloadLink: "", + hasBeenPaid: false, + _id: "5ebf5d04cba8b038394e0d62", + schemaVersion: 1, + size: 2374, + fileId: "uppy-ipfs/js-1e-application/octet-stream", + fileName: "ipfs.js", + fileExtension: "js", + createdTimestamp: "1589599492.952", + hostingCost: 4201, + walletIndex: 36, + bchAddr: "bchtest:qqymyk4zfvnw8rh6hcvl922ewudkyrn9jvk6gtuae6", + __v: 0 + } +} + +mockNewFileModel = { + 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 + } +} + +const unpaidFileData = { + file: { + payloadLink: "", + hasBeenPaid: false, + _id: "5ec7392c2acfe57aa62e945a", + schemaVersion: 1, + size: 726, + fileName: "ipfs-e2e.js", + fileExtension: "js", + createdTimestamp: "1590114604.986", + hostingCost: 4403, + walletIndex: 56, + bchAddr: "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk", + __v: 0 + } +} + +const paidFileData = { + file: { + payloadLink: "QmRDHPhY5hCNVRMVQvS2H9uty8P1skdwgLaHpUAkEvsjcE", + hasBeenPaid: true, + _id: "5ec7392c2acfe57aa62e945a", + schemaVersion: 1, + size: 726, + fileName: "ipfs-e2e.js", + fileExtension: "js", + createdTimestamp: "1590114604.986", + hostingCost: 4403, + walletIndex: 56, + bchAddr: "bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk", + __v: 0 + } +} + +module.exports = { + uploadData, + paymentInfo, + mockNewFileModel, + unpaidFileData, + paidFileData +} diff --git a/test/unit/fixtures/mnemonic.json b/test/unit/fixtures/mnemonic.json new file mode 100644 index 0000000..e1e2b6b --- /dev/null +++ b/test/unit/fixtures/mnemonic.json @@ -0,0 +1,203 @@ +{ + "fromEntropy": [ + { + "mnemonic": "rare dinosaur army fat spend average design order ritual town brave spike", + "entropy": "b207cc3029bd141f8ef4e1ba9cc86d68" + }, + { + "mnemonic": "rabbit track ahead moral either upper tiger paper echo snow multiply trial kind two cram", + "entropy": "b03cd014c7d471de387cff45f9b245f417a9d74c" + }, + { + "mnemonic": "shadow steak gospel heart life wedding loud jar marriage during lecture stove exclude price worry story art relief", + "entropy": "c4daa193352815f1e10bba884885fc6b54ef54ff76b40cd6" + }, + { + "mnemonic": "supply salute they blood coyote century envelope circle baby armed comfort sport jazz clarify museum portion wolf hour donkey frog juice", + "entropy": "d9d7db820bf31a4ad2f149110178b8e9577653e46d42fccdc904ae97" + }, + { + "mnemonic": "tunnel ugly degree bid merry world pupil tornado ski rent casino rent security length wet twice luxury rookie invite destroy busy leopard escape shoe", + "entropy": "ea9d80e70b08bbfb6b7f2bca56c88ddb2c2d003e675a855779d81e11f3009346" + } + ], + "toKeypairs": [ + { + "mnemonic": "curve quiz lesson bone wave endless rigid symbol plug shift access dragon", + "output": [ + { + "privateKeyWIF": "L5NHWZusDiBVi5eMc6mBGufDMewTqnN9fzi6zyCQSVp39iKPqH7v", + "privateKeyWIFRegTest": "cVjGyUuiemsksX7czWaJeEAGytEsWETqk2ra7PeuwcU3QTKYFYX7", + "address": "bitcoincash:qr3r0yazu6s4ed0ckgpv96zcwx3wt9jy6vv56ywm2w" + }, + { + "privateKeyWIF": "L3nGc7sEDQ6CQV7LPzyLwec8uWwQFWhXKcYdPC6dmx8xZuq4mmDM", + "privateKeyWIFRegTest": "cU9G52s5eTnTZvabnQnUJy7CXkEouxoDPeh6VcZ9H4nxpeqnn4y7", + "address": "bitcoincash:qqzc32v777yz4qprp0tk443j5j35q5q0xgzuvprgv0" + }, + { + "privateKeyWIF": "KxD3i4YUdzEdDggNbNjRbYqta85KPGko9mRxcZH1Dh7MYQ5PFjcq", + "privateKeyWIFRegTest": "cNa3AyYL53vtP89dynYYxsLxCMNj3irVDoaRiyjWiomMo9AginKE", + "address": "bitcoincash:qre988ur4gp5k2ph3lmxltqtg4q2zgw6eye0l6k6eg" + }, + { + "privateKeyWIF": "L1emnG2hYd3VKGVRHpZS3qHXQHMBgvDy4he4UtFu747wVyT5sQGT", + "privateKeyWIFRegTest": "cS1mFB2YygjkUhxggENZR9nb2WebMNKf8jnXbJiQcAmwkiSwiEsC", + "address": "bitcoincash:qzrjsum46ecex9qs28eyhw27ltydu37wzcq3v80ld8" + }, + { + "privateKeyWIF": "L28uLVU1Kt8fPJtbK7HUB436atC4dSN1WQPJLde1w6dtyhNmLMyt", + "privateKeyWIFRegTest": "cSVtoQTrkwpvYkMrhX6bYNYAD7VUHtThaSXmT46XSDHuESQ7LCaQ", + "address": "bitcoincash:qqqnmqmpy9vz899ctv4chtx5n857pv9l4ykuea44xz" + } + ] + }, + { + "mnemonic": "weapon undo scatter empty ride cement team protect ugly sudden mom zebra nurse label leopard", + "output": [ + { + "privateKeyWIF": "KxGbR7jBWA7jm7GVz9SQvUghX4cYyf1WoP7oGjVXgzbeCDBFy5Qi", + "privateKeyWIFRegTest": "cNdat2j2wDozvYjmNZFYHoBm9Huxe77CsRGGP9x3C7FeSxGV9GtJ", + "address": "bitcoincash:qr3r0yazu6s4ed0ckgpv96zcwx3wt9jy6vv56ywm2w" + }, + { + "privateKeyWIF": "L2Er6iZ87yeQVDREKQWw8xr7Uj2ZTmdF4fzfG2MYP3TYHHgXBphK", + "privateKeyWIFRegTest": "cSbqZdYyZ3LfeetVhpL4WHMB6xKy8Diw8i98NSp3tA7YY2o3PGAC", + "address": "bitcoincash:qqzc32v777yz4qprp0tk443j5j35q5q0xgzuvprgv0" + }, + { + "privateKeyWIF": "KwQW5F2oFfeqaB9H8AEWX6e8SFyw7qdNcVc3i9RBY6o1oDWYLuM3", + "privateKeyWIFRegTest": "cMmVYA2egjM6jccYWa3dtR9C4VHLnHj4gXkWpZsh3DT23xZ7Zhgx", + "address": "bitcoincash:qre988ur4gp5k2ph3lmxltqtg4q2zgw6eye0l6k6eg" + }, + { + "privateKeyWIF": "L1wrABy4xHXNct68ZkBsNUfRN3MHvSW4KJPPDbusUCTjPq3zm3AB", + "privateKeyWIFRegTest": "cSJqd6xvPMDdnKZPx9zzjoAUzGehatbkPLXrL2NNyK7jea72uxB7", + "address": "bitcoincash:qzrjsum46ecex9qs28eyhw27ltydu37wzcq3v80ld8" + }, + { + "privateKeyWIF": "L5fQnDhGNYH6qFjh4pgtxYtZ6SSa6PNvqsvXL3Xxc3UTckwo35WU", + "privateKeyWIFRegTest": "cW2QF8h7obyMzhCxTEW2KsPcifjykqUcuv4zSTzU7A8TsW2nQGUh", + "address": "bitcoincash:qqqnmqmpy9vz899ctv4chtx5n857pv9l4ykuea44xz" + } + ] + }, + { + "mnemonic": "energy special regular coin crop auto acquire clinic high liberty educate pride wage grace spoil ugly cable trust", + "output": [ + { + "privateKeyWIF": "Kwv3n87vwRrMbxa4vTWKY9gaAZtthe3NR1iebRTraaVSAwY5ty8Q", + "privateKeyWIFRegTest": "cNH3F37nNVYcmQ3LJsKSuUBdnoCJN694V3s7hqvN5h9SRgYiXW9A", + "address": "bitcoincash:qr3r0yazu6s4ed0ckgpv96zcwx3wt9jy6vv56ywm2w" + }, + { + "privateKeyWIF": "KzucDPxzkExCHTzzJBXo17V7LwRtKj6movuYWmbGW2HjVsFTSBdk", + "privateKeyWIFRegTest": "cRGbgJxrBJeTSuUFgbLvNRzAyAjHzBCTsy41dC3n18wjkcGHbiRX", + "address": "bitcoincash:qqzc32v777yz4qprp0tk443j5j35q5q0xgzuvprgv0" + }, + { + "privateKeyWIF": "Kye3NjRtM2NYtS4kfQgSNK2RvtzUM9mXzy6szZTVii33ETd5jNrU", + "privateKeyWIFRegTest": "cQ12qeRjn64p3sY23pVZjdXVZ8Ht1bsE51FM6yv1Dph3VCj31tVz", + "address": "bitcoincash:qre988ur4gp5k2ph3lmxltqtg4q2zgw6eye0l6k6eg" + }, + { + "privateKeyWIF": "KzZWMxZGgu8cZF2aEaP1sYnh3E4vtCHEKVzY2fWaHG44MFosYmq1", + "privateKeyWIFRegTest": "cQvVpsZ87xpsigVqczC9EsHkfTNLYeNvPY9195y5nNi4bzvNh7Zt", + "address": "bitcoincash:qzrjsum46ecex9qs28eyhw27ltydu37wzcq3v80ld8" + }, + { + "privateKeyWIF": "L4iVLdaMkKvwtZKeD1ishayo2XHGWLm37imjpQFyV4XSkv1XTkGk", + "privateKeyWIFRegTest": "cV5UoYaDBPdD3znubRY14uUrekagAnrjBkvCvpiUzBBT1f5bgdjT", + "address": "bitcoincash:qqqnmqmpy9vz899ctv4chtx5n857pv9l4ykuea44xz" + } + ] + }, + { + "mnemonic": "dwarf all expect planet margin glare tribe hard hybrid cable fish key ordinary equip foil trash surprise carry account shove later", + "output": [ + { + "privateKeyWIF": "KwZ38dBpAk1BY53mmo6tSkt5we3FEAw8nvPE5m3L8iGJvfgCtM1r", + "privateKeyWIFRegTest": "cMv2bYBfbohShWX3ACv1p5P9ZsLetd2prxXhCBVqdpvKBQpZYKBJ", + "address": "bitcoincash:qr3r0yazu6s4ed0ckgpv96zcwx3wt9jy6vv56ywm2w" + }, + { + "privateKeyWIF": "L1VCKD5oq7s5iaWC2ZY79KuRxDJrQ6tjRiB3VaScYsxYD8TxsSan", + "privateKeyWIFRegTest": "cRrBn85fGBZLt1yTQyMEWeQVaScG4YzRVkKWbzu83zcYTsVYovue", + "address": "bitcoincash:qqzc32v777yz4qprp0tk443j5j35q5q0xgzuvprgv0" + }, + { + "privateKeyWIF": "KxojM7WKQqxS1FNKGVWowDPtzMXaXQ7UUsdwhxJFUwE7HxSofqUK", + "privateKeyWIFRegTest": "cPAip2WAquehAgqaeuKwJXtxcapzBrDAYunQpNkkz3t7YhTkSbJy", + "address": "bitcoincash:qre988ur4gp5k2ph3lmxltqtg4q2zgw6eye0l6k6eg" + }, + { + "privateKeyWIF": "KyWdb9m9TJpxqGHkZjHLipQiLUVmhvZT8p49N761RUc2Nq23FBxd", + "privateKeyWIFRegTest": "cPsd44kztNXDzhm1x96U68umxhoBNNf9CrCcUXYWvbG2daC2pDgL", + "address": "bitcoincash:qzrjsum46ecex9qs28eyhw27ltydu37wzcq3v80ld8" + }, + { + "privateKeyWIF": "L33wzMLV6m6DLHPnzuf27ZSjLv8HGG3DCjMdxDDTieDt7P1ih1ic", + "privateKeyWIFRegTest": "cTQwTGLLXpnUVis4PKU9Uswny9Rgvi8uGmW74dfyDkstN874DDMJ", + "address": "bitcoincash:qqqnmqmpy9vz899ctv4chtx5n857pv9l4ykuea44xz" + } + ] + }, + { + "mnemonic": "sibling parent owner rack velvet head boy bicycle exchange gossip despair supply brain office blanket topic slot baby hire rebuild apology radar strike age", + "output": [ + { + "privateKeyWIF": "KyapaU8zwrHxLoqVfwRrF9e5a35DoXhV6C7zTkVnneuLQhMHBd7G", + "privateKeyWIFRegTest": "cPwp3P8rNuzDWFJm4MEycU99CGNdTyoBAEGTaAxJHmZLfSTUM32i", + "address": "bitcoincash:qr3r0yazu6s4ed0ckgpv96zcwx3wt9jy6vv56ywm2w" + }, + { + "privateKeyWIF": "L2WCL7KEJT415BPYBhW3vUp89x1AwFrLfMyjMrbC444N1tFKBhXY", + "privateKeyWIFRegTest": "cSsBo2K5jWkGEcroa7KBHoKBnBJabhx2jQ8CUH3hZAiNGdPAbR1L", + "address": "bitcoincash:qqzc32v777yz4qprp0tk443j5j35q5q0xgzuvprgv0" + }, + { + "privateKeyWIF": "L3Dz8kbwNf6EShtjnpZ65k8RuyD2reMcbZAnFY4GvQjy7CtvtmZX", + "privateKeyWIFRegTest": "cTaybfbnoinVc9N1BENDT4dVYCWSX6TJfbKFMxWnRXPyMwwVTigN", + "address": "bitcoincash:qre988ur4gp5k2ph3lmxltqtg4q2zgw6eye0l6k6eg" + }, + { + "privateKeyWIF": "L3Dy5sJB31JBnRtFZq6QJaKEaijo3wCnvJpjRvr8hYov7LUu8E13", + "privateKeyWIFRegTest": "cTaxYnJ2U4zSwsMWxEuXftpJCx3CiPJUzLyCYMJeCfTvN5VU9AG6", + "address": "bitcoincash:qzrjsum46ecex9qs28eyhw27ltydu37wzcq3v80ld8" + }, + { + "privateKeyWIF": "L4DTuvT3XWFW7vs6gvS88v4vYBMuJK3GjzMBTjD8XkpfJwoeqewD", + "privateKeyWIFRegTest": "cUaTNqStxZwmHNLN5LFFWEZzAQfJxm8xp2Vea9fe2sUfZgtqShVL", + "address": "bitcoincash:qqqnmqmpy9vz899ctv4chtx5n857pv9l4ykuea44xz" + } + ] + } + ], + "findNearestWord": [ + { + "word": "ab", + "foundWord": "abandon", + "language": "english" + }, + { + "word": "octu", + "foundWord": "octupler", + "language": "french" + }, + { + "word": "reda", + "foundWord": "area", + "language": "english" + }, + { + "word": "foobaro", + "foundWord": "forro", + "language": "spanish" + }, + { + "word": "nv", + "foundWord": "neve", + "language": "italian" + } + ] +} diff --git a/test/unit/fixtures/ninsight-mock.js b/test/unit/fixtures/ninsight-mock.js new file mode 100644 index 0000000..aca70ae --- /dev/null +++ b/test/unit/fixtures/ninsight-mock.js @@ -0,0 +1,26 @@ +/* + 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" +} + +module.exports = { + utxo +} diff --git a/test/unit/fixtures/openbazaar-mock.js b/test/unit/fixtures/openbazaar-mock.js new file mode 100644 index 0000000..fad84ce --- /dev/null +++ b/test/unit/fixtures/openbazaar-mock.js @@ -0,0 +1,84 @@ +/* + Unit test mocks for OpenBazaar endpoints. +*/ + +const balance = { + page: 1, + totalPages: 1, + itemsOnPage: 1000, + addrStr: "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9", + balance: "0.00001", + totalReceived: "0.00001", + totalSent: "0", + unconfirmedBalance: "0", + unconfirmedTxApperances: 0, + txApperances: 1, + transactions: [ + "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7" + ] +} + +const utxo = [ + { + txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + vout: 0, + amount: "0.00001", + satoshis: 1000, + height: 602405, + confirmations: 11 + } +] + +const tx = { + txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7", + version: 2, + vin: [ + { + txid: "5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac", + vout: 1, + sequence: 4294967295, + n: 0, + scriptSig: { + hex: + "473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9" + }, + addresses: ["bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe"], + value: "0.00047504" + } + ], + vout: [ + { + value: "0.00001", + n: 0, + scriptPubKey: { + hex: "76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac", + addresses: ["bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9"] + }, + spent: false + }, + { + value: "0.00046256", + n: 1, + scriptPubKey: { + hex: "76a9142dbf5e1804c39a497b908c876097d63210c8490288ac", + addresses: ["bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e"] + }, + spent: false + } + ], + blockhash: "0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5", + blockheight: 602405, + confirmations: 11, + blocktime: 1569792892, + valueOut: "0.00047256", + valueIn: "0.00047504", + fees: "0.00000248", + hex: + "0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000" +} + +module.exports = { + balance, + utxo, + tx +} diff --git a/test/unit/fixtures/script.json b/test/unit/fixtures/script.json new file mode 100644 index 0000000..db95b38 --- /dev/null +++ b/test/unit/fixtures/script.json @@ -0,0 +1,658 @@ +{ + "decodeScriptSig": [ + { + "scriptSigHex": "483045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a6012102fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb", + "cashAddress": "bitcoincash:qqzdnxncgm3v247u5v0gqnglr2vpdhe0hu4wl0rmwt", + "legacyAddress": "1SeP7kmdWTwjFmWiRMpqtSkDpW39zrVLK" + }, + { + "scriptSigHex": "473044022018458971c2e73aec49b1b8848013a51f8e647c73f0ca5d38ab12ecaa0205acbb0220645e4bd59262152e59263f2b385e9af034e8a3edd5459ba616f2c4671331cfce012102d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b", + "cashAddress": "bitcoincash:qzkej6g2zr9c9k83chyqh5pllzv6pkw62ckf2m82ks", + "legacyAddress": "1GpugKfjEycPRhk8A8ALPh1zAJmTQGdJPp" + }, + { + "scriptSigHex": "483045022100fae8546afcb30a5cfc6dee150d3f57fa39959aaa1919eb64dde942f3975c8a4a0220044c01943218817880119e1e6de3806b227d645ce8645186f3b8a93f93fa155d0121028cb053ed1e26b22c3b1354b8d36dde92a2e9387a8b63168246d3b5d20869ccce", + "cashAddress": "bitcoincash:qqz4f6xwz5matms0sef8whspw89a6clvecgwtand6r", + "legacyAddress": "1VC6ZuSpcKcYEQZupNS6yMxddauCuq7dc" + }, + { + "scriptSigHex": "473044022001613ae26fabc34672072e30538a09f5b8965c79803aeb90f8fe74aeca4a84af022048b0b1437e2365663975cc12177dcbddb9458b4d7a2888fffb4bf0a400d3d7360121033c071f19057f11140f80308031fa58b6e7d8f9e889252805cae6a29c1248f843", + "cashAddress": "bitcoincash:qqh7evl4w7h6lsu7uvakvadm0z5hj535lqk098l670", + "legacyAddress": "15NQKRj7rAVsFL5rWMmhkAgx2K1dYoHoDW" + }, + { + "scriptSigHex": "483045022100f7be5e42327b41c301bd0e66cf70efcf4692856f6559284607da34c3b23d7c87022016685a280e5d65464f97374025b072e6014ac3cdef10c0fd6723583869f8600b0121024933d7cc962415fda8249d6ee6820009fb6f0cf592385d91f8d9f39de507b7e1", + "cashAddress": "bitcoincash:qq4g2nmk64wt7a2mx85cl07n45nkh2p7lcytfgmqdl", + "legacyAddress": "14sq5dL1TeFdQJpj3vVMQvjrdmQoqVoM6m" + } + ], + "decodeScriptPubKey": [ + { + "scriptPubKeyHex": "76a91424e9c07804d0ee7e5bda934e0a3ae8710fc007dd88ac", + "pubKeyHex": "02a4766c2b0330a1e1c2cdc0b2f5df288bbfed3744bc2ac629ddc898599ec5e8ba" + }, + { + "scriptPubKeyHex": "76a914ad8b8d8c2d3626ac744a84328c8afd20ceb717c488ac", + "pubKeyHex": "031f3a008f31d050a3a0656fe75f25b2672d6e51d333f94476dba0186606fb60ae" + }, + { + "scriptPubKeyHex": "76a9144fc7e213e4c475492f8c801012a2be3dbb62a4bf88ac", + "pubKeyHex": "03a0ef9f3169f53511fd860823549a2de2307ac5b9c19b5bd8a0d24462f9b71900" + }, + { + "scriptPubKeyHex": "76a914c22a85d87a749419e429d4060b1c3f7f86e3e95888ac", + "pubKeyHex": "03520d443e292c2cc6934e9dfc682326dffcf680b72fcc76e4f5f38b40c3ec4e26" + }, + { + "scriptPubKeyHex": "76a9143edc53ca0723a3d3c2c6c8dc75f38262f82af71f88ac", + "pubKeyHex": "036effd263eb7b3f48a0a26920a8972c711af30606bf3a651843455e91cf385cf0" + } + ], + "encodeScriptSig": [ + { + "scriptSigChunks": [ + "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601", + "02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + ] + }, + { + "scriptSigChunks": [ + "3044022018458971c2e73aec49b1b8848013a51f8e647c73f0ca5d38ab12ecaa0205acbb0220645e4bd59262152e59263f2b385e9af034e8a3edd5459ba616f2c4671331cfce01", + "02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b" + ] + }, + { + "scriptSigChunks": [ + "3045022100fae8546afcb30a5cfc6dee150d3f57fa39959aaa1919eb64dde942f3975c8a4a0220044c01943218817880119e1e6de3806b227d645ce8645186f3b8a93f93fa155d01", + "028cb053ed1e26b22c3b1354b8d36dde92a2e9387a8b63168246d3b5d20869ccce" + ] + }, + { + "scriptSigChunks": [ + "3044022001613ae26fabc34672072e30538a09f5b8965c79803aeb90f8fe74aeca4a84af022048b0b1437e2365663975cc12177dcbddb9458b4d7a2888fffb4bf0a400d3d73601", + "033c071f19057f11140f80308031fa58b6e7d8f9e889252805cae6a29c1248f843" + ] + }, + { + "scriptSigChunks": [ + "3045022100f7be5e42327b41c301bd0e66cf70efcf4692856f6559284607da34c3b23d7c87022016685a280e5d65464f97374025b072e6014ac3cdef10c0fd6723583869f8600b01", + "024933d7cc962415fda8249d6ee6820009fb6f0cf592385d91f8d9f39de507b7e1" + ] + } + ], + "encodeScriptPubKey": [ + { + "scriptPubKeyHex": "76a91424e9c07804d0ee7e5bda934e0a3ae8710fc007dd88ac" + }, + { + "scriptPubKeyHex": "76a914ad8b8d8c2d3626ac744a84328c8afd20ceb717c488ac" + }, + { + "scriptPubKeyHex": "76a9144fc7e213e4c475492f8c801012a2be3dbb62a4bf88ac" + }, + { + "scriptPubKeyHex": "76a914c22a85d87a749419e429d4060b1c3f7f86e3e95888ac" + }, + { + "scriptPubKeyHex": "76a9143edc53ca0723a3d3c2c6c8dc75f38262f82af71f88ac" + } + ], + "scriptSigToASM": [ + { + "scriptSigChunks": [ + "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601", + "02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + ], + "asm": "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601 02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + }, + { + "scriptSigChunks": [ + "3045022100baaa13f933f528721972fd8ffc685eb8017e33aa30822bb6bd462a7cf5e7a2fa0220239be52243487b2d8d619f7076a75d3f90bbbe0325a0cf4ef5f12187996b5f84", + "037eb440a8f01b18c681aa0193570980949dce19ad3767c2bdd6381e912b2004b9" + ], + "asm": "3045022100baaa13f933f528721972fd8ffc685eb8017e33aa30822bb6bd462a7cf5e7a2fa0220239be52243487b2d8d619f7076a75d3f90bbbe0325a0cf4ef5f12187996b5f84 037eb440a8f01b18c681aa0193570980949dce19ad3767c2bdd6381e912b2004b9" + }, + { + "scriptSigChunks": [ + "304402206d063e9b9e10ac373ac18ce6c11aa590661970309e4e89158fc4852460da554c022037b54465c4ba407b17d83a2f207ecfa6b1966fe17ff12afd6c865cdab3a6a5c9", + "025836f9a2024a2dd3dbb808dca3a30eb1cef72e38cd70d86fdffbfb05ac52d613" + ], + "asm": "304402206d063e9b9e10ac373ac18ce6c11aa590661970309e4e89158fc4852460da554c022037b54465c4ba407b17d83a2f207ecfa6b1966fe17ff12afd6c865cdab3a6a5c9 025836f9a2024a2dd3dbb808dca3a30eb1cef72e38cd70d86fdffbfb05ac52d613" + }, + { + "scriptSigChunks": [ + "304402201bf30e8c74afdf1a81a5c9e36ee25ea9c0ed0da133aaca604e960039499cfc50022023ca25a688d9792d3d289c3bbf6fe324dabce956f664e6a0c8bfdfd60962d281", + "038d594622ea23f7c105770bdad52f86a5854ec7809805a16b861d659dbe4248fd" + ], + "asm": "304402201bf30e8c74afdf1a81a5c9e36ee25ea9c0ed0da133aaca604e960039499cfc50022023ca25a688d9792d3d289c3bbf6fe324dabce956f664e6a0c8bfdfd60962d281 038d594622ea23f7c105770bdad52f86a5854ec7809805a16b861d659dbe4248fd" + }, + { + "scriptSigChunks": [ + "3044022044134e2b5e17337a145efde74324754ac02029f6059646365a4974d79b8bb9aa02205e73d44349b8e04dba8154abcfcf318f4e74dfe083cf280664786e87d829868a", + "034a72f4e88f47c3ea10b7949b83555f5553fa2358b3f1821ab0b4894e7a039cc5" + ], + "asm": "3044022044134e2b5e17337a145efde74324754ac02029f6059646365a4974d79b8bb9aa02205e73d44349b8e04dba8154abcfcf318f4e74dfe083cf280664786e87d829868a 034a72f4e88f47c3ea10b7949b83555f5553fa2358b3f1821ab0b4894e7a039cc5" + }, + { + "scriptSigChunks": [ + "304502210096478f7b5758bf5f1753a7c3624f727f3251f5c94c107f253a49eff5952e58bc022005d33eaa3f0c172561b40bd2d9e2890ddbbc6a96b22961e7f070687acba82d0e", + "020a50e91df19b66d0fecb509f5983fe9c53ceb78bab2967a3a31e6145a3c95639" + ], + "asm": "304502210096478f7b5758bf5f1753a7c3624f727f3251f5c94c107f253a49eff5952e58bc022005d33eaa3f0c172561b40bd2d9e2890ddbbc6a96b22961e7f070687acba82d0e 020a50e91df19b66d0fecb509f5983fe9c53ceb78bab2967a3a31e6145a3c95639" + } + ], + "scriptPubKeyToASM": [ + { + "scriptPubKeyHex": "76a914bee4182d9fbc8931a728410a0cd3e0f340f2995a88ac", + "asm": "OP_DUP OP_HASH160 bee4182d9fbc8931a728410a0cd3e0f340f2995a OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a9145fdffba08fa0289af38bc43239d5dc31879b432288ac", + "asm": "OP_DUP OP_HASH160 5fdffba08fa0289af38bc43239d5dc31879b4322 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a91414343842516b4af7d8bdb5f16195f9421898cf0f88ac", + "asm": "OP_DUP OP_HASH160 14343842516b4af7d8bdb5f16195f9421898cf0f OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a914dd4eb9879eb4901d5a0fd0493f1d684eeae43a4688ac", + "asm": "OP_DUP OP_HASH160 dd4eb9879eb4901d5a0fd0493f1d684eeae43a46 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a914633039b5adf5e4723cb4d41276eac2fd43178e3e88ac", + "asm": "OP_DUP OP_HASH160 633039b5adf5e4723cb4d41276eac2fd43178e3e OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "scriptSigFromASM": [ + { + "scriptSigChunks": [ + "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601", + "02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + ], + "asm": "3045022100877e2f9c28421f0a850cc8ff66ba1d0f6c8dbe9e63e199c2c2600c9c15bf9d4402204d35b13d3cc202aa25722b2b1791442ebc5c39d898b609515260ad08f0e766a601 02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb" + }, + { + "scriptSigChunks": [ + "3045022100baaa13f933f528721972fd8ffc685eb8017e33aa30822bb6bd462a7cf5e7a2fa0220239be52243487b2d8d619f7076a75d3f90bbbe0325a0cf4ef5f12187996b5f84", + "037eb440a8f01b18c681aa0193570980949dce19ad3767c2bdd6381e912b2004b9" + ], + "asm": "3045022100baaa13f933f528721972fd8ffc685eb8017e33aa30822bb6bd462a7cf5e7a2fa0220239be52243487b2d8d619f7076a75d3f90bbbe0325a0cf4ef5f12187996b5f84 037eb440a8f01b18c681aa0193570980949dce19ad3767c2bdd6381e912b2004b9" + }, + { + "scriptSigChunks": [ + "304402206d063e9b9e10ac373ac18ce6c11aa590661970309e4e89158fc4852460da554c022037b54465c4ba407b17d83a2f207ecfa6b1966fe17ff12afd6c865cdab3a6a5c9", + "025836f9a2024a2dd3dbb808dca3a30eb1cef72e38cd70d86fdffbfb05ac52d613" + ], + "asm": "304402206d063e9b9e10ac373ac18ce6c11aa590661970309e4e89158fc4852460da554c022037b54465c4ba407b17d83a2f207ecfa6b1966fe17ff12afd6c865cdab3a6a5c9 025836f9a2024a2dd3dbb808dca3a30eb1cef72e38cd70d86fdffbfb05ac52d613" + }, + { + "scriptSigChunks": [ + "304402201bf30e8c74afdf1a81a5c9e36ee25ea9c0ed0da133aaca604e960039499cfc50022023ca25a688d9792d3d289c3bbf6fe324dabce956f664e6a0c8bfdfd60962d281", + "038d594622ea23f7c105770bdad52f86a5854ec7809805a16b861d659dbe4248fd" + ], + "asm": "304402201bf30e8c74afdf1a81a5c9e36ee25ea9c0ed0da133aaca604e960039499cfc50022023ca25a688d9792d3d289c3bbf6fe324dabce956f664e6a0c8bfdfd60962d281 038d594622ea23f7c105770bdad52f86a5854ec7809805a16b861d659dbe4248fd" + }, + { + "scriptSigChunks": [ + "3044022044134e2b5e17337a145efde74324754ac02029f6059646365a4974d79b8bb9aa02205e73d44349b8e04dba8154abcfcf318f4e74dfe083cf280664786e87d829868a", + "034a72f4e88f47c3ea10b7949b83555f5553fa2358b3f1821ab0b4894e7a039cc5" + ], + "asm": "3044022044134e2b5e17337a145efde74324754ac02029f6059646365a4974d79b8bb9aa02205e73d44349b8e04dba8154abcfcf318f4e74dfe083cf280664786e87d829868a 034a72f4e88f47c3ea10b7949b83555f5553fa2358b3f1821ab0b4894e7a039cc5" + }, + { + "scriptSigChunks": [ + "304502210096478f7b5758bf5f1753a7c3624f727f3251f5c94c107f253a49eff5952e58bc022005d33eaa3f0c172561b40bd2d9e2890ddbbc6a96b22961e7f070687acba82d0e", + "020a50e91df19b66d0fecb509f5983fe9c53ceb78bab2967a3a31e6145a3c95639" + ], + "asm": "304502210096478f7b5758bf5f1753a7c3624f727f3251f5c94c107f253a49eff5952e58bc022005d33eaa3f0c172561b40bd2d9e2890ddbbc6a96b22961e7f070687acba82d0e 020a50e91df19b66d0fecb509f5983fe9c53ceb78bab2967a3a31e6145a3c95639" + } + ], + "scriptPubKeyFromASM": [ + { + "scriptPubKeyHex": "76a914bee4182d9fbc8931a728410a0cd3e0f340f2995a88ac", + "asm": "OP_DUP OP_HASH160 bee4182d9fbc8931a728410a0cd3e0f340f2995a OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a9145fdffba08fa0289af38bc43239d5dc31879b432288ac", + "asm": "OP_DUP OP_HASH160 5fdffba08fa0289af38bc43239d5dc31879b4322 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a91414343842516b4af7d8bdb5f16195f9421898cf0f88ac", + "asm": "OP_DUP OP_HASH160 14343842516b4af7d8bdb5f16195f9421898cf0f OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a914dd4eb9879eb4901d5a0fd0493f1d684eeae43a4688ac", + "asm": "OP_DUP OP_HASH160 dd4eb9879eb4901d5a0fd0493f1d684eeae43a46 OP_EQUALVERIFY OP_CHECKSIG" + }, + { + "scriptPubKeyHex": "76a914633039b5adf5e4723cb4d41276eac2fd43178e3e88ac", + "asm": "OP_DUP OP_HASH160 633039b5adf5e4723cb4d41276eac2fd43178e3e OP_EQUALVERIFY OP_CHECKSIG" + } + ], + "opcodes": { + "OP_FALSE": 0, + "OP_0": 0, + "OP_PUSHDATA1": 76, + "OP_PUSHDATA2": 77, + "OP_PUSHDATA4": 78, + "OP_1NEGATE": 79, + "OP_RESERVED": 80, + "OP_TRUE": 81, + "OP_1": 81, + "OP_2": 82, + "OP_3": 83, + "OP_4": 84, + "OP_5": 85, + "OP_6": 86, + "OP_7": 87, + "OP_8": 88, + "OP_9": 89, + "OP_10": 90, + "OP_11": 91, + "OP_12": 92, + "OP_13": 93, + "OP_14": 94, + "OP_15": 95, + "OP_16": 96, + "OP_NOP": 97, + "OP_VER": 98, + "OP_IF": 99, + "OP_NOTIF": 100, + "OP_VERIF": 101, + "OP_VERNOTIF": 102, + "OP_ELSE": 103, + "OP_ENDIF": 104, + "OP_VERIFY": 105, + "OP_RETURN": 106, + "OP_TOALTSTACK": 107, + "OP_FROMALTSTACK": 108, + "OP_2DROP": 109, + "OP_2DUP": 110, + "OP_3DUP": 111, + "OP_2OVER": 112, + "OP_2ROT": 113, + "OP_2SWAP": 114, + "OP_IFDUP": 115, + "OP_DEPTH": 116, + "OP_DROP": 117, + "OP_DUP": 118, + "OP_NIP": 119, + "OP_OVER": 120, + "OP_PICK": 121, + "OP_ROLL": 122, + "OP_ROT": 123, + "OP_SWAP": 124, + "OP_TUCK": 125, + "OP_CAT": 126, + "OP_SPLIT": 127, + "OP_NUM2BIN": 128, + "OP_BIN2NUM": 129, + "OP_SIZE": 130, + "OP_INVERT": 131, + "OP_AND": 132, + "OP_OR": 133, + "OP_XOR": 134, + "OP_EQUAL": 135, + "OP_EQUALVERIFY": 136, + "OP_RESERVED1": 137, + "OP_RESERVED2": 138, + "OP_1ADD": 139, + "OP_1SUB": 140, + "OP_2MUL": 141, + "OP_2DIV": 142, + "OP_NEGATE": 143, + "OP_ABS": 144, + "OP_NOT": 145, + "OP_0NOTEQUAL": 146, + "OP_ADD": 147, + "OP_SUB": 148, + "OP_MUL": 149, + "OP_DIV": 150, + "OP_MOD": 151, + "OP_LSHIFT": 152, + "OP_RSHIFT": 153, + "OP_BOOLAND": 154, + "OP_BOOLOR": 155, + "OP_NUMEQUAL": 156, + "OP_NUMEQUALVERIFY": 157, + "OP_NUMNOTEQUAL": 158, + "OP_LESSTHAN": 159, + "OP_GREATERTHAN": 160, + "OP_LESSTHANOREQUAL": 161, + "OP_GREATERTHANOREQUAL": 162, + "OP_MIN": 163, + "OP_MAX": 164, + "OP_WITHIN": 165, + "OP_RIPEMD160": 166, + "OP_SHA1": 167, + "OP_SHA256": 168, + "OP_HASH160": 169, + "OP_HASH256": 170, + "OP_CODESEPARATOR": 171, + "OP_CHECKSIG": 172, + "OP_CHECKSIGVERIFY": 173, + "OP_CHECKMULTISIG": 174, + "OP_CHECKMULTISIGVERIFY": 175, + "OP_NOP1": 176, + "OP_NOP2": 177, + "OP_CHECKLOCKTIMEVERIFY": 177, + "OP_NOP3": 178, + "OP_CHECKSEQUENCEVERIFY": 178, + "OP_NOP4": 179, + "OP_NOP5": 180, + "OP_NOP6": 181, + "OP_NOP7": 182, + "OP_NOP8": 183, + "OP_NOP9": 184, + "OP_NOP10": 185, + "OP_PUBKEYHASH": 253, + "OP_PUBKEY": 254, + "OP_INVALIDOPCODE": 255 + }, + "classifyInput": [ + { + "script": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "type": "pubkey" + }, + { + "script": "304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f41", + "type": "pubkey" + }, + { + "script": "3044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a1077438441", + "type": "pubkey" + }, + { + "script": "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441", + "type": "pubkey" + }, + { + "script": "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441", + "type": "pubkey" + }, + { + "script": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "type": "pubkeyhash" + }, + { + "script": "304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f41 022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3", + "type": "pubkeyhash" + }, + { + "script": "3044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a1077438441 024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0", + "type": "pubkeyhash" + }, + { + "script": "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441 02969479fa9bea3082697dce683ac05b13ae63016b41d5ca1a450ad40f6c543751", + "type": "pubkeyhash" + }, + { + "script": "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441 0259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15b", + "type": "pubkeyhash" + }, + { + "script": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "type": "multisig" + }, + { + "script": "OP_0 3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441", + "type": "multisig" + }, + { + "script": "OP_0 3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01 3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901 3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01", + "type": "multisig" + }, + { + "script": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501 522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae", + "type": "scripthash" + } + ], + "classifyOutput": [ + { + "script": "OP_RETURN 5454", + "type": "nulldata" + }, + { + "script": "OP_RETURN 877 8106baaeb4f1b3f7f2558b788be6cb62d76b5ea8eba64a05a509edd504d76fb7 f09fa496203120424348203d203536312e30322420f09f938820362e3533250a54697073203d206675656cefb88f", + "type": "nulldata" + }, + { + "script": "OP_RETURN 424348466f7245766572796f6e65", + "type": "nulldata" + }, + { + "script": "OP_RETURN 4361726c6f73204761627269656c20436172646f6e61", + "type": "nulldata" + }, + { + "script": "OP_RETURN 5361746f736869204e616b616d6f746f", + "type": "nulldata" + }, + { + "script": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 OP_CHECKSIG", + "type": "pubkey" + }, + { + "script": "02d9bb8da1de26d390b6f3dcb4e589857730536b646995fa948a8319ede2ca1c15 OP_CHECKSIG", + "type": "pubkey" + }, + { + "script": "022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3 OP_CHECKSIG", + "type": "pubkey" + }, + { + "script": "024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0 OP_CHECKSIG", + "type": "pubkey" + }, + { + "script": "0259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15b OP_CHECKSIG", + "type": "pubkey" + }, + { + "script": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "type": "pubkeyhash" + }, + { + "script": "OP_DUP OP_HASH160 4a7955c1000a13d0de0b782026e7c277df4456a2 OP_EQUALVERIFY OP_CHECKSIG", + "type": "pubkeyhash" + }, + { + "script": "OP_DUP OP_HASH160 e8e3dccea89523c7a5fc0ad3b27e4f62b425089e OP_EQUALVERIFY OP_CHECKSIG", + "type": "pubkeyhash" + }, + { + "script": "OP_DUP OP_HASH160 0b2bc35f9142d34783cad481b08b163c9ad2c219 OP_EQUALVERIFY OP_CHECKSIG", + "type": "pubkeyhash" + }, + { + "script": "OP_DUP OP_HASH160 0ab4c51af69d6feab0d29cf37c5078e353b39b8a OP_EQUALVERIFY OP_CHECKSIG", + "type": "pubkeyhash" + }, + { + "script": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "type": "multisig" + }, + { + "script": "OP_3 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a 022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3 OP_3 OP_CHECKMULTISIG", + "type": "multisig" + }, + { + "script": "OP_3 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340 024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34 OP_3 OP_CHECKMULTISIG", + "type": "multisig" + }, + { + "script": "OP_HASH160 722ff0bc2c3f47b35c20df646c395594da24e90e OP_EQUAL", + "type": "scripthash" + } + ], + "nullDataTemplate": [ + { + "data": "BCHForEveryone", + "hex": "6a0e424348466f7245766572796f6e65" + }, + { + "data": "Carlos Gabriel Cardona", + "hex": "6a164361726c6f73204761627269656c20436172646f6e61" + }, + { + "data": "Satoshi Nakamoto", + "hex": "6a105361746f736869204e616b616d6f746f" + } + ], + "pubKeyInputTemplate": [ + { + "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "hex": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801" + }, + { + "signature": "304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f41", + "hex": "48304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f41" + }, + { + "signature": "3044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a1077438441", + "hex": "473044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a1077438441" + }, + { + "signature": "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441", + "hex": "4730440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441" + }, + { + "signature": "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441", + "hex": "483045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441" + } + ], + "pubKeyOutputTemplate": [ + { + "pubKey": "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "hex": "2102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1ac" + }, + { + "hex": "2102d9bb8da1de26d390b6f3dcb4e589857730536b646995fa948a8319ede2ca1c15ac", + "pubKey": "02d9bb8da1de26d390b6f3dcb4e589857730536b646995fa948a8319ede2ca1c15" + }, + { + "hex": "21022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3ac", + "pubKey": "022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3" + }, + { + "hex": "21024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0ac", + "pubKey": "024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0" + }, + { + "hex": "210259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15bac", + "pubKey": "0259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15b" + } + ], + "pubKeyHashInputTemplate": [ + { + "signature": "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "pubKey": "02d9bb8da1de26d390b6f3dcb4e589857730536b646995fa948a8319ede2ca1c15", + "hex": "47304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28012102d9bb8da1de26d390b6f3dcb4e589857730536b646995fa948a8319ede2ca1c15" + }, + { + "signature": "304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f41", + "pubKey": "022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3", + "hex": "48304502210080da7c89e203c0fbfd28ce88508aaa2af81e38136e6dbf6d884b53842eac84ca02206e0edbf92c535273368a4cf8d1da359661972e03e6957ddadeafa1834e727b1f4121022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3" + }, + { + "signature": "3044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a1077438441", + "pubKey": "024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0", + "hex": "473044022063136391ad2bc25f256038a11b4816ca6e601887eeb96c9fe93874e8688f70e90220709ce5a856c68d8bf293fd1e49bd4027f841cde4d43b2c2fc2795d0a107743844121024e61dcf27b337780b14dcf5ae5976a16bb37ac1caa901c9cc0db9b822924cef0" + }, + { + "signature": "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441", + "pubKey": "02969479fa9bea3082697dce683ac05b13ae63016b41d5ca1a450ad40f6c543751", + "hex": "4730440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab4412102969479fa9bea3082697dce683ac05b13ae63016b41d5ca1a450ad40f6c543751" + }, + { + "signature": "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441", + "pubKey": "0259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15b", + "hex": "483045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441210259a3f18d4982fe1afc9e11ee4b376b42297e1581932e643d384314b1605ab15b" + } + ], + "pubKeyHashOutputTemplate": [ + { + "xpriv": "xprv9xoxVbZ7L8jmvKx7e1hgd7muo8H35ysTx1LCKFey5nVHUkHSPBxpzBzt2HVK16hu4m6oN5vfaCWSZQvqtDhfJTCY3t9ocp7H7zcTZ2fVRwL", + "hex": "76a9146ee7ded4f9d0deb6f4a63d68df5ccc4e41ad896788ac" + }, + { + "xpriv": "xprv9xoxVbZ7L8jn7w5z3b2SX6f7psRBusWmoYGRAG3AGqzK9Ar3pvZwC9b1i9tcye39WN8d4T8QY6wJNrgdWe44yheUMC7B369mkme6ftJ9rNs", + "hex": "76a9144a7955c1000a13d0de0b782026e7c277df4456a288ac" + }, + { + "xpriv": "xprv9xoxVbZ7L8jnMEqy9ezK6ad1fxGDvs6YEHkp82jeh8b1AAkVY1dUP7VkhxV3ztq5iBeGdKGVMcPNDBefRGxok3uqZ2SK2rQMVuEymMoUwdA", + "hex": "76a914e8e3dccea89523c7a5fc0ad3b27e4f62b425089e88ac" + }, + { + "xpriv": "tprv8guMMTZV3RDBBDqCiK9VuiZYqn7ZZwnyuhPyDHNaCBHeCFouhK62G9CYJokwdFNB3iE1Y7NBtbaH1fp5aL7quCt41i4c77vbfyF5ij2Pn7E", + "hex": "76a9140b2bc35f9142d34783cad481b08b163c9ad2c21988ac" + }, + { + "xpriv": "tprv8guMMTZV3RDBCEpSAqXFdzXPdhnoWFXjEwnAkT1sjwatYsaFAsfZ22mXJrSjsStbKgTV7fjmhVdLuGmC877JTrHz4fnvPCaioqes4V3HTZ5", + "hex": "76a9140ab4c51af69d6feab0d29cf37c5078e353b39b8a88ac" + } + ], + "multisigInputTemplate": [ + { + "signatures": [ + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + ], + "hex": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501" + }, + { + "signatures": [ + "3045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c6441", + "304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801", + "30440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441" + ], + "hex": "00483045022100ba2c3b717e023966cb16df65ca83f77029e2a5b80c47c47b6956474ac9ff281302201d48ee3292439e284a6654a0e79ac2b8f7fff5c6b0d715260aa296501a239c644147304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca28014730440220280d4a9954c5afe24089bdd545466bd7a8caad8b295e30de9d3cb5e56fccf64e022036663b2c53b5fac674b4b935b53e2a4ea88dfc71c9b879870976d82887542ab441" + }, + { + "signatures": [ + "3045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01", + "3044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901", + "3045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + ], + "hex": "00483045022100fe324541215798b2df68cbd44039615e23c506d4ec1a05572064392a98196b82022068c849fa6699206da2fc6d7848efc1d3804a5816d6293615fe34c1a7f34e1c2f01473044022001ab168e80b863fdec694350b587339bb72a37108ac3c989849251444d13ebba02201811272023e3c1038478eb972a82d3ad431bfc2408e88e4da990f1a7ecbb263901483045022100aaeb7204c17eee2f2c4ff1c9f8b39b79e75e7fbf33e92cc67ac51be8f15b75f90220659eee314a4943a6384d2b154fa5821ef7a084814d7ee2c6f9f7f0ffb53be34b01" + } + ], + "multisigOutputTemplate": [ + { + "pubKeys": [ + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a" + ], + "hex": "522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + }, + { + "pubKeys": [ + "02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1", + "0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a", + "022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f3" + ], + "hex": "532102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a21022d426ef365d6480b127b4980afa4b9415cad5e6f0a9e11b1536d3523597197f353ae" + }, + { + "pubKeys": [ + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "02b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f848340", + "024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a34" + ], + "hex": "53210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817982102b80011a883a0fd621ad46dfc405df1e74bf075cbaf700fd4aebef6e96f84834021024289801366bcee6172b771cf5a7f13aaecd237a0b9a1ff9d769cabc2e6b70a3453ae" + } + ], + "scriptHashInputTemplate": [ + { + "redeemScript": "OP_2 02359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1 0395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a OP_2 OP_CHECKMULTISIG", + "redeemScriptSig": "OP_0 304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801 3045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d140501", + "hex": "0047304402207515cf147d201f411092e6be5a64a6006f9308fad7b2a8fdaab22cd86ce764c202200974b8aca7bf51dbf54150d3884e1ae04f675637b926ec33bf75939446f6ca2801483045022100ef253c1faa39e65115872519e5f0a33bbecf430c0f35cf562beabbad4da24d8d02201742be8ee49812a73adea3007c9641ce6725c32cd44ddb8e3a3af460015d14050147522102359c6e3f04cefbf089cf1d6670dc47c3fb4df68e2bad1fa5a369f9ce4b42bbd1210395a9d84d47d524548f79f435758c01faec5da2b7e551d3b8c995b7e06326ae4a52ae" + } + ], + "scriptHashOutputTemplate": [ + { + "output": "OP_DUP OP_HASH160 aa4d7985c57e011a8b3dd8e0e5a73aaef41629c5 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "a9141b61ebed0c2a16c699a99c3d5ef4d08de7fb1cb887" + } + ] +} diff --git a/test/unit/fixtures/slp/address.json b/test/unit/fixtures/slp/address.json new file mode 100644 index 0000000..6959092 --- /dev/null +++ b/test/unit/fixtures/slp/address.json @@ -0,0 +1,93 @@ +{ + "mainnet": { + "legacyP2PKH": [ + "18xHZ8g2feo4ceejGpvzHkvXT79fi2ZdTG", + "1K7Qb1dWkiYwPrZhLws3uUKhxKEU7dRnbQ", + "1Au7gWXS2zAgFSdWwT5QnTNeasye16kxoG", + "174uecjfRgh1XkVBYFzZymkV1JwmFQ91s9", + "1KLXUPMdq4vaU3VoQzSvLUx2mub5qzFkTc" + ], + "legacyP2SH": [ + "3DA6RBcFgLwLTpnF6BRAee8w6a9H6JQLCm", + "3AbtU1JSaiQijyGT21stxpBRZj1hixWcGB", + "3KfgmLeczB525pV2tJLQ6RM5qFMLaB2Kn1", + "3Bsr5dvAJ2Q8CpHxJSZkNdgD12Tkb9TaR7", + "3J78iYD4i4ht8Btp8pyRx71jg5yrRTkQaM" + ], + "cashAddressP2PKH": [ + "bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh", + "bitcoincash:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsn9h5vuvr", + "bitcoincash:qpkfg4kck99wksyss6nvaqtafeahfnyrpsj0ed372t", + "bitcoincash:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvy0c2jvx7", + "bitcoincash:qryj8x4s7vfsc864jm0xaak9qfe8qgk245y9ska57l" + ], + "cashAddressP2SH": [ + "bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug", + "bitcoincash:ppsup5akyvql5w46q9eszd9fxpx970acpyqkw79vq2", + "bitcoincash:prznrkhnf6zqsap6l664ayzu2xue67ue4gv686sjyu", + "bitcoincash:pphmm80pznl6pnkmzakz3ahafydmvhwzcslea4v5mz", + "bitcoincash:pz6pr25g6mulp0kes9xnmsda0u4rf442ase2un89pl" + ], + "slpAddressP2PKH": [ + "simpleledger:qptnmya5wkly7xf97wm5ak23yqdsz3l2cy79a7ey6f", + "simpleledger:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsl7u0euja", + "simpleledger:qpkfg4kck99wksyss6nvaqtafeahfnyrps75jky754", + "simpleledger:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvg5n38vcq", + "simpleledger:qryj8x4s7vfsc864jm0xaak9qfe8qgk245g7mdg5qp" + ], + "slpAddressP2SH": [ + "simpleledger:pp7ushdxf5we8mcpaa3wqgsuqt639cu59u00d8pfzk", + "simpleledger:ppsup5akyvql5w46q9eszd9fxpx970acpyvd99sv75", + "simpleledger:prznrkhnf6zqsap6l664ayzu2xue67ue4gqpvp9j6z", + "simpleledger:pphmm80pznl6pnkmzakz3ahafydmvhwzcsnzkwe59u", + "simpleledger:pz6pr25g6mulp0kes9xnmsda0u4rf442as43hgj9lp" + ], + "slpTxids": [ + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + ] + }, + "testnet": { + "legacyP2PKH": [ + "mhTg9sgNgvAGfmJs192oUzQWqAXHH5nqLE", + "mmpjQ24UyGGfJ39k44prH6W2A7y1qVaQti", + "muEnSveRwu8cEJSvXzDTJZQWrr7y9i331i", + "n3jZn7rb3Bjepap4TWo8pyqcwuxAw439HW", + "mqHUen3SUNjQkAPjMxZfSnuyd8cmcbL6fq" + ], + "legacyP2SH": [ + "2N7euSnwuJP93QmK5TD4AkWmaTfBbBLrxUs", + "2N38BPRHX4ejA7QJEfYGikw2PM7QRaVnFg3", + "2N46fAZgsFSwKxM5reRdQCmmtAXaotVqakP", + "2MzFSj4mQM3bCFYEKmhxSecuoPA3aziudov", + "2N4Y11CqifLKEDfCFRHa6Dt36X9BKCP2K2o" + ], + "cashAddressP2PKH": [ + "bchtest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u22nzt946", + "bchtest:qpzj67wmlsq8uttddddapjjawyusureca59ug9cak8", + "bchtest:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5m6kyejfd", + "bchtest:qrem2cg43ksmvlampheur8gfgdhhk57mygy26y7f2e", + "bchtest:qp4jf3n740kffkladul96xnq5dtrflg4x5w5rfy22r" + ], + "cashAddressP2SH": [ + "bchtest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqh2hmtpyr", + "bchtest:ppk9ctxq9feg56vmwyznlgqs8sk6astknq75a27y9c", + "bchtest:ppms42pganhr9fqnkfz7xnwcj7emxng3u5526prj59", + "bchtest:ppxd8qmgse5h2gy03vf7swxg4rrfpxckx5mnszkjw8", + "bchtest:ppaat8w24kdu74782fu8jr27k42dwhem45ycrhxgae" + ], + "slpAddressP2PKH": [ + "slptest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u375e3j88", + "slptest:qpzj67wmlsq8uttddddapjjawyusureca57g07z2y6", + "slptest:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5qw3lr9ms", + "slptest:qrem2cg43ksmvlampheur8gfgdhhk57mygl7aly7cy", + "slptest:qp4jf3n740kffkladul96xnq5dtrflg4x54qyj7ac7" + ], + "slpAddressP2SH": [ + "slptest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqv7sq3kk7", + "slptest:ppk9ctxq9feg56vmwyznlgqs8sk6astknq9q63ynh9", + "slptest:ppms42pganhr9fqnkfz7xnwcj7emxng3u507a6e9xc", + "slptest:ppxd8qmgse5h2gy03vf7swxg4rrfpxckx5q8hev9u6", + "slptest:ppaat8w24kdu74782fu8jr27k42dwhem45lvyvul0y" + ] + } +} diff --git a/test/unit/fixtures/slp/ecpair.json b/test/unit/fixtures/slp/ecpair.json new file mode 100644 index 0000000..d4c9f52 --- /dev/null +++ b/test/unit/fixtures/slp/ecpair.json @@ -0,0 +1,10 @@ +{ + "wif": [ + "cUCSrdhu7mCzx4sWqL6irqzprkofxPmLHYgkSnG2WaWVqJDXtWRS", + "cNVP2nTzUMFerfpjrTuDgoFGnKAfjZznKomknUVKQSdFHqK5cRc5" + ], + "address": [ + "slptest:qq835u5srlcqwrtwt6xm4efwan30fxg9hcqag6fk03", + "slptest:qrj9k49drcsk4al8wxn53hnkfvts6ew5jvv32952nh" + ] +} diff --git a/test/unit/fixtures/slp/mock-utils.js b/test/unit/fixtures/slp/mock-utils.js new file mode 100644 index 0000000..8e145f0 --- /dev/null +++ b/test/unit/fixtures/slp/mock-utils.js @@ -0,0 +1,1135 @@ +/* + Contains mocked data used by unit tests. +*/ + +const mockList = [ + { + decimals: 0, + timestamp: "2019-04-29 08:59", + timestampUnix: 1539218362, + versionType: 1, + documentUri: "", + symbol: "WMW", + name: "WheresMyWallet", + containsBaton: false, + id: "8fc284dcbc922f7bb7e2a443dc3af792f52923bba403fcf67ca028c88e89da0e", + documentHash: null, + initialTokenQty: 1000, + blockCreated: 580336, + blockLastActiveSend: 580336, + blockLastActiveMint: null, + txnsSinceGenesis: 1, + validAddresses: 1, + totalMinted: 1000, + totalBurned: 0, + circulatingSupply: 1000, + mintingBatonStatus: "NEVER_CREATED" + }, + { + decimals: 0, + timestamp: "2019-04-29 08:59", + timestampUnix: 1539218362, + versionType: 1, + documentUri: "", + symbol: "WMW", + name: "Where'sMyWallet", + containsBaton: false, + id: "471d1f33e8a69cf59ce174ce43174feeecdf1f475ccc4cc3705600a5d6d2cd06", + documentHash: null, + initialTokenQty: 1000, + blockCreated: 580336, + blockLastActiveSend: 580351, + blockLastActiveMint: null, + txnsSinceGenesis: 2, + validAddresses: 2, + totalMinted: 1000, + totalBurned: 0, + circulatingSupply: 1000, + mintingBatonStatus: "NEVER_CREATED" + } +] + +const mockToken = { + decimals: 0, + timestamp: "2018-08-25 01:54", + timestampUnix: 1539218362, + versionType: 1, + documentUri: "", + symbol: "USDT", + name: "US Dollar Tether", + containsBaton: false, + id: "4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84", + documentHash: null, + initialTokenQty: 10000000000000000, + blockCreated: 544903, + blockLastActiveSend: 544905, + blockLastActiveMint: null, + txnsSinceGenesis: 3, + validAddresses: 0, + totalMinted: 10000000000000000, + totalBurned: 10000000000000000, + circulatingSupply: 0, + mintingBatonStatus: "NEVER_CREATED" +} + +const mockTokens = [ + { + decimals: 0, + timestamp: "2018-08-25 01:54", + timestampUnix: 1539218362, + versionType: 1, + documentUri: "", + symbol: "USDT", + name: "US Dollar Tether", + containsBaton: false, + id: "4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84", + documentHash: null, + initialTokenQty: 10000000000000000, + blockCreated: 544903, + blockLastActiveSend: 544905, + blockLastActiveMint: null, + txnsSinceGenesis: 3, + validAddresses: 0, + totalMinted: 10000000000000000, + totalBurned: 10000000000000000, + circulatingSupply: 0, + mintingBatonStatus: "NEVER_CREATED" + }, + { + decimals: 0, + timestamp: "2019-04-29 08:59", + timestampUnix: 1539218362, + versionType: 1, + documentUri: "", + symbol: "WMW", + name: "Where'sMyWallet", + containsBaton: false, + id: "471d1f33e8a69cf59ce174ce43174feeecdf1f475ccc4cc3705600a5d6d2cd06", + documentHash: null, + initialTokenQty: 1000, + blockCreated: 580336, + blockLastActiveSend: 580351, + blockLastActiveMint: null, + txnsSinceGenesis: 2, + validAddresses: 2, + totalMinted: 1000, + totalBurned: 0, + circulatingSupply: 1000, + mintingBatonStatus: "NEVER_CREATED" + } +] + +const balancesForAddress = [ + { + tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + balance: "1", + balanceString: "1", + slpAddress: "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + decimalCount: 8 + }, + { + tokenId: "a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37", + balance: "1", + decimalCount: 7 + } +] + +const balancesForAddresses = [ + [ + { + tokenId: + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + balance: 1, + balanceString: "1", + slpAddress: "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + decimalCount: 8 + }, + { + tokenId: + "a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37", + balance: 1, + balanceString: "1", + slpAddress: "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + decimalCount: 7 + } + ], + [ + { + tokenId: + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + balance: 10, + balanceString: "10", + slpAddress: "simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57", + decimalCount: 8 + } + ] +] + +const mockBalance = { + tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + balance: 1, + balanceString: "1" +} + +const mockRawTx = [ + "0100000002b3b54b72de3cdff8d00a5a26e2aa7897f730c2889c46b41a83294004a2c6c9c6020000006a47304402202fff3979f9cf0a5052655c8699081a77a653903de41547928db0b94601aa082502207cdb909e3a7b2b7f8a3eb80243a1bd2fd8ad9449a0ec30242ae4b187436d11a0412103b30e7096c6e3a3b45e5aba4ad8fe48a1fdd7c04de0de55a43095e7560b52e19dfeffffffd25817de09517a6af6c3dbb332041f85d844052b32ea1dbca123365b18953726000000006a473044022011a39acbbb80c4723822d434445fc4b3d72ad0212902fdb183a5408af00e158c02200eb3778b1af9f3a8fe28b6670f5fe543fb4c190f79f349273860125be05269b2412103b30e7096c6e3a3b45e5aba4ad8fe48a1fdd7c04de0de55a43095e7560b52e19dfeffffff030000000000000000336a04534c500001010747454e45534953084e414b414d4f544f084e414b414d4f544f4c004c0001084c0008000775f05a07400022020000000000001976a91433c0448680ca324225eeca7a230cf191ab88400288ac8afc0000000000001976a91433c0448680ca324225eeca7a230cf191ab88400288ac967a0800" +] + +const mockIsValidTxid = [ + { + txid: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + valid: true + } +] + +const mockBalancesForToken = [ + { + tokenBalance: 1000, + slpAddress: "simpleledger:qzhfd7ssy9nt4gw7j9w5e7w5mxx5w549rv7mknzqkz" + } +] + +const mockTokenStats = { + tokenId: "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + documentUri: "", + symbol: "NAKAMOTO", + name: "NAKAMOTO", + decimals: 8, + txnsSinceGenesis: 367, + validUtxos: 248, + validAddresses: 195, + circulatingSupply: 20995990, + totalBurned: 4010, + totalMinted: 21000000, + satoshisLockedUp: 135408 +} + +const mockTransactions = [ + { + txid: "27e27170b546f05b2af69d6eddff8834038facf5d81302e9e562df09a5c4445f", + tokenDetails: { + valid: true, + detail: { + decimals: null, + tokenIdHex: + "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", + timestamp: null, + transactionType: "SEND", + versionType: 1, + documentUri: null, + documentSha256Hex: null, + symbol: null, + name: null, + batonVout: null, + containsBaton: null, + genesisOrMintQuantity: null, + sendOutputs: [ + { + $numberDecimal: "0" + }, + { + $numberDecimal: "25" + }, + { + $numberDecimal: "77" + } + ] + }, + invalidReason: null, + schema_version: 30 + } + } +] + +const mockBurnTotal = { + transactionId: + "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de", + inputTotal: 100000000, + outputTotal: 100000000, + burnTotal: 0 +} + +const nonSLPTxDetailsWithoutOpReturn = { + txid: "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec", + hash: "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec", + version: 1, + size: 704, + locktime: 0, + vin: [ + { + txid: "15a34ece03e13c20ee41dd113a982964a92dafb49328a6c395c8ecec12901bd5", + vout: 0, + scriptSig: { + asm: + "3045022100f020493f2c62740c89f07fba92d2916ad238c6965ef69fdf8338b4d6ee6c5a4502200251693e9883868cc5fb227bfc9bdaa5c333f14f712c0f3d8d2efb781f1a15fa[ALL|FORKID] 02a8087ad440c428aede8cf39eabe12b9807356b04d48aa0c361fa0d9d68a1aeac", + hex: + "483045022100f020493f2c62740c89f07fba92d2916ad238c6965ef69fdf8338b4d6ee6c5a4502200251693e9883868cc5fb227bfc9bdaa5c333f14f712c0f3d8d2efb781f1a15fa412102a8087ad440c428aede8cf39eabe12b9807356b04d48aa0c361fa0d9d68a1aeac" + }, + sequence: 4294967295 + }, + { + txid: "52f720a5c6b5ad2765ecabc51b375d4aa741339f2a6fbd1524dcb3d45e230337", + vout: 0, + scriptSig: { + asm: + "3045022100b3e65b51cd31c081070ebf5c91e43d10431a71e748c86ff6bcc7e4f5ecc7f54d02202d67edefe3a065cbda2e1782dd77bce2d891de2a428f5dd92eae15e4a843527c[ALL|FORKID] 03d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29", + hex: + "483045022100b3e65b51cd31c081070ebf5c91e43d10431a71e748c86ff6bcc7e4f5ecc7f54d02202d67edefe3a065cbda2e1782dd77bce2d891de2a428f5dd92eae15e4a843527c412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29" + }, + sequence: 4294967295 + }, + { + txid: "58e607b7ae35a970a50bc3f78bb7b3f786c906cc07241bdbe7e439bbd026036a", + vout: 0, + scriptSig: { + asm: + "3045022100c450cdf99fa8eca980b4c6878ff02de46b22bca9b08c01152739d12003ef14c902203d93e6f3811acfac0f531eca1afbf5c5416b19ac154a6672e9f3afc53d1b56a6[ALL|FORKID] 03d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29", + hex: + "483045022100c450cdf99fa8eca980b4c6878ff02de46b22bca9b08c01152739d12003ef14c902203d93e6f3811acfac0f531eca1afbf5c5416b19ac154a6672e9f3afc53d1b56a6412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29" + }, + sequence: 4294967295 + }, + { + txid: "9e2bc2f0e39cff9fd6f8cf56586bfd24389cba0367d70304914be277b2f7268b", + vout: 0, + scriptSig: { + asm: + "3045022100cd25d6e5de8057335d3db0e0ad05dd4802ebb45ae2e4ea52ba26670787eaa3bd02207024b366b61e6f28324fc040dad5fa589ee7f2487c8e4c54848827360c792cc3[ALL|FORKID] 03d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29", + hex: + "483045022100cd25d6e5de8057335d3db0e0ad05dd4802ebb45ae2e4ea52ba26670787eaa3bd02207024b366b61e6f28324fc040dad5fa589ee7f2487c8e4c54848827360c792cc3412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 36.01397342, + n: 0, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 84c49aa95f145334b125c80c2cc9d077d08e00ce OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91484c49aa95f145334b125c80c2cc9d077d08e00ce88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qzzvfx4ftu29xd93yhyqctxf6pmaprsqecm3rhd0lv"] + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 1aec955da539e59b32fa97e96fc0f53f018ae8a2 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9141aec955da539e59b32fa97e96fc0f53f018ae8a288ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqdwe92a55u7txejl2t7jm7q75lsrzhg5grz36dzh5"] + } + }, + { + value: 13.4288476, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 1af7e01ee75e22c645a5b37e401bd560168abc07 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9141af7e01ee75e22c645a5b37e401bd560168abc0788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqd00cq7ua0z93j95kehusqm64spdz4uqur3lepgqm"] + } + } + ], + hex: + "0100000004d51b9012ececc895c3a62893b4af2da96429983a11dd41ee203ce103ce4ea315000000006b483045022100f020493f2c62740c89f07fba92d2916ad238c6965ef69fdf8338b4d6ee6c5a4502200251693e9883868cc5fb227bfc9bdaa5c333f14f712c0f3d8d2efb781f1a15fa412102a8087ad440c428aede8cf39eabe12b9807356b04d48aa0c361fa0d9d68a1aeacffffffff3703235ed4b3dc2415bd6f2a9f3341a74a5d371bc5abec6527adb5c6a520f752000000006b483045022100b3e65b51cd31c081070ebf5c91e43d10431a71e748c86ff6bcc7e4f5ecc7f54d02202d67edefe3a065cbda2e1782dd77bce2d891de2a428f5dd92eae15e4a843527c412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29ffffffff6a0326d0bb39e4e7db1b2407cc06c986f7b3b78bf7c30ba570a935aeb707e658000000006b483045022100c450cdf99fa8eca980b4c6878ff02de46b22bca9b08c01152739d12003ef14c902203d93e6f3811acfac0f531eca1afbf5c5416b19ac154a6672e9f3afc53d1b56a6412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29ffffffff8b26f7b277e24b910403d76703ba9c3824fd6b5856cff8d69fff9ce3f0c22b9e000000006b483045022100cd25d6e5de8057335d3db0e0ad05dd4802ebb45ae2e4ea52ba26670787eaa3bd02207024b366b61e6f28324fc040dad5fa589ee7f2487c8e4c54848827360c792cc3412103d0ea0c7798905c7f8dee6180321c92bf67df9dfde42da0f3cf25413d914c0e29ffffffff035ef6a8d6000000001976a91484c49aa95f145334b125c80c2cc9d077d08e00ce88ac22020000000000001976a9141aec955da539e59b32fa97e96fc0f53f018ae8a288ac98cb0a50000000001976a9141af7e01ee75e22c645a5b37e401bd560168abc0788ac00000000", + blockhash: "0000000000000000014bf37d1253e3c46f0e6f151bd6cca13f8093e76c31496d", + confirmations: 1571, + time: 1565380800, + blocktime: 1565380800 +} + +const nonSLPTxDetailsWithOpReturn = { + txid: "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f", + hash: "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f", + version: 1, + size: 271, + locktime: 0, + vin: [ + { + txid: "6a81a401ec165eccd08c5c5a57a2185056675bbf2380d515c418b7a4c04db40a", + vout: 1, + scriptSig: { + asm: + "30440220168a04932175c6d6b10947ebef23c53ff2e905bb8dfb7d1aec961944f83bf20e0220724ad7a459bb31174b9e6cbbc73260c9a62d32dda2c47f5d7c5351b5eb9cd944[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3", + hex: + "4730440220168a04932175c6d6b10947ebef23c53ff2e905bb8dfb7d1aec961944f83bf20e0220724ad7a459bb31174b9e6cbbc73260c9a62d32dda2c47f5d7c5351b5eb9cd94441410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN -802180445 46386600368a3e883cd68c6939ab8c0a3c91537a3be6d9be35e42b8e37cfc92c", + hex: + "6a045d4dd0af2046386600368a3e883cd68c6939ab8c0a3c91537a3be6d9be35e42b8e37cfc92c", + type: "nulldata" + } + }, + { + value: 0.00002626, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914066ebee590278f32aedc8a4865700c49e717f1d788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"] + } + } + ], + hex: + "01000000010ab44dc0a4b718c415d58023bf5b67565018a2575a5c8cd0cc5e16ec01a4816a010000008a4730440220168a04932175c6d6b10947ebef23c53ff2e905bb8dfb7d1aec961944f83bf20e0220724ad7a459bb31174b9e6cbbc73260c9a62d32dda2c47f5d7c5351b5eb9cd94441410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d4dd0af2046386600368a3e883cd68c6939ab8c0a3c91537a3be6d9be35e42b8e37cfc92c420a0000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000", + blockhash: "0000000000000000014bf37d1253e3c46f0e6f151bd6cca13f8093e76c31496d", + confirmations: 1571, + time: 1565380800, + blocktime: 1565380800 +} + +txDetailsSLPGenesis = { + txid: "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + hash: "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + version: 2, + size: 357, + locktime: 0, + vin: [ + { + txid: "86190b4a1ef25f09a388e2e86c299fc4bbc54c310386dcf37611b2958b964e25", + vout: 0, + scriptSig: { + asm: + "304402203cdf545000c17bce9dfa99a1515e5e3dc17b0abcfa55d1eacc01fa3f876d574102203170f0d7280dc7f4de887a141c94fb43bb3b5b0da12e4f400d3c89abf2a1c842[ALL|FORKID] 026ad033bd90dfa45766f4fdd377153468f1287e8fa0834fdad7dc11662e83dca8", + hex: + "47304402203cdf545000c17bce9dfa99a1515e5e3dc17b0abcfa55d1eacc01fa3f876d574102203170f0d7280dc7f4de887a141c94fb43bb3b5b0da12e4f400d3c89abf2a1c8424121026ad033bd90dfa45766f4fdd377153468f1287e8fa0834fdad7dc11662e83dca8" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 1 47454e45534953 534c5053444b 534c502053444b206578616d706c65207573696e6720424954424f58 646576656c6f7065722e626974636f696e2e636f6d 0 8 2 0000000bcdf49b00", + hex: + "6a04534c500001010747454e4553495306534c5053444b1c534c502053444b206578616d706c65207573696e6720424954424f5815646576656c6f7065722e626974636f696e2e636f6d4c0001080102080000000bcdf49b00", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 70083e743742ad726a3a8f3a511d9a89f979dd63 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91470083e743742ad726a3a8f3a511d9a89f979dd6388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 70083e743742ad726a3a8f3a511d9a89f979dd63 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91470083e743742ad726a3a8f3a511d9a89f979dd6388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05"] + } + }, + { + value: 0.00002015, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 70083e743742ad726a3a8f3a511d9a89f979dd63 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91470083e743742ad726a3a8f3a511d9a89f979dd6388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05"] + } + } + ], + hex: + "0200000001254e968b95b21176f3dc8603314cc5bbc49f296ce8e288a3095ff21e4a0b1986000000006a47304402203cdf545000c17bce9dfa99a1515e5e3dc17b0abcfa55d1eacc01fa3f876d574102203170f0d7280dc7f4de887a141c94fb43bb3b5b0da12e4f400d3c89abf2a1c8424121026ad033bd90dfa45766f4fdd377153468f1287e8fa0834fdad7dc11662e83dca8ffffffff040000000000000000596a04534c500001010747454e4553495306534c5053444b1c534c502053444b206578616d706c65207573696e6720424954424f5815646576656c6f7065722e626974636f696e2e636f6d4c0001080102080000000bcdf49b0022020000000000001976a91470083e743742ad726a3a8f3a511d9a89f979dd6388ac22020000000000001976a91470083e743742ad726a3a8f3a511d9a89f979dd6388acdf070000000000001976a91470083e743742ad726a3a8f3a511d9a89f979dd6388ac00000000", + blockhash: "000000000000000002f45ff275b5c073422b2bf1c3e4babae823145c2bbff55a", + confirmations: 1744, + time: 1565274914, + blocktime: 1565274914 +} + +const txDetailsSLPMint = { + txid: "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4", + hash: "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4", + version: 2, + size: 474, + locktime: 0, + vin: [ + { + txid: "023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4", + vout: 2, + scriptSig: { + asm: + "3045022100f73afe1ba320dfdf191c7219e37a6dcd8d52f3e0155efee8a3e17556965e031b022021a9689e57210697236f644cc91c43d73e894020b227445776afe9a5b79c0f23[ALL|FORKID] 036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d", + hex: + "483045022100f73afe1ba320dfdf191c7219e37a6dcd8d52f3e0155efee8a3e17556965e031b022021a9689e57210697236f644cc91c43d73e894020b227445776afe9a5b79c0f234121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d" + }, + sequence: 4294967295 + }, + { + txid: "023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4", + vout: 3, + scriptSig: { + asm: + "3045022100cc549d3659875931ea5ba7bd71072ac650864fc046c8239d2f8948f67486d7b30220103641cc9566283c7706d702dc92c2f442673cbc51ce84b5d383ba68da97b157[ALL|FORKID] 036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d", + hex: + "483045022100cc549d3659875931ea5ba7bd71072ac650864fc046c8239d2f8948f67486d7b30220103641cc9566283c7706d702dc92c2f442673cbc51ce84b5d383ba68da97b1574121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 1 1414416717 023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4 2 00000002540be400", + hex: + "6a04534c50000101044d494e5420023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e401020800000002540be400", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 210a88277de7a80a1dac90a8153c0e7c8a2f08a7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5u5k0yyr2q"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 210a88277de7a80a1dac90a8153c0e7c8a2f08a7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5u5k0yyr2q"] + } + }, + { + value: 0.00007528, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 210a88277de7a80a1dac90a8153c0e7c8a2f08a7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5u5k0yyr2q"] + } + } + ], + hex: + "0200000002e454f384ced298581b0b0c6e9b9d7a93474c9a5ad14f998b0547395ae9d33c02020000006b483045022100f73afe1ba320dfdf191c7219e37a6dcd8d52f3e0155efee8a3e17556965e031b022021a9689e57210697236f644cc91c43d73e894020b227445776afe9a5b79c0f234121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850dffffffffe454f384ced298581b0b0c6e9b9d7a93474c9a5ad14f998b0547395ae9d33c02030000006b483045022100cc549d3659875931ea5ba7bd71072ac650864fc046c8239d2f8948f67486d7b30220103641cc9566283c7706d702dc92c2f442673cbc51ce84b5d383ba68da97b1574121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850dffffffff040000000000000000396a04534c50000101044d494e5420023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e401020800000002540be40022020000000000001976a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac22020000000000001976a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac681d0000000000001976a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac00000000", + blockhash: "000000000000000000182bc50810ab0296a6f1f7b6313bf535b6e78dffca8db6", + confirmations: 154, + time: 1566227300, + blocktime: 1566227300 +} + +const txDetailsSLPSend = { + txid: "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa", + hash: "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa", + version: 2, + size: 626, + locktime: 0, + vin: [ + { + txid: "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4", + vout: 1, + scriptSig: { + asm: + "30440220392bd0f72f0ff7ce983fe6320383e6f52d6921064e95fcd1599b672d1ff074b4022061922fb13f7477708dc88cb11a5448c702c4bad059526253821c7ccc5e182932[ALL|FORKID] 036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d", + hex: + "4730440220392bd0f72f0ff7ce983fe6320383e6f52d6921064e95fcd1599b672d1ff074b4022061922fb13f7477708dc88cb11a5448c702c4bad059526253821c7ccc5e1829324121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d" + }, + sequence: 4294967295 + }, + { + txid: "023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4", + vout: 1, + scriptSig: { + asm: + "3044022010e57d2db06c7013ef9fd6848a6e307674a25cb62cb44a666b03800b322a1e3f022013f41bbf2ef462be799a71e1392ca0a37687d3602002bb3b8d772e2afad498a7[ALL|FORKID] 036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d", + hex: + "473044022010e57d2db06c7013ef9fd6848a6e307674a25cb62cb44a666b03800b322a1e3f022013f41bbf2ef462be799a71e1392ca0a37687d3602002bb3b8d772e2afad498a74121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d" + }, + sequence: 4294967295 + }, + { + txid: "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4", + vout: 3, + scriptSig: { + asm: + "3044022064a522467b966777592ddb699ce0536a9a94e7f3c9a3c428bec929f78ee3efc60220700c6e1506ab8b5253c12d7f1682aac3bb5f6c869ec257a851d19d36d8c1f10f[ALL|FORKID] 036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d", + hex: + "473044022064a522467b966777592ddb699ce0536a9a94e7f3c9a3c428bec929f78ee3efc60220700c6e1506ab8b5253c12d7f1682aac3bb5f6c869ec257a851d19d36d8c1f10f4121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850d" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 1 1145980243 023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4 0000000011e1a300 0000000e101edc00", + hex: + "6a04534c500001010453454e4420023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4080000000011e1a300080000000e101edc00", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 99e5a8229a9af0dbf3cadb25ea981df49c9d93bf OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 210a88277de7a80a1dac90a8153c0e7c8a2f08a7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5u5k0yyr2q"] + } + }, + { + value: 0.00006898, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 210a88277de7a80a1dac90a8153c0e7c8a2f08a7 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5u5k0yyr2q"] + } + } + ], + hex: + "0200000003a4f3380ed4e4599eeb58e4c4a2aa7823fe9d1a868ee315b55e5e54cdbf1bf265010000006a4730440220392bd0f72f0ff7ce983fe6320383e6f52d6921064e95fcd1599b672d1ff074b4022061922fb13f7477708dc88cb11a5448c702c4bad059526253821c7ccc5e1829324121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850dffffffffe454f384ced298581b0b0c6e9b9d7a93474c9a5ad14f998b0547395ae9d33c02010000006a473044022010e57d2db06c7013ef9fd6848a6e307674a25cb62cb44a666b03800b322a1e3f022013f41bbf2ef462be799a71e1392ca0a37687d3602002bb3b8d772e2afad498a74121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850dffffffffa4f3380ed4e4599eeb58e4c4a2aa7823fe9d1a868ee315b55e5e54cdbf1bf265030000006a473044022064a522467b966777592ddb699ce0536a9a94e7f3c9a3c428bec929f78ee3efc60220700c6e1506ab8b5253c12d7f1682aac3bb5f6c869ec257a851d19d36d8c1f10f4121036a0ccfe281fc5f83f0b1d33fbc0da65cffc6f7f265bb2e17b9c7d8278323850dffffffff040000000000000000406a04534c500001010453454e4420023cd3e95a3947058b994fd15a9a4c47937a9d9b6e0c0b1b5898d2ce84f354e4080000000011e1a300080000000e101edc0022020000000000001976a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88ac22020000000000001976a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788acf21a0000000000001976a914210a88277de7a80a1dac90a8153c0e7c8a2f08a788ac00000000", + blockhash: "000000000000000000182bc50810ab0296a6f1f7b6313bf535b6e78dffca8db6", + confirmations: 154, + time: 1566227300, + blocktime: 1566227300 +} + +const txDetailsSLPGenesisNoBaton = { + txid: "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + hash: "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + version: 1, + size: 285, + locktime: 575672, + vin: [ + { + txid: "805728012bc3349d1a05dc503aaf389c7a743917d7af6adfb844baff8ff2f89f", + vout: 2, + scriptSig: { + asm: + "3045022100e4c0e18d97ea6d24c15d60e4032131e985091dec0d844fe00065cb6852b2cfaa02207b6334f72e1aa70f26a88e0e0bff183966405b59bf3a9eaa89d03155cd155505[ALL|FORKID] 03adb6ee2ccaf17f407704c91aae7327bd12fa81aa1bad63bc1685a9ded76d6f2a", + hex: + "483045022100e4c0e18d97ea6d24c15d60e4032131e985091dec0d844fe00065cb6852b2cfaa02207b6334f72e1aa70f26a88e0e0bff183966405b59bf3a9eaa89d03155cd155505412103adb6ee2ccaf17f407704c91aae7327bd12fa81aa1bad63bc1685a9ded76d6f2a" + }, + sequence: 4294967294 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 1 47454e45534953 544f4b2d4348 546f6b796f43617368 0 0 8 0 000775f05a074000", + hex: + "6a04534c500001010747454e4553495306544f4b2d434809546f6b796f436173684c004c0001084c0008000775f05a074000", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 17c068626a1085ab782b94fe5577b67b9168a1d9 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a91417c068626a1085ab782b94fe5577b67b9168a1d988ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qqtuq6rzdgggt2mc9w20u4thkeaez69pmy6ur897sr"] + } + }, + { + value: 0.00474263, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 8b3decf88562b3a8037d8e88171e14bff010ea3d OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9148b3decf88562b3a8037d8e88171e14bff010ea3d88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qz9nmm8cs43t82qr0k8gs9c7zjllqy82853g26y3tc"] + } + } + ], + hex: + "01000000019ff8f28fffba44b8df6aafd71739747a9c38af3a50dc051a9d34c32b01285780020000006b483045022100e4c0e18d97ea6d24c15d60e4032131e985091dec0d844fe00065cb6852b2cfaa02207b6334f72e1aa70f26a88e0e0bff183966405b59bf3a9eaa89d03155cd155505412103adb6ee2ccaf17f407704c91aae7327bd12fa81aa1bad63bc1685a9ded76d6f2afeffffff030000000000000000326a04534c500001010747454e4553495306544f4b2d434809546f6b796f436173684c004c0001084c0008000775f05a07400022020000000000001976a91417c068626a1085ab782b94fe5577b67b9168a1d988ac973c0700000000001976a9148b3decf88562b3a8037d8e88171e14bff010ea3d88acb8c80800", + blockhash: "0000000000000000003e44676df0c4f80b68aff24bf04823444c0069729631f8", + confirmations: 21249, + time: 1553714591, + blocktime: 1553714591 +} + +const txDetailsSLPSendAlt = { + txid: "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae", + hash: "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae", + version: 2, + size: 438, + locktime: 0, + vin: [ + { + txid: "984a8fc8093e1db5489a8856ab0ecbaef188662535f08de6f87da8622978146f", + vout: 0, + scriptSig: { + asm: + "3045022100f3f84a7c0a72e6df55ad8ff4596aae2d040403b38f8054d95ee48f3d554790050220776b0833891e65c52685ce3a498e3ee0aa804a85e9b79f21bb74a367ad88a569[ALL|FORKID] 03440d292d554f524a1a16fab1c4384c82a15aa191b6a25187c03f6ec4db57d61e", + hex: + "483045022100f3f84a7c0a72e6df55ad8ff4596aae2d040403b38f8054d95ee48f3d554790050220776b0833891e65c52685ce3a498e3ee0aa804a85e9b79f21bb74a367ad88a569412103440d292d554f524a1a16fab1c4384c82a15aa191b6a25187c03f6ec4db57d61e" + }, + sequence: 4294967295 + }, + { + txid: "164ff37f47a1be6550a81f3d76f3d57e121d82ed04ed8b7bc932e2273141ebbc", + vout: 1, + scriptSig: { + asm: + "30440220177d3583516caf6a3d8e99ed9c0a76595a4187d04c575c0f7dab6a6dfa4630c502207fc842e03ca73a2c9d1f9d7406145bb4ea6eb90d1585ea7c4e82491707fadb74[ALL|FORKID] 0252996f42e5908cc6fe5e2df42888a7226f352f2d496e7f9bb17aaf55e41d997b", + hex: + "4730440220177d3583516caf6a3d8e99ed9c0a76595a4187d04c575c0f7dab6a6dfa4630c502207fc842e03ca73a2c9d1f9d7406145bb4ea6eb90d1585ea7c4e82491707fadb7441210252996f42e5908cc6fe5e2df42888a7226f352f2d496e7f9bb17aaf55e41d997b" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 256 1145980243 73db55368981e4878440637e448d4abe7f661be5c3efdcbcb63bd86a01a76b5a 0000000000000001", + hex: + "6a04534c50000200010453454e442073db55368981e4878440637e448d4abe7f661be5c3efdcbcb63bd86a01a76b5a080000000000000001", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 f037d66efd7235ae4eeb03f666845a7c23ace91a OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914f037d66efd7235ae4eeb03f666845a7c23ace91a88ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qrcr04nwl4erttjwavplve5ytf7z8t8frg94efy6ts"] + } + }, + { + value: 0.00047367, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 d5669ba347fd2abe6a06d0310f817d1f1304ba71 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914d5669ba347fd2abe6a06d0310f817d1f1304ba7188ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qr2kdxargl7j40n2qmgrzrup0503xp96wyn5ju6p5l"] + } + } + ], + hex: + "02000000026f14782962a87df8e68df035256688f1aecb0eab56889a48b51d3e09c88f4a98000000006b483045022100f3f84a7c0a72e6df55ad8ff4596aae2d040403b38f8054d95ee48f3d554790050220776b0833891e65c52685ce3a498e3ee0aa804a85e9b79f21bb74a367ad88a569412103440d292d554f524a1a16fab1c4384c82a15aa191b6a25187c03f6ec4db57d61effffffffbceb413127e232c97b8bed04ed821d127ed5f3763d1fa85065bea1477ff34f16010000006a4730440220177d3583516caf6a3d8e99ed9c0a76595a4187d04c575c0f7dab6a6dfa4630c502207fc842e03ca73a2c9d1f9d7406145bb4ea6eb90d1585ea7c4e82491707fadb7441210252996f42e5908cc6fe5e2df42888a7226f352f2d496e7f9bb17aaf55e41d997bffffffff030000000000000000386a04534c50000200010453454e442073db55368981e4878440637e448d4abe7f661be5c3efdcbcb63bd86a01a76b5a08000000000000000122020000000000001976a914f037d66efd7235ae4eeb03f666845a7c23ace91a88ac07b90000000000001976a914d5669ba347fd2abe6a06d0310f817d1f1304ba7188ac00000000" +} + +const mockTxDetails = { + txid: "9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0", + version: 2, + locktime: 0, + vin: [ + { + txid: "203f21da29235b290cca3ec7cf14479533b3fd15d75ecb2e72f57bcff984bd94", + vout: 1, + sequence: 4294967295, + n: 0, + scriptSig: { + hex: + "4730440220068b4d98542760aeff6dd3b17958611c12cfe36ca23d1a2df42241b0c993286202202db64ad12f62fe0f13ed904d095c4ed06555e0632d9d55db50fabba7d0153aea412103021c8dd3baebdbc1b437b063ffe631cac3d6ce6aa62e3c3b8eb83a0dfceef542", + asm: + "30440220068b4d98542760aeff6dd3b17958611c12cfe36ca23d1a2df42241b0c993286202202db64ad12f62fe0f13ed904d095c4ed06555e0632d9d55db50fabba7d0153aea[ALL|FORKID] 03021c8dd3baebdbc1b437b063ffe631cac3d6ce6aa62e3c3b8eb83a0dfceef542" + }, + value: 546, + legacyAddress: "1Fy3KxcwycjXyjpcBMMxbiZwGxDgFmNeSw", + cashAddress: "bitcoincash:qzjz4l6fvdhgpjdfhs8gg8664en0l942p5sjf3lwh7", + slpAddress: "simpleledger:qzjz4l6fvdhgpjdfhs8gg8664en0l942p5ufz22wfq" + }, + { + txid: "50dd2b262bf60e605a4749f7187c6c6fe57af9067f07744d28df1602b7685d89", + vout: 1, + sequence: 4294967295, + n: 1, + scriptSig: { + hex: + "483045022100f1de68dfab06aba82b10cf8d63be91a31e44087c624de01a63fc0bc209d637ed02200c5182100391a9b486b83cb3caa533451c84247594a5c2ceabfb3b0c9dc68bfd412103021c8dd3baebdbc1b437b063ffe631cac3d6ce6aa62e3c3b8eb83a0dfceef542", + asm: + "3045022100f1de68dfab06aba82b10cf8d63be91a31e44087c624de01a63fc0bc209d637ed02200c5182100391a9b486b83cb3caa533451c84247594a5c2ceabfb3b0c9dc68bfd[ALL|FORKID] 03021c8dd3baebdbc1b437b063ffe631cac3d6ce6aa62e3c3b8eb83a0dfceef542" + }, + value: 546, + legacyAddress: "1Fy3KxcwycjXyjpcBMMxbiZwGxDgFmNeSw", + cashAddress: "bitcoincash:qzjz4l6fvdhgpjdfhs8gg8664en0l942p5sjf3lwh7", + slpAddress: "simpleledger:qzjz4l6fvdhgpjdfhs8gg8664en0l942p5ufz22wfq" + }, + { + txid: "d3ec152a06d1f9f474c22015f63deeaadf39a399b516d5e60eebe1ad3c9315ec", + vout: 1, + sequence: 4294967295, + n: 2, + scriptSig: { + hex: + "483045022100d8e0d89ad11bf281026463a34d4b1a7e4465cab43bc26f8cb0f6999cb359f7d1022052e74af271323259977bb55c116efbb7988b4f670119ba247f24a2a56f4ac063412103da9d0ed61cc8010a01ed9d8bc64230ba72afe0c0ffdae0195911fbe9aa3e0112", + asm: + "3045022100d8e0d89ad11bf281026463a34d4b1a7e4465cab43bc26f8cb0f6999cb359f7d1022052e74af271323259977bb55c116efbb7988b4f670119ba247f24a2a56f4ac063[ALL|FORKID] 03da9d0ed61cc8010a01ed9d8bc64230ba72afe0c0ffdae0195911fbe9aa3e0112" + }, + value: 422955, + legacyAddress: "18TRupzu6qbvEJUFAwoWEsDBQXaQcmUdXk", + cashAddress: "bitcoincash:qpgusltsseyslth9azccyxel5gne2257fq0p9q2nkj", + slpAddress: "simpleledger:qpgusltsseyslth9azccyxel5gne2257fqr6wmlngv" + } + ], + vout: [ + { + value: "0.00000000", + n: 0, + scriptPubKey: { + hex: + "6a04534c500001010453454e44207353603832726dc0bd67afaac2acdd0fbd9fbe562710a68fd1e88943211277fc0800000000000000ca", + asm: + "OP_RETURN 5262419 1 1145980243 7353603832726dc0bd67afaac2acdd0fbd9fbe562710a68fd1e88943211277fc 00000000000000ca" + }, + spentTxId: null, + spentIndex: null, + spentHeight: null + }, + { + value: "0.00000546", + n: 1, + scriptPubKey: { + hex: "76a9140c036a1ee180c958f97afa5a8a6272ab47091fbd88ac", + asm: + "OP_DUP OP_HASH160 0c036a1ee180c958f97afa5a8a6272ab47091fbd OP_EQUALVERIFY OP_CHECKSIG", + addresses: ["126XCYnDwqMBXfQTvsTfpYRwX59moZUZDS"], + type: "pubkeyhash", + cashAddrs: ["bitcoincash:qqxqx6s7uxqvjk8e0ta94znzw245wzglh5h9wdssan"], + slpAddrs: ["simpleledger:qqxqx6s7uxqvjk8e0ta94znzw245wzglh5m79k9srd"] + }, + spentTxId: null, + spentIndex: null, + spentHeight: null + }, + { + value: "0.00422880", + n: 2, + scriptPubKey: { + hex: "76a91451c87d7086490faee5e8b1821b3fa227952a9e4888ac", + asm: + "OP_DUP OP_HASH160 51c87d7086490faee5e8b1821b3fa227952a9e48 OP_EQUALVERIFY OP_CHECKSIG", + addresses: ["18TRupzu6qbvEJUFAwoWEsDBQXaQcmUdXk"], + type: "pubkeyhash", + cashAddrs: ["bitcoincash:qpgusltsseyslth9azccyxel5gne2257fq0p9q2nkj"], + slpAddrs: ["simpleledger:qpgusltsseyslth9azccyxel5gne2257fqr6wmlngv"] + }, + spentTxId: null, + spentIndex: null, + spentHeight: null + } + ], + blockhash: "0000000000000000028713785942eddfd39daa60e8768b9b0513ff077d1ecd2f", + blockheight: 600711, + confirmations: 1668, + time: 1568761257, + blocktime: 1568761257, + valueOut: 0.00423426, + size: 585, + valueIn: 0.00424047, + fees: 0.00000621, + tokenInfo: { + versionType: 1, + transactionType: "SEND", + tokenIdHex: + "7353603832726dc0bd67afaac2acdd0fbd9fbe562710a68fd1e88943211277fc", + sendOutputs: ["0", "202"] + }, + tokenIsValid: true +} + +const mockDualValidation = [ + { + txid: "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + valid: true + }, + { + txid: "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + valid: true + } +] + +const mockDualOpData = { + tokenType: 1, + transactionType: "send", + tokenId: "dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d", + spendData: [ + { + quantity: "1", + sentTo: "bitcoincash:qqavn6wspzy7nsy7ex9dj9t7a8va7nv6ey05jkul6l", + vout: 1 + }, + { + quantity: "5", + sentTo: "bitcoincash:qpyx5hv7nhxk9cmug3vp7jnasdt8akselvteqypm9m", + vout: 2 + } + ] +} + +const mockInvalidSlpSend = { + txid: "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415", + hash: "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415", + version: 2, + size: 473, + locktime: 0, + vin: [ + { + txid: "3ad621d46ddb7bdccb6e5e7b6505ee639d9327a2b2bdaa2937b8e3aa55c4f2a7", + vout: 0, + scriptSig: { + asm: + "3045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda5227[ALL|FORKID] 0351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb", + hex: + "483045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda522741210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb" + }, + sequence: 4294967295 + }, + { + txid: "a31c3167686f892d835727bc46b74bb11a46d74a8a6b08dc6cd82abb6e987b43", + vout: 1, + scriptSig: { + asm: + "3044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec4[ALL|FORKID] 0351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb", + hex: + "473044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec441210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fb" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 1 1145980243 091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8 1 fffffffffffffffe", + hex: + "6a04534c500001010453454e4420091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8010108fffffffffffffffe", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 c0c53d84b5420c9bfe4015d1ae69017c3f1ddef8 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914c0c53d84b5420c9bfe4015d1ae69017c3f1ddef888ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qrqv20vyk4pqexl7gq2artnfq97r78w7lqj5548mny"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 b505afc357a7e911b207332f13e8e674a72099c3 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914b505afc357a7e911b207332f13e8e674a72099c388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qz6stt7r27n7jydjquej7ylgue62wgyecvs9zm4gff"] + } + }, + { + value: 0.00044091, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 b505afc357a7e911b207332f13e8e674a72099c3 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a914b505afc357a7e911b207332f13e8e674a72099c388ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qz6stt7r27n7jydjquej7ylgue62wgyecvs9zm4gff"] + } + } + ], + hex: + "0200000002a7f2c455aae3b83729aabdb2a227939d63ee05657b5e6ecbdc7bdb6dd421d63a000000006b483045022100f962fdc585261007f114f0470828bbe9ac6197cefc4d0897fe4a7dd1d4cb2f5402202e104b617a1c14fc293c0ace9d4fd5e1cfa819a68b4ff976b4c3c199feda522741210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fbffffffff437b986ebb2ad86cdc086b8a4ad7461ab14bb746bc2757832d896f6867311ca3010000006a473044022027dcd4ef752819ba4e61b892039ea0cb8d3ab0ffaeb78384dce9cf43dd76967e0220594fdf41e3d26cd207b6df939e3d655f6e09b01f05948edeb096aa644e1b6ec441210351c450ded2d7747dcad69982c9d954f3f785bab018d89fa09723bba7397e91fbffffffff040000000000000000396a04534c500001010453454e4420091c80cee60cc3a6dd7b8c6c04cf0cf2d8103ba2d75daa105dcf1aaa53551fe8010108fffffffffffffffe22020000000000001976a914c0c53d84b5420c9bfe4015d1ae69017c3f1ddef888ac22020000000000001976a914b505afc357a7e911b207332f13e8e674a72099c388ac3bac0000000000001976a914b505afc357a7e911b207332f13e8e674a72099c388ac00000000", + blockhash: "000000000000000000915bd33a7241f34800b190f7cf51f90b42b2b05d2b7ed8", + confirmations: 90327, + time: 1535577007, + blocktime: 1535577007 +} + +const txDetailsSLPNftGenesis = { + txid: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + hash: "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + version: 2, + size: 344, + locktime: 0, + vin: [ + { + txid: "4041bc842554b796de1dbb625bbb6994379bc5132a47522677bef7323f3e5051", + vout: 2, + scriptSig: { + asm: + "3045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb[ALL|FORKID] 029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c", + hex: + "483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72c" + }, + sequence: 4294967295 + } + ], + vout: [ + { + value: 0, + n: 0, + scriptPubKey: { + asm: + "OP_RETURN 5262419 -1 47454e45534953 4e46545454 4e4654205465737420546f6b656e 68747470733a2f2f46756c6c537461636b2e63617368 0 0 2 0000000000000001", + hex: + "6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c0001000102080000000000000001", + type: "nulldata" + } + }, + { + value: 0.00000546, + n: 1, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + }, + { + value: 0.00000546, + n: 2, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + }, + { + value: 0.0001562, + n: 3, + scriptPubKey: { + asm: + "OP_DUP OP_HASH160 6011206cd60db8b634f85edf46da22a6d1351e54 OP_EQUALVERIFY OP_CHECKSIG", + hex: "76a9146011206cd60db8b634f85edf46da22a6d1351e5488ac", + reqSigs: 1, + type: "pubkeyhash", + addresses: ["bitcoincash:qpspzgrv6cxm3d35lp0d73k6y2ndzdg72s2304ttr8"] + } + } + ], + hex: + "020000000151503e3f32f7be772652472a13c59b379469bb5b62bb1dde96b7542584bc4140020000006b483045022100f8d678e7cd6a2fc317fe4c0c8b633f5941aa40bceb6d7472559955908869544b02206d82e45a6658bb9b5ad39734aa93832b1de2d3c9650e1b59c2ee2f15aa26f3bb4121029547345d63f86ea89ea92ea4ed26386c4493ff60c78d871cc7b38f517f3fd72cffffffff0400000000000000004b6a04534c500001810747454e45534953054e465454540e4e4654205465737420546f6b656e1668747470733a2f2f46756c6c537461636b2e636173684c000100010208000000000000000122020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac22020000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac043d0000000000001976a9146011206cd60db8b634f85edf46da22a6d1351e5488ac00000000", + blockhash: "000000000000000002405a44302888bc29bf0e9f4d99b97303038bcc83c15e33", + confirmations: 2, + time: 1591329189, + blocktime: 1591329189 +} + +module.exports = { + mockList, + mockToken, + mockTokens, + balancesForAddress, + balancesForAddresses, + mockRawTx, + mockIsValidTxid, + mockBalancesForToken, + mockTokenStats, + mockTransactions, + mockBalance, + mockBurnTotal, + nonSLPTxDetailsWithoutOpReturn, + nonSLPTxDetailsWithOpReturn, + txDetailsSLPGenesis, + txDetailsSLPMint, + txDetailsSLPSend, + txDetailsSLPGenesisNoBaton, + txDetailsSLPSendAlt, + txDetailsSLPNftGenesis, + mockTxDetails, + mockDualValidation, + mockDualOpData, + mockInvalidSlpSend +} diff --git a/test/unit/fixtures/transaction-builder.json b/test/unit/fixtures/transaction-builder.json new file mode 100644 index 0000000..f3c8da4 --- /dev/null +++ b/test/unit/fixtures/transaction-builder.json @@ -0,0 +1,1841 @@ +{ + "hashTypes": [ + { + "SIGHASH_ALL": "0x01" + }, + { + "SIGHASH_NONE": "0x02" + }, + { + "SIGHASH_SINGLE": "0x03" + }, + { + "SIGHASH_ANYONECANPAY": "0x80" + }, + { + "SIGHASH_BITCOINCASH_BIP143": "0x40" + }, + { + "ADVANCED_TRANSACTION_MARKER": "0x00" + }, + { + "ADVANCED_TRANSACTION_FLAG": "0x01" + } + ], + "scripts": { + "p2pk": { + "toOne": { + "mainnet": [ + { + "xpriv": "xprvA33Uz6qJD2ae6dN6cR5usc6mpygMCj9Zusyb6dPe9ziMbEAX1Tps8Cgfk1ixzoyTCKgY99adbsDDd3oexQMJh1i5mzHuEkNAmZ2Gn2dDpTH", + "txHash": "b7f428fbd1cd5066e78f13ce5bc475e45583fe1c879785d70148b630edb1a542", + "hex": "020000000142a5b1ed30b64801d78597871cfe8355e475c45bce138fe76650cdd1fb28f4b70000000048473044022078138d100e90055f2b7deeba2fe21787e61728e0494323c675fce8e1c33bf594022078dddf8cceaf4225fd313afb107e89c09133e37da0185936dfff8fde8dea846841ffffffff017a2000000000000023210383bc181a0c8d19939dba7400cffb28666580e75531448b2060e968a753620dafac00000000", + "amount": 8574 + }, + { + "xpriv": "xprvA6QQAFpdX9JhyZ4YutETuBuqtaS8oPRcoB9CHzurnvzTB1gfvmFNZ7RHBT6wPWE4SsNg7XK3pkK4PeM6us7ZapckhYccnyKbDytDYAPN3VE", + "txHash": "2b87a075185cf666c79be1928e2e062b909791d218329710e949d53000c1bc69", + "hex": "020000000169bcc10030d549e910973218d29197902b062e8e92e19bc766f65c1875a0872b000000004847304402205605f368aa969fdd57fa20c06dc6dbeeee255d6113361e3a71446a724ed27598022031492dab3d0b66ff26b23342a276613e2ee8ead5e12faee2530a434a80b0f7b241ffffffff01e71b000000000000232103874609cf663c73219154c205d51dc502107858439d3f537960d2e1584b9ae6a0ac00000000", + "amount": 7403 + }, + { + "xpriv": "xprvA6QQAFpdX9JiNBksoV8PCCGmT7FRYmV3ZTAAzSByvFNkP7MQSeXKwfDQjMjX8A1gTtJeyHYNQRH7ZmzLJ9jv7qQURguBKPtpwHF4pKwCATL", + "txHash": "3d73ce485ae1b7ffbb873a8b924d326b71b3960633c7f622f5da7add0e6b3ba0", + "hex": "0200000001a03b6b0edd7adaf522f6c7330696b3716b324d928b3a87bbffb7e15a48ce733d0000000048473044022075c1111ee18039180989ca13829bb75d2cb4f6e279b68be4191da13f28f44928022021ef6903cedded4bf9711c1814589d7e000cdcbd7fa90c48d9bb992b3fd257c641ffffffff01301b0000000000002321031ee2b7f281cee035cef2dc4305e65a89a6ae5cd57f1b6104462a4045e3457d6dac00000000", + "amount": 7220 + }, + { + "xpriv": "xprvA6QQAFpdX9JiZPmzJDGXyvwyf1VimYcuVU2gT1Z6HXkWEk3EKQ8z1zAHPHSHY8UG4gbRSprd31UeJrrhnBM17dc9bDKdJzhyWxHVKhLQhtb", + "txHash": "aca28fdfd3196a5665245500110cd3b43061082cc3d2e89c2a521c76d596fafc", + "hex": "0200000001fcfa96d5761c522a9ce8d2c32c086130b4d30c1100552465566a19d3df8fa2ac0000000049483045022100945387142685daddaef85b18f2fd87e07d7b21022dad62a341bb4b409be7ec57022003fa4d67652b6da63e45feba29b244768d977d335e211716161460b369eb2b5741ffffffff01d014000000000000232102bf4f86292b4745cc726c1655f9a5ef57383838f5399663b0349649503d77224bac00000000", + "amount": 5588 + }, + { + "xpriv": "xprvA6QQAFpdX9Jin23ZGrS56ogWHtKBycCpgKTHdp8cCU2gz4v6xiMYNK3deeKoeeardZmLserHqGphtA9Z6E69NTvyQKMfzyxWRujaeYSzhXX", + "txHash": "0a7353c4877dd454d492d42d1d42f61d03490bcc786eae2041311fd3c63f1e7f", + "hex": "02000000017f1e3fc6d31f314120ae6e78cc0b49031df6421d2dd492d454d47d87c453730a0000000049483045022100c558d6c23c28aa10ce989c8cc1ce70a86fb2844c8448d1c2e09af87d893e7a850220294af31f3e90b708e7cb761cf0339b33594623d16e3adc10f5ae55657c87938841ffffffff01700e000000000000232102fcfcce4233f821b955030b29d1b01f248118425b99556332702a00bac88bf4f3ac00000000", + "amount": 3956 + } + ], + "testnet": [ + { + "xpriv": "tprv8oRd5TEGp5LE2QezxG2D1VgJoQiGw79A1FVzcqGzSEkFar3bUncpTmW6TfKVAk2R8xVoAGuy3KiSsN9MsNKLLnZjeymaPWcUoXGSTXqB3nX", + "txHash": "394ee800aec7b18f8f607e3ad56461100c8dc881dc87c6bf9ab958af72132854", + "hex": "020000000154281372af58b99abfc687dc81c88d0c106164d53a7e608f8fb1c7ae00e84e39000000004847304402201dfa743dfcc93ef2a7909b962bbbdb5505c45fba2af7d8830df8de0558dfefaa022021d0f91b2f837c12e4b6433bf68c145d5b687210e8c24402f53d4ea2780ad0cb41ffffffff01a8bdeb0b000000002321031fafbb098b601af5b3710f2f80278e66ec33a8180bfe154249f11cd9b5f55ac8ac00000000", + "amount": 199999444 + }, + { + "xpriv": "tprv8oRd5TEGp5LE3pUWdAj5AEfS3KEe3VyoB6qsAaEUmRW3qndctXvfM16u12z2UkUj6J9BTCVywXx78jVDscUEAqLqizBo1TWjMZZtUY18G6Z", + "txHash": "b8cf04e6aa3a953cbe2b5dd11dccc237618517d27027694a16c6d3dd4135cb08", + "hex": "020000000108cb3541ddd3c6164a692770d217856137c2cc1dd15d2bbe3c953aaae604cfb8000000004847304402204772d94d0d0f7ab5e221ec06425127431d5ee0ec88abb472b6d3363e03c6a9e4022059edc97837796ed218ef5662a638002fa442ac57c3716e8f6e4f3fdd648f758c41ffffffff0150b9eb0b00000000232103edc6423e03b8dade7fa279d01a696ad047f46c3cc7e1143cc7d426ca4147826fac00000000", + "amount": 199998332 + }, + { + "xpriv": "tprv8oRd5TEGp5LE7M9shBMwvqrDkk9sxMn72KAbQndkaukpGSYb6sihhUPy4cx3NbqZD8G4dcsfcjg19aBSRiBYDm45VkScQqAoMz9mrtnEPaA", + "txHash": "4ac0023be3898e34d073a772d01f9fa083c6a840aeba2e4b03ea5eb2d595e3ee", + "hex": "0200000001eee395d5b25eea034b2ebaae40a8c683a09f1fd072a773d0348e89e33b02c04a0000000048473044022002094a2700cd14a917f366fe2a7d6163a63aa9f510eb6456a7009dea9d6b9e25022027c71821c9829ae1b67fdfebf05fde61122980ba9cc8d21b89a8e4e75c937fd541ffffffff01f8b4eb0b00000000232103d01674b2e8e0645b5b06ee992e62198c46ce77e3f55d8dc946af5656eab9b2d4ac00000000", + "amount": 199997220 + }, + { + "xpriv": "tprv8oRd5TEGp5LE9ijReSQBJ9zdkkyqqtR2AeSYZRpJN4696vMa5saChwDa3nFMbwhLRdkWS2wjDkLM1zwAg7pZVaDbB2aUujXjWWN9sfwsEAA", + "txHash": "61669704e2ef31320143d2a8b5152ce5614948a207ff3ca97411708d29569600", + "hex": "0200000001009656298d701174a93cff07a2484961e52c15b5a8d243013231efe204976661000000004847304402207514619027c414edc670594c9190972c2d65bb04db0b45b150820f08708d95b8022056ef2d660fef639c727a62928b5a1897a299ecec2823eb9e212becc662bf885e41ffffffff01a0b0eb0b0000000023210314328f938a9c5a8b8e1612d4f54f3cb5fbf3f8afeb57fa2c3858120f5ec10488ac00000000", + "amount": 199996108 + }, + { + "xpriv": "tprv8oRd5TEGp5LECkAijCgvVjQc8bV5vYPNXBvTAN2fUyfs9ZobJpDvUjYYaAudAyJnAhgi8WCqz1q4Qkg3MbLhtPeWxL46CfdmfEZ5j2oaubZ", + "txHash": "37f71f312e2459adf7179655cb81a3663a0b5f4625538defbb9a4269757b04c0", + "hex": "0200000001c0047b7569429abbef8d5325465f0b3a66a381cb559617f7ad59242e311ff737000000004847304402203dbefddc9b737a47c696e7d1c7f39aa942915e94b5aa280a84552d32b37362bc02204dcd48085accd3d89ab8cce0003bdf945e84c3e645bf0149aa3b8b72eeacc64041ffffffff0148aceb0b00000000232103cf97f3c6b942021ec7b4481bb97b61eddfe507d75abb8625c37f56289cfc31deac00000000", + "amount": 199994996 + } + ] + }, + "toMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA33Uz6qJD2ae6dN6cR5usc6mpygMCj9Zusyb6dPe9ziMbEAX1Tps8Cgfk1ixzoyTCKgY99adbsDDd3oexQMJh1i5mzHuEkNAmZ2Gn2dDpTH", + "xprvA6oFrgM3M4LH1wPgq3MeCmhPBgbZA3JwchUNmkTYivUsqPYRsSvHnL6wSyA24Si9WRfZB3biGLP6ykPENHYbYuDkcedSYmcr5LWD8TY3gR2", + "xprvA6oFrgM3M4LH5KRNs2DJvgah9k1MToFXkAkSSKtKXuJchKNHepMqM6BmLMBdSUu1jtMGi8gjHbDKwXvVBYNLNYmMNoBaMZvgLZKozgTLcph" + ], + "txHash": "90c9413b72f26509f21c27c8045f71e09c83af46095b0c9e605c1c7ce1a5da44", + "hex": "020000000144daa5e17c1c5c609e0c5b0946af839ce0715f04c8271cf20965f2723b41c9900000000049483045022100d85c3471301f0c36f05481c4b5881fd0d39df82e0a199e781562340c542ec8d802204ee7c7f43d724cacd30d7182c1bdc7e5db579ab5cb1ec8a4f1990e1fcf91ff2341ffffffff02bb0f0000000000002321020af843e0bc1cb33c697c72cb5e476997e598cf243c70e049a86c10a9dcb4bb14acbb0f000000000000232102468c04e9694f50e5717d0190f0426e6ff15a4de225c32da53df618939186fed7ac00000000", + "amount": 8314 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JhyZ4YutETuBuqtaS8oPRcoB9CHzurnvzTB1gfvmFNZ7RHBT6wPWE4SsNg7XK3pkK4PeM6us7ZapckhYccnyKbDytDYAPN3VE", + "xprvA6QQAFpdX9Ji2pinEHyc1636YcvwTyJsTu3dUaxiVbNPhMebD1w2eNZMjuMK9dEsMSpHDxLiegaXaB6siQarThcJzxLicDbuzWrwYiiQTCW", + "xprvA6QQAFpdX9Ji5YqBFQzSDwfKymncMdu1ns7iUFAhYBNnRSZbc9q3ik6vACaLgAyM5ow8YY8Wved3JnNZxBHxoATkX3UVcamJ9EuydDoAHwv" + ], + "txHash": "86299bd7d78335b59b2f4a237c18e51b43753c6ab475588f8a46c5d84cc3afb7", + "hex": "0200000001b7afc34cd8c5468a8f5875b46a3c75431be5187c234a2f9bb53583d7d79b29860000000049483045022100a76d59704d4cf66b91ec7b9a147d77d710658f1c39b6cee41cd93e3a11f1c9df022004daa2a1b5aeea21d2a0d3135ba20baca98e441fe0bdefc6c6bfeddf1515613541ffffffff02710d000000000000232103f67db1f5e44b77c92eb9b1ecb3b92e3500f852a11fb13a34fafebca021987b70ac710d00000000000023210234842cd6cffca06dd577cdf2adec7a91809b2c5249f83706aecd652a1c8865daac00000000", + "amount": 7143 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JiNBksoV8PCCGmT7FRYmV3ZTAAzSByvFNkP7MQSeXKwfDQjMjX8A1gTtJeyHYNQRH7ZmzLJ9jv7qQURguBKPtpwHF4pKwCATL", + "xprvA6QQAFpdX9JiQEfby4yzhBRax7xNJ1ZMpaUmCrh8BvKNs4kyxo27xSo39LyvsdZEuiMjPAmVw3o4chmBZwr7WSt8kKPBxqweCjEjm3qGkU2", + "xprvA6QQAFpdX9JiRpXBSSZ92Zhxo443GLeojKgPQzxXPzUDJkwE22A5rmbYGNYhFKeqAWv6QXUWMi8Zeew3DE4dGfkcdBbudxqrcFboXv6cKTV" + ], + "txHash": "79dbbe1f09760f4a2285d971ab19e61beb327b6ddda67de3b5cf85c51b8ffcb3", + "hex": "0200000001b3fc8f1bc585cfb5e37da6dd6d7b32eb1be619ab71d985224a0f76091fbedb79000000004847304402203c74202348a704555b3d178dce64b6750e6d67ce52560b7842f3348c27bdc6fd02204ad99f53b3c11b4979d78b2bd29f216e8895fc684a56e509baf7b4d720b3c95541ffffffff02160d00000000000023210236d3a577db7debd5b1b97d5f630d27d30e75a727cfc7687451f909dcf29fda69ac160d000000000000232102600bc9c16673400ff695b7a320be87f7fcf4060f0b709a7f7fb5fdb0a4e3b2d3ac00000000", + "amount": 6960 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JiZPmzJDGXyvwyf1VimYcuVU2gT1Z6HXkWEk3EKQ8z1zAHPHSHY8UG4gbRSprd31UeJrrhnBM17dc9bDKdJzhyWxHVKhLQhtb", + "xprvA6QQAFpdX9JicUMhmpxUayGZJ9vewp4DY2TzfHYqAucGudTbgDE3QNZSNktSzsRUpjFjgDiZNhL7S4K67VAAv7E2ptdBmGCjBPjG3mU4Nz9", + "xprvA6QQAFpdX9JifVVZRemkR3DBZAeVPXn5TEhZg4Wdz25oFhJCa6sBDzTAjyx36vtQsVn2cC2kJXXPyUAwhxpYxg97FLxBdn53jLvCMTsnxhf" + ], + "txHash": "26b7360713ff5d8786dfa71ba8cd9f888487efce42009936b913c72bc4589a3c", + "hex": "02000000013c9a58c42bc713b936990042ceef8784889fcda81ba7df86875dff130736b726000000004948304502210088481649370689b40645c793d584a5caba6c20d97039a050e1905baee864649a022030c540ecb33a7847ff4f8484f0a3a2e7dfd54b62852472390b5939c07288cc2a41ffffffff02e609000000000000232102063d41c788741627ed30b31177a9eec4ba0879d6d8d839daabd01103dc233d86ace609000000000000232103f284e5ae28bc897f126bbee1c9438ef4eeba375710829fd9af94d37d0167feedac00000000", + "amount": 5328 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9Jin23ZGrS56ogWHtKBycCpgKTHdp8cCU2gz4v6xiMYNK3deeKoeeardZmLserHqGphtA9Z6E69NTvyQKMfzyxWRujaeYSzhXX", + "xprvA6QQAFpdX9JipLUHDfF7fcqmg4cVjVznt6rBBTivkWPMjk6GuUKoa6FPxbcu3VvBuzRrMZtxbqWAiyrAhbcvGkbhKP937ahVFKGd9r9FXGd", + "xprvA6QQAFpdX9JisaFFrPL2eP1cTwwiNpH9WD6kTZNAHxrU1fZstVSXzGNCnzj1Yks3bmKW1r5Vvk8rQHFvB94jjBLy8yTzLRcjmVn5YEidLfE" + ], + "txHash": "8bb36c376d9b370406c93ad3e765938f7f5959bff1273bccb724818e697104ee", + "hex": "0200000001ee0471698e8124b7cc3b27f1bf59597f8f9365e7d33ac90604379b6d376cb38b0000000048473044022018dfa49b228ecf2d04a59e277c2e5277287885da17d1c1aed4e51e5d2aa4fa23022079354c7d49310d53b43aa1e2383a70312aecfcf1951b81a79ee61af437fadef841ffffffff02b6060000000000002321032a03513acf4908321e5861c75931f9b995e8e4cbf733ee291c142a421faf67cbacb6060000000000002321030e8826761de9bf57f9ce382a6d44cbcdb76514227d0e3853011538f3b15d2d69ac00000000", + "amount": 3696 + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8oRd5TEGp5LECkAijCgvVjQc8bV5vYPNXBvTAN2fUyfs9ZobJpDvUjYYaAudAyJnAhgi8WCqz1q4Qkg3MbLhtPeWxL46CfdmfEZ5j2oaubZ", + "tprv8oRd5TEGp5LEDAtpbvWwbkHSktaEZ2DoUXEp5Uah5Q7audrNdfXYYoBQMxBXzSsNu2dgGiB9XPR8Fkv7Y3XmknKDYPJXZ9By8Y84KckWiFH", + "tprv8oRd5TEGp5LEHtNCs4DPf9WSdxzbS1qTRQyiFYNRFKCTPuLQt4yUG96LzhrTDdXLRGdELEfpKCCHeSzCvKd9JdSwXERFNFVkHB9PZ1XAo7D" + ], + "txHash": "91e610e0b3ef96b62fe0cedf65a32b43e958c4be51aa52ef028d3aa79d2757b4", + "hex": "0200000001b457279da73a8d02ef52aa51bec458e9432ba365dfcee02fb696efb3e010e6910000000049483045022100c0f11860b4e7c1933ee72339ca528bb4c4e3f790317133abab94a1801d61595b022061af31dbe60371d3fc2b522976ce694ea0972182eb73b07b7841694906f79fc241ffffffff020ed5f50500000000232103758d09a3f615fd3ecc5cf66c0a9fc984e731322e11011f6f58e63da33db2ca84ac0ed5f50500000000232103c17ad80b667160691ac7d69fb6155b8842cede857551a0eb866e71b97c09e5fbac00000000", + "amount": 199994440 + }, + { + "xprivs": [ + "tprv8oRd5TEGp5LEJS8GqMqurpd4tiawJMkWSTaiJ579LzHc524n4ksiLR2QuULVEJZNzqAskQamXajFczNMB1bN8c23Qi8Efn8tZjw4GwkifUe", + "tprv8oRd5TEGp5LEMQSMZtG6e6e2HJQyYRJBu7fMF8x5Jmi36jLpz4rBBtkp3CMzrD3psmSDuUutiqHNJnkRqEYZUXmu4u6Va4uQNRJnmbF4wS4", + "tprv8oRd5TEGp5LEQ83hHtj8oRW9LJaTNZp7rRaKXP5RqZenG8mio82w4j51BQhBRjtZAMr82kHaBBEbeWhKPKAK9RYPS2ErWkSpmP2YRE277bP" + ], + "txHash": "dd30b6e4de19bc798658979b3d7b705e10adff1185a804940ac720c1bc15594e", + "hex": "02000000014e5915bcc120c70a9404a88511ffad105e707b3d9b97588679bc19dee4b630dd00000000484730440220578fbad978c652ae65f980135d5dab951c4359b733ea7b10b575be6123b13078022038563a8401e4419d9738a8a64e315b93556ea228d35663b7cb885c9c6058423b41ffffffff02e2d2f50500000000232102e2ac19a916304b3243d0b60aa2918e7cf6cc7f31c6e5b20a9377fe254000c510ace2d2f505000000002321034cd70bfb7052425a90963527f7e0675053e7db2761f350da084faceda3e4d0c3ac00000000", + "amount": 199993328 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpR1JPDA4C1nUKJx7EE7WRFoPCzQSmkZxi7hFQHLY5mdaMDyfEbWe9eH1gGA6GDgPXpCFLW7rTVh74Gmq4eyYnBseGkg6ZriX", + "tprv8oerBwBqNPZpRga7kx8xtFvcYAD6ZfqhtGP3ij6vEQKVnsuV7NQUG1RsRX3QmEDzhmm6X9y2sFJtakJEUgyX8r6dXTzJn9FBbEy7iKy8nk9", + "tprv8oerBwBqNPZpVUwRmVwi6GgskzFdKSBdrbgqdpkNnrrigyPAbcFJnFZfg1gS4qHCXRKPegFxG1NkhB2jjdDBWbtCn2EDQ7j6WsuV3iLmHNd" + ], + "txHash": "01af1749f46cda94b32b5525f2297ce3efc9b213a47e79270c01a8b727ebfbb2", + "hex": "0200000001b2fbeb27b7a8010c27797ea413b2c9efe37c29f225552bb394da6cf44917af0100000000484730440220594409439ceb47314eec0e1126d401eb3e1ac43bfd475817b22d4ba76fe07ad9022028d7042ffcdecc7b1175b854f68779a3ace20ae0ff66676293d760c4a1dfcc6a41ffffffff0254eefa0200000000232103f11a04487a79f9893f2e66a74702605eccc0a45073bd794313c70a6690bd8111ac54eefa0200000000232102be477f1f9b3169235588e37c79af06681abdf89f2fcffd0fe241a0dbc5931526ac00000000", + "amount": 99999444 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpYDApxZUrzdpjLxQkgUdxBd6MPUaron5wNW4c9ZLVEDjseqD51S8DAuMj7h9fdDigXpermHnqTBKjtk49FUk2oaWsh83eJSj", + "tprv8oerBwBqNPZpZAmkv69tTECMgWKaHvnVYkciRgpGonb3E9aQ7xQKQcnPSjqxED6jGChziy7ZQE3Kjg5FYbWSuJyyuPY421PL98MHgdKLWSj", + "tprv8oerBwBqNPZpd278mrGGCQBtasxFtPzBKpBWbehzoHpVDzfAj9AqriWMnVnWNQHGaAMXuWTBrXicbsYsryoQoRicyvtpo28FFVy2CjZ1JbD" + ], + "txHash": "ca8457279b0fd24d5fabb8c7849cb0df58ec66f5f95561b49875a7fa524d6b36", + "hex": "0200000001366b4d52faa77598b46155f9f566ec58dfb09c84c7b8ab5f4dd20f9b275784ca000000004847304402201af9cbde6560ad8e66fd3e9cb4ac1fd50afe91f2d99d12ef63bbb0fc32cd1ed7022025dfc521c49744b353392eae82bed76c212ae53b11182c43f5dbba5e7e635bec41ffffffff0228ecfa02000000002321021432e55cc59d1f2d6c794b490cc9b3b1c0ca73c2baab56fa7e8789732282a366ac28ecfa0200000000232103291f1cac081411d596c9a84ea3a28f6d38368c9bb6c6a8ebdbdbc77577bf5decac00000000", + "amount": 99998332 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpg3SCbQffLieaioBs2dGH659nHzdLxUZmf5GBnb66CQBBD964RokWYozzi72m9v9RVZyp4Fp3KzHfdrrrvLboDH3GPamXRJN", + "tprv8oerBwBqNPZpigWyyQayz7vVqYeYQVBjQU1WdeURj46jNw9XDbta8h5suUnAua4TTaJDvmEokskV6ACPqAtEY5wjF32m6LBRnUtrgB9z17w", + "tprv8oerBwBqNPZpkhmVaMFKk3rNRVrLPaaKX1C7JKLtvVd37o8jbAU3PcPppjeW9MTrJ41nYQ3yinXQfP6e49y9oNbTDdyFD9BSWJVbzMjo6Gi" + ], + "txHash": "d2a502ee41f6972a9eb05b05cc5862948182b7bafa04b5fce48921a08eeb8acb", + "hex": "0200000001cb8aeb8ea02189e4fcb504fabab78281946258cc055bb09e2a97f641ee02a5d20000000048473044022067be6f0bcb06642d07f5d88ab001419bf98fb90f4563c95b8906aa1ebd7658c60220679abceac4eed3ce3e80ce2f48984cc6aa6ba10908e068d68509fa689e65e35b41ffffffff029f494c0000000000232102ec3f7c09d01f29ab84cc5c646a58e3ee65d3610b8174a626e6e0b2550b0f7865ac9f494c000000000023210212555cf40a85ef6d889f83818eac7eebc867ca72c2f917faee8fdf0f390c48d9ac00000000", + "amount": 9999722 + } + ] + }, + "manyToMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA6oFrgM3M4LH1wPgq3MeCmhPBgbZA3JwchUNmkTYivUsqPYRsSvHnL6wSyA24Si9WRfZB3biGLP6ykPENHYbYuDkcedSYmcr5LWD8TY3gR2", + "xprvA6oFrgM3M4LH5KRNs2DJvgah9k1MToFXkAkSSKtKXuJchKNHepMqM6BmLMBdSUu1jtMGi8gjHbDKwXvVBYNLNYmMNoBaMZvgLZKozgTLcph", + "xprvA6oFrgM3M4LH7QbYvSgKmiUUin7dquJ2nCoLKY2Y8ghFC2WnADhSjHiy6phmeWpdpJM35NkbXRszPao7KrHkH42sUE5ycQF5ZaeiVh28jQw", + "xprvA6oFrgM3M4LH9bd1VioucUx1vCynmdAP5wQBq6BAn9BfakqGvrFdQsVmvA9xesZ566UqHdGYgzeTCLrYazrJD4w5JFYBucb7EESx6ptQ4AN" + ], + "txHash": "96f4295830134962e9c1e33fb50e8680af320199b0a4ebb78c1a079eee58b452", + "hex": "020000000252b458ee9e071a8cb7eba4b0990132af80860eb53fe3c1e9624913305829f496000000004847304402201739d5a45fdba4ebd8d95dd2692bb29b4030d2249a3a7a248e6b984dbe0d930e0220142e61a062ce365b116f0210323998f51a46d005aaef1c4871f2a3509fde3c5b41ffffffff52b458ee9e071a8cb7eba4b0990132af80860eb53fe3c1e9624913305829f4960100000049483045022100b7c79b2dee40e9e99cbf641f5ce1b1baecf094b60d7aa852b5b914b093498121022009df0fd607c7e62ac209441450d3163746c2d11a4a1cf9cf18f4388db2dffc4d41ffffffff02a50e0000000000002321036320e0ce7c2e5b8f49bdce0ce7d603892b18ddaafd2aa8e4c1018bf514959963aca50e000000000000232103ab298d47c06c7506b05002de8607b6174b88d5806f2936579d5f7bc21cef7372ac00000000", + "amount": 8054 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9Ji2pinEHyc1636YcvwTyJsTu3dUaxiVbNPhMebD1w2eNZMjuMK9dEsMSpHDxLiegaXaB6siQarThcJzxLicDbuzWrwYiiQTCW", + "xprvA6QQAFpdX9Ji5YqBFQzSDwfKymncMdu1ns7iUFAhYBNnRSZbc9q3ik6vACaLgAyM5ow8YY8Wved3JnNZxBHxoATkX3UVcamJ9EuydDoAHwv", + "xprvA6QQAFpdX9Ji8vtMcqGRjLGYzd4NKAv4a4yGKDhmp37bMXSHACJXLvWdULRHmMb6V9g2vqpFpo8gNZ4d6mkxWzx8fwpEjWKV9SG8UwVxotR", + "xprvA6QQAFpdX9JiBEQ6kaGEjxgumqpreVEn7hXcZjEL63ZLioUeByAd5uNQSEYUoRFMX2VSexwSWh2j489PiXHFmb3XTF66t1uDD9cwoBghiUx" + ], + "txHash": "e7211e3896932e2b3cc92d1489cbc5f726e04685c969005111bf99c4b02559dc", + "hex": "0200000002dc5925b0c499bf11510069c98546e026f7c5cb89142dc93c2b2e9396381e21e700000000484730440220252c55d88713e6ad1b7a3b57a98b43f7c792c2d91d71b3c7849fda1ac1b95a7102204d211b521b393b148b826d42cb4ade93c7d830d0a9b18dec13ae91fe1919438f41ffffffffdc5925b0c499bf11510069c98546e026f7c5cb89142dc93c2b2e9396381e21e7010000004948304502210088d3de0d5bc794ae34e4cd1b88ad86964443a5278209d884cc4ca8149d473f43022046d8088cc8e902fe2841d2e40233f77e4cafdbe7f2d313a0b15f7a0db3c896da41ffffffff025b0c000000000000232102a70c7e1475a001838df196863ace3893313a3b437bd783fbf11bffec1d9914e5ac5b0c000000000000232103afb8d456f340c970d004c6aec07cd96cec280ffd3bbba804d26fe96c311728efac00000000", + "amount": 6882 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JiQEfby4yzhBRax7xNJ1ZMpaUmCrh8BvKNs4kyxo27xSo39LyvsdZEuiMjPAmVw3o4chmBZwr7WSt8kKPBxqweCjEjm3qGkU2", + "xprvA6QQAFpdX9JiRpXBSSZ92Zhxo443GLeojKgPQzxXPzUDJkwE22A5rmbYGNYhFKeqAWv6QXUWMi8Zeew3DE4dGfkcdBbudxqrcFboXv6cKTV", + "xprvA6QQAFpdX9JiUEYjjCtBBhAmoAMi3C118tnnyPNVmaAPy1jqsLX8e47BtWbG8ftwq6G4Ru3dSDDJBsZCMYBVEQfh9794VEWyS6oHMUsgWwB", + "xprvA6QQAFpdX9JiWaQyziNdA8gNHeS44VooC1H4uEd9GzLhmANb23Gu79jkdf7ye1oL32ymzdwLTvjhPfNWgVGNBULfUQuyVxgvzRHzQZcDDN2" + ], + "txHash": "5f2256325d99ff95fd00444cd98c4b65a22783b6effa465df194a9983bd9969f", + "hex": "02000000029f96d93b98a994f15d46faefb68327a2654b8cd94c4400fd95ff995d3256225f000000004847304402202cf42137228513cf35e08a2d4b9a64c4ad0a92850997c9044ac5ff80a4a54b53022064725380b1d187fcfab73962b79cd7fd0b0bc2571ed9d3baca5b19bd3651f9cc41ffffffff9f96d93b98a994f15d46faefb68327a2654b8cd94c4400fd95ff995d3256225f010000004847304402205f80bd73727b30163a2a944ae28d9e7c1cbbf6e0107bb6cb1f078d32e507072802204bb16d66b4dff75c91d06fa0c87132807cd2e5abb7578fb128689a887dfd302e41ffffffff02000c0000000000002321032939055e5c815484c9f7f8dff27f1d6741faa25249247c542b3eacca7628402dac000c00000000000023210358596a67c9f9dbf82d3a17c7554b1507c01665c0052afac810eef52d276ecd06ac00000000", + "amount": 6700 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JicUMhmpxUayGZJ9vewp4DY2TzfHYqAucGudTbgDE3QNZSNktSzsRUpjFjgDiZNhL7S4K67VAAv7E2ptdBmGCjBPjG3mU4Nz9", + "xprvA6QQAFpdX9JifVVZRemkR3DBZAeVPXn5TEhZg4Wdz25oFhJCa6sBDzTAjyx36vtQsVn2cC2kJXXPyUAwhxpYxg97FLxBdn53jLvCMTsnxhf", + "xprvA6QQAFpdX9JifVVZRemkR3DBZAeVPXn5TEhZg4Wdz25oFhJCa6sBDzTAjyx36vtQsVn2cC2kJXXPyUAwhxpYxg97FLxBdn53jLvCMTsnxhf", + "xprvA6QQAFpdX9JijKwoe9ajBeeBy2pKcnsZPr1c4fpLSSGWadwCxj8Y52VhpBfLgtN2BdmpuaRRa2M42jZ4CHscL3svJDq1VBDR3enquVLyiQF" + ], + "txHash": "795332702aeb95f9a53c9d5c2a8c4a98f356b8301a6d477bc51cdd88f47c6b08", + "hex": "0200000002086b7cf488dd1cc57b476d1a30b856f3984a8c2a5c9d3ca5f995eb2a703253790000000048473044022046a3679959ec5cd93c0df769ef4c4e5b6bd1438dcd58631abddbdb68af828060022037b94bc1f809a99d5ac0cb9742d4d01b9fdaa682dbadcff715e371a3bd6e41b341ffffffff086b7cf488dd1cc57b476d1a30b856f3984a8c2a5c9d3ca5f995eb2a7032537901000000494830450221008104de89807c5c9f583e2ac3d466edcc7eb75dd22ff1d7b39969f816910fb077022023ca97146b009bcda6d5119387a0d68e6dd7b404847d75f12e99a18432b546fb41ffffffff02d008000000000000232103f284e5ae28bc897f126bbee1c9438ef4eeba375710829fd9af94d37d0167feedacd00800000000000023210282266c45bd2603efabaaa2bb190c6f77912ed5f8c74223de228c397a2d1a5c29ac00000000", + "amount": 5068 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JipLUHDfF7fcqmg4cVjVznt6rBBTivkWPMjk6GuUKoa6FPxbcu3VvBuzRrMZtxbqWAiyrAhbcvGkbhKP937ahVFKGd9r9FXGd", + "xprvA6QQAFpdX9JisaFFrPL2eP1cTwwiNpH9WD6kTZNAHxrU1fZstVSXzGNCnzj1Yks3bmKW1r5Vvk8rQHFvB94jjBLy8yTzLRcjmVn5YEidLfE", + "xprvA6QQAFpdX9JivUsejGiXE2WFBHAznUmhP5BjtmzpPSTkYN7AZWiG9Ggc3gPyoJPZJZ1JFGuytzGABjmsy9JCCnupforS6Zvu1f9sPKMPyzd", + "xprvA6QQAFpdX9Jiy186fRPLpxFYaTfziT9kCq4RKRieTB9bcZEHiyASm4EwG3PHhx7wNxdXCjPD8VotHHuwG8RrzUxvmAUPYYM1SEreNoBCuW7" + ], + "txHash": "b428a5c019d8e36fe19fe37f94ff833d977d2801a8ef99b5549f9dd8deef8b0c", + "hex": "02000000020c8befded89d9f54b599efa801287d973d83ff947fe39fe16fe3d819c0a528b40000000049483045022100dc414f77ab5f57a26c620c81bd7fef9bc5f208d2ee130a10424f06fb4afe717e02204eabf09a4b26e9823595abf25598f313e4bb16a140afd9d48083ca7aea762beb41ffffffff0c8befded89d9f54b599efa801287d973d83ff947fe39fe16fe3d819c0a528b40100000049483045022100e1227755dcdadb2e889dba1f60c4a7c0ec7819e2cce964d97dfb17c982191b5a0220139558c3d7d9707941e4f53e4fd7749527e683756782e5ecd520bdedb5107bf141ffffffff02a005000000000000232102af847c989b97b849966de9ee5346a43e04fb0831134d1ef362bfde6fe4f85ec9aca005000000000000232103492472f2fb6c03ca83c5bb1c720d45da4d276f74998ffa62240c6cecdf47a7d8ac00000000", + "amount": 3436 + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8oerBwBqNPZpvC3g4yi2xbHWBAYZn38ZUUQv1QUPb1aarn9vXk4sx2eUj3USCR1ZpmMq5P5PXv5xJm4ha4nFGGPaab4G3FRX4YmxLJz3yE7", + "tprv8oerBwBqNPZpyYwd7FpKQfzdVQBVL63dPaEHeXqJoBqhDGzVJfim5CcA624ZEkZcCVvdMtVxzXfaf3pQVKB9UQdSqyED1H1sw6WyNxG5888", + "tprv8oerBwBqNPZq2py5C4S4y58X7mvokirfn69tFRkZX3Jx53UPqHBmG7t311vk4pA6Yc4uZbiep946ND7fqZzZ7b2xhamxTwH93AfsPv8WCqb", + "tprv8oerBwBqNPZq52M7vjAPACbrGNpYwNVxFUPnsA8Zicp2R8m3V2bQbQXdhe9D52rRwt51VWEDdDdrB3TZp316F8i2xD9J46Ktze5zLqYUfBS" + ], + "txHash": "46cb904e223ae7eb878855d6da7823ab3575286192769164882f5db217575bd8", + "hex": "0200000002d85b5717b25d2f886491769261287535ab2378dad6558887ebe73a224e90cb46000000004948304502210093a56650a31e4d873dd76d0b3ad9be9425e59307d0a0eeaa9eca16132b2d463b022052c8f9e6c482ecb65b70ee86c1996e3bf4b80e112a5e0f69368703651a12fc2641ffffffffd85b5717b25d2f886491769261287535ab2378dad6558887ebe73a224e90cb460100000049483045022100f7a1d73d7e84bcfec13cd3d325b57ecf7fa5516531ee90f29374c0bbfdb837c8022033ccf0e7d8c7d083bbddcd5f40643330ec852d065cdaafc54e3af091e192580741ffffffff02e8737d0100000000232102e09a968b6c493af3d53a4c62a299861b93463a8c6bf5f7b23cfbc9a7d8d3626face8737d01000000002321023cb109b586dad88793a09fe041577d1f0aedd0d31734dc0f53eb6ae9ffe01b83ac00000000", + "amount": 49998332 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZq2py5C4S4y58X7mvokirfn69tFRkZX3Jx53UPqHBmG7t311vk4pA6Yc4uZbiep946ND7fqZzZ7b2xhamxTwH93AfsPv8WCqb", + "tprv8oerBwBqNPZq52M7vjAPACbrGNpYwNVxFUPnsA8Zicp2R8m3V2bQbQXdhe9D52rRwt51VWEDdDdrB3TZp316F8i2xD9J46Ktze5zLqYUfBS", + "tprv8oerBwBqNPZq5gC8FfAi84v6841PEWDeC7e8WCqxCoZwf2aFt4YTQfQonqHNqiWHVGgBRkvPY6ThJYK5cPykcKZbQHMLKAVAFfBh6CDwzkd", + "tprv8oerBwBqNPZq8niUdusKkzZJURJv4buZW3RiBFoDiyWie3R1GADeA1HrSZGPn88UTAKLtc4ybrhtqnUTYyF8qEWhQeVPziwy3P1csF45Zv5" + ], + "txHash": "8be860ef7eeed06dc77b940b29081f03fabff0aebad0b8778ad4d5259edb8d70", + "hex": "0200000002708ddb9e25d5d48a77b8d0baaef0bffa031f08290b947bc76dd0ee7eef60e88b0000000049483045022100b8ac325d4d134f22ffb51a165a00397de85244eb48374817e3ecdf525832118002206327056e4bbb13f719e435875dab5875d0f9112a2fe1cfff792aa252b455797841ffffffff708ddb9e25d5d48a77b8d0baaef0bffa031f08290b947bc76dd0ee7eef60e88b0100000049483045022100d4bdac5a3e74804d98e344ec92ca30f22fcdcc0cf28959174346b3196709a35f02205350e1e81351625052d863fa09e48344840378b2917128d2556e15335a2b726541ffffffff02d2727d01000000002321023557b9f71b2c7a477cd5c6d5d5f733233db3b4d38eff5a45cd5026c1cece8dc4acd2727d0100000000232103d3b5a70353be5a91d289aabbe475a70030a735d1828c23f6b1d07609bfe2b0a1ac00000000", + "amount": 49997776 + }, + { + "xprivs": [ + "tprv8oLXEqWmZfT6NuegXAcHgBsieFbCuCREgFn44QVPsUzwLTwrV3bDKJcPjhuo9v1kF2MVc5ShLUA5MjsCyxr1DbqruuPLB39wTNRM8dNwoXZ", + "tprv8oLXEqWmZfT6S8f4DtneFjSiMxcG5bY59YdgGUmwccnPRF3E6vwc95CVwwDp3rG66mtjBYsXDLkiDpfR3TKLr3BjLmMowK7Nzcvp2z8isnz", + "tprv8oLXEqWmZfT6SdgzpeWFiZDGBz45vSaE9z6bz4KZELcokcLbeyVkBCE91cAeZekEYebfGqtvMGN3mTXimtbxtjj1XpoRGSc5ZGqaBdHBQZ9", + "tprv8oLXEqWmZfT6WUmujWaFSTdd9WSFbvdaqtzTNByTd2PWCme1RwW7Z9SxsCbNUgZQNzaE746XHEUXJmcpNNrnua6LCWqWttzmcwojkYGmTGA" + ], + "txHash": "8d85f3a023382962ca935daabf087f130bd11b8596c2a322e0d806e9e9c34e3e", + "hex": "02000000023e4ec3e9e906d8e022a3c296851bd10b137f08bfaa5d93ca62293823a0f3858d0000000048473044022009d30888f07912776adca769df217b52371b77a549b5d198c243a225d80faa65022020fad40ff75c0701f9f7b4b77cfacb96c76107c5f77c068042d0a617bf3cb18641ffffffff3e4ec3e9e906d8e022a3c296851bd10b137f08bfaa5d93ca62293823a0f3858d0100000049483045022100e534cc8e5488f850464b161a71482e836d5708f367e21b6955651d5ef29341e202203607ac8e54ee589858745a6d091ec00093b9dbdc13f15e5828297e33ce5b5d0b41ffffffff02f4b9be000000000023210288fac026ff646a869eb9230d8a8b45b51ad51df37802e38fda6b2eefa5592ba0acf4b9be0000000000232103d2e70538915881cf6651c37f48d8df406bdf69a102ac90fabd13aa1698898b82ac00000000", + "amount": 24999444 + }, + { + "xprivs": [ + "tprv8oLXEqWmZfT6SdgzpeWFiZDGBz45vSaE9z6bz4KZELcokcLbeyVkBCE91cAeZekEYebfGqtvMGN3mTXimtbxtjj1XpoRGSc5ZGqaBdHBQZ9", + "tprv8oLXEqWmZfT6WUmujWaFSTdd9WSFbvdaqtzTNByTd2PWCme1RwW7Z9SxsCbNUgZQNzaE746XHEUXJmcpNNrnua6LCWqWttzmcwojkYGmTGA", + "tprv8oLXEqWmZfT6YdMJpMzHhMbCiDyGrrL6n5Lj6rENbN2DDMYoWMGmj9n69oNMP6kFtVLaU3beCSeg5dmkhUoaNZVsJn99HSUTdQyyZTokvHN", + "tprv8oLXEqWmZfT6cSdeFifvHo8sqGhYPU2ovJyaCd9yVN9FMqgzVccHQhANjhddKSFQ5SA5n7gTBPPN9pvHvTBcVYEy34FZKVxNsssXUKojP6q" + ], + "txHash": "bc4aae26278ac6aab827e08a5dcdc3f5e8b288b907180552d62f5c6b9d56c1dc", + "hex": "0200000002dcc1569d6b5c2fd652051807b988b2e8f5c3cd5d8ae027b8aac68a2726ae4abc000000004847304402207b8c70d6f51655fc5b096f54a4bf530ad665489015172078822e3766a24c41c60220343b2f2095e1625f3f2b5dcb4f65c280779990d6a9147b327e3fbce1935799c041ffffffffdcc1569d6b5c2fd652051807b988b2e8f5c3cd5d8ae027b8aac68a2726ae4abc0100000049483045022100811fb5ee681e6ad8441c4e9500d96f8a094e4f374b332f7337e4bf639985479702205eeba22c9d131b4828923bfa45afdd39d5effc50cf5cae6f385249d29c06fd4a41ffffffff02deb8be00000000002321031450a18e237271e3eeda565cb3871d361f183e3cc0df1022411218e1b8c6e14aacdeb8be000000000023210359cfc98e7d8b680862416f5acced770f4bf53d45356fce19db224d349cb1c9feac00000000", + "amount": 24998888 + }, + { + "xprivs": [ + "tprv8oLXEqWmZfT6YdMJpMzHhMbCiDyGrrL6n5Lj6rENbN2DDMYoWMGmj9n69oNMP6kFtVLaU3beCSeg5dmkhUoaNZVsJn99HSUTdQyyZTokvHN", + "tprv8oLXEqWmZfT6cSdeFifvHo8sqGhYPU2ovJyaCd9yVN9FMqgzVccHQhANjhddKSFQ5SA5n7gTBPPN9pvHvTBcVYEy34FZKVxNsssXUKojP6q", + "tprv8oLXEqWmZfT6eb4vYUMboWiuVZybGgJkRazv4xkYzwpewC6XpMs2GKu5yvXgaKneStegmbxCokcc7MXK3wgZ5nqkav27CvWAFVd5jwx8hjk", + "tprv8oLXEqWmZfT6hK7FwzcRpUDPveXNa8rBaNrXUXN5LeByvyUJMYS88cAc6JiFkxdcodXVdEAvqwMbLrzGq3GLnKURLbYuyd5zRNJCM2WErgY" + ], + "txHash": "967e871a1a3da8200986b7106a383ce14923bc5310f16c89ad103e81ad1792f5", + "hex": "0200000002f59217ad813e10ad896cf11053bc2349e13c386a10b7860920a83d1a1a877e96000000004847304402202b037d20fd3c7e93e64b281f3dd014bf946f330eab9e5aad48bd4460cdd6645402204563617c72f981c3947f188a70dc0d8b75b2aa4970621907518487f0e20d733241fffffffff59217ad813e10ad896cf11053bc2349e13c386a10b7860920a83d1a1a877e96010000004847304402203f42e537e017ea27d551917d109ebefeaaa3da4f2cb40bceca46eea917730c5c0220466869e994de0883189e90d9fc344c38ea72f3b4433d6de4032b815cc69ca51b41ffffffff02c8b7be00000000002321032a6e00ff589cdb6a8a55c76d8c34f9470e082ebd8b14449b4e558cae6f7ee08facc8b7be00000000002321029f02a24e72079ff9a9f7c4c0682e3ab14a90f008205e6423b90d4e1930cc7120ac00000000", + "amount": 24998332 + } + ] + }, + "fromMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA6oFrgM3M4LH7QbYvSgKmiUUin7dquJ2nCoLKY2Y8ghFC2WnADhSjHiy6phmeWpdpJM35NkbXRszPao7KrHkH42sUE5ycQF5ZaeiVh28jQw", + "xprvA6oFrgM3M4LH9bd1VioucUx1vCynmdAP5wQBq6BAn9BfakqGvrFdQsVmvA9xesZ566UqHdGYgzeTCLrYazrJD4w5JFYBucb7EESx6ptQ4AN", + "xprvA6oFrgM3M4LHBPnEU6hEg6HFyy3Qj43eN1CWrqyLtSxvf4P99ZpZ42N65LpAKWa45z6MYTS7pqSqwxAfseCcTB68ipJuHKaYA8ngff5h2At" + ], + "txHash": "37befaad146f25cc68a6aa7523703595bc1ef47a71877dbf60158ce4f82d0901", + "hex": "020000000201092df8e48c1560bf7d87717af41ebc9535702375aaa668cc256f14adfabe370000000049483045022100c1f4041e88e168ae74ce6daefc40344f29662e4f8bcf54d02107f7b7aedffc5902202d3f82c138e50613792fa84c49f3a825098f144998665928a2e91b9283b484a441ffffffff01092df8e48c1560bf7d87717af41ebc9535702375aaa668cc256f14adfabe37010000004847304402202c9d8e7c423be3f1feba6249c4b86779651450796bace63c627cef73d623f44e02201e24ff72cc0794e446a67ae6ce56e872f5027e150d34a3bf62259b151bac6c6941ffffffff011e1b000000000000232103f6c0aa61c32d4e13ab186724ff099549a32fef34ea50b621a4322be2ddca07feac00000000", + "amount": 7498 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JiDogbtC1CuNj5x9pnFJA1zkTHWR8MVdpKnfH2TZB7PTDotnBc8G8UE6xWya7Fd1cBAGQPwibKN3BbQnMqabCiLt2uHgzUEPS", + "xprvA6QQAFpdX9JiFGJjdnrZyZm8WJJgyoWMfb2pV2CbXeuYfey9xaPqmpZwTRdYMCGHz1u1mbA1txxZyQdTcKFiBWekF52xQQevbXPtPCiuH1W", + "xprvA6QQAFpdX9JiHv3DpdXkRSL1WnqZeYG6gaVyUst4qKz3qdisBdLtf64rJHbhSMMrTBGSGH4Wget4N9uR3rRpoTF6yRPS7g1jRY5Znt6XFF8" + ], + "txHash": "e85081fbaeec6ace3f77d2b7fe932ee8b40c657205852d03bf5de5614f78b757", + "hex": "020000000257b7784f61e55dbf032d850572650cb4e82e93feb7d2773fce6aecaefb8150e800000000484730440220504e6f8b9c870cbce06c0d0cc8a2a0658290c6c9a6910976ee8d601440ccfbf8022063a83f355b1896cf21e30cf0cfd6e4a460d328821065d4818a1bf5f54f0f58cf41ffffffff57b7784f61e55dbf032d850572650cb4e82e93feb7d2773fce6aecaefb8150e8010000004948304502210092a3777eebfd257ec21515c1eed1eb278a6e1e5dd2aef4913d065006efe35b6d0220490aceefbf26370d04c610a0c94a2eeb6a61fd6398afa127cf2e36214bf4acb441ffffffff01381d000000000000232102cb78b2479a1a0e0e5bc62e0d3ba6ac14ed018104235fa11000d81915a899c8f7ac00000000", + "amount": 8036 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JiUEYjjCtBBhAmoAMi3C118tnnyPNVmaAPy1jqsLX8e47BtWbG8ftwq6G4Ru3dSDDJBsZCMYBVEQfh9794VEWyS6oHMUsgWwB", + "xprvA6QQAFpdX9JiWaQyziNdA8gNHeS44VooC1H4uEd9GzLhmANb23Gu79jkdf7ye1oL32ymzdwLTvjhPfNWgVGNBULfUQuyVxgvzRHzQZcDDN2", + "xprvA6QQAFpdX9JiZPmzJDGXyvwyf1VimYcuVU2gT1Z6HXkWEk3EKQ8z1zAHPHSHY8UG4gbRSprd31UeJrrhnBM17dc9bDKdJzhyWxHVKhLQhtb" + ], + "txHash": "0dd589ec363dd370cf37e430abf78e0b705f2b80a3a46976443e0b8c14280f09", + "hex": "0200000002090f28148c0b3e447669a4a3802b5f700b8ef7ab30e437cf70d33d36ec89d50d0000000049483045022100ec86ade357af4039c59e3f5f7f02238fbf6c46fe50a20feb68ffe76766e8914d02204c254147097f7b3ef2ade42c7efe3e27cf3abb51ac11f27ec96dc5d383b5cdfa41ffffffff090f28148c0b3e447669a4a3802b5f700b8ef7ab30e437cf70d33d36ec89d50d01000000484730440220185ba5a48464c82cb62f637e43de709468881b677fddbceea6039f632a82123102206dde94e884a14f9af0053554009a948dd050d7282232cbc8634a924eb85feaf641ffffffff01d415000000000000232102bf4f86292b4745cc726c1655f9a5ef57383838f5399663b0349649503d77224bac00000000", + "amount": 6144 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JifVVZRemkR3DBZAeVPXn5TEhZg4Wdz25oFhJCa6sBDzTAjyx36vtQsVn2cC2kJXXPyUAwhxpYxg97FLxBdn53jLvCMTsnxhf", + "xprvA6QQAFpdX9JijKwoe9ajBeeBy2pKcnsZPr1c4fpLSSGWadwCxj8Y52VhpBfLgtN2BdmpuaRRa2M42jZ4CHscL3svJDq1VBDR3enquVLyiQF", + "xprvA6QQAFpdX9Jin23ZGrS56ogWHtKBycCpgKTHdp8cCU2gz4v6xiMYNK3deeKoeeardZmLserHqGphtA9Z6E69NTvyQKMfzyxWRujaeYSzhXX" + ], + "txHash": "7032bd976d06d5fb1b9871eea766d2b6cd95519a704fd0536eea11811087e160", + "hex": "020000000260e187108111ea6e53d04f709a5195cdb6d266a7ee71981bfbd5066d97bd3270000000004948304502210093ca6626bf6e608a9cda85fdfa0aacf27ee0f6dacbb291747b7e21f116b4466a02200f7966c7c1fe8a2705ec4e296fca91fd12aefc79882b0d491abb1fc3e30b618b41ffffffff60e187108111ea6e53d04f709a5195cdb6d266a7ee71981bfbd5066d97bd32700100000048473044022014c088f755d5b9bf88fd8d9b69db40647db7fd47d49baa0451f01e57c6da9ff7022068b70a021c2f589053571599472f7b33cb329bcd978acc2375def0db6aa1a7ec41ffffffff01740f000000000000232102fcfcce4233f821b955030b29d1b01f248118425b99556332702a00bac88bf4f3ac00000000", + "amount": 4512 + }, + { + "xprivs": [ + "xprvA6QQAFpdX9JivUsejGiXE2WFBHAznUmhP5BjtmzpPSTkYN7AZWiG9Ggc3gPyoJPZJZ1JFGuytzGABjmsy9JCCnupforS6Zvu1f9sPKMPyzd", + "xprvA6QQAFpdX9Jiy186fRPLpxFYaTfziT9kCq4RKRieTB9bcZEHiyASm4EwG3PHhx7wNxdXCjPD8VotHHuwG8RrzUxvmAUPYYM1SEreNoBCuW7", + "xprvA6QQAFpdX9JizRqb6tdVVGkWqGCXoC9yWGU4v3hkjzh9mEoYNhLN5nT1bCRLkAmJFgQb5iBMLo7DpoK1zEQAbMCWRH21XMa9yvU1P8TYsYG" + ], + "txHash": "285c0e300988ff8dc0b56d2d62002574a7824a102d6d1d7abeea037416ca6688", + "hex": "02000000028866ca167403eabe7a1d6d2d104a82a7742500622d6db5c08dff8809300e5c280000000049483045022100e217e2a676bb9f0b666590c180cfa38c916213bbbdba2de2c1dab400368f3c080220207bfa2f904eeede9af0d5ce84e8a3e5e34e4ba716ec5ae992e07370eb3851af41ffffffff8866ca167403eabe7a1d6d2d104a82a7742500622d6db5c08dff8809300e5c28010000004847304402204fac209286dcdbb3ec57d3ef5a75b646ec89ab2f843fca318ec5924a5f2486870220260a2b959d9d51f5e36d5832ab565d04a9746d2521a3d3d35bf128c4c3da24f241ffffffff0114090000000000002321020904c047afcd4f0c4675108fb271dd85bb63d915ded1dc47b1e3b12cc8edb56dac00000000", + "amount": 2880 + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8oRd5TEGp5LEDAtpbvWwbkHSktaEZ2DoUXEp5Uah5Q7audrNdfXYYoBQMxBXzSsNu2dgGiB9XPR8Fkv7Y3XmknKDYPJXZ9By8Y84KckWiFH", + "tprv8oRd5TEGp5LEHtNCs4DPf9WSdxzbS1qTRQyiFYNRFKCTPuLQt4yUG96LzhrTDdXLRGdELEfpKCCHeSzCvKd9JdSwXERFNFVkHB9PZ1XAo7D", + "tprv8oRd5TEGp5LEJS8GqMqurpd4tiawJMkWSTaiJ579LzHc524n4ksiLR2QuULVEJZNzqAskQamXajFczNMB1bN8c23Qi8Efn8tZjw4GwkifUe" + ], + "txHash": "ecad8de3ddac73ac64501f99900813c1b4b4e2c9728f5cb8305497b10920e14b", + "hex": "02000000024be12009b1975430b85c8f72c9e2b4b4c1130890991f5064ac73acdde38dadec000000004847304402203d5cde76040b2656a3a29e6a2077ef0722a04f29b94fdc2f186734ba30efd94b02207d78edba6ba7743cd99b459d3e1c2cdeae055ac4f5eaba1e22802dbbeaebcc4d41ffffffff4be12009b1975430b85c8f72c9e2b4b4c1130890991f5064ac73acdde38dadec0100000049483045022100952c039db22d83c2fe83ef267f8c8641c11c606b313ce85a87b525be31ed3d01022012074e02fddc58f32645d1be4c86b7523920c13aa4f6cc93c6f7c0f9682d0a1341ffffffff01f0a7eb0b00000000232102889bfff1ab36186b4ab9ed6356abbd3a7851a0e44b9dfade22962b742c4b508dac00000000", + "amount": 199993884 + }, + { + "xprivs": [ + "tprv8oRd5TEGp5LEMQSMZtG6e6e2HJQyYRJBu7fMF8x5Jmi36jLpz4rBBtkp3CMzrD3psmSDuUutiqHNJnkRqEYZUXmu4u6Va4uQNRJnmbF4wS4", + "tprv8oRd5TEGp5LEQ83hHtj8oRW9LJaTNZp7rRaKXP5RqZenG8mio82w4j51BQhBRjtZAMr82kHaBBEbeWhKPKAK9RYPS2ErWkSpmP2YRE277bP", + "tprv8oRd5TEGp5LET6UymmvLe6CpmT77C2iSpwdcnJCuFwE2k3qW7Q3c5AkErZLS7gMdJ2A8vR4jkGxWGmxizYyYCcV3PRrinC7Avs3hXETx6Zw" + ], + "txHash": "48349ccf40c4dd540411841e3a5ae52513917e6013d0e499dd65f9092678d15c", + "hex": "02000000025cd1782609f965dd99e4d013607e911325e55a3a1e84110454ddc440cf9c34480000000049483045022100c24f6552094a2e3c09b31c3dc301292828b28e0496304e7d9d48b60e8a4da3b602206a841fc2ca7c2a2849fe5b7fba987454ae0bfb5acd10ddd8025a8c301520cd2041ffffffff5cd1782609f965dd99e4d013607e911325e55a3a1e84110454ddc440cf9c34480100000048473044022007227e4ce38f63a83f1479ca220c8c69fa5fcea55af2d2723ca20196f4c7934f022001152536c16983fde750fd853c0cdd7f0dbdc950dacddfc7a612d750e450aafd41ffffffff0198a3eb0b000000002321023e67b9ab509fbb4cd9f877a1db5b8962fc94f875020316177288a1ca459ef492ac00000000", + "amount": 199992772 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpRga7kx8xtFvcYAD6ZfqhtGP3ij6vEQKVnsuV7NQUG1RsRX3QmEDzhmm6X9y2sFJtakJEUgyX8r6dXTzJn9FBbEy7iKy8nk9", + "tprv8oerBwBqNPZpVUwRmVwi6GgskzFdKSBdrbgqdpkNnrrigyPAbcFJnFZfg1gS4qHCXRKPegFxG1NkhB2jjdDBWbtCn2EDQ7j6WsuV3iLmHNd", + "tprv8oerBwBqNPZpYDApxZUrzdpjLxQkgUdxBd6MPUaron5wNW4c9ZLVEDjseqD51S8DAuMj7h9fdDigXpermHnqTBKjtk49FUk2oaWsh83eJSj" + ], + "txHash": "7b38eeeea6a88ee17857c6b98a10f9d3826c5554c7a62b9b74f7596530b42df2", + "hex": "0200000002f22db4306559f7749b2ba6c754556c82d3f9108ab9c65778e18ea8a6eeee387b0000000049483045022100fd8b782d496defb4410a4b68e22639e85967825461587921b0914802018cdbb0022032bf1b9d8499a2ac5580ec3b76d6dd4165d22f5163dd64014b49e96b657a6de041fffffffff22db4306559f7749b2ba6c754556c82d3f9108ab9c65778e18ea8a6eeee387b0100000048473044022040519d90e9a4b0facd5a45c56f2ff71ea0d3855e8b9099519a4593356f4997e102201ccf36c2abbfd2f08ffc3ea6ecc167ecb02ab9a11a1a81699ad73213786268bc41ffffffff017cdaf50500000000232103a7d694153178f66e4912e34986654e85f5f85e9b316481179bb4d0c4b79ae948ac00000000", + "amount": 99998888 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpZAmkv69tTECMgWKaHvnVYkciRgpGonb3E9aQ7xQKQcnPSjqxED6jGChziy7ZQE3Kjg5FYbWSuJyyuPY421PL98MHgdKLWSj", + "tprv8oerBwBqNPZpd278mrGGCQBtasxFtPzBKpBWbehzoHpVDzfAj9AqriWMnVnWNQHGaAMXuWTBrXicbsYsryoQoRicyvtpo28FFVy2CjZ1JbD", + "tprv8oerBwBqNPZpg3SCbQffLieaioBs2dGH659nHzdLxUZmf5GBnb66CQBBD964RokWYozzi72m9v9RVZyp4Fp3KzHfdrrrvLboDH3GPamXRJN" + ], + "txHash": "43624694178dcfdc2d81e95deece09eb33436858dea4f58be9de71e28a5bbc56", + "hex": "020000000256bc5b8ae271dee98bf5a4de58684333eb09ceee5de9812ddccf8d17944662430000000049483045022100b3c171ee73862545641a4a978ea3afd7d1c1f532120f436643a5baaddc3cd070022054d09b22c6a7fa58495be95557fdf170660f2d9e16c5908e9577131732925d5841ffffffff56bc5b8ae271dee98bf5a4de58684333eb09ceee5de9812ddccf8d1794466243010000004847304402206aa1628ec10b460c761ed569dd6e9b8bf7e6646dcc5af3c54b52f29b7e76eeb602202d9b268fb9c13b61fa3a54c0371f544d855644beb20a2bbf120ef99feeab02ed41ffffffff0124d6f50500000000232102685e050f401fb238b7e1b502bde936c4fbbdcdc48847f233431875fd2f625a6cac00000000", + "amount": 99997776 + }, + { + "xprivs": [ + "tprv8oerBwBqNPZpmqsreVTCrzZHb2cJi8v2YKfEDcZb8Bp29jNaGoa9hBBbhTp2n8hBynF1LCVwjNxZx9eAisNYACXJZM2HmNxTVaZVTw3kgoV", + "tprv8oerBwBqNPZppf2gjor99U7Pm7rGWqFQ94tDGNGzc3BnTxFComtFKFzcFCEZefo2zJcnyQWG3oZtYx6C4EnY78PrKXYjKtVu3hTM8jiEyJx", + "tprv8oerBwBqNPZptQq5s58qXjGmWcnRurako7fbbeGsN9jA4QiLKkhAVLCgP59ndRDrmhumzSrmuxSpLNYDyNV1PTX3sFU2FCUZ6FR9KGbn5g4" + ], + "txHash": "b55e3cca9e139057b6bfd5736b0887785573d163af95b25dea02480bf39dfb79", + "hex": "020000000279fb9df30b4802ea5db295af63d173557887086b73d5bfb65790139eca3c5eb50000000049483045022100a61c694c64ac3507f13d2172214aa3e928ec3e0cd0ec5aaec577f5b7c4bc85ed02202c936486cf938a37a2ab32b903f4404e9de1fcfa99ccf6e3056e5cc84765c81241ffffffff79fb9df30b4802ea5db295af63d173557887086b73d5bfb65790139eca3c5eb5010000004847304402200b2790430aae6048aad7c2703564fa47d327b85a7ad13fbec1b9472ef5660714022060c0a59995e288069a1d0becaef9eca5213ff51098a7c43c4b695db4d685dbef41ffffffff0128ecfa0200000000232103f967dfae61ddad6b7fca1e5acee3c090144d74826751bf014fb1a77124e15de0ac00000000", + "amount": 49999444 + } + ] + } + }, + "p2pkh": { + "toOne": { + "mainnet": [ + { + "xpriv": "xprvA3GKKCqgFzp2snPvkFs6Rna6FJjwSt6yKV8LxupLy6aBWi3MMnsJXd2e1F8ppMFyvg4yDmpQxbEwuPZKzCQFxePBi4E1S64wuLyrr88oc9w", + "txHash": "30fa9b61f010bc8368f04e9f8ccee32e711c1bbfcd0fe67379c40da855c1e25e", + "hex": "02000000015ee2c155a80dc47973e60fcdbf1b1c712ee3ce8c9f4ef06883bc10f0619bfa30000000006b483045022100b0420ffaeda350ef725cb4beaa6c56a0510d2bb6be941005e0cd259c0aaf319a0220413de6c6b1a559523fbd6125e32e57e690312458442931e1bfb4be4d18bdbaa14121024076d5c28b55faa708ae69ee42a3c9d27197f6d8df821ed6fc5aa3b295ec4dd3ffffffff017e210000000000001976a91440793fd662b8b1daf43d0c8b8fe95039bc6eefed88ac00000000", + "amount": 8766, + "vout": 0, + "outputs": [ + "bitcoincash:qpq8j07kv2utrkh585xghrlf2qumcmh0a5hjuuxue9" + ] + }, + { + "xpriv": "xprvA3GKKCqgFzp2t4taVPYeChqx12PJiYfu7NVnrXi8kqNFBN1cm4xsZKofrVEWQGn2RoHjWJTWLgFQJLBRfadWPv5wGfLAcYgHVEdwbQTnj3J", + "txHash": "635421041c0bd1c14953a6b6149abfc8f7b26d3261ed92851d3cbca3b821f36d", + "hex": "02000000016df321b8a3bc3c1d8592ed61326db2f7c8bf9a14b6a65349c1d10b1c04215463000000006a47304402205978814bdb7d31abf35d5e651a7e4ae2eafb38226cf27b1faf63c36704857bd7022053d183e71ef3fb5b9c6e9a028b371767fc2929ad8a143717cac5795cb17c23b24121037e04aa55787df424b4f1160aa64c65f64b2daa69e53d16c75b9a40708b01651dffffffff01fe1f0000000000001976a9140dc3cc4b6f7de9e6e9d21ab6a8d071063a81a7cb88ac00000000", + "amount": 8382, + "vout": 0, + "outputs": [ + "bitcoincash:qqxu8nztda77nehf6gdtd2xswyrr4qd8evzwsq6pts" + ] + }, + { + "xpriv": "xprvA3GKKCqgFzp2y7mi8BLjWE94ugT75mrcf5hAAMbpccctuCEiJUGepevSESsU3tsggunCPwgxWN9DRqD1MyxqFtsRa4scuEXeye1ZDFrQ3xn", + "txHash": "22f8cf267109815d31a4966d42183bf189d974b58bec9127e1b2ac532edc110b", + "hex": "02000000010b11dc2e53acb2e12791ec8bb574d989f13b18426d96a4315d81097126cff822000000006b483045022100e6857dd6257c921cf60b7555ec331882a5615c93abb9e8989ac03853ea0e5bc702201f3cf368657aa9036da29df50075c1ebae3d95f1589dc0c570eafcc17682d5ca412103b230a14bfbdaacb71b2005314de971fa58b14edb6a8dd7bfbd3fdd2ba41be329ffffffff017e1e0000000000001976a9143cb6602d5ab7dc5c283f34c361fe9ce152f5c46888ac00000000", + "amount": 7998, + "vout": 0, + "outputs": [ + "bitcoincash:qq7tvcpdt2machpg8u6vxc07nns49awydqe6w5htfz" + ] + }, + { + "xpriv": "xprvA3GKKCqgFzp31AYF2KCAkzaXqB9vssWkmSzNJaYJMPSjEuQLMumf96PrpZStdVjVUCZgn9DjksA9yKXZBHgjefTdcegXE6QRNn1AL24Jnrm", + "txHash": "eab9dd4302647019bceef531535b29a43a9ed3489cdad478ca33044efc041727", + "hex": "0200000001271704fc4e0433ca78d4da9c48d39e3aa4295b5331f5eebc1970640243ddb9ea000000006b483045022100a25e21d7e43bea0a068b05529150bf04e27e88b9ce260e3f1987628fc8ca4ea5022009a61a0707b058c0627fc5310bbcc0835d0f907c6aa3ab6bcd4b48f0e51f986e412102193e4ead30e7008945e5c98589668762814624ed97a6fdbe89b5cb9cef90ec76ffffffff01fe1c0000000000001976a91481a5df1328bc195ee8affe9cf497bbdb79957bb588ac00000000", + "amount": 7614, + "vout": 0, + "outputs": [ + "bitcoincash:qzq6thcn9z7pjhhg4llfeayhh0dhn9tmk5a8pfx3jk" + ] + }, + { + "xpriv": "xprvA3GKKCqgFzp31rSFMMrhjeMfdVUEqUvdmLGD3rt8Suaga5iPK66r4EM72MmvpeDD5YrFjzu12759RV5ni3mkxKEcLqUYcdRUKiQHjr2JhAy", + "txHash": "d923ae54e043a73392f4219165a6d6aea3b94ec87b499e58f962811e37c286d4", + "hex": "0200000001d486c2371e8162f9589e497bc84eb9a3aed6a6659121f49233a743e054ae23d9000000006a47304402200f2a53116b73fcde38658af2812f17fba6a8b0e9c2fb352644b1cea4b3adbc720220551c3b09ce6e679e5504eafc2ff2bcc538e22c646bfdccc7f2d8c7e9fb77deb041210355cfbd19413917309ac59f1dfe4bd7ea9ea86628ed54872a9c70eb20d453e518ffffffff017e1b0000000000001976a914305166fda99a88d62955a21adc6d5515c88b194f88ac00000000", + "amount": 7230, + "vout": 0, + "outputs": [ + "bitcoincash:qqc9zeha4xdg343f2k3p4hrd252u3zcefuaq52k7vl" + ] + } + ], + "testnet": [ + { + "xpriv": "tprv8jwG6YA1fGe7Ut879xQ9NMTwK9oWx4huSvQuix8bEorixxkhkSJd55B7mfQAQeALoEpWWQ5GW2qCmBjAnnyTCyMXoJYUGuQLQLPN367M98Y", + "txHash": "adcda2424f8768bdf3d5064e9a71a61d6d80b22bcc12f5eae0372822aac4c293", + "hex": "020000000193c2c4aa222837e0eaf512cc2bb2806d1da6719a4e06d5f3bd68874f42a2cdad000000006b483045022100dce87e98844fe01c7f0bffeb3ec6dbd92cada2751570653abb5db5bb1c40eeec02202eeac6d4f79f4774b40c121c6b99b8f431357a17dc8bae39003a16a8246a2bae4121037e04aa55787df424b4f1160aa64c65f64b2daa69e53d16c75b9a40708b01651dffffffff0140a0eb0b000000001976a9143cb6602d5ab7dc5c283f34c361fe9ce152f5c46888ac00000000", + "amount": 199994240, + "vout": 0, + "outputs": [ + "bchtest:qq7tvcpdt2machpg8u6vxc07nns49awydqag2n4uw7" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7Zw1EnkCEfsm4DosKKHtczdcH2n2H6b7NgnyoHqcQLQHt9d384GG14MJyQ3Jifij1tgkkVCJn4x926i5vZbFhtjkyewCFBYD", + "txHash": "0ed1f82a7581f694fa77e9bdcb36a6e67e95ff75bb141e1df78e1f3de7109af4", + "hex": "0200000001f49a10e73d1f8ef71d1e14bb75ff957ee6a636cbbde977fa94f681752af8d10e000000006a4730440221009e36bba2b64d1aaf6fe509e27cffdeab3c5f5b4bd464c014e0abb0cfa8594880021f3f8111b1f19894f902b592ba967e55b98568dea4c5e14e1f9260bfc988ed9c412103b230a14bfbdaacb71b2005314de971fa58b14edb6a8dd7bfbd3fdd2ba41be329ffffffff010095eb0b000000001976a91481a5df1328bc195ee8affe9cf497bbdb79957bb588ac00000000", + "amount": 199991360, + "vout": 0, + "outputs": [ + "bchtest:qzq6thcn9z7pjhhg4llfeayhh0dhn9tmk5e49wyx42" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7bymmgt3fveCX9Ja97PYm6zuVAzxkqMwD2W9RMH7QeqmJjjcYds7oqe6TnEqVvDjxSB5JJW2gTijE9HtptT8UHskamocUgrA", + "txHash": "62c0967f5fedbde7b5d485b8a98cf64c6f59c49b52667407e1e4f08f8868e80a", + "hex": "02000000010ae868888ff0e4e1077466529bc4596f4cf68ca9b885d4b5e7bded5f7f96c062000000006b483045022100810a26ea991a98f3bc38764095b00be6f86795a6b165117726f1401f2714757b02207d6ee4551f4dc7e053cc29abd63b24bbb6ef150024b656fde0289bec52053633412102193e4ead30e7008945e5c98589668762814624ed97a6fdbe89b5cb9cef90ec76ffffffff01c089eb0b000000001976a914305166fda99a88d62955a21adc6d5515c88b194f88ac00000000", + "amount": 199988480, + "vout": 0, + "outputs": [ + "bchtest:qqc9zeha4xdg343f2k3p4hrd252u3zcefuejsd5ftr" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7cffn1viCuHyewctT4zxe6tBKvHJavt5AMgTUJTSbZyiYwXwaq1bXSzP2k6WmBTewtLdXqG7hmNWCsUgrGz9XEp9iBaZcjz2", + "txHash": "8095f9aacd64c3119ed07b1152e0aaaa2d9a2da8886e442b6e43ee0c5474cdc0", + "hex": "0200000001c0cd74540cee436e2b446e88a82d9a2daaaae052117bd09e11c364cdaaf99580000000006b4830450221008226da5999b699f941b120824e6f2f0eac1cfd1ee0265758996a1351c5e0f800022002a8125bacee157b482fff4e4412d2926a5cdb8ba154f1de14aac68858403c7a41210355cfbd19413917309ac59f1dfe4bd7ea9ea86628ed54872a9c70eb20d453e518ffffffff01807eeb0b000000001976a9145c529406c0577109811a9e4305900c283783b5ff88ac00000000", + "amount": 199985600, + "vout": 0, + "outputs": [ + "bchtest:qpw999qxcpthzzvpr20yxpvsps5r0qa4lu25mr3hn5" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7fLkrKHuZWCM8uy911tAZKrqM8RRaF6aJUku9Vw9uYCYtRdAoLG6cyfBcfyVrXBAqaXUxFZZXPRxqiszMj5U9zTGL52PZxPD", + "txHash": "10b55f91a2dd91ad28bffb32b8967f5dcfd4f1d93f6bf2fafd089f5899b341fd", + "hex": "0200000001fd41b399589f08fdfaf26b3fd9f1d4cf5d7f96b832fbbf28ad91dda2915fb510000000006b483045022100b289038c20d65b2e2403df6bcc5d3ab56ecae10d2b04e75aa98e4a1418f670f40220079d059ec3546bfd6baa1355bd7f65cb38a997439d99afb1e7ab21c7506a15ec412103ee46afc68bb38a9a5598950e51e4466a8b33573e8605126b0779040d740f88f0ffffffff014073eb0b000000001976a9148a8fba478ebfa32d2f1c537de8e5a36b96c4772588ac00000000", + "amount": 199982720, + "vout": 0, + "outputs": [ + "bchtest:qz9glwj836l6xtf0r3fhm6895d4ed3rhy5s907urel" + ] + } + ], + "regtest": [ + { + "xpriv": "tprv8jwG6YA1fGe7Ut879xQ9NMTwK9oWx4huSvQuix8bEorixxkhkSJd55B7mfQAQeALoEpWWQ5GW2qCmBjAnnyTCyMXoJYUGuQLQLPN367M98Y", + "txHash": "adcda2424f8768bdf3d5064e9a71a61d6d80b22bcc12f5eae0372822aac4c293", + "hex": "020000000193c2c4aa222837e0eaf512cc2bb2806d1da6719a4e06d5f3bd68874f42a2cdad000000006b483045022100dce87e98844fe01c7f0bffeb3ec6dbd92cada2751570653abb5db5bb1c40eeec02202eeac6d4f79f4774b40c121c6b99b8f431357a17dc8bae39003a16a8246a2bae4121037e04aa55787df424b4f1160aa64c65f64b2daa69e53d16c75b9a40708b01651dffffffff0140a0eb0b000000001976a9143cb6602d5ab7dc5c283f34c361fe9ce152f5c46888ac00000000", + "amount": 199994240, + "vout": 0, + "outputs": [ + "bchreg:qq7tvcpdt2machpg8u6vxc07nns49awydq85ujk0dc" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7Zw1EnkCEfsm4DosKKHtczdcH2n2H6b7NgnyoHqcQLQHt9d384GG14MJyQ3Jifij1tgkkVCJn4x926i5vZbFhtjkyewCFBYD", + "txHash": "0ed1f82a7581f694fa77e9bdcb36a6e67e95ff75bb141e1df78e1f3de7109af4", + "hex": "0200000001f49a10e73d1f8ef71d1e14bb75ff957ee6a636cbbde977fa94f681752af8d10e000000006a4730440221009e36bba2b64d1aaf6fe509e27cffdeab3c5f5b4bd464c014e0abb0cfa8594880021f3f8111b1f19894f902b592ba967e55b98568dea4c5e14e1f9260bfc988ed9c412103b230a14bfbdaacb71b2005314de971fa58b14edb6a8dd7bfbd3fdd2ba41be329ffffffff010095eb0b000000001976a91481a5df1328bc195ee8affe9cf497bbdb79957bb588ac00000000", + "amount": 199991360, + "vout": 0, + "outputs": [ + "bchreg:qzq6thcn9z7pjhhg4llfeayhh0dhn9tmk5rfn084kv" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7bymmgt3fveCX9Ja97PYm6zuVAzxkqMwD2W9RMH7QeqmJjjcYds7oqe6TnEqVvDjxSB5JJW2gTijE9HtptT8UHskamocUgrA", + "txHash": "62c0967f5fedbde7b5d485b8a98cf64c6f59c49b52667407e1e4f08f8868e80a", + "hex": "02000000010ae868888ff0e4e1077466529bc4596f4cf68ca9b885d4b5e7bded5f7f96c062000000006b483045022100810a26ea991a98f3bc38764095b00be6f86795a6b165117726f1401f2714757b02207d6ee4551f4dc7e053cc29abd63b24bbb6ef150024b656fde0289bec52053633412102193e4ead30e7008945e5c98589668762814624ed97a6fdbe89b5cb9cef90ec76ffffffff01c089eb0b000000001976a914305166fda99a88d62955a21adc6d5515c88b194f88ac00000000", + "amount": 199988480, + "vout": 0, + "outputs": [ + "bchreg:qqc9zeha4xdg343f2k3p4hrd252u3zcefurwxvh6g9" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7cffn1viCuHyewctT4zxe6tBKvHJavt5AMgTUJTSbZyiYwXwaq1bXSzP2k6WmBTewtLdXqG7hmNWCsUgrGz9XEp9iBaZcjz2", + "txHash": "8095f9aacd64c3119ed07b1152e0aaaa2d9a2da8886e442b6e43ee0c5474cdc0", + "hex": "0200000001c0cd74540cee436e2b446e88a82d9a2daaaae052117bd09e11c364cdaaf99580000000006b4830450221008226da5999b699f941b120824e6f2f0eac1cfd1ee0265758996a1351c5e0f800022002a8125bacee157b482fff4e4412d2926a5cdb8ba154f1de14aac68858403c7a41210355cfbd19413917309ac59f1dfe4bd7ea9ea86628ed54872a9c70eb20d453e518ffffffff01807eeb0b000000001976a9145c529406c0577109811a9e4305900c283783b5ff88ac00000000", + "amount": 199985600, + "vout": 0, + "outputs": [ + "bchreg:qpw999qxcpthzzvpr20yxpvsps5r0qa4lusgdzjysj" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7fLkrKHuZWCM8uy911tAZKrqM8RRaF6aJUku9Vw9uYCYtRdAoLG6cyfBcfyVrXBAqaXUxFZZXPRxqiszMj5U9zTGL52PZxPD", + "txHash": "10b55f91a2dd91ad28bffb32b8967f5dcfd4f1d93f6bf2fafd089f5899b341fd", + "hex": "0200000001fd41b399589f08fdfaf26b3fd9f1d4cf5d7f96b832fbbf28ad91dda2915fb510000000006b483045022100b289038c20d65b2e2403df6bcc5d3ab56ecae10d2b04e75aa98e4a1418f670f40220079d059ec3546bfd6baa1355bd7f65cb38a997439d99afb1e7ab21c7506a15ec412103ee46afc68bb38a9a5598950e51e4466a8b33573e8605126b0779040d740f88f0ffffffff014073eb0b000000001976a9148a8fba478ebfa32d2f1c537de8e5a36b96c4772588ac00000000", + "amount": 199982720, + "vout": 0, + "outputs": [ + "bchreg:qz9glwj836l6xtf0r3fhm6895d4ed3rhy52eells6e" + ] + } + ] + }, + "toMany": { + "mainnet": [ + { + "xpriv": "xprvA3hydgfmtBEMsKzc4iJK5eJjnS18wT7eWqMPtq8byHqhDVuvEbXEK59JH7qdfj1vBJkHkvj9ynFQGmDNbZ1yHVcWs8Ai9GRYAeDJf95uC6J", + "txHash": "f2f0f68860b6b6984438dcf37bb4d02991a6305f4e9912826bd377770549ee60", + "hex": "020000000160ee49057777d36b8212994e5f30a69129d0b47bf3dc384498b6b66088f6f0f2010000006b483045022100e2207008c510bd646225f740f2dd0bbb4b53d8def4a118592e4a99f70256cc7702206d1f7d3220c2f0b94c89b369a62ec26f7e9874f77c586610128bf75341bb89b5412103c042a0b67986e283701fe140fc1262913c7b5d1b8e9a4c8e6d082a039248f4a2ffffffff025f550000000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac5f550000000000001976a91481e45f955d9d02d0f9a14ae1035ccb6a8520cf2788ac00000000", + "amount": 43937, + "vout": 1, + "outputs": [ + "bitcoincash:qzcvep37jmkjfyhsshepg9z8myww84g67uujr8fd8t", + "bitcoincash:qzq7ghu4tkws958e599wzq6ued4g2gx0yu5etyzckj" + ] + }, + { + "xpriv": "xprvA3hydgfmtBELshoRSJStQVbHKAka6WC2NJwkqf7YzQGwNYAATwTNpZ6RuyRizwqbewcTWSis6q4MF6p9CkDFdJUQWBtSy5mWN7W8c2e9S2p", + "txHash": "fcfe45ad14baf62c769d73f7abe925339dbfc770cebb8671a4cbc72e5aabdd5e", + "hex": "02000000015eddab5a2ec7cba47186bbce70c7bf9d3325e9abf7739d762cf6ba14ad45fefc000000006b483045022100a777005296160c99bc2fa9bea4ce5775d4a708db880225d9d2cb3b3e025f98ab02200821eb15ed9735429fa1a6b36947dba151f2271120938d08f2b21f69324528d0412103be6fa8c2a2eb24451ee92fe7da50be7188b3ff56765c7c2ec6f88155ff110f5affffffff0289530000000000001976a914bcd374aa01787ade7cbfd2edda620fecae2ce41188ac89530000000000001976a914ce396bbc8da4fb57a7826ea7dce823e15df31af988ac00000000", + "amount": 42996, + "vout": 0, + "outputs": [ + "bitcoincash:qz7dxa92q9u84hnuhlfwmknzplk2ut8yzy20ck9mc8", + "bitcoincash:qr8rj6au3kj0k4a8sfh20h8gy0s4muc6lynh5jfhak" + ] + }, + { + "xpriv": "xprvA3hydgfmtBEM6GUJTFTxiYBYh5dJuY3kvPybyvwafJ7Qu1gGWCQdDNkxRViEoTDMVUpuXgRmbzyGcnVz1rwDVTfany2dDiXWEEJv3CfmjEd", + "txHash": "38dc16e719f8b4db438dc13ecde0f8f2cf5d75887df24f1be4c258ba4370e0fa", + "hex": "0200000001fae07043ba58c2e41b4ff27d88755dcff2f8e0cd3ec18d43dbb4f819e716dc38000000006a473044022078eb7a6e0da1ffe92c1177298bfad756a44080e7f7f67c1f901753c12a2720cc022043a99bbf8ce12f26418643e526276c10895fc207d413b52f8e8852bc986a566141210227deb553ed5cffa161bc8525b33490b580f3c36e43150dece195ecb295fc7097ffffffff02a1280000000000001976a9143d56143e62216a864fec92cff126482311a6c08188aca1280000000000001976a9146f0e3397e8eb89a58cbc8e6e8edb0394f600f52f88ac00000000", + "amount": 21028, + "vout": 0, + "outputs": [ + "bitcoincash:qq74v9p7vgsk4pj0ajfvlufxfq33rfkqsy6qacsrlw", + "bitcoincash:qphsuvuhar4cnfvvhj8xarkmqw20vq849uh28pstsn" + ] + }, + { + "xpriv": "xprvA3hydgfmtBELnvsE1er68XY5NuaNz1GG8y2xHNmrhs5T3rNwT4mFKfwQfopGvGKKJXT9jjipUXo7TNxvXtBpBJiowZPWPoPoeZ4DahHuGke", + "txHash": "43d11e3a079da3c67a6efc02834f4bd4420110c2394b997d2406ef437445dcc4", + "hex": "0200000001c4dc457443ef06247d994b39c2100142d44b4f8302fc6e7ac6a39d073a1ed143000000006a473044022031fee8dbddbf166dccb9d16e982076b7e07f4274b8e433c3e39b142ecdbb77c402201755a6677fb54d7b8dbb65253c20e1a97a18ec60b522635b2872f745d95d296a412102bca9f51d62f1cab2aecf63875abf938308e660d5b4f6f7d421eaa02f4b658089ffffffff0202530000000000001976a914f64c5275bde32489a5c838d4de3f43e3583be2c688ac02530000000000001976a91437e6779efc5d49a574d6c6e564c1c67adb4ae2c788ac00000000", + "amount": 42726, + "vout": 0, + "outputs": [ + "bitcoincash:qrmyc5n4hh3jfzd9equdfh3lg034swlzccr0zwffy3", + "bitcoincash:qqm7vau7l3w5nft56mrw2expceadkjhzcujz3lqz07" + ] + }, + { + "xpriv": "xprvA3hydgfmtBELzsuiLULs85JY4mwgzkKpFE1rrzHmHRjvGWmtwLwBMZUudXb9zQiyDc7wZvpiPA6oVqmVC1Wesfa97npKXnv9n2T1zAeRGQv", + "txHash": "fc824fb03d41e8d8a8661d97a043f80df937572c81d636d6d54ca79aa123320a", + "hex": "02000000010a3223a19aa74cd5d636d6812c5737f90df843a0971d66a8d8e8413db04f82fc000000006a47304402202409711ffee876313b7b126f4ed67d8aac4076949fde8ef244cf45161b40724e02203963ab3e16339687cb4df18abd1ceeec44f1c7d23e1f0c0204cb96dbc6a4443d412102af21b66adffa1eea0a88f57b35ecea654ff3b40d654d77bdfd76e588a270c9ccffffffff022c510000000000001976a9146f0e3397e8eb89a58cbc8e6e8edb0394f600f52f88ac2c510000000000001976a9143d56143e62216a864fec92cff126482311a6c08188ac00000000", + "amount": 41786, + "vout": 0, + "outputs": [ + "bitcoincash:qphsuvuhar4cnfvvhj8xarkmqw20vq849uh28pstsn", + "bitcoincash:qq74v9p7vgsk4pj0ajfvlufxfq33rfkqsy6qacsrlw" + ] + } + ], + "testnet": [ + { + "xpriv": "tprv8jwG6YA1fGe7jgUsatzgsqH941oUDZs9FejN6rVRZcMaL4nPoShBwD6LNFThDNNg1zpVe1k4zHeK82KJMU4iJMCeEbinn86QuFd5eXutaUL", + "txHash": "4edb662b14424d7bbbbe4b9fd1533d49690aefb2a15af8fa0704eab1ce41210b", + "hex": "02000000010b2141ceb1ea0407faf85aa1b2ef0a69493d53d19f4bbebb7b4d42142b66db4e000000006b483045022100bf8c2e6b8d3bcbfbaa64a5e9c2cbe5ce71471f87ff1d9f6a0bcec1c58c4a8a2102201504868d60e6f75ccda4c3fe899df0094d878fe8ca5ec55893da5f61517b49a04121029cc6fa90d901a5474249facbb32b187a1de63e853c43b272752109abdef19728ffffffff0201b3f505000000001976a914b3a223ad62ca6c8669a69a508216cd374612cb5688ac01b3f505000000001976a914b4e71aaf9bf4afd806d0560b17995c873ef61e9e88ac00000000", + "amount": 199979840, + "vout": 0, + "outputs": [ + "bchtest:qze6ygadvt9xepnf56d9pqske5m5vykt2c7vwv30af", + "bchtest:qz6wwx40n062lkqx6ptqk9uetjrnaas7ncmajyntv0" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7vmRFrLKrsa216JHN44gEbnqUDThD1YtcFfVnb4a68bN6vdr3SqTPh1PX4CBgRoHcK8Ub4HCCei1q8c8HdstHsY93fxvQaEz", + "txHash": "7bc7e2f13ee5aab5c561dadde8ccdaa9968a75af074605c8d9a07545d3c03c8b", + "hex": "02000000018b3cc0d34575a0d9c8054607af758a96a9dacce8ddda61c5b5aae53ef1e2c77b000000006b483045022100d01c1c4039bd57bd18bd6d1185f7ca68477f6f347c977f740464685d80ae8b0402202de873b559814282a8a54b032c3bfc4cd7aa3a6e556356be1e78e999a5f478e94121028495390a7537738c2b0b1709eeba8bc74cc21279a35a45d0a7ff5a31d0416be2ffffffff027797f505000000001976a91417995bdbdd04ffa5d6fcf475d147d71fb7fb9d2088ac7797f505000000001976a91438b9d2974d586b062af9fe25b5fb8a2a87e3b27888ac00000000", + "amount": 199965740, + "vout": 0, + "outputs": [ + "bchtest:qqtejk7mm5z0lfwkln68t5286u0m07uayqhcr5jl0t", + "bchtest:qqutn55hf4vxkp32l8lztd0m3g4g0caj0q99ft3uw5" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8AkaYjYBgP4uCdBzEc1KNJTEmjaf7bwyysea4qt275maGXt9ergfGz7JeUULZok9UNKT5uuPMoseGJzYJUJgac2pqS7FLhgP", + "txHash": "84f8380b4f4166e332a9aba700b7c616061ac77f85593245470ef0657c000f92", + "hex": "0200000001920f007c65f00e47453259857fc71a0616c6b700a7aba932e366414f0b38f884010000006b483045022100b342eaa7f357a19fc24918b772b73c13e2060db4fcde690eb5df526e6341b9a2022030b36ec3f862e03081af98f0d64dabba0ecde8605f70c4ef08a93b60af662739412102547aa38203e296e558c0ab77fff7b2f12669e7e39fe1511993ce2038eebf0fbeffffffff0261daf505000000001976a914b65a6f84ba3b239f4258192562c3b99ec576e28688ac61daf505000000001976a914fe64266e2cdee4ed1f9708a71d9e6883451e067088ac00000000", + "amount": 200000000, + "vout": 1, + "outputs": [ + "bchtest:qzm95muyhgaj886ztqvj2ckrhx0v2ahzschy99w56c", + "bchtest:qrlxgfnw9n0wfmgljuy2w8v7dzp528sxwq5z4r4kp5" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8M9RrTT7WZc4NJshwsfEW8RKp8b2oCNNNLa5YJfPPJhDgXHFQJQsyrioTeQi4D1Xdh28rA5c1FYSpe5QpD39QdYeReUFErSV", + "txHash": "ab6c8f1b141f5a169afb2abe44d91d0eaa2b294e3df8326c3ffeeba62c7fcad0", + "hex": "0200000001d0ca7f2ca6ebfe3f6c32f83d4e292baa0e1dd944be2afb9a165a1f141b8f6cab000000006b483045022100f975e3114ff17c7b4ca5ad19c268b89135f1664163acd68a7f1fc516e2f554e102200bdeb773f5f1d466f22e476ae35e2ac8e810c4bdca7f8f477519c4d889224e4341210223c633d50fb10af9de12f9ef3aecc39ebc26a76e57fa510c3455e5cd1da827d9ffffffff02d7bef505000000001976a914a1612a990756144cd374bc77395b38ce3a4e4afc88acd7bef505000000001976a914d324c14c2fedd81ee2ce94cf4ef896f4b303391088ac00000000", + "amount": 199985900, + "vout": 0, + "outputs": [ + "bchtest:qzskz25eqatpgnxnwj78ww2m8r8r5nj2lsa9vmr9mu", + "bchtest:qrfjfs2v9lkas8hze62v7nhcjm6txqeezqu2rfa0l0" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8aEi5iXr42XpRDHANmSKvjkGQDhUEn4E3vFKU6uyxmzmDvH4XRDkykP56gr37YshRMJ6Q4ZPqmG3UD9bMvVPkv5oyEUZKoE3", + "txHash": "83d388b50f42e4dd4bc0dd6629c912e5a1ee2e78b3d8851bdab841112efe04ca", + "hex": "0200000001ca04fe2e1141b8da1b85d8b3782eeea1e512c92966ddc04bdde4420fb588d383000000006a473044022024c944f77c00cf8cca8641cf9e0b3cad498f04c2919e0067832a1031c64caa8a02204d7cc3e0181f56dd72039219d2eb139728a77172d1a2010379f0c00e97d443574121037c5b346363b720a74c5fd732ba2002f16682744fb0d728a24826ed83c2f6f633ffffffff024da3f505000000001976a91444f3088ca3a476f9d8c1d217cfe47d5df0e83a7088ac4da3f505000000001976a914fed561e699c41d9ed361265f9942e5ce3bdbae8188ac00000000", + "amount": 199971800, + "vout": 0, + "outputs": [ + "bchtest:qpz0xzyv5wj8d7wcc8fp0nly04wlp6p6wqjgrw9ah8", + "bchtest:qrld2c0xn8zpm8knvyn9lx2zuh8rhkawsyat9mtkg4" + ] + } + ], + "regtest": [ + { + "xpriv": "tprv8jwG6YA1fGe7jgUsatzgsqH941oUDZs9FejN6rVRZcMaL4nPoShBwD6LNFThDNNg1zpVe1k4zHeK82KJMU4iJMCeEbinn86QuFd5eXutaUL", + "txHash": "4edb662b14424d7bbbbe4b9fd1533d49690aefb2a15af8fa0704eab1ce41210b", + "hex": "02000000010b2141ceb1ea0407faf85aa1b2ef0a69493d53d19f4bbebb7b4d42142b66db4e000000006b483045022100bf8c2e6b8d3bcbfbaa64a5e9c2cbe5ce71471f87ff1d9f6a0bcec1c58c4a8a2102201504868d60e6f75ccda4c3fe899df0094d878fe8ca5ec55893da5f61517b49a04121029cc6fa90d901a5474249facbb32b187a1de63e853c43b272752109abdef19728ffffffff0201b3f505000000001976a914b3a223ad62ca6c8669a69a508216cd374612cb5688ac01b3f505000000001976a914b4e71aaf9bf4afd806d0560b17995c873ef61e9e88ac00000000", + "amount": 199979840, + "vout": 0, + "outputs": [ + "bchreg:qze6ygadvt9xepnf56d9pqske5m5vykt2cyscdju70", + "bchreg:qz6wwx40n062lkqx6ptqk9uetjrnaas7ncppy9sc0f" + ] + }, + { + "xpriv": "tprv8jwG6YA1fGe7vmRFrLKrsa216JHN44gEbnqUDThD1YtcFfVnb4a68bN6vdr3SqTPh1PX4CBgRoHcK8Ub4HCCei1q8c8HdstHsY93fxvQaEz", + "txHash": "7bc7e2f13ee5aab5c561dadde8ccdaa9968a75af074605c8d9a07545d3c03c8b", + "hex": "02000000018b3cc0d34575a0d9c8054607af758a96a9dacce8ddda61c5b5aae53ef1e2c77b000000006b483045022100d01c1c4039bd57bd18bd6d1185f7ca68477f6f347c977f740464685d80ae8b0402202de873b559814282a8a54b032c3bfc4cd7aa3a6e556356be1e78e999a5f478e94121028495390a7537738c2b0b1709eeba8bc74cc21279a35a45d0a7ff5a31d0416be2ffffffff027797f505000000001976a91417995bdbdd04ffa5d6fcf475d147d71fb7fb9d2088ac7797f505000000001976a91438b9d2974d586b062af9fe25b5fb8a2a87e3b27888ac00000000", + "amount": 199965740, + "vout": 0, + "outputs": [ + "bchreg:qqtejk7mm5z0lfwkln68t5286u0m07uayqdy443vvd", + "bchreg:qqutn55hf4vxkp32l8lztd0m3g4g0caj0qlel2j0dj" ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8AkaYjYBgP4uCdBzEc1KNJTEmjaf7bwyysea4qt275maGXt9ergfGz7JeUULZok9UNKT5uuPMoseGJzYJUJgac2pqS7FLhgP", + "txHash": "84f8380b4f4166e332a9aba700b7c616061ac77f85593245470ef0657c000f92", + "hex": "0200000001920f007c65f00e47453259857fc71a0616c6b700a7aba932e366414f0b38f884010000006b483045022100b342eaa7f357a19fc24918b772b73c13e2060db4fcde690eb5df526e6341b9a2022030b36ec3f862e03081af98f0d64dabba0ecde8605f70c4ef08a93b60af662739412102547aa38203e296e558c0ab77fff7b2f12669e7e39fe1511993ce2038eebf0fbeffffffff0261daf505000000001976a914b65a6f84ba3b239f4258192562c3b99ec576e28688ac61daf505000000001976a914fe64266e2cdee4ed1f9708a71d9e6883451e067088ac00000000", + "amount": 200000000, + "vout": 1, + "outputs": [ + "bchreg:qzm95muyhgaj886ztqvj2ckrhx0v2ahzscdcnyd8e7", + "bchreg:qrlxgfnw9n0wfmgljuy2w8v7dzp528sxwqw7rzk9zj" ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8M9RrTT7WZc4NJshwsfEW8RKp8b2oCNNNLa5YJfPPJhDgXHFQJQsyrioTeQi4D1Xdh28rA5c1FYSpe5QpD39QdYeReUFErSV", + "txHash": "ab6c8f1b141f5a169afb2abe44d91d0eaa2b294e3df8326c3ffeeba62c7fcad0", + "hex": "0200000001d0ca7f2ca6ebfe3f6c32f83d4e292baa0e1dd944be2afb9a165a1f141b8f6cab000000006b483045022100f975e3114ff17c7b4ca5ad19c268b89135f1664163acd68a7f1fc516e2f554e102200bdeb773f5f1d466f22e476ae35e2ac8e810c4bdca7f8f477519c4d889224e4341210223c633d50fb10af9de12f9ef3aecc39ebc26a76e57fa510c3455e5cd1da827d9ffffffff02d7bef505000000001976a914a1612a990756144cd374bc77395b38ce3a4e4afc88acd7bef505000000001976a914d324c14c2fedd81ee2ce94cf4ef896f4b303391088ac00000000", + "amount": 199985900, + "vout": 0, + "outputs": [ + "bchreg:qzskz25eqatpgnxnwj78ww2m8r8r5nj2ls8e66qkc6", + "bchreg:qrfjfs2v9lkas8hze62v7nhcjm6txqeezqxk4g7uuf" ] + }, + { + "xpriv": "tprv8jwG6YA1fGe8aEi5iXr42XpRDHANmSKvjkGQDhUEn4E3vFKU6uyxmzmDvH4XRDkykP56gr37YshRMJ6Q4ZPqmG3UD9bMvVPkv5oyEUZKoE3", + "txHash": "83d388b50f42e4dd4bc0dd6629c912e5a1ee2e78b3d8851bdab841112efe04ca", + "hex": "0200000001ca04fe2e1141b8da1b85d8b3782eeea1e512c92966ddc04bdde4420fb588d383000000006a473044022024c944f77c00cf8cca8641cf9e0b3cad498f04c2919e0067832a1031c64caa8a02204d7cc3e0181f56dd72039219d2eb139728a77172d1a2010379f0c00e97d443574121037c5b346363b720a74c5fd732ba2002f16682744fb0d728a24826ed83c2f6f633ffffffff024da3f505000000001976a91444f3088ca3a476f9d8c1d217cfe47d5df0e83a7088ac4da3f505000000001976a914fed561e699c41d9ed361265f9942e5ce3bdbae8188ac00000000", + "amount": 199971800, + "vout": 0, + "outputs": [ + "bchreg:qpz0xzyv5wj8d7wcc8fp0nly04wlp6p6wqg540xw5p", + "bchreg:qrld2c0xn8zpm8knvyn9lx2zuh8rhkawsy8hn6g9tn" ] + } + ] + }, + "manyToMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA3hydgfmtBELhHoTQiRPejsWYKwKvPTEy9EkioWdYDgzF7aWD7R6DY6zpeHPdUqAs5M4rMh5LhcDmNp3Tp5xqKXVpsg5y33NsAtXhPWKt8Q", + "xprvA3hydgfmtBELkK8Ac8k7N3qwCwwNjzA4rMQK5PmJ7Bt3u5Xi756Hei9RWA3ZUdDDj53RMJYkdHZbKUQX6DMDKusnCpThD2n5WapyxSmJEJp" + ], + "txHash": "13dc1722f711f47e9c2e7733e8934086f7d7f57759a8c18d5ba5534f8ce911be", + "hex": "0200000002be11e98c4f53a55b8dc1a85977f5d7f7864093e833772e9c7ef411f72217dc13000000006a4730440220462bb8f3b1304c7af4aa14b3851e667b610c7ff2f8ac3e9bee03931b99b8e37e022011d13c3beb4189101a9ed3a8b9c968ebb882d31f573da8b71dced3d4ee661bdf4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffffbe11e98c4f53a55b8dc1a85977f5d7f7864093e833772e9c7ef411f72217dc13010000006a473044022000d47aa59885a86b90300f91d0e3db324913a065f53cf35efbe45f6e02f584a10220336cc3da209cfe5cabe1b28083a8b42dc0caa9a8da34e7d2d764671a80426961412102a8f2e00e308fe33d8fad912e854e71cedd29ff9ffecb1f17a10bfb76f88f3c67ffffffff02a4540000000000001976a9145d1b3ddb176b51cbdccbe7c16f7fdea83a91543488aca4540000000000001976a914f64c5275bde32489a5c838d4de3f43e3583be2c688ac00000000", + "amounts": [ + 21855, + 21855 + ], + "outputs": [ + "bitcoincash:qpw3k0wmza44rj7ue0nuzmmlm65r4y25xsd032kkc8", + "bitcoincash:qrmyc5n4hh3jfzd9equdfh3lg034swlzccr0zwffy3" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBELwJ2HVip1q2C5NVDDMW8Zqe8sAxY8oJ6m5RJ1FAXKwZ9zYsoZbpGV26bFT41uqyNi2ctHNCQXrerA4KcRxfdsSBeHrcD1D9t", + "xprvA3hydgfmtBELxov7ks3DCbnNdEXjh97GNTCHgDAMSUB15Hwq7N7Vjff9hxCA4wzXmgHqWX7HBjTmK9K1iK8KrHL5Ze5JDQMNtiGECMJ5Wi8" + ], + "txHash": "25fb952b7aa1991c7217692174a04ea012a969843d6b31efe26d5bcc2ce254bf", + "hex": "0200000002bf54e22ccc5b6de2ef316b3d8469a912a04ea074216917721c99a17a2b95fb25000000006b483045022100f60ec88f0dfad4d9c81090cd3fe61e652a4fb5dba7f2b344c4f6e4c4f690933502200c6c140db4f4f3de8c5937156e01cebd740a6b61689b5d0145034e998ef0a711412103380248716e988a9ccb7c761514c830648dfa5d9d4c7dcd7ba26939cec4f46586ffffffffbf54e22ccc5b6de2ef316b3d8469a912a04ea074216917721c99a17a2b95fb25010000006a47304402207f4ea99bc07403ff9fe2c793323652cd341ba11b840aac9647d85d2ff449719f02201262b6de6cd8c0269943e2706f9fcbfd8c7185b8898aedcfb7b14d94f04a8d474121022acd74d82b2ca7e024e6bbd6753eab472136d86b2ea978f7a6924346ec18f641ffffffff02ce520000000000001976a9147a0ec987fed5e529fc5fe7bfb439d621fde9804488acce520000000000001976a9146f0e3397e8eb89a58cbc8e6e8edb0394f600f52f88ac00000000", + "amounts": [ + 21385, + 21385 + ], + "outputs": [ + "bitcoincash:qpaqajv8lm27220utlnmldpe6cslm6vqgsjk9ateh8", + "bitcoincash:qphsuvuhar4cnfvvhj8xarkmqw20vq849uh28pstsn" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBEM9Sy1Ea7UPSKPpiXgYgTCszjakoXWM7ZUTGyTpLJ3pXPzXwaBysYmUaWLKUHm6dDrjR8mmpTgyPnw7G3mvJWPfRBMx3mieWT", + "xprvA3hydgfmtBEMAfXZR93hkjVkZKagY5wRNQ8QK9xcWCFt4wSJHbdCJtAZZy3VGr7pxgwMdihJE7K5WJmdjU2koqFHD74qshALWpCPeeMWjVU" + ], + "txHash": "cff186ce48ea1ddb4abaaa5cef97a0ae43ddf6a3071e4add2a4f097e027b7f32", + "hex": "0200000002327f7b027e094f2add4a1e07a3f6dd43aea097ef5caaba4adb1dea48ce86f1cf000000006a4730440220180ce777ccbf6ddb1500a82a2146c49e0db2039e866efc3606b80d9d1feaaf12022070f65b4cb3271bf10ba42251da56babbabbc4a84c6ddbbc29709a83e1865b8634121020d8583bb60d8bbf97add96358f95866b97f85229139c8b71f1affcc7e70ecc77ffffffff327f7b027e094f2add4a1e07a3f6dd43aea097ef5caaba4adb1dea48ce86f1cf010000006b483045022100ec528b75fd86cee6eed676dd603c203ffe590e78dfa8cb7ef8c86f9c93a8277d02203a2110a01573c42816f47b9ed000e9a12dda7b86515b2ff8814d708a3545d7b3412103535dff2bde6c45d36e8c7da293cf723427f1fadf6eba8cd6323b866030f96a6bffffffff02f2540000000000001976a91486a59fb8b6d5b786e0f2ab14f596f33dec5c850288acf2540000000000001976a914e230feced69e233295ffe8e9023adb331758c4cd88ac00000000", + "amounts": [ + 21933, + 21933 + ], + "outputs": [ + "bitcoincash:qzr2t8ackm2m0phq7243favk7v77chy9qgaj6cna83", + "bitcoincash:qr3rplkw660zxv54ll5wjq36mve3wkxye56ajqdx9n" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBELqgUfppiWSj81pCBFDw1GozSwfNY5RdLjq36HyfMaLefF5Qf71uYw5frMqnNw2ibnWdb86ctYkAcWv2q1DoHdZJ14cVMjuso", + "xprvA3hydgfmtBELshoRSJStQVbHKAka6WC2NJwkqf7YzQGwNYAATwTNpZ6RuyRizwqbewcTWSis6q4MF6p9CkDFdJUQWBtSy5mWN7W8c2e9S2p" + ], + "txHash": "210e69fdf7d78a89b0f99f4c4184396c7a913de764b074b615ebafa9630128a8", + "hex": "0200000002a8280163a9afeb15b674b064e73d917a6c3984414c9ff9b0898ad7f7fd690e21000000006b4830450221008246d5d9aa8659c1e4878b116bb1804846d422ebae057927eac4cd9cf241206a022035c5dcc2cda616a42fde858c2625c2bd19515142aeecf66e759168b68c89b6bd412102a231e0f84611f7801c9461472e39cdb4aeed4c3b13c23d06f9e5858b83631edcffffffffa8280163a9afeb15b674b064e73d917a6c3984414c9ff9b0898ad7f7fd690e21010000006b483045022100d14babce645d83258362c9b331ae656a9a5cf5c69697519fb9a89a907ff6460502203133660d87e63c9d6349e41bd63414196ba539e8b892782f759f8a63350b182d412103be6fa8c2a2eb24451ee92fe7da50be7188b3ff56765c7c2ec6f88155ff110f5affffffff0247520000000000001976a914bcd374aa01787ade7cbfd2edda620fecae2ce41188ac47520000000000001976a914ce396bbc8da4fb57a7826ea7dce823e15df31af988ac00000000", + "amounts": [ + 21250, + 21250 + ], + "outputs": [ + "bitcoincash:qz7dxa92q9u84hnuhlfwmknzplk2ut8yzy20ck9mc8", + "bitcoincash:qr8rj6au3kj0k4a8sfh20h8gy0s4muc6lynh5jfhak" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBEM2tnWfVeQU9CFBBP3ZsfUwzwZLNp7NwbYWonMpapndoZTdx5VgZ53PcQHA3SavfYNibpPbcw7Bt4BcjdgcaqgGoCtjY48xe6", + "xprvA3hydgfmtBEM6GUJTFTxiYBYh5dJuY3kvPybyvwafJ7Qu1gGWCQdDNkxRViEoTDMVUpuXgRmbzyGcnVz1rwDVTfany2dDiXWEEJv3CfmjEd" + ], + "txHash": "6cf8f19cd905adc9568e9fdfc966528391109f738b1247876b611ad97463dbd2", + "hex": "0200000002d2db6374d91a616b8747128b739f1091835266c9df9f8e56c9ad05d99cf1f86c000000006a473044022009a6a01b70538a24daff5bc6fa83d9a4fa7443ee591e8a6d6b80a1d82d32d878022022f2d0f0268c93ee9e48d149c192cbb6b874b3160b6571b7f3450659f266570f412102aadf803c68a5d1cd0da6604ea9874e027cf38a51c9ea568559e5c33a2fceaab4ffffffffd2db6374d91a616b8747128b739f1091835266c9df9f8e56c9ad05d99cf1f86c010000006b4830450221008410e3a1822151ba4faff98b112a19d20a96b8a8a95700ba00925c732600aa32022023c903f259f61fbd3ace15fedaa01ec3dc4141c585a144cbd2f79eeb6b2db40641210227deb553ed5cffa161bc8525b33490b580f3c36e43150dece195ecb295fc7097ffffffff0271500000000000001976a91471a0b66d62aeb86433a950a7589a4bbd669aca1888ac71500000000000001976a9146688d09ccb8d59251e577f0afbddc34ab9b4998b88ac00000000", + "amounts": [ + 20780, + 20780 + ], + "outputs": [ + "bitcoincash:qpc6pdndv2htsepn49g2wky6fw7kdxk2rq9wltk2d5", + "bitcoincash:qpng35yuewx4jfg72als477acd9tndye3vcx74rumz" + ] + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7kqL9HPxnBAJtzwzRmUq5qYAgA37BWw2PCFn3Tr56qikGoMpXxdPJ8vD7JokXN4rmLuygZMQ5nRTFUSodZtsD735UTDn99ff", + "tprv8jwG6YA1fGe7phE5EFsL5bSMK2EG4UvxHZb69tuajqGqmUas2R4JCA3G9gHeHGt8w71MVDEH7G1pYnqE2GhECtRFDYWpEovnnnnqdRhvS5e" + ], + "txHash": "a0367edacec483e77e2401ed73e035d4fe65b8c456b66175280701cd486af271", + "hex": "020000000271f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0000000006a47304402207ff1867ad6e9b7dc7a1d3968113e20965c0ff77ad739e12b17e5dce7fe76e57502202e656dc0f57c3e7884228fe6c0f6161de74842fe2dc4b40296d0e49e118c778a412103206d484ae9751634d0a059acfbee14de2967f9da04159ac8fdd6fa8f1cb486f7ffffffff71f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0010000006b483045022100b9115cf9024e5d61a17e6002fb5f9a71c9ecc1f50604c266638bda197579511d0220035b31d1a51a72136c1813e3a90a1e785f94650dc78bd0f0e9f8bbd4422e82d541210318a9bba01a9b343b3469af1ecd27815bdb6797c0eb21b1734a0d0f47290f44faffffffff020ca8f505000000001976a91439890e01e03442581056f0e3f606083cb552f66488ac0ca8f505000000001976a91443802909a4a51575217b553bb5b2670631fdc3cb88ac00000000", + "amounts": [ + 99988225, + 99988225 + ], + "outputs": [ + "bchtest:qqucjrspuq6yykqs2mcw8asxpq7t25hkvslhf9dam4", + "bchtest:qppcq2gf5jj32afp0d2nhddjvurrrlwrevm37d6avt" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe81FvYckTjBt9uYG7XAmBMo59BRnytSBDUcTwTuRk2xidoA75t3b8rBEBnDxc2yV9nG4FiE9eedcwdFfzEg9Jaifqva5AXeQo", + "tprv8jwG6YA1fGe822yK9p2ojos9qESLKr3VXUFHca2jwv5Q32vBqMaMsXgH9S4iXQJVMTq4pPEFT7SWKv4Tx2fP45ktk7CXJaDSQYDCyWZo57T" + ], + "txHash": "860cdbf1c532dcb8b5c1e0a663f826400c55f7ccd7377e4d3bad9fcaa144e204", + "hex": "020000000204e244a1ca9fad3b4d7e37d7ccf7550c4026f863a6e0c1b5b8dc32c5f1db0c86000000006b483045022100cb3c583edc2a4023bcd3bdc1504f8a1584730a86eb3f1e947478cdb06914f9a10220553feed668967a194008d68da28010cf6dab0b9d0bc195d28fdc71f54abd44544121031b276c55b432444553e2739eaf77a98fbd21fb6b1597fea52dfa2a4692350972ffffffff04e244a1ca9fad3b4d7e37d7ccf7550c4026f863a6e0c1b5b8dc32c5f1db0c86010000006a473044022050e8437b4bc64890a1420ad74c0b51bc1d6bf2cc7e889d0e062c9ba41d50205402204b129553250cf13aa0269a048ad3e70fbde3747cd62efbad9d899f31121a795e412103fc35b3829b5fa081121470caa84cb9f4893f3ae542432ae1b1d05443d31f48d6ffffffff02828cf505000000001976a91431bcd80a099174adbd2355f03a3a9d1f39f60cb688ac828cf505000000001976a91405dd4318185a8726ee26bd344a535d7b61af082088ac00000000", + "amounts": [ + 99981175, + 99981175 + ], + "outputs": [ + "bchtest:qqcmekq2pxghftdayd2lqw36n50nnasvkc9fdahsue", + "bchtest:qqza6sccrpdgwfhwy67ngjjnt4akrtcgyqvl597shw" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8E31BpkuCMurbLYKyuiaozJkMSqF2pS5UzygRQDh1Wb6uLWA6v9oQPNPHUi3VrxeT766HcJ5ApnDgU51REx1XA5s1f2vVnsN", + "tprv8jwG6YA1fGe8Fnr2eFwWmFMqK6t2YnEbXNLAS44iHmfaubrj7VQ6o1WnqLEG8CD7Q4ghoJbce9W8U2nG7WntiDX3Enk2y3SJuKuJubukkYY" + ], + "txHash": "d0e96087e7b5e684de362792a7e7a1d88b8647568fe372e4f45160eb4b927a67", + "hex": "0200000002677a924beb6051f4e472e38f5647868bd8a1e7a7922736de84e6b5e78760e9d0000000006b483045022100f3a9e17a8b9e2062c51376a9781015da460857165b183b33cad3fc611d28705f022016418087ac42ecfac3b411fbdc39e9a2aa24061e368769b1a782500fbacab9fd412103bf3e95bf65f053d883ed590a8bb3839e4028ffcba86a26d3e8c6ae12af3ed461ffffffff677a924beb6051f4e472e38f5647868bd8a1e7a7922736de84e6b5e78760e9d0010000006b483045022100dd9a7b17336da13c89aa35b5a8c78ad2cf5d62c82d89a9ac9a55a984827864cd02202baaf9b9dbd35da72720559e97b24e31ed54b7a76d120d42f3934d4322f50a08412103ba2abd8d0a84d0ed16c07569581aeebc1619661627527c48a6a7778d529e5cf7ffffffff026ccff505000000001976a914fe64266e2cdee4ed1f9708a71d9e6883451e067088ac6ccff505000000001976a9145ff601b23938962a743651a2bc682fdbae08171088ac00000000", + "amounts": [ + 99998305, + 99998305 + ], + "outputs": [ + "bchtest:qrlxgfnw9n0wfmgljuy2w8v7dzp528sxwq5z4r4kp5", + "bchtest:qp0lvqdj8yufv2n5xeg690rg9ld6uzqhzq7x2mcjy8" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8QJVJJ4sFfteUDybajahxa8NhkPgazaUFyEZ66C3WT99KnCuSpd9tbroDRqeD6oBTrjLFm8JMzcUSexbS2NAB8CrGzXS5CCj", + "tprv8jwG6YA1fGe8SiGV9L54byiMzUwms12gLrY8c7MRA6movJxrcnUHSmMqo5G27nET3ecF3ZhGMtXDDCWabhudscgnaxy5iVm1davE9JZ99sX" + ], + "txHash": "be6545f91b961d7b317d23f0b74f433bf4cae002317ebe9721a5b2e938a1887e", + "hex": "02000000027e88a138e9b2a52197be7e3102e0caf43b434fb7f0237d317b1d961bf94565be000000006a473044022016b8d887dcf44f95242a9b6bc5b85a35c71de80c3a5238261aa7c46010a92ce302205ae014669368148d070d3492e954fec156107248e468161c1ad05289a8c0d7454121020a2b22cd2eb901dfd79f12c5e4789223e88f5fadb560dd9a695d4228104a06ecffffffff7e88a138e9b2a52197be7e3102e0caf43b434fb7f0237d317b1d961bf94565be010000006a473044022052fc4bb44921e65ea9b4c57a495e3e10f0c43e69dc355d684bba5d6f8ca903920220642067e7a047964be39f6d4cc35aacf1041c7cf56a9fe1f352cefe97043d872b41210353183b2da1e76e1722a9ec5583ac22a75f6f79a8cfe9606a02ace3072e81ebb3ffffffff02e2b3f505000000001976a9145df278d466856014987fa0f921b5ee28622ac51d88ace2b3f505000000001976a914f9cfec9585d99d929a39644e34b9fc27ddbf65bf88ac00000000", + "amounts": [ + 99991255, + 99991255 + ], + "outputs": [ + "bchtest:qpwly7x5v6zkq9yc07s0jgd4ac5xy2k9r5unqm5k8j", + "bchtest:qruulmy4shvemy5689jyud9elsnam0m9hultsul8qk" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8ajHkiGHL6oh97Thw7zBQSWhc5AHyFbLgEr5PnY6akBLpj4hvWcJRUzTD39bLqy4wzT5G8vsXAJBMX21GPTxWeuycvdPnaST", + "tprv8jwG6YA1fGe8dDLRhAmXv5rYxM3G7o8B7wEdKWMrBgCk2A2u8Q2mmhBEX2Xz7YVNUfS44CDuKZkQ552C1ybshigst6G1YvroBN2TrksrFec" + ], + "txHash": "bfc8eb9070d97f34f5b923a07036b1559b5260f5daa0adacb944c235103e68c2", + "hex": "0200000002c2683e1035c244b9acada0daf560529b55b13670a023b9f5347fd97090ebc8bf000000006a473044022073daa422f4825fea6a0d14e3b3574d35eb2ae8acd24a44cacc179e7fcef3d92d0220261e5ca416e98e0bdab9727ecc1fb2af0e14ffd7e1839546fe2af31a48be5784412102611ad099d44f5c6e1c765f36557287da7e190db591ce1b5a59439edb493511b9ffffffffc2683e1035c244b9acada0daf560529b55b13670a023b9f5347fd97090ebc8bf010000006b48304502210094ce1d26331f37f4679e2b3dc7cfb098e383dd54315371522feecd7162db184c02203fb3810d8252e6e208ebb347d58fba97408611dba507058ad0ec7e71d5fa2304412102b1746ad583eb093774063036a847250ac9c424edf1498cae7e7a0bad20df9640ffffffff025898f505000000001976a91495a5bddee9ad7fc612ea237f326c5ac77e751f8888ac5898f505000000001976a9149b6f0485d5dea7e74e66a2207d5f6d1a90fa3c1288ac00000000", + "amounts": [ + 99984205, + 99984205 + ], + "outputs": [ + "bchtest:qz26t0w7axkhl3sjag3h7vnvttrhuagl3qw35j39wx", + "bchtest:qzdk7py96h020e6wv63zql2ld5dfp73uzgl9hqe9lw" + ] + } + ], + "regtest": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7kqL9HPxnBAJtzwzRmUq5qYAgA37BWw2PCFn3Tr56qikGoMpXxdPJ8vD7JokXN4rmLuygZMQ5nRTFUSodZtsD735UTDn99ff", + "tprv8jwG6YA1fGe7phE5EFsL5bSMK2EG4UvxHZb69tuajqGqmUas2R4JCA3G9gHeHGt8w71MVDEH7G1pYnqE2GhECtRFDYWpEovnnnnqdRhvS5e" + ], + "txHash": "a0367edacec483e77e2401ed73e035d4fe65b8c456b66175280701cd486af271", + "hex": "020000000271f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0000000006a47304402207ff1867ad6e9b7dc7a1d3968113e20965c0ff77ad739e12b17e5dce7fe76e57502202e656dc0f57c3e7884228fe6c0f6161de74842fe2dc4b40296d0e49e118c778a412103206d484ae9751634d0a059acfbee14de2967f9da04159ac8fdd6fa8f1cb486f7ffffffff71f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0010000006b483045022100b9115cf9024e5d61a17e6002fb5f9a71c9ecc1f50604c266638bda197579511d0220035b31d1a51a72136c1813e3a90a1e785f94650dc78bd0f0e9f8bbd4422e82d541210318a9bba01a9b343b3469af1ecd27815bdb6797c0eb21b1734a0d0f47290f44faffffffff020ca8f505000000001976a91439890e01e03442581056f0e3f606083cb552f66488ac0ca8f505000000001976a91443802909a4a51575217b553bb5b2670631fdc3cb88ac00000000", + "amounts": [ + 99988225, + 99988225 + ], + "outputs": [ + "bchreg:qqucjrspuq6yykqs2mcw8asxpq7t25hkvs9tlywwcn", + "bchreg:qppcq2gf5jj32afp0d2nhddjvurrrlwrevpdgvew0d" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe81FvYckTjBt9uYG7XAmBMo59BRnytSBDUcTwTuRk2xidoA75t3b8rBEBnDxc2yV9nG4FiE9eedcwdFfzEg9Jaifqva5AXeQo", + "tprv8jwG6YA1fGe822yK9p2ojos9qESLKr3VXUFHca2jwv5Q32vBqMaMsXgH9S4iXQJVMTq4pPEFT7SWKv4Tx2fP45ktk7CXJaDSQYDCyWZo57T" + ], + "txHash": "860cdbf1c532dcb8b5c1e0a663f826400c55f7ccd7377e4d3bad9fcaa144e204", + "hex": "020000000204e244a1ca9fad3b4d7e37d7ccf7550c4026f863a6e0c1b5b8dc32c5f1db0c86000000006b483045022100cb3c583edc2a4023bcd3bdc1504f8a1584730a86eb3f1e947478cdb06914f9a10220553feed668967a194008d68da28010cf6dab0b9d0bc195d28fdc71f54abd44544121031b276c55b432444553e2739eaf77a98fbd21fb6b1597fea52dfa2a4692350972ffffffff04e244a1ca9fad3b4d7e37d7ccf7550c4026f863a6e0c1b5b8dc32c5f1db0c86010000006a473044022050e8437b4bc64890a1420ad74c0b51bc1d6bf2cc7e889d0e062c9ba41d50205402204b129553250cf13aa0269a048ad3e70fbde3747cd62efbad9d899f31121a795e412103fc35b3829b5fa081121470caa84cb9f4893f3ae542432ae1b1d05443d31f48d6ffffffff02828cf505000000001976a91431bcd80a099174adbd2355f03a3a9d1f39f60cb688ac828cf505000000001976a91405dd4318185a8726ee26bd344a535d7b61af082088ac00000000", + "amounts": [ + 99981175, + 99981175 + ], + "outputs": [ + "bchreg:qqcmekq2pxghftdayd2lqw36n50nnasvkcl4mu5rll", + "bchreg:qqza6sccrpdgwfhwy67ngjjnt4akrtcgyqkrzyar5g" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8E31BpkuCMurbLYKyuiaozJkMSqF2pS5UzygRQDh1Wb6uLWA6v9oQPNPHUi3VrxeT766HcJ5ApnDgU51REx1XA5s1f2vVnsN", + "tprv8jwG6YA1fGe8Fnr2eFwWmFMqK6t2YnEbXNLAS44iHmfaubrj7VQ6o1WnqLEG8CD7Q4ghoJbce9W8U2nG7WntiDX3Enk2y3SJuKuJubukkYY" + ], + "txHash": "d0e96087e7b5e684de362792a7e7a1d88b8647568fe372e4f45160eb4b927a67", + "hex": "0200000002677a924beb6051f4e472e38f5647868bd8a1e7a7922736de84e6b5e78760e9d0000000006b483045022100f3a9e17a8b9e2062c51376a9781015da460857165b183b33cad3fc611d28705f022016418087ac42ecfac3b411fbdc39e9a2aa24061e368769b1a782500fbacab9fd412103bf3e95bf65f053d883ed590a8bb3839e4028ffcba86a26d3e8c6ae12af3ed461ffffffff677a924beb6051f4e472e38f5647868bd8a1e7a7922736de84e6b5e78760e9d0010000006b483045022100dd9a7b17336da13c89aa35b5a8c78ad2cf5d62c82d89a9ac9a55a984827864cd02202baaf9b9dbd35da72720559e97b24e31ed54b7a76d120d42f3934d4322f50a08412103ba2abd8d0a84d0ed16c07569581aeebc1619661627527c48a6a7778d529e5cf7ffffffff026ccff505000000001976a914fe64266e2cdee4ed1f9708a71d9e6883451e067088ac6ccff505000000001976a9145ff601b23938962a743651a2bc682fdbae08171088ac00000000", + "amounts": [ + 99998305, + 99998305 + ], + "outputs": [ + "bchreg:qrlxgfnw9n0wfmgljuy2w8v7dzp528sxwqw7rzk9zj", + "bchreg:qp0lvqdj8yufv2n5xeg690rg9ld6uzqhzqy6u6mp8p" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8QJVJJ4sFfteUDybajahxa8NhkPgazaUFyEZ66C3WT99KnCuSpd9tbroDRqeD6oBTrjLFm8JMzcUSexbS2NAB8CrGzXS5CCj", + "tprv8jwG6YA1fGe8SiGV9L54byiMzUwms12gLrY8c7MRA6movJxrcnUHSmMqo5G27nET3ecF3ZhGMtXDDCWabhudscgnaxy5iVm1davE9JZ99sX" + ], + "txHash": "be6545f91b961d7b317d23f0b74f433bf4cae002317ebe9721a5b2e938a1887e", + "hex": "02000000027e88a138e9b2a52197be7e3102e0caf43b434fb7f0237d317b1d961bf94565be000000006a473044022016b8d887dcf44f95242a9b6bc5b85a35c71de80c3a5238261aa7c46010a92ce302205ae014669368148d070d3492e954fec156107248e468161c1ad05289a8c0d7454121020a2b22cd2eb901dfd79f12c5e4789223e88f5fadb560dd9a695d4228104a06ecffffffff7e88a138e9b2a52197be7e3102e0caf43b434fb7f0237d317b1d961bf94565be010000006a473044022052fc4bb44921e65ea9b4c57a495e3e10f0c43e69dc355d684bba5d6f8ca903920220642067e7a047964be39f6d4cc35aacf1041c7cf56a9fe1f352cefe97043d872b41210353183b2da1e76e1722a9ec5583ac22a75f6f79a8cfe9606a02ace3072e81ebb3ffffffff02e2b3f505000000001976a9145df278d466856014987fa0f921b5ee28622ac51d88ace2b3f505000000001976a914f9cfec9585d99d929a39644e34b9fc27ddbf65bf88ac00000000", + "amounts": [ + 99991255, + 99991255 + ], + "outputs": [ + "bchreg:qpwly7x5v6zkq9yc07s0jgd4ac5xy2k9r5x0k6h9y5", + "bchreg:qruulmy4shvemy5689jyud9elsnam0m9hu9hxau5rs" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8ajHkiGHL6oh97Thw7zBQSWhc5AHyFbLgEr5PnY6akBLpj4hvWcJRUzTD39bLqy4wzT5G8vsXAJBMX21GPTxWeuycvdPnaST", + "tprv8jwG6YA1fGe8dDLRhAmXv5rYxM3G7o8B7wEdKWMrBgCk2A2u8Q2mmhBEX2Xz7YVNUfS44CDuKZkQ552C1ybshigst6G1YvroBN2TrksrFec" + ], + "txHash": "bfc8eb9070d97f34f5b923a07036b1559b5260f5daa0adacb944c235103e68c2", + "hex": "0200000002c2683e1035c244b9acada0daf560529b55b13670a023b9f5347fd97090ebc8bf000000006a473044022073daa422f4825fea6a0d14e3b3574d35eb2ae8acd24a44cacc179e7fcef3d92d0220261e5ca416e98e0bdab9727ecc1fb2af0e14ffd7e1839546fe2af31a48be5784412102611ad099d44f5c6e1c765f36557287da7e190db591ce1b5a59439edb493511b9ffffffffc2683e1035c244b9acada0daf560529b55b13670a023b9f5347fd97090ebc8bf010000006b48304502210094ce1d26331f37f4679e2b3dc7cfb098e383dd54315371522feecd7162db184c02203fb3810d8252e6e208ebb347d58fba97408611dba507058ad0ec7e71d5fa2304412102b1746ad583eb093774063036a847250ac9c424edf1498cae7e7a0bad20df9640ffffffff025898f505000000001976a91495a5bddee9ad7fc612ea237f326c5ac77e751f8888ac5898f505000000001976a9149b6f0485d5dea7e74e66a2207d5f6d1a90fa3c1288ac00000000", + "amounts": [ + 99984205, + 99984205 + ], + "outputs": [ + "bchreg:qz26t0w7axkhl3sjag3h7vnvttrhuagl3q5dznjkdq", + "bchreg:qzdk7py96h020e6wv63zql2ld5dfp73uzg9epp6kug" + ] + } + ] + }, + "fromMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA3hydgfmtBELnvsE1er68XY5NuaNz1GG8y2xHNmrhs5T3rNwT4mFKfwQfopGvGKKJXT9jjipUXo7TNxvXtBpBJiowZPWPoPoeZ4DahHuGke", + "xprvA3hydgfmtBELqgUfppiWSj81pCBFDw1GozSwfNY5RdLjq36HyfMaLefF5Qf71uYw5frMqnNw2ibnWdb86ctYkAcWv2q1DoHdZJ14cVMjuso" + ], + "txHash": "7261c1a7eb8bcd39d38f3ddc7038b8efcfcdd0292219f4bb07ca369382154683", + "hex": "0200000002834615829336ca07bbf4192229d0cdcfefb83870dc3d8fd339cd8beba7c16172000000006b4830450221009d5c6b6bff371e6832d993a412982956e5e21593e95a6216a393a7ac792a2f0b02203c156db5cd82b90f0ac600c898a649d29cd64590d1224753ee404edf4e8a72f6412102bca9f51d62f1cab2aecf63875abf938308e660d5b4f6f7d421eaa02f4b658089ffffffff834615829336ca07bbf4192229d0cdcfefb83870dc3d8fd339cd8beba7c16172010000006b4830450221009ca8888b3bc8d8c118f2370d33a24b20d6252fd92617d74bce65a955d16b307d0220303b4919426d890dced3c154fffd2e0d890871b3df791b9a3770e01468764df1412102a231e0f84611f7801c9461472e39cdb4aeed4c3b13c23d06f9e5858b83631edcffffffff01f4a70000000000001976a91437e6779efc5d49a574d6c6e564c1c67adb4ae2c788ac00000000", + "amounts": [ + 21668, + 21668 + ], + "outputs": [ + "bitcoincash:qqm7vau7l3w5nft56mrw2expceadkjhzcujz3lqz07" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBELzsuiLULs85JY4mwgzkKpFE1rrzHmHRjvGWmtwLwBMZUudXb9zQiyDc7wZvpiPA6oVqmVC1Wesfa97npKXnv9n2T1zAeRGQv", + "xprvA3hydgfmtBEM2tnWfVeQU9CFBBP3ZsfUwzwZLNp7NwbYWonMpapndoZTdx5VgZ53PcQHA3SavfYNibpPbcw7Bt4BcjdgcaqgGoCtjY48xe6" + ], + "txHash": "87e9d04dc71a3497f5d9f013eb2e2d3986a0937444df299ff6e6757d4b80436b", + "hex": "02000000026b43804b7d75e6f69f29df447493a086392d2eeb13f0d9f597341ac74dd0e987000000006a473044022058422b1852ff5e6e5d6b983ba6d95bd7b6c27244f017927dadf9c952fe707661022016263e2ad90449ba504b23f6c231edf329627abebd8c61ff211d11dfa3b14a9d412102af21b66adffa1eea0a88f57b35ecea654ff3b40d654d77bdfd76e588a270c9ccffffffff6b43804b7d75e6f69f29df447493a086392d2eeb13f0d9f597341ac74dd0e987010000006b48304502210091e7e9e4688cd15d3d4782cd25121087afd3eb0a5d10b1006bd80f753c5745ad02207375fb09f9dd3fb092720e9dbcd53a0b1a345822513a26dddeda8f54f51d64fd412102aadf803c68a5d1cd0da6604ea9874e027cf38a51c9ea568559e5c33a2fceaab4ffffffff0148a40000000000001976a9143d56143e62216a864fec92cff126482311a6c08188ac00000000", + "amounts": [ + 21198, + 21198 + ], + "outputs": [ + "bitcoincash:qq74v9p7vgsk4pj0ajfvlufxfq33rfkqsy6qacsrlw" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBELhHoTQiRPejsWYKwKvPTEy9EkioWdYDgzF7aWD7R6DY6zpeHPdUqAs5M4rMh5LhcDmNp3Tp5xqKXVpsg5y33NsAtXhPWKt8Q", + "xprvA3hydgfmtBELkK8Ac8k7N3qwCwwNjzA4rMQK5PmJ7Bt3u5Xi756Hei9RWA3ZUdDDj53RMJYkdHZbKUQX6DMDKusnCpThD2n5WapyxSmJEJp" + ], + "txHash": "a03df5e1763e68ff06f7af1e3659053ae61f6756ce4d7d143bd212fcb8a499a2", + "hex": "0200000002a299a4b8fc12d23b147d4dce56671fe63a0559361eaff706ff683e76e1f53da0000000006a4730440220418df308df3b6bfb2e6b810df016f1e2ba27434cba4dd0b1858349470e6a21b702202b47a50b5af1523eecd2f228fe6b5dc0058b649a220d4dcad6701b959acf71ca4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffffa299a4b8fc12d23b147d4dce56671fe63a0559361eaff706ff683e76e1f53da0010000006b483045022100f87ff05daa57ef2f67999b7c261a7274b224cbcb7841db5aac11620568ad0898022074feb22f35b69207fac22b3786176c2b3e119bc51afe157b5515e386ef45897d412102a8f2e00e308fe33d8fad912e854e71cedd29ff9ffecb1f17a10bfb76f88f3c67ffffffff01e6a60000000000001976a9145d1b3ddb176b51cbdccbe7c16f7fdea83a91543488ac00000000", + "amounts": [ + 21533, + 21533 + ], + "outputs": [ + "bitcoincash:qpw3k0wmza44rj7ue0nuzmmlm65r4y25xsd032kkc8" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBELwJ2HVip1q2C5NVDDMW8Zqe8sAxY8oJ6m5RJ1FAXKwZ9zYsoZbpGV26bFT41uqyNi2ctHNCQXrerA4KcRxfdsSBeHrcD1D9t", + "xprvA3hydgfmtBELxov7ks3DCbnNdEXjh97GNTCHgDAMSUB15Hwq7N7Vjff9hxCA4wzXmgHqWX7HBjTmK9K1iK8KrHL5Ze5JDQMNtiGECMJ5Wi8" + ], + "txHash": "83e7d3688df43f6dc4c3fc1c6ff55ab202446eeab7942dfff39a0291f162886d", + "hex": "02000000026d8862f191029af3ff2d94b7ea6e4402b25af56f1cfcc3c46d3ff48d68d3e783000000006a4730440220635ed81351ed8f077b97df7b8e912f5124dd0edc853d0e68c7dd176b06501d1902204fc6df8a2d69d4f72920eff73f9eaaf273bde4c15e84b87dbe3a71aa79f087d0412103380248716e988a9ccb7c761514c830648dfa5d9d4c7dcd7ba26939cec4f46586ffffffff6d8862f191029af3ff2d94b7ea6e4402b25af56f1cfcc3c46d3ff48d68d3e783010000006a4730440220662d07cf0b6b8555f16780ebb65278bfda8cc0f6a7028bff5d874ceaa35e72a5022045279379e39595914bfcdf18a079fcae5cd7b52fd36d9f6fc8631bce248ae4e44121022acd74d82b2ca7e024e6bbd6753eab472136d86b2ea978f7a6924346ec18f641ffffffff013aa30000000000001976a9147a0ec987fed5e529fc5fe7bfb439d621fde9804488ac00000000", + "amounts": [ + 21063, + 21063 + ], + "outputs": [ + "bitcoincash:qpaqajv8lm27220utlnmldpe6cslm6vqgsjk9ateh8" + ] + }, + { + "xprivs": [ + "xprvA3hydgfmtBEM9Sy1Ea7UPSKPpiXgYgTCszjakoXWM7ZUTGyTpLJ3pXPzXwaBysYmUaWLKUHm6dDrjR8mmpTgyPnw7G3mvJWPfRBMx3mieWT", + "xprvA3hydgfmtBEMAfXZR93hkjVkZKagY5wRNQ8QK9xcWCFt4wSJHbdCJtAZZy3VGr7pxgwMdihJE7K5WJmdjU2koqFHD74qshALWpCPeeMWjVU" + ], + "txHash": "8ccb8840e3de7ccd290d5504f96a6a7dad4cd3e8ae39ad5cbdcf39a9efcb754f", + "hex": "02000000024f75cbefa939cfbd5cad39aee8d34cad7d6a6af904550d29cd7cdee34088cb8c000000006a473044022060fd87a6e1410c91772502b628650f09bf454a544deb778ba8fad2947b0130fe02201800999f3fa6d5bf687870a4bbf7bd12b114caed4d8f81ababdd5ac90500a1824121020d8583bb60d8bbf97add96358f95866b97f85229139c8b71f1affcc7e70ecc77ffffffff4f75cbefa939cfbd5cad39aee8d34cad7d6a6af904550d29cd7cdee34088cb8c010000006a47304402201d080b09551eb8327db4cb98bf2121cbf9400bec25017bdb9490b278638f9c500220583ed70bb053243899c18eabcb7bd929c2023ca59cfe07dd8e97ec4fa16e2bc6412103535dff2bde6c45d36e8c7da293cf723427f1fadf6eba8cd6323b866030f96a6bffffffff018e9f0000000000001976a914a0f531f4ff810a415580c12e54a7072946bb927e88ac00000000", + "amounts": [ + 20593, + 20593 + ], + "outputs": [ + "bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" + ] + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7r2bbsoGrUt7gSo8auZgv6aSaS59FVjZjRiJkK1SYqPGY3vr9gMEXvfKNzRNjW6VjMRpt3GVwXXF6gT8SgNfdSMarW2mU5JC", + "tprv8jwG6YA1fGe7u7wh93Si5YXPWAm6mZD4MMqEmdPiYzgWUCC3BDJ6Sg8XR14Rw4QmyBFu5fSF7ksjDU3Xk94KAjSY6P3f372X5uk9T3iSugg" + ], + "txHash": "812391dbaf4dc6b39dcbbb85a613a183aea48fb994212449bdbd4dd1b74b3518", + "hex": "020000000218354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381000000006a473044022058c446eec373bb5cae046e12a57cbf5f0b85f7c5a022b88ca17e77f72c2f8d7b022049cfdcb56bf6291db71c5fcbe82812f1e1a98c5e083c405b855b9532eb055f85412103dbd4cae70953a0c34550d59120bc8aa6ed6c01adf833c1d9d3118247f6175084ffffffff18354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381010000006a473044022045df2a8ccca97dedeb22bf74fc890bc6a2d2998147621b78a208938bf43a2fc4022077b4b5b21a04d27629535e7a44d3e96bf6d10bf84a20d68c1fccdaca41eb5fc8412103b744479db3b9b26ae110236568fdaff6637bb0e7a874154bf3060468fc5de09dffffffff012c3ceb0b000000001976a91456f61499e2617482f2a63e8aceae5e8cc4c16b5a88ac00000000", + "amounts": [ + 99985420, + 99985420 + ], + "outputs": [ + "bchtest:qpt0v9yeufshfqhj5clg4n4wt6xvfstttgw0zrer9n" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe85moRDL8ZmSRmsHDxgdje16KyYvnc2N4wzTpXoQQVgygVwUYzEGWeMNTuMZCRGAiReSGNdFm2R5mEmfvTEPpdiqYWWLNmdSk", + "tprv8jwG6YA1fGe86yJEbtnWSGSLZQ9Ep7cRptNFLYChBAfDqnR9EA12hN4eum2wTDd3rgVFrVW97M4yPSHDzEQ2KE3PwnuSvVt87ZQMLoA5zyj" + ], + "txHash": "29aa7fe4cb2ab3a0ab3e8d3d98ef6acd95be66e6c0a2709d58c4991b57fb6a8d", + "hex": "02000000028d6afb571b99c4589d70a2c0e666be95cd6aef983d8d3eaba0b32acbe47faa29000000006b483045022100960f60296536abf61573aa012a91492874a52bfab4ab220ba67011616f309e7002206f48e6d34ff2a91e76b329dff9dc5766a1e9a04030a9529d334b521e459bdc054121023f30c27ee820b656ef9c4286f657b1a3353f9bea94b3fde7446181e080217777ffffffff8d6afb571b99c4589d70a2c0e666be95cd6aef983d8d3eaba0b32acbe47faa29010000006b483045022100a02e8bc1bf5df3a45d79d16aadc1e0256fcc44355896d35179471a71be0934bc0220253d2d57428e05570829bbf18f7d3fb7120a34046797673457edead77749a606412103aa369a3edc60b06b099b20fc624ff5213a44212911e7b89b561ecd2e8089cbd2ffffffff011805eb0b000000001976a914cf0abb634cfdb82c73544f625697eb464b008b0288ac00000000", + "amounts": [ + 99978370, + 99978370 + ], + "outputs": [ + "bchtest:qr8s4wmrfn7mstrn238ky45hadrykqytqgt5el4std" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8Fnr2eFwWmFMqK6t2YnEbXNLAS44iHmfaubrj7VQ6o1WnqLEG8CD7Q4ghoJbce9W8U2nG7WntiDX3Enk2y3SJuKuJubukkYY", + "tprv8jwG6YA1fGe8HbFCHAmLcKgz7Ft8c3LBGACiohF5AdFR6PQLHGfxgyESph9FLm8p7zM4wahBjFCEkgAtSQEyjNCuy8yE8jwpZxfCe1KSwjG" + ], + "txHash": "017232691d2d6d1c6c366943d536a1d1818101a6289a9f544c49fa54dd00c2e1", + "hex": "0200000002e1c200dd54fa494c549f9a28a6018181d1a136d54369366c1c6d2d1d69327201000000006b483045022100bb85f1b6a241a1c594c7686589e62438b2a9d25de106b4a9bb241f05beaed76e02204dd2e495462a448adf73197bfea811d7f76fde1657a988dfaba588ad2b964260412103ba2abd8d0a84d0ed16c07569581aeebc1619661627527c48a6a7778d529e5cf7ffffffffe1c200dd54fa494c549f9a28a6018181d1a136d54369366c1c6d2d1d69327201010000006a47304402202e7f45406b0225cffa0d9de135a9233b98accc9ea2601f3b980bcd030d2dfdc802205c3b32c93bc5321e0040a01b2062a31037f2235ce37e9883f17e0919d1b3ad5841210300251d96a333cb1712a6ce2491670ccf156ba51d56265d23b3f1ca88dc0911d1ffffffff01ec8aeb0b000000001976a914fe7325733cb8a245f39300b3c01eb218f54f8cc188ac00000000", + "amounts": [ + 99995500, + 99995500 + ], + "outputs": [ + "bchtest:qrl8xftn8ju2y30njvqt8sq7kgv02nuvcypr0ygsvs" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8TDaPJgNPpGg4Lw4ui6ypX9MBJGwgrQEfSifG7sA3L5kX4mif1ToufcMQFfRKavBew5rw9HKcHjjbZBE3zYJFbrAR6QvxZZK", + "tprv8jwG6YA1fGe8WP5MiS2b8JrrqMTcttJ11FMBv9Z8UAzkj3G2NHcxyJsbmjPQ6sX5NLtcVBgcmPFP7mjNZewNZ7A2v6HeofXS6nPvVKXHkoD" + ], + "txHash": "d39387deba6166d99e3fe35a54d715bfccae883c303575e97d555b7c57d1975c", + "hex": "02000000025c97d1577c5b557de97535303c88aeccbf15d7545ae33f9ed96661bade8793d3000000006a47304402206f49ad053ed79c0e7cbeeb0463889e9e51da503bebd6615108455e2e5e23e86e0220510041031e44eb466fbea0ffd5ccc3e749a01e7135d014b0e7050d5d74e9a620412102338993f9038fbff3c595d8a5eca8f36c5c7c1f837f60a2aec1e9bd3f06acbb2dffffffff5c97d1577c5b557de97535303c88aeccbf15d7545ae33f9ed96661bade8793d3010000006a47304402202b47efa8d290ef8bab184ed297a08aecfe772dd1fc889a91664d6127fe32f6b702204f6f83351f81df5703a9fa64c1ba2e1fb418d3944eee025408d6d30ea2907d9d412102101fd2a369bf6ef3e3ae69bd0e3a86f4c9d5670dae8588792ec0dc23a7bed47dffffffff01d853eb0b000000001976a9144156292b93d85702651e163326b0f6374031cb6888ac00000000", + "amounts": [ + 99988450, + 99988450 + ], + "outputs": [ + "bchtest:qpq4v2ftj0v9wqn9rctrxf4s7cm5qvwtdqfk964a4c" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8fvc31CxrmL81j3Le2iGmkm6HkpC2Mw8ZYKAQMCd5y2UZ8rMFossHwwPVfL28Cjizd2fweeQmPeWtnirBEbZLFZ3LutsE6Q5", + "tprv8jwG6YA1fGe8icVTQp2n5nWJKSLCErHXqGRy6fcby9h1QUN9Nxy6Agxg8atUL687XtwV8bJvotdjVLaZ6rMwea2k9Em2Hk418LkFK79FKgj" + ], + "txHash": "89b1b44fca675b0a8740c7493af3a67be3824985d37ffd52798fccbcc12fb0af", + "hex": "0200000002afb02fc1bccc8f7952fd7fd3854982e37ba6f33a49c740870a5b67ca4fb4b189000000006a47304402203689aab908c142791d5e7c84ab5b5e4cc3332bf921292216464326d489729bb702200f3d31fe6c77482946d7d84571c6a05b78e6ed01813a0c4ac50bd57f01f5ebf4412102747da20b143a15a33c9d7165815f79ede81881044e23066d2fc803d43a21deb2ffffffffafb02fc1bccc8f7952fd7fd3854982e37ba6f33a49c740870a5b67ca4fb4b189010000006b483045022100c574a287502e7f782a244156445b7130bbc7934e5f784a55b3535f83576c667602200f9c636dce7e7414ce0dac5a652ddc9f4ebeb48144cd01f860b2ea8d984b4e134121032bfc541ed6be30e1e02b5f3827a81c4b59426611b697fd202d40cbd65d6e53deffffffff01c41ceb0b000000001976a91484a2862dc63631bb615a16807779c2c5a664c15f88ac00000000", + "amounts": [ + 99981400, + 99981400 + ], + "outputs": [ + "bchtest:qzz29p3dccmrrwmptgtgqamectz6vexpturce5juax" + ] + } + ], + "regtest": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7r2bbsoGrUt7gSo8auZgv6aSaS59FVjZjRiJkK1SYqPGY3vr9gMEXvfKNzRNjW6VjMRpt3GVwXXF6gT8SgNfdSMarW2mU5JC", + "tprv8jwG6YA1fGe7u7wh93Si5YXPWAm6mZD4MMqEmdPiYzgWUCC3BDJ6Sg8XR14Rw4QmyBFu5fSF7ksjDU3Xk94KAjSY6P3f372X5uk9T3iSugg" + ], + "txHash": "812391dbaf4dc6b39dcbbb85a613a183aea48fb994212449bdbd4dd1b74b3518", + "hex": "020000000218354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381000000006a473044022058c446eec373bb5cae046e12a57cbf5f0b85f7c5a022b88ca17e77f72c2f8d7b022049cfdcb56bf6291db71c5fcbe82812f1e1a98c5e083c405b855b9532eb055f85412103dbd4cae70953a0c34550d59120bc8aa6ed6c01adf833c1d9d3118247f6175084ffffffff18354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381010000006a473044022045df2a8ccca97dedeb22bf74fc890bc6a2d2998147621b78a208938bf43a2fc4022077b4b5b21a04d27629535e7a44d3e96bf6d10bf84a20d68c1fccdaca41eb5fc8412103b744479db3b9b26ae110236568fdaff6637bb0e7a874154bf3060468fc5de09dffffffff012c3ceb0b000000001976a91456f61499e2617482f2a63e8aceae5e8cc4c16b5a88ac00000000", + "amounts": [ + 99985420, + 99985420 + ], + "outputs": [ + "bchreg:qpt0v9yeufshfqhj5clg4n4wt6xvfstttg5n5z6sx4" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe85moRDL8ZmSRmsHDxgdje16KyYvnc2N4wzTpXoQQVgygVwUYzEGWeMNTuMZCRGAiReSGNdFm2R5mEmfvTEPpdiqYWWLNmdSk", + "tprv8jwG6YA1fGe86yJEbtnWSGSLZQ9Ep7cRptNFLYChBAfDqnR9EA12hN4eum2wTDd3rgVFrVW97M4yPSHDzEQ2KE3PwnuSvVt87ZQMLoA5zyj" + ], + "txHash": "29aa7fe4cb2ab3a0ab3e8d3d98ef6acd95be66e6c0a2709d58c4991b57fb6a8d", + "hex": "02000000028d6afb571b99c4589d70a2c0e666be95cd6aef983d8d3eaba0b32acbe47faa29000000006b483045022100960f60296536abf61573aa012a91492874a52bfab4ab220ba67011616f309e7002206f48e6d34ff2a91e76b329dff9dc5766a1e9a04030a9529d334b521e459bdc054121023f30c27ee820b656ef9c4286f657b1a3353f9bea94b3fde7446181e080217777ffffffff8d6afb571b99c4589d70a2c0e666be95cd6aef983d8d3eaba0b32acbe47faa29010000006b483045022100a02e8bc1bf5df3a45d79d16aadc1e0256fcc44355896d35179471a71be0934bc0220253d2d57428e05570829bbf18f7d3fb7120a34046797673457edead77749a606412103aa369a3edc60b06b099b20fc624ff5213a44212911e7b89b561ecd2e8089cbd2ffffffff011805eb0b000000001976a914cf0abb634cfdb82c73544f625697eb464b008b0288ac00000000", + "amounts": [ + 99978370, + 99978370 + ], + "outputs": [ + "bchreg:qr8s4wmrfn7mstrn238ky45hadrykqytqg3g07krgt" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8Fnr2eFwWmFMqK6t2YnEbXNLAS44iHmfaubrj7VQ6o1WnqLEG8CD7Q4ghoJbce9W8U2nG7WntiDX3Enk2y3SJuKuJubukkYY", + "tprv8jwG6YA1fGe8HbFCHAmLcKgz7Ft8c3LBGACiohF5AdFR6PQLHGfxgyESph9FLm8p7zM4wahBjFCEkgAtSQEyjNCuy8yE8jwpZxfCe1KSwjG" + ], + "txHash": "017232691d2d6d1c6c366943d536a1d1818101a6289a9f544c49fa54dd00c2e1", + "hex": "0200000002e1c200dd54fa494c549f9a28a6018181d1a136d54369366c1c6d2d1d69327201000000006b483045022100bb85f1b6a241a1c594c7686589e62438b2a9d25de106b4a9bb241f05beaed76e02204dd2e495462a448adf73197bfea811d7f76fde1657a988dfaba588ad2b964260412103ba2abd8d0a84d0ed16c07569581aeebc1619661627527c48a6a7778d529e5cf7ffffffffe1c200dd54fa494c549f9a28a6018181d1a136d54369366c1c6d2d1d69327201010000006a47304402202e7f45406b0225cffa0d9de135a9233b98accc9ea2601f3b980bcd030d2dfdc802205c3b32c93bc5321e0040a01b2062a31037f2235ce37e9883f17e0919d1b3ad5841210300251d96a333cb1712a6ce2491670ccf156ba51d56265d23b3f1ca88dc0911d1ffffffff01ec8aeb0b000000001976a914fe7325733cb8a245f39300b3c01eb218f54f8cc188ac00000000", + "amounts": [ + 99995500, + 99995500 + ], + "outputs": [ + "bchreg:qrl8xftn8ju2y30njvqt8sq7kgv02nuvcymle9tr0k" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8TDaPJgNPpGg4Lw4ui6ypX9MBJGwgrQEfSifG7sA3L5kX4mif1ToufcMQFfRKavBew5rw9HKcHjjbZBE3zYJFbrAR6QvxZZK", + "tprv8jwG6YA1fGe8WP5MiS2b8JrrqMTcttJ11FMBv9Z8UAzkj3G2NHcxyJsbmjPQ6sX5NLtcVBgcmPFP7mjNZewNZ7A2v6HeofXS6nPvVKXHkoD" + ], + "txHash": "d39387deba6166d99e3fe35a54d715bfccae883c303575e97d555b7c57d1975c", + "hex": "02000000025c97d1577c5b557de97535303c88aeccbf15d7545ae33f9ed96661bade8793d3000000006a47304402206f49ad053ed79c0e7cbeeb0463889e9e51da503bebd6615108455e2e5e23e86e0220510041031e44eb466fbea0ffd5ccc3e749a01e7135d014b0e7050d5d74e9a620412102338993f9038fbff3c595d8a5eca8f36c5c7c1f837f60a2aec1e9bd3f06acbb2dffffffff5c97d1577c5b557de97535303c88aeccbf15d7545ae33f9ed96661bade8793d3010000006a47304402202b47efa8d290ef8bab184ed297a08aecfe772dd1fc889a91664d6127fe32f6b702204f6f83351f81df5703a9fa64c1ba2e1fb418d3944eee025408d6d30ea2907d9d412102101fd2a369bf6ef3e3ae69bd0e3a86f4c9d5670dae8588792ec0dc23a7bed47dffffffff01d853eb0b000000001976a9144156292b93d85702651e163326b0f6374031cb6888ac00000000", + "amounts": [ + 99988450, + 99988450 + ], + "outputs": [ + "bchreg:qpq4v2ftj0v9wqn9rctrxf4s7cm5qvwtdqn2nmkwk7" + ] + }, + { + "xprivs": [ + "tprv8jwG6YA1fGe8fvc31CxrmL81j3Le2iGmkm6HkpC2Mw8ZYKAQMCd5y2UZ8rMFossHwwPVfL28Cjizd2fweeQmPeWtnirBEbZLFZ3LutsE6Q5", + "tprv8jwG6YA1fGe8icVTQp2n5nWJKSLCErHXqGRy6fcby9h1QUN9Nxy6Agxg8atUL687XtwV8bJvotdjVLaZ6rMwea2k9Em2Hk418LkFK79FKgj" + ], + "txHash": "89b1b44fca675b0a8740c7493af3a67be3824985d37ffd52798fccbcc12fb0af", + "hex": "0200000002afb02fc1bccc8f7952fd7fd3854982e37ba6f33a49c740870a5b67ca4fb4b189000000006a47304402203689aab908c142791d5e7c84ab5b5e4cc3332bf921292216464326d489729bb702200f3d31fe6c77482946d7d84571c6a05b78e6ed01813a0c4ac50bd57f01f5ebf4412102747da20b143a15a33c9d7165815f79ede81881044e23066d2fc803d43a21deb2ffffffffafb02fc1bccc8f7952fd7fd3854982e37ba6f33a49c740870a5b67ca4fb4b189010000006b483045022100c574a287502e7f782a244156445b7130bbc7934e5f784a55b3535f83576c667602200f9c636dce7e7414ce0dac5a652ddc9f4ebeb48144cd01f860b2ea8d984b4e134121032bfc541ed6be30e1e02b5f3827a81c4b59426611b697fd202d40cbd65d6e53deffffffff01c41ceb0b000000001976a91484a2862dc63631bb615a16807779c2c5a664c15f88ac00000000", + "amounts": [ + 99981400, + 99981400 + ], + "outputs": [ + "bchreg:qzz29p3dccmrrwmptgtgqamectz6vexptuey04307q" + ] + } + ] + } + }, + "p2ms": { + "toOne": { + "mainnet": [ + { + "xprivs": [ + "xprvA7icF82QxB8Yceytan3gExmvk42pVxyV5wty9bCfe6xsh8VQQ89njqLUNwXUjcJNouTsNVFQWtFotRQiUecs344U6bvYzWgybRKX8Pe7VD4", + "xprvA7icF82QxB8Yf9QoKWYgv1HurbMjjFJqoprJRd2pPBy7BZiY4LXzDQcvyCtc7xvafRaF5qm8MBj9njwxFVhiXi2M7qNJQ2K8UAbsJmzh7z8", + "xprvA7icF82QxB8YjC4eCEsfMeNdnax3TtPB7hawSuYu1awBMGkbF3fKtnM6oJhiwL44dtDeK6EKCcTLAncL4itTfQ2SZFUVM8GFnPixxNwf2XJ", + "xprvA7icF82QxB8YmMUJS1g9XrKua1pqDnsoTzAeQyxPWFQxzJkbuD9aHEYxJkPXFxMpxRSRGKJoj6ek2JLtgtBx9AQx2efL59BbNdqCzeBGR5H" + ], + "txHash": "acd63b6ff8a7487767a12e13d810f01fd293bdbc11a6dc3bcd5d1f9fe8399449", + "hex": "0200000001499439e89f1f5dcd3bdca611bcbd93d21ff010d8132ea1677748a7f86f3bd6ac000000004a00483045022100a22833ce480af0e72abfd9c1fe1e3396f4671f531c7617d5c0d1a1c8b11b74e502205bcf3eb315e576141e825ddd9210a14fce823302fc57b69a4f1ad5d2d440a79241ffffffff01551c00000000000047512103d493e03c12ddda479aa7cff3c7d64d8bd0d66722957f1e67263fa8c7a7b1582c2102c345e1af9c49dc2b2073de92f3ab63c83fae641f6df9b43e0e59dfd0c771206952ae00000000", + "amount": 7809 + }, + { + "xprivs": [ + "xprvA7icF82QxB8YjC4eCEsfMeNdnax3TtPB7hawSuYu1awBMGkbF3fKtnM6oJhiwL44dtDeK6EKCcTLAncL4itTfQ2SZFUVM8GFnPixxNwf2XJ", + "xprvA7icF82QxB8YmMUJS1g9XrKua1pqDnsoTzAeQyxPWFQxzJkbuD9aHEYxJkPXFxMpxRSRGKJoj6ek2JLtgtBx9AQx2efL59BbNdqCzeBGR5H", + "xprvA7icF82QxB8YoTSEMJKFtmosaMU7n2f3PWpb5aWAKr3SbLuSdvkiYJzzaF8oPtdfFyfA3LPsV95rE4VdPCgwm8EJSprV2w9RnuRJMgRZVpu", + "xprvA7icF82QxB8Yr3ie7TD2hTaUxiMLbGx812oKj5PdMp9YcNQHHsGEjszyhSqEDSWYkGe1rJCKSmhx3m2do5yxhxGmhsUCwPQJ8Ria74MCHcg" + ], + "txHash": "1ffc187932d0142a4db94a4c2993ca46f378b16a1facb9c40680dcae68c3dcf4", + "hex": "0200000001f4dcc368aedc8006c4b9ac1f6ab178f346ca93294c4ab94d2a14d0327918fc1f00000000490047304402200fd3e5936fe61c6a650f1516cce980fa00cf4f545075004908ed7b22668a341d02204609d421b3c633c345747979310de9f581b3fcca3fc464d5f2ce6cd0c924766a41ffffffff01291a00000000000047512103972ad18a53b2b7049e46c9fd8ff866ffe6b24d9c1742bb53bcca89058164957621031e5d210ff49df1fc2408fd7d8679949f4684d9dc1c90016e65dc0c02e707176252ae00000000", + "amount": 7253 + }, + { + "xprivs": [ + "xprvA7icF82QxB8YoTSEMJKFtmosaMU7n2f3PWpb5aWAKr3SbLuSdvkiYJzzaF8oPtdfFyfA3LPsV95rE4VdPCgwm8EJSprV2w9RnuRJMgRZVpu", + "xprvA7icF82QxB8Yr3ie7TD2hTaUxiMLbGx812oKj5PdMp9YcNQHHsGEjszyhSqEDSWYkGe1rJCKSmhx3m2do5yxhxGmhsUCwPQJ8Ria74MCHcg", + "xprvA7icF82QxB8Yv1LagKeMmX1HmDCzRZPXRvKMCFTXQ1X4vkWcG3oh9bSUEGQJTkQfKvTfv7aNW2xTCqJJtfUPErc973PtE7S5hRVn8bRTmpb", + "xprvA7icF82QxB8YxP2Eh13mcngwEY7Q9Cr4BpzPGkLCPMMhSVq3ci7WTZiCGRP7mdQyXRff9cSMvDxsa8byxAPAHLVmec1h2KmGocUc6povdxq" + ], + "txHash": "508a1d84a74707bc542cf7cb977d1b4b03828842e9484c54528e1799131c4f81", + "hex": "0200000001814f1c1399178e52544c48e9428882034b1b7d97cbf72c54bc0747a7841d8a50000000004a00483045022100b6bc83766db9fdd2d4f89819681b73f3d2f6775db2cb8961d3cfb22922cc007e02204ac624fb6834299edfb96a1d8ebef9266e8f166c1eee37b5f2c47e9b368f2fae41ffffffff01fd170000000000004751210352f6a58f1c3d2d430f9a87b58db407c21d183ae35d65fb4bf1dff73adafac87c2103938520f916c41f69319c47c1eebab77bc56317593026e7eace061cbbc2b2601852ae00000000", + "amount": 6697 + }, + { + "xprivs": [ + "xprvA7icF82QxB8Yv1LagKeMmX1HmDCzRZPXRvKMCFTXQ1X4vkWcG3oh9bSUEGQJTkQfKvTfv7aNW2xTCqJJtfUPErc973PtE7S5hRVn8bRTmpb", + "xprvA7icF82QxB8YxP2Eh13mcngwEY7Q9Cr4BpzPGkLCPMMhSVq3ci7WTZiCGRP7mdQyXRff9cSMvDxsa8byxAPAHLVmec1h2KmGocUc6povdxq", + "xprvA7icF82QxB8Z17gjh7ZAujh5C6AMjrvycPJNyoJj3ynczXeuPMttF5PxHJXaQFkmzjJsx3d3SAwm6XMXyy9WAUcGB4kZqpRzZz5xyGN6yVt", + "xprvA7icF82QxB8Z2kQZmnrLde27PryauLg9FDz1nuKnJ98tDhgEvuvUmg4Yp375eFfq3aeCKy9wK3Zo5QZbtvybpxWRipjyaTsi832Tithb5Rk" + ], + "txHash": "759d93b196f517c8375c10057af8cd7ee6538916be1d8f3deab264880a2ce614", + "hex": "020000000114e62c0a8864b2ea3d8f1dbe168953e67ecdf87a05105c37c817f596b1939d75000000004a0048304502210092a2d41bd33e08f4ebf0425d434ae50b13bb6c92e4df4e3adedd41cf9106aac50220516edd08cfd77b334a19ce4522e4c2f763a54dc5c2fe90779e17b87a0a08840d41ffffffff01d1150000000000004751210397909868c652014d9824ba033a27e453de6075c418fd05c357daa88e7c4047f72103b112b1e3cbe64285750f7a297c4e32a4fd0c8c20c128fd4adbfaccd9b1c3b82952ae00000000", + "amount": 6141 + }, + { + "xprivs": [ + "xprvA7icF82QxB8Z17gjh7ZAujh5C6AMjrvycPJNyoJj3ynczXeuPMttF5PxHJXaQFkmzjJsx3d3SAwm6XMXyy9WAUcGB4kZqpRzZz5xyGN6yVt", + "xprvA7icF82QxB8Z2kQZmnrLde27PryauLg9FDz1nuKnJ98tDhgEvuvUmg4Yp375eFfq3aeCKy9wK3Zo5QZbtvybpxWRipjyaTsi832Tithb5Rk", + "xprvA7icF82QxB8Z5Ve4zHqKvW6SSRxcSpFRq2fkoczMBJ8NHPLYbXRab68jCVcZHnMNch5bG3R7afp2vmp5eU5xbTcfoxRNGtCUcnJVAqqK1pr", + "xprvA7icF82QxB8Z6hnNyC1D6iouWF2bD4zBsPd7DVUhnmEKajejhawXYZtZqntoBZeJqSqmsYW6Vyusy4YZzMiCcRNqccJZDkVYrEzQACpXxea" + ], + "txHash": "3c10a4a508aa1c8aef11dcf4c042df2c78cea886e94812501f3b4e0f7e28f881", + "hex": "020000000181f8287e0f4e3b1f501248e986a8ce782cdf42c0f4dc11ef8a1caa08a5a4103c00000000490047304402200e571182a812701b29d972a939a9be4d6d940759208bca37cc78aacdb5ca24260220460c07a7babb91a156f4ea6766bb4c815a9e0151d0ed5f86f5bcea3ee858a23141ffffffff01a51300000000000047512102304924636622fda4b7fe3ebfe1f1de274bba81df41768f53d7cc3a45c75ad44e21022db65557063bad6a76a23c5d1b82d8cf8b28bbb83df24a6abc21815d60fa048552ae00000000", + "amount": 5585 + } + ], + "testnet": [ + { + "xpriv": "tprv8jwG6YA1fGe7Ut879xQ9NMTwK9oWx4huSvQuix8bEorixxkhkSJd55B7mfQAQeALoEpWWQ5GW2qCmBjAnnyTCyMXoJYUGuQLQLPN367M98Y", + "txHash": "adcda2424f8768bdf3d5064e9a71a61d6d80b22bcc12f5eae0372822aac4c293", + "hex": "020000000193c2c4aa222837e0eaf512cc2bb2806d1da6719a4e06d5f3bd68874f42a2cdad000000006b483045022100dce87e98844fe01c7f0bffeb3ec6dbd92cada2751570653abb5db5bb1c40eeec02202eeac6d4f79f4774b40c121c6b99b8f431357a17dc8bae39003a16a8246a2bae4121037e04aa55787df424b4f1160aa64c65f64b2daa69e53d16c75b9a40708b01651dffffffff0140a0eb0b000000001976a9143cb6602d5ab7dc5c283f34c361fe9ce152f5c46888ac00000000", + "amount": 199994240, + "vout": 0, + "outputs": [ + "bchtest:qq7tvcpdt2machpg8u6vxc07nns49awydqag2n4uw7" + ] + } + ] + }, + "toMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7icF82QxB8Z5Ve4zHqKvW6SSRxcSpFRq2fkoczMBJ8NHPLYbXRab68jCVcZHnMNch5bG3R7afp2vmp5eU5xbTcfoxRNGtCUcnJVAqqK1pr", + "xprvA7icF82QxB8Z6hnNyC1D6iouWF2bD4zBsPd7DVUhnmEKajejhawXYZtZqntoBZeJqSqmsYW6Vyusy4YZzMiCcRNqccJZDkVYrEzQACpXxea", + "xprvA7icF82QxB8ZAzaHKF19HrwBcV4sbe4BaNdzbWMnKjifQ9A1bQhg4LFegaaR1pAGDYfKU7HPjix6W5UUAJk55puhC4gzUkH4au5XGNfCZax", + "xprvA7icF82QxB8ZDSWnLYH55gcoT6Mpy7AMCDTAYD2Z5PhdPTpzUnhk9evSsx868rjSWU1aKcNRPLDK7RhoqAMbQGPHz9HNngSitJERJ45UU93", + "xprvA7icF82QxB8ZFT5zjYeRn5MqFuJi1NSBoqxswndCCTEpNGtrVDgi3XdjKudZVDdarksmNT4HWAUNitSE2UZBVQJcg5bJsXM6DmGoDCrhJr9", + "xprvA7icF82QxB8ZHXGFm7g66kN4muMAefDfQ7DNWRWg1hpR7LSYXBr5XkWGhWocvv5pbDQBNjGsPzSGawdeaJ7YHsXyevbVFpagBwk1tjdMFVg" + ], + "txHash": "79decc7af8dff5f52a60805f8c8c2f93dd02c9570c495d904a5acac3438cccc4", + "hex": "0200000001c4cc8c43c3ca5a4a905d490c57c902dd932f8c8c5f80602af5f5dff87accde7900000000490047304402205c664b27664811115baacadab141b018de2a99942779a82439a0edc6a4e29fac0220204aa68174a491919215b986804776d99927118cb05f37bfa2391a18de0f97c741ffffffff02bc08000000000000475121030115a43c16094f6d7f8073ba143ccd9432ee8791e6f07eaa721861588e0824ae21027fd154f8d42d3bc8e2d38b4827af21559b64462764ed7814225e9c0c25e398f652aebc08000000000000475121026c3bc32ad1d20263f2bbe62cf3e10b1c2f7ecf1aced0c6b715a3c13b2d5214572102897a1fad02686122c53910196048ec5e7ac5875259134bc2f0e79749be3a086252ae00000000", + "amount": 5029 + }, + { + "xprivs": [ + "xprvA7icF82QxB8afyTitZSe1STeqkJZR7FhCDvtuWj8TQbKSJ1zTa33J8eDNNr6C64K11uqfoXtxvSbpZRK6zcRHwfduotzr2Xkrtv15gL2tuF", + "xprvA7icF82QxB8agTiBFt1v93qkgVEw9WKrsSd3JNFsXwTwHXSkeqTEHwt8abErLRMzNgSZpRGmarNWCPK2i9zxs646UA4ucAmGmzDvfKph6N5", + "xprvA7icF82QxB8amGM6RAFtS267PedatbG4RirAjRtCXpuahyC2EvHqBB5cWSx4F25obHHeBWJRhZikPbc7tpD2s5ERY5kGmptRy3uKuLyaYB2", + "xprvA7icF82QxB8aokYDbRNNVPMfLS1EauHLyNdAAPPm9Zv9RJePu12Q9UHv9Zkrsx4m6iz6egQj6cEaa15XKBiYBvMwe3GdofvtTRWhPzfYnrb", + "xprvA7icF82QxB8aqFgUdDrV4UNdN24SMgMgtn3iNeDKxNCfe9BuE7phr7twxaBe4WTMGHv9QiMaQ1zx86bMsLhZ586zfXN8zXk6dEVCRLSrdP8", + "xprvA7icF82QxB8asxrc5oRcUf2hsFxDn8e4CKyC4rZs3VyVXGmVaVXqeAXMcCrNS4LgjGAsUGZ2rH1rbd6fbdzMTUQmoZqPydR891aHG3e4ydu" + ], + "txHash": "fe358d444fd8c77e9dc57d1d8f3b8578a6cb796ae3463e66b7a910d5fd86d321", + "hex": "020000000121d386fdd510a9b7663e46e36a79cba678853b8f1d7dc59d7ec7d84f448d35fe000000004a00483045022100914f9ca1c592816dbe3e2c327f27ab538be21361b9fa60c4f52100e3f3fa576b02207f56adb3fb3b329a4b0577ef270d60c35bc2d2cade8350af8a5fe1d76621a95a41ffffffff02ce5100000000000047512102741d365bfe3b58d83ef7f8e2c86252985827675851ad6363f9130537703258f021030ef6c971891cf527b875365b2e5a80553df6f34fa8d9a654a141e035377d509052aece5100000000000047512102cb05940a62c705b919504a725c68897d60e01c7324325f040c8dcae5947ee7cb2103f83da8f3d924b136d3a6d16b30b9d8787a8f24b3f091057bba6c7acae13db02d52ae00000000", + "amount": 42440 + } + ], + "testnet": [ + { + "xpriv": "tprv8jwG6YA1fGe7jgUsatzgsqH941oUDZs9FejN6rVRZcMaL4nPoShBwD6LNFThDNNg1zpVe1k4zHeK82KJMU4iJMCeEbinn86QuFd5eXutaUL", + "txHash": "4edb662b14424d7bbbbe4b9fd1533d49690aefb2a15af8fa0704eab1ce41210b", + "hex": "02000000010b2141ceb1ea0407faf85aa1b2ef0a69493d53d19f4bbebb7b4d42142b66db4e000000006b483045022100bf8c2e6b8d3bcbfbaa64a5e9c2cbe5ce71471f87ff1d9f6a0bcec1c58c4a8a2102201504868d60e6f75ccda4c3fe899df0094d878fe8ca5ec55893da5f61517b49a04121029cc6fa90d901a5474249facbb32b187a1de63e853c43b272752109abdef19728ffffffff0201b3f505000000001976a914b3a223ad62ca6c8669a69a508216cd374612cb5688ac01b3f505000000001976a914b4e71aaf9bf4afd806d0560b17995c873ef61e9e88ac00000000", + "amount": 199979840, + "vout": 0, + "outputs": [ + "bchtest:qze6ygadvt9xepnf56d9pqske5m5vykt2c7vwv30af", + "bchtest:qz6wwx40n062lkqx6ptqk9uetjrnaas7ncmajyntv0" + ] + } + ] + }, + "manyToMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7icF82QxB8ZSikvkLNigfv5GsTvoSTSdBTJPEmYPPzF4iDh1QH49BhBFjo7MMcKnKX3SV5NX4nraBb6WEaQ3nhz6WnC6SoHyhNE7rqfUUy", + "xprvA7icF82QxB8ZT7mE7ajBr4nB8YzC1twv5G3YigJhxWUgxkVcKYg94cwz6qBLmR432toM5e6FjbtwNaYtHtS7VnX5fiuv7EeJZDgEC1zUmgo", + "xprvA7icF82QxB8ZVYj2s97gKjfXgGQW4JRLpPVetJEAyMQfkGjXCr7T7k6k43kLT8i5sBYddcyJCRWwgPW5cLWVs75Tmp7r48xV4UKTZLCyjsC", + "xprvA7icF82QxB8Za3ECXZak1o1ZjNXaRya8jEEy24RmNLmMenYAYWrcxUeZumSsD1GchmREkmQVBKwyLxhjwJipfRSGC5gjL4rMRrcLZed6iWT", + "xprvA7icF82QxB8Zd5EjUqP76EVzN5ghDFVqAWcAfw176mtqYRR9p6gT8mkzqtQRyizHCu55K5DkFmhvEnBD16111ZNo9nc1iVZ4TasN8HLbY7R", + "xprvA7icF82QxB8ZeSGJaHz1zrjKtgCtxCikjsEUrxC4qJRLEi2NeDcuUSQQRYPDjdXs3Gi81pZC2557LmhuRZmdBvDUebcbnrGDRapEGFkHLqW", + "xprvA7icF82QxB8ZhCBfx1ChAiHQyhpKLXFW18aApsPbnJXs3RpiGwujWQ6BabCYKv8G6CGKf3ueamQktSJafxqcasw5UmLAbTLiQsb2KGR5iqS", + "xprvA7icF82QxB8ZjkYazQf32kyRe7sxvp5nQjXFaza3cpn3G114GJFnUUsdMFBKsqXr8k8PirqttG6oCx2GyWWC91y2TxupieShaPrP8kxkvhL" + ], + "txHash": "465d8d6c5eb93673003c23f83a75b3299ac9f5ff542888a15d5f57dd9a40277a", + "hex": "02000000027a27409add575f5da1882854fff5c99a29b3753af8233c007336b95e6c8d5d46000000004a00483045022100b05a9489303710e4452fde06b1c633a65ea0761febc837befdee1d3dd3ee212d0220425ff8bc7524d8ee1746fd954f91b4c1c003004f301195d10e9a4a6b4a9948e541ffffffff7a27409add575f5da1882854fff5c99a29b3753af8233c007336b95e6c8d5d4601000000490047304402206126c73a56325d91418d6ca170857c064683ca1e33fdd5bc2a42a208e786ba8202205e0161a154e7d096ddd0826fa656d9dee919327c8e5057509ff52bfc4437db2641ffffffff027a0500000000000047512102884ab5b78c33c329216f32cfcdbf4e28ca12e80b18ec870656f591e21c2b2e112103caaa33a304947e48f2f655e3fc61f97ae805d061bcd754b9a2d29f851a39cb7752ae7a0500000000000047512102c9ad08d76e91f86c18bfaec7a95b181473aa5a60b77a74b3d754d7e8205b802821032b5f7309c3fbf5e8622af41d2d580baeaa82aaa0359264641ca1fe68d8ef721152ae00000000", + "amount": 3360 + }, + { + "xprivs": [ + "xprvA7icF82QxB8Zd5EjUqP76EVzN5ghDFVqAWcAfw176mtqYRR9p6gT8mkzqtQRyizHCu55K5DkFmhvEnBD16111ZNo9nc1iVZ4TasN8HLbY7R", + "xprvA7icF82QxB8ZeSGJaHz1zrjKtgCtxCikjsEUrxC4qJRLEi2NeDcuUSQQRYPDjdXs3Gi81pZC2557LmhuRZmdBvDUebcbnrGDRapEGFkHLqW", + "xprvA7icF82QxB8ZhCBfx1ChAiHQyhpKLXFW18aApsPbnJXs3RpiGwujWQ6BabCYKv8G6CGKf3ueamQktSJafxqcasw5UmLAbTLiQsb2KGR5iqS", + "xprvA7icF82QxB8ZjkYazQf32kyRe7sxvp5nQjXFaza3cpn3G114GJFnUUsdMFBKsqXr8k8PirqttG6oCx2GyWWC91y2TxupieShaPrP8kxkvhL", + "xprvA7icF82QxB8ZmLmyGp37AvuZXr3UstaTLfrmFFjU2ErqiMsC9Kdve6swjLFhUwYvLhpzpFwKsiZfhmVwpectgECUNyi37nSbwwDWKDHjTvj", + "xprvA7icF82QxB8Zpn5Sc8BXbEw8XnsEp7tTnL2TxStNi6X6FLUFUiT7bFDwHBasNa9CuJQr7GSZpMstNPoGDehkA1FbURJJ7gucugcDtmMTrFK", + "xprvA7icF82QxB8Zrq5wQjiUBjFPoJQ1Kb6iHCkYj91HbzwgeXMyDFJnphVsVHmYhf9hkfruwvGEAosXGBSuaEkGQnGcwBpqmG7YavZxh9TmEPd", + "xprvA7icF82QxB8ZudKk3LeAN395qr6ZW8V2fbeXapaxWb4wXu5sM85YC2WFpx9pRHwo8xJJTP3Kga3rHUvzVMcFHJ1rnGiyr8h7B7dxuVFpPXx" + ], + "txHash": "771ff329749dcf5ded610473448c214ffd258a20310815b8a6b6cb4eb263007d", + "hex": "02000000027d0063b24ecbb6a6b8150831208a25fd4f218c44730461ed5dcf9d7429f31f7700000000490047304402205d6b5c9bdcb8abf8fa39addb6c72d6119a938b1a413d0120b37d284f7497964602200997f6354b0244e6037ccf14fcc5e9a4d7851f4ef048579a1ec4b6f1fe349bc641ffffffff7d0063b24ecbb6a6b8150831208a25fd4f218c44730461ed5dcf9d7429f31f7701000000490047304402203f16418056acc7da5ef56c4d400483cd11809efd0be929a9269aa0129c8ac64d02200326b7762302ec28ce35a3bd43e9c8ad8e0b3732de5903372cabc4e5f63d596241ffffffff02640400000000000047512102a3008eb0646c09ab639195424b50c78921884a81830ea22b375f540a8bd0dbb72102594aac157629a75bb0708a1b4b4925acf8e2d428885e19e466cf248b6472dcff52ae64040000000000004751210230d01cbbe3697e3d1c451075f25bc44651cba89528337dd81be313879e44a78121029c06bc585ec0053a54c61e896efad1b78cb161998a6f9a1beba42ab6effb704d52ae00000000", + "amount": 2804 + }, + { + "xprivs": [ + "xprvA7icF82QxB8ZmLmyGp37AvuZXr3UstaTLfrmFFjU2ErqiMsC9Kdve6swjLFhUwYvLhpzpFwKsiZfhmVwpectgECUNyi37nSbwwDWKDHjTvj", + "xprvA7icF82QxB8Zpn5Sc8BXbEw8XnsEp7tTnL2TxStNi6X6FLUFUiT7bFDwHBasNa9CuJQr7GSZpMstNPoGDehkA1FbURJJ7gucugcDtmMTrFK", + "xprvA7icF82QxB8Zrq5wQjiUBjFPoJQ1Kb6iHCkYj91HbzwgeXMyDFJnphVsVHmYhf9hkfruwvGEAosXGBSuaEkGQnGcwBpqmG7YavZxh9TmEPd", + "xprvA7icF82QxB8ZudKk3LeAN395qr6ZW8V2fbeXapaxWb4wXu5sM85YC2WFpx9pRHwo8xJJTP3Kga3rHUvzVMcFHJ1rnGiyr8h7B7dxuVFpPXx", + "xprvA7icF82QxB8ZwjkrvDtZRCS4vmpHAKP7cyuHLxLSuxQZZGquJywom4u9PL1xzHpTxdApy9GcCgeC5VGVoWF3CrTXMjsyP2L8m8Uv8tDv3Zb", + "xprvA7icF82QxB8a1Tca3RzwNHZEhP2hL1NaQjvx3DQoR3pjcxGncv5vQTDCm158LqBcChNbqNeYQv92qtcDDjVCGNnMXoGxfPyyhgTGdbTxp4X", + "xprvA7icF82QxB8a2nzGik1BRWErM5NV7sFyTJPy9FnoJ567vXUaw9aFwW2yoYTi7iVtavNyWi8pZ4QxQ2WZHB3xYaTeD7U9jyvmZBkDn2vcvBY", + "xprvA7icF82QxB8a79yLazYJQM3RcFoANDSrWGvHm1MaWUwieDc71oU2cPgpKdYi61Ws7hB8WUzkSeVHgUJZubN7x2yXfrawDZcTq4QFMiXTy7P" + ], + "txHash": "072d3807b2d8d4457bcb002f7a89bf6dc28060bce9c56b967dadda0ecc4d345e", + "hex": "02000000025e344dcc0edaad7d966bc5e9bc6080c26dbf897a2f00cb7b45d4d8b207382d070000000049004730440220569f1cf2117b59a07c51cef449ba72d94da88ce7cd56739723d320fb9339a3db022043192a89e8da3208679605416d3a954b989fa6e9b6bdc136669057c38b28b7c041ffffffff5e344dcc0edaad7d966bc5e9bc6080c26dbf897a2f00cb7b45d4d8b207382d0701000000490047304402204b190b056738e6c7013ee5762c05b457dc6d122a4bd7dfacb2bf217ecac45433022020c5309f1d964fba4c1126e814abf6b112746d5cd72d85284eb3361ed5097bbf41ffffffff024e030000000000004751210394ca39f76f6dd3c719af0db9a7ffae8161b56e77e18d309e7007bc0a25513ab321027aa6e28597035e83a63112d9e24b1fc93b97d977d9500d8cf1869af22ecc366e52ae4e0300000000000047512102f5b01c6f2f669511677b52fcf6496a4835af592bf42202cb3fa658f7a436dc6f210333c0256189f893c614841e35258c7a8a3655cc90fe0462d285e595348fa8d76552ae00000000", + "amount": 2248 + }, + { + "xprivs": [ + "xprvA7icF82QxB8a9EusuXsq6Ls4xmpQdfxrAEnGX7CWNyXKEP33geYTbjxamr4FRoibZLkM5UmgUkLdPjyytR4zHXt9D1ec77ABatvWo9g53Pm", + "xprvA7icF82QxB8aBiUyo3GwQ5YkKutJ99oxpZUWTcPEECJtxp96mgH7fToCRCEqdyTt5GtHp31MoVzHBwxHWSQ5XeLWtTfP3PmX4SFMxFLEPpF", + "xprvA7icF82QxB8aECVR1KjwrzQ4uw7hcnNeCZh74CRUwCky9jioBqoD6QWDTeG6GAatVZghKoUKVhz1JjvAGQaUiGdw7Qpu6EVw75QJ2kxXdgs", + "xprvA7icF82QxB8aGGtkokgs5ik22YDEjDRFcAszqyrfwCBp1WaSGF8KvyDtjMDEHuVu4uN5bi1tqdCQzGsR8ZAdXZPNxmFXYSno4PeW4Z6stMf", + "xprvA7icF82QxB8aKkFq4h9cpmLJBqH1GGP2MLZ8pJounMv3vzk2GvWFDqne8vs6AnTRZVwK5EeAD7K6ujTAzwVzNPQ8Ez2XDazNVoX2HDpZ7BY", + "xprvA7icF82QxB8aNHcfqUQUvpYMaRTKSm2Ly5qi4qMcsqTfcRHR1mgs941jgk1NufCdzz5GekNbMu5ny5CBbmydD7SRtFvQZ6UU46yDUAurVEU", + "xprvA7icF82QxB8aPNXLbLsVuYr9W4DR9zDE6RJc6rWqUQmW6ymW7ZSgtyqBBKzh13TJY1eg9nSX8qho9kGh7M11wZhrwZTuxdB4xm9nFpUJyeA", + "xprvA7icF82QxB8aRo1sxU2h4CRhzB9ehC8R1u3BhcePdr8miKZmpHRSN76AfCPNMx7gVuTFmdQjxQCnd9TC96S9KWPrztsQLQ7X5KyXSJhG6EY" + ], + "txHash": "97ed1e1c73d7578987b9f85be8568028d68028eab4b7fe570821bcd529db91f1", + "hex": "0200000002f191db29d5bc210857feb7b4ea2880d6288056e85bf8b9878957d7731c1eed97000000004a00483045022100ae0442907471df30176d834387a5898abf6743bbe341295ce2cc0f7d8c572daf0220752125f1c6298384ec346463ad226556e65fef8cafaa0291fa7fa93bd1eafcd841fffffffff191db29d5bc210857feb7b4ea2880d6288056e85bf8b9878957d7731c1eed970100000049004730440220049940ca13fd92e9560f13229d987db47d34e9ba08fc5911536331f951767d8302207322da684129f48a29110c90ba0c852cd7df0180a8022b5608b1afb39d538d7941ffffffff0210550000000000004751210215aec25e5447c77a8346d99f96da5f6f9590f5fe1d113f5312fd9fa1e2fd93702103fa11d7e027d59bd4894fbbbfa8d3da86ad132fd9f496ffaa78cbd0c0e5597a7452ae105500000000000047512102ea9e02bc7052f6ffad02788fb1dd3355de79478445d1829bb105a7ed91890d4e210373097499d6ebfc5fe6187679e7f85252717b86137a0965347e389d98827d832252ae00000000", + "amount": 44108 + }, + { + "xprivs": [ + "xprvA7icF82QxB8aKkFq4h9cpmLJBqH1GGP2MLZ8pJounMv3vzk2GvWFDqne8vs6AnTRZVwK5EeAD7K6ujTAzwVzNPQ8Ez2XDazNVoX2HDpZ7BY", + "xprvA7icF82QxB8aNHcfqUQUvpYMaRTKSm2Ly5qi4qMcsqTfcRHR1mgs941jgk1NufCdzz5GekNbMu5ny5CBbmydD7SRtFvQZ6UU46yDUAurVEU", + "xprvA7icF82QxB8aPNXLbLsVuYr9W4DR9zDE6RJc6rWqUQmW6ymW7ZSgtyqBBKzh13TJY1eg9nSX8qho9kGh7M11wZhrwZTuxdB4xm9nFpUJyeA", + "xprvA7icF82QxB8aRo1sxU2h4CRhzB9ehC8R1u3BhcePdr8miKZmpHRSN76AfCPNMx7gVuTFmdQjxQCnd9TC96S9KWPrztsQLQ7X5KyXSJhG6EY", + "xprvA7icF82QxB8aU6ZMaoR5MkaGAnq9PBLow93o4nEBEzUm8Kt5kKUmGdQ3wW54MDQs9Vd8R8cxEAfqpwnmyQ8fXyqA8cp66ZPbjqFnQdNfD3A", + "xprvA7icF82QxB8aYANEgoCt9nuLYp2UM2TdxC9ozSTgEuGiJtruwe9fDXpGC2iRKnq7VmT1mLE6jKfKVBkDXTjbL45rLuecH78PpFRsDwidH3T", + "xprvA7icF82QxB8aZHgQaTE4idmmgkVMDBvmQFK4FMiRSLVbo1RuATNAsfHGGrh6zHBckKF34onhw9PUHn6uphcapSL8tDWPqP5nxMQJjitM2un", + "xprvA7icF82QxB8acaHzRxbstodMQMDaFfEvU2aY9uGiSaKYeTbgdHYQpHwJucQP11yo2e9k1UgS1YLzW9Vdx5UaqmZdF5oW53ApVSy5RFxHEQx" + ], + "txHash": "b24d8ee0a2a9910230f884c483aeee3daa1f9cc3130b26477f7705d3c22cfbe3", + "hex": "0200000002e3fb2cc2d305777f47260b13c39c1faa3deeae83c484f8300291a9a2e08e4db2000000004a00483045022100c91481b92cab1c71768667b17cf78763087ab77ccaf00adce6c04b3a686754280220146259289a49231259336d93fe00b60822c6b3bdbefb3ccc4eb7ed7a270b403541ffffffffe3fb2cc2d305777f47260b13c39c1faa3deeae83c484f8300291a9a2e08e4db2010000004a00483045022100f543ff0ea644a57e53f8298584bca7237d59fbe22ec053ea3cde3712a7d17a0f022068f57ccb0550fbc974fe53ae3bd6d9e4ea273537e642cb5453f36ccdd0d1c3e741ffffffff02fa53000000000000475121026282c31e790d31a9749f802a25521c6fddd3a817c2a01bebd2a686f212a490412103015d0deaca4e1ac92eb7e9da839d8c2f001c9b984c36b86d7529405dc406d49952aefa530000000000004751210225de384fe688ffdf18426c790369976bcb717ef80627740117067a513dfd072e21023e2202f82e19bcc6830e1bf658a3656680bc8b6c212e866137a8ffffc9e1a26f52ae00000000", + "amount": 43552 + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7kqL9HPxnBAJtzwzRmUq5qYAgA37BWw2PCFn3Tr56qikGoMpXxdPJ8vD7JokXN4rmLuygZMQ5nRTFUSodZtsD735UTDn99ff", + "tprv8jwG6YA1fGe7phE5EFsL5bSMK2EG4UvxHZb69tuajqGqmUas2R4JCA3G9gHeHGt8w71MVDEH7G1pYnqE2GhECtRFDYWpEovnnnnqdRhvS5e" + ], + "txHash": "a0367edacec483e77e2401ed73e035d4fe65b8c456b66175280701cd486af271", + "hex": "020000000271f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0000000006a47304402207ff1867ad6e9b7dc7a1d3968113e20965c0ff77ad739e12b17e5dce7fe76e57502202e656dc0f57c3e7884228fe6c0f6161de74842fe2dc4b40296d0e49e118c778a412103206d484ae9751634d0a059acfbee14de2967f9da04159ac8fdd6fa8f1cb486f7ffffffff71f26a48cd0107287561b656c4b865fed435e073ed01247ee783c4ceda7e36a0010000006b483045022100b9115cf9024e5d61a17e6002fb5f9a71c9ecc1f50604c266638bda197579511d0220035b31d1a51a72136c1813e3a90a1e785f94650dc78bd0f0e9f8bbd4422e82d541210318a9bba01a9b343b3469af1ecd27815bdb6797c0eb21b1734a0d0f47290f44faffffffff020ca8f505000000001976a91439890e01e03442581056f0e3f606083cb552f66488ac0ca8f505000000001976a91443802909a4a51575217b553bb5b2670631fdc3cb88ac00000000", + "amounts": [ + 99988225, + 99988225 + ], + "outputs": [ + "bchtest:qqucjrspuq6yykqs2mcw8asxpq7t25hkvslhf9dam4", + "bchtest:qppcq2gf5jj32afp0d2nhddjvurrrlwrevm37d6avt" + ] + } + ] + }, + "fromMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7icF82QxB8aU6ZMaoR5MkaGAnq9PBLow93o4nEBEzUm8Kt5kKUmGdQ3wW54MDQs9Vd8R8cxEAfqpwnmyQ8fXyqA8cp66ZPbjqFnQdNfD3A", + "xprvA7icF82QxB8aYANEgoCt9nuLYp2UM2TdxC9ozSTgEuGiJtruwe9fDXpGC2iRKnq7VmT1mLE6jKfKVBkDXTjbL45rLuecH78PpFRsDwidH3T", + "xprvA7icF82QxB8aZHgQaTE4idmmgkVMDBvmQFK4FMiRSLVbo1RuATNAsfHGGrh6zHBckKF34onhw9PUHn6uphcapSL8tDWPqP5nxMQJjitM2un", + "xprvA7icF82QxB8acaHzRxbstodMQMDaFfEvU2aY9uGiSaKYeTbgdHYQpHwJucQP11yo2e9k1UgS1YLzW9Vdx5UaqmZdF5oW53ApVSy5RFxHEQx", + "xprvA7icF82QxB8afyTitZSe1STeqkJZR7FhCDvtuWj8TQbKSJ1zTa33J8eDNNr6C64K11uqfoXtxvSbpZRK6zcRHwfduotzr2Xkrtv15gL2tuF", + "xprvA7icF82QxB8agTiBFt1v93qkgVEw9WKrsSd3JNFsXwTwHXSkeqTEHwt8abErLRMzNgSZpRGmarNWCPK2i9zxs646UA4ucAmGmzDvfKph6N5" + ], + "txHash": "d7e6963e4de6dc2462d8ca646a1a59cc081cd971a46dcdd89dc1c5311febe93a", + "hex": "02000000023ae9eb1f31c5c19dd8cd6da471d91c08cc591a6a64cad86224dce64d3e96e6d7000000004a0048304502210092f065e61a2b53058924a4f7c519330293853946cad1da7a39f829bc7c5dd98b02202c2cae05b369858f85ea1fe4f79b13c68cde7034a282b57f203b361a372ac16441ffffffff3ae9eb1f31c5c19dd8cd6da471d91c08cc591a6a64cad86224dce64d3e96e6d701000000490047304402205d2c75bc692c94eee9eacc5b357774ebe493b788ad6495669e6cd2f2092864e3022020c92a82245f05e46f32038ca4ce0a27791525f41d5e1b2ecc9b23da1ea9c3a841ffffffff01c8a500000000000047512103a955ad3dae2078c2693ea07768add0ddc7c4e9720a09228b50801b3d5a973cd821034f1aa7c5bf81cb52aa795a24f1d6bedd81601710dd7691ddeeeb293a29929c0152ae00000000", + "amount": 42996 + } + ], + "testnet": [ + { + "xprivs": [ + "tprv8jwG6YA1fGe7r2bbsoGrUt7gSo8auZgv6aSaS59FVjZjRiJkK1SYqPGY3vr9gMEXvfKNzRNjW6VjMRpt3GVwXXF6gT8SgNfdSMarW2mU5JC", + "tprv8jwG6YA1fGe7u7wh93Si5YXPWAm6mZD4MMqEmdPiYzgWUCC3BDJ6Sg8XR14Rw4QmyBFu5fSF7ksjDU3Xk94KAjSY6P3f372X5uk9T3iSugg" + ], + "txHash": "812391dbaf4dc6b39dcbbb85a613a183aea48fb994212449bdbd4dd1b74b3518", + "hex": "020000000218354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381000000006a473044022058c446eec373bb5cae046e12a57cbf5f0b85f7c5a022b88ca17e77f72c2f8d7b022049cfdcb56bf6291db71c5fcbe82812f1e1a98c5e083c405b855b9532eb055f85412103dbd4cae70953a0c34550d59120bc8aa6ed6c01adf833c1d9d3118247f6175084ffffffff18354bb7d14dbdbd49242194b98fa4ae83a113a685bbcb9db3c64dafdb912381010000006a473044022045df2a8ccca97dedeb22bf74fc890bc6a2d2998147621b78a208938bf43a2fc4022077b4b5b21a04d27629535e7a44d3e96bf6d10bf84a20d68c1fccdaca41eb5fc8412103b744479db3b9b26ae110236568fdaff6637bb0e7a874154bf3060468fc5de09dffffffff012c3ceb0b000000001976a91456f61499e2617482f2a63e8aceae5e8cc4c16b5a88ac00000000", + "amounts": [ + 99985420, + 99985420 + ], + "outputs": [ + "bchtest:qpt0v9yeufshfqhj5clg4n4wt6xvfstttgw0zrer9n" + ] + } + ] + } + }, + "p2sh": { + "toOne": { + "mainnet": [ + { + "xprivs": [ + "xprvA7dv9WtZWUueyfvZi74AtwboMU7Ss7MU2QgrYCvpXmfTU2MDF1Z2ZV2UZEGegCodNLHbWXYqNfBzGHtQc5UhQo28YmBjmttvaF4PJfXDhcP", + "xprvA7dv9WtZWUuf2ZzCn3oPzz18QG7Kyjc6gm9TAdyZsg1ETNeVZCqxqUWfmQyMjU3ctDWXLLZSu4Kz1qAwZkg1wVDLtrcktm64B2heM6bzF6c" + ], + "txHash": "7bc71120ae9d20a4bb5dd87c1cdb5917bbe0afd2212598bbf65270cc46e1bd3e", + "hex": "02000000013ebde146cc7052f6bb982521d2afe0bb1759db1c7cd85dbba4209dae2011c77b000000008548304502210083302bc178b40202458fca75ade0cec75f147b47be922e7f0e9f34624f6cc89702200da9bb82597308d9409bea96617ca89c2874a2335ad4bac1c52aa2546676c1b8412103c3b569e8d0d754bd2827c7f0baff6e73a62120a9deedcc91e5e7498eb94d1e751976a9149dc673cfd7df70c2ef649dbae945af14a87e091e88acffffffff01c41b00000000000017a914ed9bf38611456a0408e405b092c6c4d2b59250d18700000000", + "amount": 7448 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUuf2ZzCn3oPzz18QG7Kyjc6gm9TAdyZsg1ETNeVZCqxqUWfmQyMjU3ctDWXLLZSu4Kz1qAwZkg1wVDLtrcktm64B2heM6bzF6c", + "xprvA7dv9WtZWUuf5PLxqBDQYpNgtaErnTuTsont3HcqDiAQ8XtbQ4PEGPWESQsAruF14VaHDzL6TAsHwkd5nbBzRFonxArPB52KPskq8nc3ahh" + ], + "txHash": "72c76ad87daa8d33f22e74d863c1e24db19d47910cc5c4086362f5d07cd40efa", + "hex": "0200000001fa0ed47cd0f5626308c4c50c91479db14de2c163d8742ef2338daa7dd86ac772000000008447304402203ed9b12b2703dec77f833551d4e488e3a028412ad248098d4d986d76e036ce5c0220234311c6915aea80b7410de47f709d5bf8229dc4828184465bbee072f39b6dbf412103272b45e8b1e214426e71d80565b543e595f1e543e10a48b899f15924db511c961976a914c804739cd583d82190ca405879fd848b96b902d688acffffffff01701a00000000000017a91481f10b18641079f8a8d4820e92aa11e4a1b0c8108700000000", + "amount": 7108 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUuf5PLxqBDQYpNgtaErnTuTsont3HcqDiAQ8XtbQ4PEGPWESQsAruF14VaHDzL6TAsHwkd5nbBzRFonxArPB52KPskq8nc3ahh", + "xprvA7dv9WtZWUuf7Ge5XoVsD7weQpbWAYABwsZgPVeAngYMZ65NL4PtNkfkoPZXsHDmgRtgVMt1PNudxXpp3yR5JsjF927JmXU8MM63wdYVgcF" + ], + "txHash": "1ce552697aff6e6fdb6582549c1e7c0a37d5d3f0d40ba9134d47cea655e1c808", + "hex": "020000000108c8e155a6ce474d13a90bd4f0d3d5370a7c1e9c548265db6f6eff7a6952e51c0000000085483045022100925f30f13522e21a16b961dba037a14902493fae7b33e9d777f9e0f1f105ad0802207da68a70375978d73bc26bc54e6bf18e89094500f60fc5e49b2e1ba0ca8c4451412103ab372149fa97c0c7bcf3a061adc9c20b12102a7351ea4e47ab84a5a8831e9eff1976a914b2e41262cba8ddb3552c6b3809a565d487b8d3f988acffffffff011c1900000000000017a914aa63a0fe0f29d569057a76694db0a6821c6bfcc78700000000", + "amount": 6768 + } + ], + "testnet": [ + ] + }, + "toMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7dv9WtZWUuf7Ge5XoVsD7weQpbWAYABwsZgPVeAngYMZ65NL4PtNkfkoPZXsHDmgRtgVMt1PNudxXpp3yR5JsjF927JmXU8MM63wdYVgcF", + "xprvA7dv9WtZWUufAULsXSXDcRjSWWLYA7M4eYeNA8Sq7pY9zM2EfhQdebH9wkRTPB7Ck2Gt7mATAtw3sY5xLQR4FWa3JUKZ69HUiQVhQYRPkx6", + "xprvA7dv9WtZWUufDNpC9FMditnmYFEurBdcnF7Du5zmDendz2sX41kwVcKGPiTCd8BGKsScqc8ihn1eDzL2PxeeBrswUZkJYuEomZyjtK7eobd" + ], + "txHash": "bb413b3cfca8cfe585b3daa71d0f3f7edc09c67cb90b26557383a3af13e68e74", + "hex": "0200000001748ee613afa3837355260bb97cc609dc7e3f0f1da7dab385e5cfa8fc3c3b41bb0000000084473044022069c8c13324b65f6f1be0eca4151540a3232165e628224bf37242ac99e66a072602204cd6bab5f16fd5279fe775fb6ac233f5194b9eec19009654da11e4ea0955c35741210292eec030b73e99e7e82d7a0d645396ec83d6c88d81abf36e5d15ee701d9173351976a914b7dcea1d4e276084bd0adbc4a5a98f6198b8427188acffffffff02890b00000000000017a91456129534099d8302a627d7bb6765c64a979aad6987890b00000000000017a914d329cc5c6d4f20df6d1a914d12c39f5b4b4fa3d68700000000", + "amount": 6428 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUufFNMZ1cTb5GUJR2VjYYpXVHzZuUeR4MRTLwhHcfnraxaT4ZVzUpHJNBj9jpBhS3EbwyTJjbBJzfB9kNVRTJfUNgeHChdmfK8", + "xprvA7dv9WtZWUufJkpRoWwYxQUdHff4hU36vmmzqcjDStZToqW6bEmwYxxVe3zYUHgpzb6vzpPA8ZBXiVRpURZ8CfSEyKsRbVLJfi4hmFsu5Na", + "xprvA7dv9WtZWUufKxwwcgnK4iau61iv4uHXyzh35mj4MtgHhJanf9v8jdyHJX9g18c6fBq2q3oej3VuLWw2U3Yqbbgu9Kk8n1QvBmjMMjVZaYp" + ], + "txHash": "2ad35ff5578cf071a052edd400b0fffbdfd2e4c7ae4f73c13e37963a323ebff5", + "hex": "0200000001f5bf3e323a96373ec1734faec7e4d2dffbffb000d4ed52a071f08c57f55fd32a000000008447304402200c84aa81fa914407579e3d256c0bf140d2fa6227d26ac786f714367476b5ca7b0220443094fa3e2d1db876633038f3f8fedd76eeca31911a6d8c933481f489b58c1241210337f2d9a6325b69b9a05ed801db10c0961ad2c686a59d451cddc249bf32d56e731976a914cb24f5a86aa09a3308559c236218796ce8765e8b88acffffffff027f0900000000000017a91435cc5fc92aecbd7ef76cf59c009f8c6e7a22b071877f0900000000000017a91460279d6c5f2b6a31015b2bdcadca7d356e41acb68700000000", + "amount": 5384 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUufPjGHLHUcP9qciXgHARRxFU6PXq3mPHzvy6UrawjAiVxSPq9swSa55bBfNtrQ2J1DgWMb5cQ8kZWuMBBDToe7viNegQuU5pe", + "xprvA7dv9WtZWUufRvtcP9BudkG5kwZve9AdrhaaXEtzypB272mgeWMVuVSLCj25JP7f6MNZ6L69FPUA9eZ2198Tr6CnzTKmgCTQirRFd6Df7Mt", + "xprvA7dv9WtZWUufTuJUk4QuBc3BrukGcynPi34FxfP7EkPGtxtUhUoHqbJotzBRUL5Az4S9PWKHC8P1kfFL7ADcMPakg6LEEPkQoiKJo4Ujvax" + ], + "txHash": "53ec1b2fe1878b2ce2b0023b66aed995e54300afe46c136a5c8ece9a2f8efc98", + "hex": "020000000198fc8e2f9ace8e5c6a136ce4af0043e595d9ae663b02b0e22c8b87e12f1bec530000000085483045022100d82ad776722edf112302b87b4150481aa53c106af72eede0ac3e486eb97e924002201b2b76df7f75303d55206ef600e491bbc53fbdc3d2be160f44a04eebfab3532f412103ee4e226c9d392956df05b4d08f4fdacf0cc9d7415c082208f855d26d00b7bbbb1976a9140efeacbd4b0fcd20d9cb235d4ee4fd9b4596714088acffffffff02750700000000000017a914eab643edbf9bf909051dc7d551d9e7ceaf28a8a387750700000000000017a9145ff3a70370687f1ed8974948cba2fc6dd6a8c8a08700000000", + "amount": 4340 + } + ], + "testnet": [ + ] + }, + "manyToMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7dv9WtZWUufaKKHvEizGdFFwmJx77MnXKWKky1xPk9co8cXHVoX23z4GQoRVXrTtDo1boAL3iCheWfUTDhiFjP9YFK8XXXdAsLUiutP4wZ", + "xprvA7dv9WtZWUufcxcMvNY8NLXZPUAMQiVCssTvSbzsnUSGgnzLA2cKWpwUzbZCPiVxoq6GCb4nX8wz3zyVuCQAr1sHAFVmYZHNcxjwRWQKqeR", + "xprvA7dv9WtZWUufemo85spMEDzLB6F5sTWPkuS9ttLSZKCLdYuZ91FTehLrC2BJGoWnRCWCjL5XMXFTC8FPu3hT2DBTFywPiVZr5ck68hXk7qb", + "xprvA7dv9WtZWUufhgHXVPtAHiE6tTWLRUzvkkqcoBNWsCi5BwiNLWQopNxCAT1W8S2ZgwbBQQkiXdFYBjt9VEQbXa1PLRJhsWta6vH8n1ujqDX" + ], + "txHash": "4724d474245d3212bbbad3d0328888cfdac3686897b32c9c15e4e11db61e97df", + "hex": "0200000002df971eb61de1e4159c2cb3976868c3dacf888832d0d3babb12325d2474d424470000000085483045022100cbec22838cc73e406eb8ed018552e855794d14ebd420b7b0952e090ecce29dc6022057808551ea731b200c7a4adadc969b6796cc23e87d0baef03668e1ea6c01f3c44121035b4dfa3b5ab1c4507524642ced2ab4b3888b1919efe749b5e782fdc1e61f11701976a914bf6736a8b5811659ab60e62dc604ab4c2dd28c3888acffffffffdf971eb61de1e4159c2cb3976868c3dacf888832d0d3babb12325d2474d42447010000008447304402204a572dd52e5778dfb1f0495428efc6d20297c4ca2d71202fc524b9e34f97eaa5022063166f3a7d5b7e5e6e55a5fb166894c7986159feefaf3e220217c2f6dab5539f412103b79a0d7f5fb050e9f8659cde59e07f120a86a435cc475ec4c04fc83c1dd622411976a914ff20135f2788e9d0478d105a5229820b4f5e0a1388acffffffff029f0300000000000017a9143fd512c8cff104e03d53ee4d36aa4733ffe22593879f0300000000000017a914a29b4db41fc8fe60936b9c17ab9972972c4069a38700000000", + "amount": 2774 + } + ], + "testnet": [ + ] + }, + "fromMany": { + "mainnet": [ + { + "xprivs": [ + "xprvA7dv9WtZWUufAULsXSXDcRjSWWLYA7M4eYeNA8Sq7pY9zM2EfhQdebH9wkRTPB7Ck2Gt7mATAtw3sY5xLQR4FWa3JUKZ69HUiQVhQYRPkx6", + "xprvA7dv9WtZWUufDNpC9FMditnmYFEurBdcnF7Du5zmDendz2sX41kwVcKGPiTCd8BGKsScqc8ihn1eDzL2PxeeBrswUZkJYuEomZyjtK7eobd", + "xprvA7dv9WtZWUufFNMZ1cTb5GUJR2VjYYpXVHzZuUeR4MRTLwhHcfnraxaT4ZVzUpHJNBj9jpBhS3EbwyTJjbBJzfB9kNVRTJfUNgeHChdmfK8" + ], + "txHash": "8d1967579ace57b338e3bc6f871153dddc4812785ee46b93750df1ddc1a85a28", + "hex": "0200000002285aa8c1ddf10d75936be45e781248dcdd5311876fbce338b357ce9a5767198d0000000085483045022100dfbe5caac700403c20ea38c31c003c2f157ff6b7eb27582d7ad9eb9e3830c40302206b35abaec471b08f0662bf176d9d9bee0db42b7cff0a720feab9a1b47809bdc44121020e8914c5c4f6887b7e0c8c6ecb1e789d3e10c81c34c232e7170fb32d42af198b1976a914b78f29ed50dbe76507731385bf5b1d664e529e1e88acffffffff285aa8c1ddf10d75936be45e781248dcdd5311876fbce338b357ce9a5767198d0100000085483045022100d7c38fc11e88c047e4fa7862f59f11299aed36665db313c22af5c3a1b13d172302202b561115217d4f5aee3847391b557fe60787b1841a29e4423373f5a559fb2a264121037ba623e827d1aed4f4a133f67007d8194efba8e26458b6ff0ef9a2f322dc9a7b1976a914fbe924663d64eb0d38e38f259a87f102c8de71eb88acffffffff01081500000000000017a9148a963c8c9dd253133f7ea4c52c9a441b253b79508700000000", + "amount": 5906 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUufJkpRoWwYxQUdHff4hU36vmmzqcjDStZToqW6bEmwYxxVe3zYUHgpzb6vzpPA8ZBXiVRpURZ8CfSEyKsRbVLJfi4hmFsu5Na", + "xprvA7dv9WtZWUufKxwwcgnK4iau61iv4uHXyzh35mj4MtgHhJanf9v8jdyHJX9g18c6fBq2q3oej3VuLWw2U3Yqbbgu9Kk8n1QvBmjMMjVZaYp", + "xprvA7dv9WtZWUufPjGHLHUcP9qciXgHARRxFU6PXq3mPHzvy6UrawjAiVxSPq9swSa55bBfNtrQ2J1DgWMb5cQ8kZWuMBBDToe7viNegQuU5pe" + ], + "txHash": "1cf4ef45643ffa39ff76d9606c2f4232979bd33dd176219810daf4037b50ef96", + "hex": "020000000296ef507b03f4da10982176d13dd39b9732422f6c60d976ff39fa3f6445eff41c000000008447304402206c53baf1f00740ff5812445b93451899b6a8555c2a59ea2d92f2073e5154075f022055b23916431054117edf91f710fb85ec2507204160dbf6923b2a2603dc915e014121020b6bf65414542f3163a762c9377ec8eb9f3299df7856bec2183c2aea223bf8e71976a91483c1d414a37c35fdd460db6d5541a73a40a757c588acffffffff96ef507b03f4da10982176d13dd39b9732422f6c60d976ff39fa3f6445eff41c0100000084473044022021f6ada1a2be396d756deea9ae692728560cdd73c4ba4b43e3fcdf2e1e077c6102205c3fdc6e9a942ec870519ad866e422e16a2eca103c852a7bdcdfef981b04fe4e41210348c1f2d759cf8cf7a48f215571e8314084dee16c3de6a2ef4e0ac871522902221976a91496628453537fc45037f129e7b3dbeb24ecd7c23b88acffffffff01f41000000000000017a9143be36e17648888cfdbb23b9b8999907c7d5ca7698700000000", + "amount": 4862 + }, + { + "xprivs": [ + "xprvA7dv9WtZWUufRvtcP9BudkG5kwZve9AdrhaaXEtzypB272mgeWMVuVSLCj25JP7f6MNZ6L69FPUA9eZ2198Tr6CnzTKmgCTQirRFd6Df7Mt", + "xprvA7dv9WtZWUufTuJUk4QuBc3BrukGcynPi34FxfP7EkPGtxtUhUoHqbJotzBRUL5Az4S9PWKHC8P1kfFL7ADcMPakg6LEEPkQoiKJo4Ujvax", + "xprvA7dv9WtZWUufYLyZ3BDA1LZStj4X3U1qJCcjXKfZ1aDLSbMD1FLfNHUKWDBSoJBJ1sDDfNxLxyc15BFAMnhn4xxQdYazsMQzVNpKwSSzMj6" + ], + "txHash": "738b53ea36cf3522eeaf71829e4f19205906a234b3a270aa51fe85b60e063e5b", + "hex": "02000000025b3e060eb685fe51aa70a2b334a2065920194f9e8271afee2235cf36ea538b730000000085483045022100e2c1a1f3508d210341aa861e5eba139ab9d3efc73cc8437f48b9dd18ec2c5f65022048be06e51313be79fe59cea35be5f95bd14c3e1466b3082b3562426195b44e3f41210343f2ab1f0262fef438dacf29ae8e99abc2af9b511c7be68b240dc7f2dbd700491976a91483c27b42776b2846ce1b6608d51490f75b49e20688acffffffff5b3e060eb685fe51aa70a2b334a2065920194f9e8271afee2235cf36ea538b7301000000844730440220611eb4452890571e31ed67271f5d83ff8fb80f36b2dceedaf6667a6cdc9c52c002204ae7a4bf542f65ba85c1f5cd03df5ee9d66e103c7ce557556c29a326d92cacf641210281f9cf01c22eae8148202af9c87712efb718997bbcbe93e8a701f7e4b0a496fc1976a91475bee9b0f16e6fc3ce1330279332d6059daa9c4388acffffffff01e00c00000000000017a914e2acfa2d73c126f1a14b543b53bfbcc8fffb128b8700000000", + "amount": 3818 + } + ], + "testnet": [ + ] + } + } + }, + "nulldata": { + "mainnet": [ + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "021eb6dcbfa33979ccb45fcf59c1c9e3805505d50418c2108c9d83fdaf6393f3", + "data": "Satoshi Nakamoto", + "amount": 8821, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "0200000001f39363affd839d8c10c21804d5055580e3c9c159cf5fb4cc7939a3bfdcb61e02000000006b483045022100e641c498c35bb23ec83d548d8686d21eebbbc963ad005a45f08d15556d5e79450220698b6a9e5f1046600adbc050b4bb337dc599449497ef6b6a8a338d7ed7433d7341210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e01839277700ffffffff022d210000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac0000000000000000126a105361746f736869204e616b616d6f746f00000000" + }, + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "24c196f28b5ad1c08c2b68e4dd3294fa307354267f028600c3001a0dea6e175f", + "data": "BCHForEveryone", + "amount": 8493, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "02000000015f176eea0d1a00c30086027f26547330fa9432dde4682b8cc0d15a8bf296c124000000006b483045022100a5ad52a88d23ac3a6f6459b3c15f55cc01a315be006341a3e0fbcaedf3a4872a022012cdfcba30eac38a64472c7e24bd32ef7d3800ff6d3093d73652ebd1ef31bfcf41210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e01839277700ffffffff02e51f0000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac0000000000000000106a0e424348466f7245766572796f6e6500000000" + }, + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "2a2dc0c27ff88ce2dfc6c4ae6bdbfc00d4947dc0144b3b0f9518f444e6c5ca11", + "data": "chancellor on brink of second bailout for banks", + "amount": 8165, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "020000000111cac5e644f418950f3b4b14c07d94d400fcdb6baec4c6dfe28cf87fc2c02d2a000000006a4730440220590a9cc0775a3ab7c652f8728a93d33142051c6a265f452eda7bafc257331efd02202b45d9d70aa101d5d037fdffaaa442203fb7278eff5c6b34c2e7aa48e29ee8a041210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e01839277700ffffffff029d1e0000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac0000000000000000316a2f6368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b7300000000" + }, + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "b2c1a097f569cc3451b0377cd88197f81f0099c9cb58c05d8ecb96d69b69c24b", + "data": "Tokenize all the things", + "amount": 7837, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "02000000014bc2699bd696cb8e5dc058cbc999001ff89781d87c37b05134cc69f597a0c1b2000000006a47304402201cfade3a7ecb44a7ed97c3aeb261ed11346e12de34573b380cc0ed946eff66ec022078b30335b133e17fcaa3821da1f7cec4d93f71cb0546768db2e14ee71c5805c141210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e01839277700ffffffff02551d0000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac0000000000000000196a17546f6b656e697a6520616c6c20746865207468696e677300000000" + }, + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "57cf86c76203485270b58ac20913728060d4ce2f423bde092d1cfb42c3688d96", + "data": "Bitcoin Cash", + "amount": 7509, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "0200000001968d68c342fb1c2d09de3b422fced46080721309c28ab57052480362c786cf57000000006a47304402207d46dce2eacadb4f1100ac6a4b3656e134a72535d5c37977d41e2f346e2b117002200a50deb653dffbbb69c4eda35138e1cabb2ba7c90bb984aa038d53676e44c00141210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e01839277700ffffffff020d1c0000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac00000000000000000e6a0c426974636f696e204361736800000000" + } + ], + "testnet": [ + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "126d9e84a51b64ca1d88b47f42fdada0ac7fee543e49feffef01114f9f61111e", + "data": "Satoshi Nakamoto", + "amount": 199999344, + "output": "bchtest:qzcvep37jmkjfyhsshepg9z8myww84g67ucq8qt6qh", + "script": "OP_RETURN 5361746f736869204e616b616d6f746f", + "hex": "02000000011e11619f4f1101effffe493e54ee7faca0adfd427fb4881dca641ba5849e6d12000000006a4730440220094fcec3637c1b7473818198558b5b6dcee0026034119927b12c11943d7d894d0220489ed60a6b01d7d4c6146ae36d6dc81a329391d6796c2b851b99ac934f96b4af4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0228beeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000126a105361746f736869204e616b616d6f746f00000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "0655231b5a008ef23bfaa16ec7b08ae178e81f033834babce84fd4114ea2b226", + "data": "BCHForEveryone", + "amount": 199999016, + "output": "bchtest:qzcvep37jmkjfyhsshepg9z8myww84g67ucq8qt6qh", + "script": "OP_RETURN 424348466f7245766572796f6e65", + "hex": "020000000126b2a24e11d44fe8bcba3438031fe878e18ab0c76ea1fa3bf28e005a1b235506000000006a47304402204d972c018376836ba0b40a0f93b5d93beb2ce9be24d1eff12a9c44eb33e6f2f3022062cf5abf201312f720d8e22eb48ca021da0364edd755f6991a7a6e90f24cd3804121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff02e0bceb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000106a0e424348466f7245766572796f6e6500000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "6b95aa8764096fb531ae235acbd281935fc7f4fc055992b7060fb36ff3cce8d6", + "data": "chancellor on brink of second bailout for banks", + "amount": 199998688, + "output": "bchtest:qzcvep37jmkjfyhsshepg9z8myww84g67ucq8qt6qh", + "script": "OP_RETURN 6368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", + "hex": "0200000001d6e8ccf36fb30f06b7925905fcf4c75f9381d2cb5a23ae31b56f096487aa956b000000006a473044022006b7dd030c3eb56ba6993e7f770e7ddb8732132aede642e61af98669f2500c62022029308c95c25a56df4aa7a3eead08d16470d8b827749d747811e2b0581273b28e4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0298bbeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000316a2f6368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b7300000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "86c1fc1dbbdcb9d42ef174f31a79520547f9da1beec4aed9184fc62ef0ecce9e", + "data": "Tokenize all the things", + "amount": 199998360, + "output": "bchtest:qzcvep37jmkjfyhsshepg9z8myww84g67ucq8qt6qh", + "script": "OP_RETURN 546f6b656e697a6520616c6c20746865207468696e6773", + "hex": "02000000019eceecf02ec64f18d9aec4ee1bdaf9470552791af374f12ed4b9dcbb1dfcc186000000006b483045022100ae630f40cb0d508115f723df49ea2b5c49f141e5623ad5030a7221109db7b86f02201aa411112dcc0a558c342ee539cfc6d9c7e49c1542957bbf52dbe5421d6de7554121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0250baeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000196a17546f6b656e697a6520616c6c20746865207468696e677300000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "dc8eb8261dd98b248bf738e5a10f10e07465d122e29f04b5e1096fe9debc9efa", + "data": "Bitcoin Cash", + "amount": 199998032, + "output": "bchtest:qzcvep37jmkjfyhsshepg9z8myww84g67ucq8qt6qh", + "script": "OP_RETURN 426974636f696e2043617368", + "hex": "0200000001fa9ebcdee96f09e1b5049fe222d16574e0100fa1e538f78b248bd91d26b88edc000000006a4730440220337ccd2d539e25c7b12388e7e416c8e90f4d416b502d0086e83d0ec809f473f2022002143e840c1ae49dc19fe5a5b291f3466c096e5105a26a26e11159916983d3034121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0208b9eb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac00000000000000000e6a0c426974636f696e204361736800000000" + } + ], + "regtest": [ + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "126d9e84a51b64ca1d88b47f42fdada0ac7fee543e49feffef01114f9f61111e", + "data": "Satoshi Nakamoto", + "amount": 199999344, + "output": "bchreg:qzcvep37jmkjfyhsshepg9z8myww84g67uzu3pgfr3", + "script": "OP_RETURN 5361746f736869204e616b616d6f746f", + "hex": "02000000011e11619f4f1101effffe493e54ee7faca0adfd427fb4881dca641ba5849e6d12000000006a4730440220094fcec3637c1b7473818198558b5b6dcee0026034119927b12c11943d7d894d0220489ed60a6b01d7d4c6146ae36d6dc81a329391d6796c2b851b99ac934f96b4af4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0228beeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000126a105361746f736869204e616b616d6f746f00000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "0655231b5a008ef23bfaa16ec7b08ae178e81f033834babce84fd4114ea2b226", + "data": "BCHForEveryone", + "amount": 199999016, + "output": "bchreg:qzcvep37jmkjfyhsshepg9z8myww84g67uzu3pgfr3", + "script": "OP_RETURN 424348466f7245766572796f6e65", + "hex": "020000000126b2a24e11d44fe8bcba3438031fe878e18ab0c76ea1fa3bf28e005a1b235506000000006a47304402204d972c018376836ba0b40a0f93b5d93beb2ce9be24d1eff12a9c44eb33e6f2f3022062cf5abf201312f720d8e22eb48ca021da0364edd755f6991a7a6e90f24cd3804121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff02e0bceb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000106a0e424348466f7245766572796f6e6500000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "6b95aa8764096fb531ae235acbd281935fc7f4fc055992b7060fb36ff3cce8d6", + "data": "chancellor on brink of second bailout for banks", + "amount": 199998688, + "output": "bchreg:qzcvep37jmkjfyhsshepg9z8myww84g67uzu3pgfr3", + "script": "OP_RETURN 6368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73", + "hex": "0200000001d6e8ccf36fb30f06b7925905fcf4c75f9381d2cb5a23ae31b56f096487aa956b000000006a473044022006b7dd030c3eb56ba6993e7f770e7ddb8732132aede642e61af98669f2500c62022029308c95c25a56df4aa7a3eead08d16470d8b827749d747811e2b0581273b28e4121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0298bbeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000316a2f6368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b7300000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "86c1fc1dbbdcb9d42ef174f31a79520547f9da1beec4aed9184fc62ef0ecce9e", + "data": "Tokenize all the things", + "amount": 199998360, + "output": "bchreg:qzcvep37jmkjfyhsshepg9z8myww84g67uzu3pgfr3", + "script": "OP_RETURN 546f6b656e697a6520616c6c20746865207468696e6773", + "hex": "02000000019eceecf02ec64f18d9aec4ee1bdaf9470552791af374f12ed4b9dcbb1dfcc186000000006b483045022100ae630f40cb0d508115f723df49ea2b5c49f141e5623ad5030a7221109db7b86f02201aa411112dcc0a558c342ee539cfc6d9c7e49c1542957bbf52dbe5421d6de7554121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0250baeb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac0000000000000000196a17546f6b656e697a6520616c6c20746865207468696e677300000000" + }, + { + "xpriv": "tprv8kNvR1z7HT4RJ72z5HGtpPVVrTMY9uVFJh9sbDw62CBU2iKbCUkqjHUSjpT3drDVEWsqrTJqW4C2EEMnb2RueNo6MWtPdPmRnGdx97XZijS", + "txHash": "dc8eb8261dd98b248bf738e5a10f10e07465d122e29f04b5e1096fe9debc9efa", + "data": "Bitcoin Cash", + "amount": 199998032, + "output": "bchreg:qzcvep37jmkjfyhsshepg9z8myww84g67uzu3pgfr3", + "script": "OP_RETURN 426974636f696e2043617368", + "hex": "0200000001fa9ebcdee96f09e1b5049fe222d16574e0100fa1e538f78b248bd91d26b88edc000000006a4730440220337ccd2d539e25c7b12388e7e416c8e90f4d416b502d0086e83d0ec809f473f2022002143e840c1ae49dc19fe5a5b291f3466c096e5105a26a26e11159916983d3034121026d0446d88c057cdf3b59f4442603609282672c4961c44e738ca48387d911f782ffffffff0208b9eb0b000000001976a914b0cc863e96ed2492f085f2141447d91ce3d51af788ac00000000000000000e6a0c426974636f696e204361736800000000" + } + ] + }, + "bip66": [ + { + "DER": "3044022029db2d5f4e1dcc04e19266cce3cb135865784c62ab653b307f0e0bb744f5c2aa022000a97f5826912cac8b44d9f577a26f169a2f8db781f2ddb7de2bc886e93b6844", + "r": "29db2d5f4e1dcc04e19266cce3cb135865784c62ab653b307f0e0bb744f5c2aa", + "s": "00a97f5826912cac8b44d9f577a26f169a2f8db781f2ddb7de2bc886e93b6844" + }, + { + "DER": "304302201ea1fdff81b3a271659df4aad19bc4ef83def389131a36358fe64b245632e777021f29e164658be9ce810921bf81d6b86694785a79ea1e52dbfa5105148d1f0bc1", + "r": "1ea1fdff81b3a271659df4aad19bc4ef83def389131a36358fe64b245632e777", + "s": "29e164658be9ce810921bf81d6b86694785a79ea1e52dbfa5105148d1f0bc1" + }, + { + "DER": "304402201b19519b38ca1e6813cd25649ad36be8bc6a6f2ad9758089c429acd9ce0b572f02203bf32193c8a3a3de1f847cd6e6eebf43c96df1ffa4d7fe920f8f71708920c65f", + "r": "1b19519b38ca1e6813cd25649ad36be8bc6a6f2ad9758089c429acd9ce0b572f", + "s": "3bf32193c8a3a3de1f847cd6e6eebf43c96df1ffa4d7fe920f8f71708920c65f" + }, + { + "DER": "3044022000c8da1836747d05a6a3d2c395825edce827147d15909e66939a5037d5916e6f022017823c2da62f539d7f8e1e186eaea7a401ab3c077dcfc44aeaf3e13fac99bdbc", + "r": "00c8da1836747d05a6a3d2c395825edce827147d15909e66939a5037d5916e6f", + "s": "17823c2da62f539d7f8e1e186eaea7a401ab3c077dcfc44aeaf3e13fac99bdbc" + }, + { + "DER": "3042021e2ff2609c8dc0392d3731a2c6312841e09c76f10b83e2b52604dc84886dd502200090ac80e787c063618192bc04758e6666d0179c377fb2f3d6105d58000f33ac", + "r": "2ff2609c8dc0392d3731a2c6312841e09c76f10b83e2b52604dc84886dd5", + "s": "0090ac80e787c063618192bc04758e6666d0179c377fb2f3d6105d58000f33ac" + }, + { + "DER": "3041021d00feb1a12c27e5fe261acc64c0923add082573883e0800d8e4080fa9bb02202e7aeb97f4046ea3be60d2896a19c8dc269ab5eb2de968912cd52a076a0a42e9", + "r": "00feb1a12c27e5fe261acc64c0923add082573883e0800d8e4080fa9bb", + "s": "2e7aeb97f4046ea3be60d2896a19c8dc269ab5eb2de968912cd52a076a0a42e9" + }, + { + "DER": "3042021d00feb1a12c27e5fe261acc64c0923add082573883e0800d8e4080fa9bb0221008aeb97f4046ea3be60d2896a19c8dc269ab5eb2de968912cd52a076a0a42e925", + "r": "00feb1a12c27e5fe261acc64c0923add082573883e0800d8e4080fa9bb", + "s": "008aeb97f4046ea3be60d2896a19c8dc269ab5eb2de968912cd52a076a0a42e925" + }, + { + "DER": "303e021d00c1d545da2e4edfbc65e9267d3c0a6fdda41793d0fd945f15acbcf0dd021d009acffda3ca5e7c349c35ba606f0a8f1ec7815b653b51695ca9ee69a6", + "r": "00c1d545da2e4edfbc65e9267d3c0a6fdda41793d0fd945f15acbcf0dd", + "s": "009acffda3ca5e7c349c35ba606f0a8f1ec7815b653b51695ca9ee69a6" + }, + { + "DER": "3006020100020100", + "r": "00", + "s": "00" + } + ], + "bip68": { + "encode": [ + { + "type": "seconds", + "value": 2048, + "result": "0x00400004" + }, + { + "type": "blocks", + "value": 54, + "result": "0x00000036" + }, + { + "type": "blocks", + "value": 200, + "result": "0x000000c8" + } + ], + "decode": [ + { + "type": "seconds", + "value": 33553920, + "result": "0x03ffffff" + + }, + { + "type": "blocks", + "value": 65534, + "result": "0x0100fffe" + } + ] + }, + "locktime": { + "mainnet": [ + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "021eb6dcbfa33979ccb45fcf59c1c9e3805505d50418c2108c9d83fdaf6393f3", + "lockTime": 500000, + "amount": 8821, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "0200000001f39363affd839d8c10c21804d5055580e3c9c159cf5fb4cc7939a3bfdcb61e02000000006a4730440220588a14faadd7f4c38ef63a87578e5484b57415acbbeafe692cae36384c3fde9c02201d6e52c3d7010faaca3d9ca89a1091a63aaf5ebbdbb9d5c822fa62f4380bd87341210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e018392777000100000001b5210000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88ac20a10700" + }, + { + "xpriv": "xprvA3oFxxSXJZ5bcURHc3Mk6bdF8fiqtyruYJkUAZjcAjPdhd4jogpKZNZRhqLMFYu4VyJmcetjDxaW34igaNfGYRvSNZQBdM7VkABsLnNP5Dd", + "txHash": "24c196f28b5ad1c08c2b68e4dd3294fa307354267f028600c3001a0dea6e175f", + "lockTime": 600000, + "amount": 8493, + "output": "bitcoincash:qzvpzzj8yylqy6amtk7xs9pfksdx3dc0nu3k8eqax5", + "hex": "02000000015f176eea0d1a00c30086027f26547330fa9432dde4682b8cc0d15a8bf296c124000000006a473044022050e94b156df6dee0af24593a60f7be5ea29986f8d7ac19d64da6b75dd311a9af022028a07ddd56d3ddc85bf0aaec5bc7b088e1ffdf5f63048d25c5e90a41e21dd51e41210351cedb293c6aef49e3e5fa4bb554a6a4833fc83702eb4e027a09e0183927770001000000016d200000000000001976a91498110a47213e026bbb5dbc681429b41a68b70f9f88acc0270900" + } + ] + } +} diff --git a/test/unit/generating.js b/test/unit/generating.js new file mode 100644 index 0000000..8937467 --- /dev/null +++ b/test/unit/generating.js @@ -0,0 +1,28 @@ +const assert = require("assert") +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const sinon = require("sinon") + +describe("#Generating", () => { + describe("#generateToAddress", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should generate", done => { + const data = [] + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "post").returns(resolved) + + bchjs.Generating.generateToAddress( + 1, + "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) +}) diff --git a/test/unit/hdnode.js b/test/unit/hdnode.js new file mode 100644 index 0000000..44c105f --- /dev/null +++ b/test/unit/hdnode.js @@ -0,0 +1,345 @@ +const fixtures = require("./fixtures/hdnode.json") +const slpFixtures = require("./fixtures/slp/address.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const Buffer = require("safe-buffer").Buffer + +describe("#HDNode", () => { + describe("#fromSeed", () => { + fixtures.fromSeed.forEach(mnemonic => { + it(`should create an HDNode from root seed buffer`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + assert.notEqual(hdNode, null) + }) + }) + }) + + describe("#derive", () => { + fixtures.derive.forEach(derive => { + it(`should derive non hardened child HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derive(hdNode, 0) + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + }) + + describe("#deriveHardened", () => { + fixtures.deriveHardened.forEach(derive => { + it(`should derive hardened child HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.deriveHardened(hdNode, 0) + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + + describe("derive BIP44 $BCH account", () => { + fixtures.deriveBIP44.forEach(derive => { + it(`should derive BIP44 $BCH account`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const purpose = bchjs.HDNode.deriveHardened(hdNode, 44) + const coin = bchjs.HDNode.deriveHardened(purpose, 145) + const childHDNode = bchjs.HDNode.deriveHardened(coin, 0) + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + }) + }) + + describe("#derivePath", () => { + describe("derive non hardened Path", () => { + fixtures.derivePath.forEach(derive => { + it(`should derive non hardened child HDNode from path`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "0") + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + }) + + describe("derive hardened Path", () => { + fixtures.deriveHardenedPath.forEach(derive => { + it(`should derive hardened child HDNode from path`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "0'") + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + }) + + describe("derive BIP44 $BCH account", () => { + fixtures.deriveBIP44.forEach(derive => { + it(`should derive BIP44 $BCH account`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(derive.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "44'/145'/0'") + assert.equal(bchjs.HDNode.toXPub(childHDNode), derive.xpub) + assert.equal(bchjs.HDNode.toXPriv(childHDNode), derive.xpriv) + }) + }) + }) + }) + + describe("#toLegacyAddress", () => { + fixtures.toLegacyAddress.forEach(fixture => { + it(`should get address ${fixture.address} from HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "0") + const addy = bchjs.HDNode.toLegacyAddress(childHDNode) + assert.equal(addy, fixture.address) + }) + }) + }) + + describe("#toCashAddress", () => { + fixtures.toCashAddress.forEach(fixture => { + it(`should get address ${fixture.address} from HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "0") + const addy = bchjs.HDNode.toCashAddress(childHDNode) + assert.equal(addy, fixture.address) + }) + + it(`should get address ${fixture.regtestAddress} from HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const childHDNode = bchjs.HDNode.derivePath(hdNode, "0") + const addr = bchjs.HDNode.toCashAddress(childHDNode, true) + assert.equal(addr, fixture.regtestAddress) + }) + }) + }) + + describe("#toWIF", () => { + fixtures.toWIF.forEach(fixture => { + it(`should get privateKeyWIF ${fixture.privateKeyWIF} from HDNode`, () => { + const hdNode = bchjs.HDNode.fromXPriv(fixture.xpriv) + assert.equal(bchjs.HDNode.toWIF(hdNode), fixture.privateKeyWIF) + }) + }) + }) + + describe("#toXPub", () => { + fixtures.toXPub.forEach(fixture => { + it(`should create xpub ${fixture.xpub} from an HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const xpub = bchjs.HDNode.toXPub(hdNode) + assert.equal(xpub, fixture.xpub) + }) + }) + }) + + describe("#toXPriv", () => { + fixtures.toXPriv.forEach(fixture => { + it(`should create xpriv ${fixture.xpriv} from an HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const xpriv = bchjs.HDNode.toXPriv(hdNode) + assert.equal(xpriv, fixture.xpriv) + }) + }) + }) + + describe("#toKeyPair", () => { + fixtures.toKeyPair.forEach(fixture => { + it(`should get ECPair from an HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const keyPair = bchjs.HDNode.toKeyPair(hdNode) + assert.equal(typeof keyPair, "object") + }) + }) + }) + + describe("#toPublicKey", () => { + fixtures.toPublicKey.forEach(fixture => { + it(`should create public key buffer from an HDNode`, async () => { + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + const hdNode = bchjs.HDNode.fromSeed(rootSeedBuffer) + const publicKeyBuffer = bchjs.HDNode.toPublicKey(hdNode) + assert.equal(typeof publicKeyBuffer, "object") + }) + }) + }) + + describe("#fromXPriv", () => { + fixtures.fromXPriv.forEach(fixture => { + const hdNode = bchjs.HDNode.fromXPriv(fixture.xpriv) + it(`should create HDNode from xpriv ${fixture.xpriv}`, () => { + assert.notEqual(hdNode, null) + }) + + it(`should export xpriv ${fixture.xpriv}`, () => { + assert.equal(bchjs.HDNode.toXPriv(hdNode), fixture.xpriv) + }) + + it(`should export xpub ${fixture.xpub}`, () => { + assert.equal(bchjs.HDNode.toXPub(hdNode), fixture.xpub) + }) + + it(`should export legacy address ${fixture.legacy}`, () => { + assert.equal(bchjs.HDNode.toLegacyAddress(hdNode), fixture.legacy) + }) + + it(`should export cashaddress ${fixture.cashaddress}`, () => { + assert.equal(bchjs.HDNode.toCashAddress(hdNode), fixture.cashaddress) + }) + + it(`should export regtest cashaddress ${fixture.regtestaddress}`, () => { + assert.equal( + bchjs.HDNode.toCashAddress(hdNode, true), + fixture.regtestaddress + ) + }) + + it(`should export privateKeyWIF ${fixture.privateKeyWIF}`, () => { + assert.equal(bchjs.HDNode.toWIF(hdNode), fixture.privateKeyWIF) + }) + }) + }) + + describe("#fromXPub", () => { + fixtures.fromXPub.forEach(fixture => { + const hdNode = bchjs.HDNode.fromXPub(fixture.xpub) + it(`should create HDNode from xpub ${fixture.xpub}`, () => { + assert.notEqual(hdNode, null) + }) + + it(`should export xpub ${fixture.xpub}`, () => { + assert.equal(bchjs.HDNode.toXPub(hdNode), fixture.xpub) + }) + + it(`should export legacy address ${fixture.legacy}`, () => { + assert.equal(bchjs.HDNode.toLegacyAddress(hdNode), fixture.legacy) + }) + + it(`should export cashaddress ${fixture.cashaddress}`, () => { + assert.equal(bchjs.HDNode.toCashAddress(hdNode), fixture.cashaddress) + }) + + it(`should export regtest cashaddress ${fixture.regtestaddress}`, () => { + assert.equal( + bchjs.HDNode.toCashAddress(hdNode, true), + fixture.regtestaddress + ) + }) + }) + }) + + describe("#bip32", () => { + describe("create accounts and addresses", () => { + fixtures.accounts.forEach(async fixture => { + const seedBuffer = await bchjs.Mnemonic.toSeed(fixture.mnemonic) + console.log(`seedBuffer: ${seedBuffer.toString()}`) + const hdNode = bchjs.HDNode.fromSeed(seedBuffer) + const a = bchjs.HDNode.derivePath(hdNode, "0'") + const external = bchjs.HDNode.derivePath(a, "0") + const account = bchjs.HDNode.createAccount([external]) + + it(`#createAccount`, () => { + assert.notEqual(account, null) + }) + + describe("#getChainAddress", () => { + const external1 = bchjs.Address.toCashAddress( + account.getChainAddress(0) + ) + it(`should create external change address ${external1}`, () => { + assert.equal(external1, fixture.externals[0]) + }) + }) + + describe("#nextChainAddress", () => { + for (let i = 0; i < 4; i++) { + const ex = bchjs.Address.toCashAddress(account.nextChainAddress(0)) + it(`should create external change address ${ex}`, () => { + assert.equal(ex, fixture.externals[i + 1]) + }) + } + }) + }) + }) + }) + + describe("#sign", () => { + fixtures.sign.forEach(fixture => { + it(`should sign 32 byte hash buffer`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.privateKeyWIF) + const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), "hex") + const signatureBuf = bchjs.HDNode.sign(hdnode, buf) + assert.equal(typeof signatureBuf, "object") + }) + }) + }) + + describe("#verify", () => { + fixtures.verify.forEach(fixture => { + it(`should verify signed 32 byte hash buffer`, () => { + const hdnode1 = bchjs.HDNode.fromXPriv(fixture.privateKeyWIF1) + const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), "hex") + const signature = bchjs.HDNode.sign(hdnode1, buf) + const verify = bchjs.HDNode.verify(hdnode1, buf, signature) + assert.equal(verify, true) + }) + }) + }) + + describe("#isPublic", () => { + fixtures.isPublic.forEach(fixture => { + it(`should verify hdnode is public`, () => { + const node = bchjs.HDNode.fromXPub(fixture.xpub) + assert.equal(bchjs.HDNode.isPublic(node), true) + }) + }) + + fixtures.isPublic.forEach(fixture => { + it(`should verify hdnode is not public`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + assert.equal(bchjs.HDNode.isPublic(node), false) + }) + }) + }) + + describe("#isPrivate", () => { + fixtures.isPrivate.forEach(fixture => { + it(`should verify hdnode is not private`, () => { + const node = bchjs.HDNode.fromXPub(fixture.xpub) + assert.equal(bchjs.HDNode.isPrivate(node), false) + }) + }) + + fixtures.isPrivate.forEach(fixture => { + it(`should verify hdnode is private`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + assert.equal(bchjs.HDNode.isPrivate(node), true) + }) + }) + }) + + describe("#toIdentifier", () => { + fixtures.toIdentifier.forEach(fixture => { + it(`should get identifier of hdnode`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const publicKeyBuffer = bchjs.HDNode.toPublicKey(node) + const hash160 = bchjs.Crypto.hash160(publicKeyBuffer) + const identifier = bchjs.HDNode.toIdentifier(node) + assert.equal(identifier.toString("hex"), hash160.toString("hex")) + }) + }) + }) +}) diff --git a/test/unit/ipfs.js b/test/unit/ipfs.js new file mode 100644 index 0000000..cc95458 --- /dev/null +++ b/test/unit/ipfs.js @@ -0,0 +1,195 @@ +/* + 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("#createFileModel", () => { + it("should throw an error if file does not exist", async () => { + try { + const path = "/non-existant-file" + + await bchjs.IPFS.createFileModel(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}/ipfs.js` + + sandbox + .stub(bchjs.IPFS.axios, "post") + .resolves({ data: mockData.mockNewFileModel }) + + const result = await bchjs.IPFS.createFileModel(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("#uploadFile", () => { + it("should throw an error if file does not exist", async () => { + try { + const path = "/non-existant-file" + + await bchjs.IPFS.uploadFile(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}/ipfs.js` + + await bchjs.IPFS.uploadFile(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}/ipfs.js` + await bchjs.IPFS.uploadFile(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}/ipfs.js` + const result = await bchjs.IPFS.uploadFile( + 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") + }) + }) +}) diff --git a/test/unit/mining.js b/test/unit/mining.js new file mode 100644 index 0000000..1586397 --- /dev/null +++ b/test/unit/mining.js @@ -0,0 +1,104 @@ +const assert = require("assert") +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const sinon = require("sinon") + +describe("#Mining", () => { + describe("#getBlockTemplate", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get block template", done => { + const data = { + data: + "01000000017f6305e3b0b05f5b57a82f4e6d4187e148bbe56a947208390e488bad36472368000000006a47304402203b0079ff5b896187feb02e2679c87ac2fb8d483b60e0721ed33601e2c0eecc700220590f8a0e1a51b53b368294861fd5fc99db3a6607d0f4e543f6217108e208c1834121024c93c841d7f576584ffbf513b7abd8283e6562669905f6554f788fce4cc67a34ffffffff0228100000000000001976a914af78709a76abc8a28e568c9210c8247dd10cff2c88ac22020000000000001976a914f339927678803f451b41400737e7dc83c6a8682188ac00000000", + txid: + "7f462d71c649a0d8cfbaa2d20d8ff86677966b308f0ac9906ee015bf4453f97a", + hash: + "7f462d71c649a0d8cfbaa2d20d8ff86677966b308f0ac9906ee015bf4453f97a", + depends: [], + fee: 226, + sigops: 2 + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Mining.getBlockTemplate("") + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getMiningInfo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get mining info", done => { + const data = { + blocks: 527816, + currentblocksize: 89408, + currentblocktx: 156, + difficulty: 568757800682.7649, + blockprioritypercentage: 5, + errors: "", + networkhashps: 4347259225696976000, + pooledtx: 184, + chain: "main" + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Mining.getMiningInfo() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) + + describe("#getNetworkHashps", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get network hashps", done => { + const data = 3586365937646890000 + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Mining.getNetworkHashps() + .then(result => { + assert.equal(data, result) + }) + .then(done, done) + }) + }) + + describe("#submitBlock", () => { + // TODO finish + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should TODO", done => { + const data = {} + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "post").returns(resolved) + + bchjs.Mining.submitBlock() + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) +}) diff --git a/test/unit/mnemonic.js b/test/unit/mnemonic.js new file mode 100644 index 0000000..2028653 --- /dev/null +++ b/test/unit/mnemonic.js @@ -0,0 +1,340 @@ +const fixtures = require("./fixtures/mnemonic.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +describe("#Mnemonic", () => { + describe("#generate", () => { + it("should generate a 12 word mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(128) + assert.equal(mnemonic.split(" ").length, 12) + }) + + it("should generate a 15 word mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(160) + assert.equal(mnemonic.split(" ").length, 15) + }) + + it("should generate a 18 word mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(192) + assert.equal(mnemonic.split(" ").length, 18) + }) + + it("should generate an 21 word mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(224) + assert.equal(mnemonic.split(" ").length, 21) + }) + + it("should generate an 24 word mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(256) + assert.equal(mnemonic.split(" ").length, 24) + }) + + it("should generate an 24 word italian mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate( + 256, + bchjs.Mnemonic.wordLists().italian + ) + assert.equal(mnemonic.split(" ").length, 24) + }) + }) + + describe("#fromEntropy", () => { + it("should generate a 12 word mnemonic from 16 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(16) + const mnemonic = bchjs.Mnemonic.fromEntropy(rand.toString("hex")) + assert.equal(mnemonic.split(" ").length, 12) + }) + + it("should generate a 15 word mnemonic from 20 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(20) + const mnemonic = bchjs.Mnemonic.fromEntropy(rand.toString("hex")) + assert.equal(mnemonic.split(" ").length, 15) + }) + + it("should generate an 18 word mnemonic from 24 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(24) + const mnemonic = bchjs.Mnemonic.fromEntropy(rand.toString("hex")) + assert.equal(mnemonic.split(" ").length, 18) + }) + + it("should generate an 21 word mnemonic from 28 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(28) + const mnemonic = bchjs.Mnemonic.fromEntropy(rand.toString("hex")) + assert.equal(mnemonic.split(" ").length, 21) + }) + + it("should generate an 24 word mnemonic from 32 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(32) + const mnemonic = bchjs.Mnemonic.fromEntropy(rand.toString("hex")) + assert.equal(mnemonic.split(" ").length, 24) + }) + + it("should generate an 24 french word mnemonic 32 bytes of entropy", () => { + const rand = bchjs.Crypto.randomBytes(32) + const mnemonic = bchjs.Mnemonic.fromEntropy( + rand.toString("hex"), + bchjs.Mnemonic.wordLists().french + ) + assert.equal(mnemonic.split(" ").length, 24) + }) + + fixtures.fromEntropy.forEach(entropy => { + const mnemonic = bchjs.Mnemonic.fromEntropy(entropy.entropy) + it(`should convert ${entropy.entropy} to ${entropy.mnemonic}`, () => { + assert.equal(mnemonic, entropy.mnemonic) + }) + }) + }) + + describe("#toEntropy", () => { + it("should turn a 12 word mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate(128) + const entropy = bchjs.Mnemonic.toEntropy(mnemonic) + assert.equal(entropy.length, 16) + }) + + it("should turn a 15 word mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate(160) + const entropy = bchjs.Mnemonic.toEntropy(mnemonic) + assert.equal(entropy.length, 20) + }) + + it("should turn a 18 word mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate(192) + const entropy = bchjs.Mnemonic.toEntropy(mnemonic) + assert.equal(entropy.length, 24) + }) + + it("should turn a 21 word mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate(224) + const entropy = bchjs.Mnemonic.toEntropy(mnemonic) + assert.equal(entropy.length, 28) + }) + + it("should turn a 24 word mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate(256) + const entropy = bchjs.Mnemonic.toEntropy(mnemonic) + assert.equal(entropy.length, 32) + }) + + it("should turn a 24 word spanish mnemonic to entropy", () => { + const mnemonic = bchjs.Mnemonic.generate( + 256, + bchjs.Mnemonic.wordLists().spanish + ) + const entropy = bchjs.Mnemonic.toEntropy( + mnemonic, + bchjs.Mnemonic.wordLists().spanish + ) + assert.equal(entropy.length, 32) + }) + + fixtures.fromEntropy.forEach(fixture => { + const entropy = bchjs.Mnemonic.toEntropy(fixture.mnemonic) + it(`should convert ${fixture.mnemonic} to ${fixture.entropy}`, () => { + assert.equal(entropy.toString("hex"), fixture.entropy) + }) + }) + }) + + describe("#validate", () => { + it("fails for a mnemonic that is too short", () => { + assert.equal( + bchjs.Mnemonic.validate( + "mixed winner", + bchjs.Mnemonic.wordLists().english + ), + "Invalid mnemonic" + ) + }) + + it("fails for a mnemonic that is too long", () => { + assert.equal( + bchjs.Mnemonic.validate( + "mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake mixed winner decide drift danger together twice planet impose asthma catch require select mask awkward spy relief front work solar pitch economy render cake", + bchjs.Mnemonic.wordLists().english + ), + "Invalid mnemonic" + ) + }) + + it("fails if mnemonic words are not in the word list", () => { + assert.equal( + bchjs.Mnemonic.validate( + "failsauce one two three four five six seven eight nine ten eleven", + bchjs.Mnemonic.wordLists().english + ), + "failsauce is not in wordlist, did you mean balance?" + ) + }) + + it("validate a 128 bit mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(128) + assert.equal( + bchjs.Mnemonic.validate(mnemonic, bchjs.Mnemonic.wordLists().english), + "Valid mnemonic" + ) + }) + + it("validate a 160 bit mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(160) + assert.equal( + bchjs.Mnemonic.validate(mnemonic, bchjs.Mnemonic.wordLists().english), + "Valid mnemonic" + ) + }) + + it("validate a 192 bit mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(192) + assert.equal( + bchjs.Mnemonic.validate(mnemonic, bchjs.Mnemonic.wordLists().english), + "Valid mnemonic" + ) + }) + + it("validate a 224 bit mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(224) + assert.equal( + bchjs.Mnemonic.validate(mnemonic, bchjs.Mnemonic.wordLists().english), + "Valid mnemonic" + ) + }) + + it("validate a 256 bit mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate(256) + assert.equal( + bchjs.Mnemonic.validate(mnemonic, bchjs.Mnemonic.wordLists().english), + "Valid mnemonic" + ) + }) + + it("validate a 256 bit chinese simplified mnemonic", () => { + const mnemonic = bchjs.Mnemonic.generate( + 256, + bchjs.Mnemonic.wordLists().chinese_simplified + ) + assert.equal( + bchjs.Mnemonic.validate( + mnemonic, + bchjs.Mnemonic.wordLists().chinese_simplified + ), + "Valid mnemonic" + ) + }) + }) + + describe("#toSeed", () => { + it("should create 512 bit / 64 byte HMAC-SHA512 root seed from a 128 bit mnemonic", async () => { + const mnemonic = bchjs.Mnemonic.generate(128) + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic, "") + assert.equal(rootSeedBuffer.byteLength, 64) + }) + + it("should create 512 bit / 64 byte HMAC-SHA512 root seed from a 160 bit mnemonic", async () => { + const mnemonic = bchjs.Mnemonic.generate(160) + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic, "") + assert.equal(rootSeedBuffer.byteLength, 64) + }) + + it("should create 512 bit / 64 byte HMAC-SHA512 root seed from a 192 bit mnemonic", async () => { + const mnemonic = bchjs.Mnemonic.generate(192) + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic, "") + assert.equal(rootSeedBuffer.byteLength, 64) + }) + + it("should create 512 bit / 64 byte HMAC-SHA512 root seed from a 224 bit mnemonic", async () => { + const mnemonic = bchjs.Mnemonic.generate(224) + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic, "") + assert.equal(rootSeedBuffer.byteLength, 64) + }) + + it("should create 512 bit / 64 byte HMAC-SHA512 root seed from a 256 bit mnemonic", async () => { + const mnemonic = bchjs.Mnemonic.generate(256) + const rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic, "") + assert.equal(rootSeedBuffer.byteLength, 64) + }) + }) + + describe("#wordLists", () => { + it("return a list of 2048 english words", () => { + assert.equal(bchjs.Mnemonic.wordLists().english.length, 2048) + }) + + it("return a list of 2048 japanese words", () => { + assert.equal(bchjs.Mnemonic.wordLists().japanese.length, 2048) + }) + + it("return a list of 2048 chinese simplified words", () => { + assert.equal(bchjs.Mnemonic.wordLists().chinese_simplified.length, 2048) + }) + + it("return a list of 2048 chinese traditional words", () => { + assert.equal(bchjs.Mnemonic.wordLists().chinese_traditional.length, 2048) + }) + + it("return a list of 2048 french words", () => { + assert.equal(bchjs.Mnemonic.wordLists().french.length, 2048) + }) + + it("return a list of 2048 italian words", () => { + assert.equal(bchjs.Mnemonic.wordLists().italian.length, 2048) + }) + + it("return a list of 2048 korean words", () => { + assert.equal(bchjs.Mnemonic.wordLists().korean.length, 2048) + }) + + it("return a list of 2048 spanish words", () => { + assert.equal(bchjs.Mnemonic.wordLists().spanish.length, 2048) + }) + }) + + describe("#toKeypairs", async () => { + fixtures.toKeypairs.forEach(async (fixture, i) => { + const keypairs = await bchjs.Mnemonic.toKeypairs(fixture.mnemonic, 5) + keypairs.forEach((keypair, j) => { + it(`Generate keypair from mnemonic`, () => { + assert.equal( + keypair.privateKeyWIF, + fixtures.toKeypairs[i].output[j].privateKeyWIF + ) + // assert.equal( + // keypair.address, + // fixtures.toKeypairs[i].output[j].address + // ) + }) + }) + + const regtestKeypairs = await bchjs.Mnemonic.toKeypairs( + fixture.mnemonic, + 5, + true + ) + regtestKeypairs.forEach((keypair, j) => { + it(`Generate keypair from mnemonic`, () => { + assert.equal( + keypair.privateKeyWIF, + fixtures.toKeypairs[i].output[j].privateKeyWIFRegTest + ) + // assert.equal( + // keypair.address, + // fixtures.toKeypairs[i].output[j].regtestAddress + // ) + }) + }) + }) + }) + + describe("#findNearestWord", () => { + fixtures.findNearestWord.forEach((fixture, i) => { + const word = bchjs.Mnemonic.findNearestWord( + fixture.word, + bchjs.Mnemonic.wordLists()[fixture.language] + ) + it(`find word ${fixture.foundWord} near ${fixture.word} in ${fixture.language}`, () => { + assert.equal(word, fixture.foundWord) + }) + }) + }) +}) diff --git a/test/unit/ninsight.js b/test/unit/ninsight.js new file mode 100644 index 0000000..9ac2b45 --- /dev/null +++ b/test/unit/ninsight.js @@ -0,0 +1,55 @@ +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, `address needs to be a string`) + } + }) + + it(`should GET utxos for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.utxo }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.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") + }) + }) +}) diff --git a/test/unit/openbazaar.js b/test/unit/openbazaar.js new file mode 100644 index 0000000..d663fb0 --- /dev/null +++ b/test/unit/openbazaar.js @@ -0,0 +1,129 @@ +const chai = require("chai") +const assert = chai.assert +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const axios = require("axios") +const sinon = require("sinon") + +const mockData = require("./fixtures/openbazaar-mock") + +describe(`#OpenBazaar`, () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + describe(`#Balance`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.balance(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + + it(`should GET balance for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.balance }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.OpenBazaar.balance(addr) + //console.log(`result: ${util.inspect(result)}`) + + assert.hasAllKeys(result, [ + "page", + "totalPages", + "itemsOnPage", + "addrStr", + "balance", + "totalReceived", + "totalSent", + "unconfirmedBalance", + "unconfirmedTxApperances", + "txApperances", + "transactions" + ]) + assert.isArray(result.transactions) + }) + }) + + describe(`#utxo`, () => { + it(`should throw an error for improper input`, async () => { + try { + const addr = 12345 + + await bchjs.OpenBazaar.utxo(addr) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input address must be a string`) + } + }) + + it(`should GET utxos for a single address`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.utxo }) + + const addr = "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9" + + const result = await bchjs.OpenBazaar.utxo(addr) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.hasAllKeys(result[0], [ + "txid", + "vout", + "amount", + "height", + "confirmations", + "satoshis" + ]) + }) + }) + + describe(`#tx`, () => { + it(`should throw an error for improper input`, async () => { + try { + const txid = 12345 + + await bchjs.OpenBazaar.tx(txid) + assert.equal(true, false, "Unexpected result!") + } catch (err) { + //console.log(`err: `, err) + assert.include(err.message, `Input txid must be a string`) + } + }) + + it(`should GET tx details for a single txid`, async () => { + // Stub the network call. + sandbox.stub(axios, "get").resolves({ data: mockData.tx }) + + const txid = + "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7" + + const result = await bchjs.OpenBazaar.tx(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, [ + "txid", + "version", + "vin", + "vout", + "blockhash", + "blockheight", + "confirmations", + "blocktime", + "valueOut", + "valueIn", + "fees", + "hex" + ]) + assert.isArray(result.vin) + assert.isArray(result.vout) + }) + }) +}) diff --git a/test/unit/price.js b/test/unit/price.js new file mode 100644 index 0000000..b60a646 --- /dev/null +++ b/test/unit/price.js @@ -0,0 +1,28 @@ +const chai = require("chai") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const axios = require("axios") +const sinon = require("sinon") + +describe("#Price", () => { + describe("#current", () => { + describe("#single currency", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get current price for single currency", done => { + const data = 46347 + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Price.current("usd") + .then(result => { + assert.deepEqual(data.price, result) + }) + .then(done, done) + }) + }) + }) +}) diff --git a/test/unit/raw-tranactions.js b/test/unit/raw-tranactions.js new file mode 100644 index 0000000..0eec0d8 --- /dev/null +++ b/test/unit/raw-tranactions.js @@ -0,0 +1,129 @@ +/* + TODO: + -Replace old unit tests mocking axios with the more generalized nock library. + See the sendRawTransaction test for an example. + -Create a mocking library of data to compare unit and integration tests. +*/ + +const assert = require("assert") +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +const sinon = require("sinon") +const nock = require("nock") // HTTP mocking + +// Used for debugging +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +describe("#RawTransactions", () => { + describe("#decodeRawTransaction", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should decode raw transaction", done => { + const data = { + txid: + "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", + hash: + "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", + size: 10, + version: 2, + locktime: 0, + vin: [], + vout: [] + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.RawTransactions.decodeRawTransaction("02000000000000000000") + .then(result => { + assert.equal(data, result) + }) + .then(done, done) + }) + }) + + describe("#decodeScript", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should decode script", async () => { + const data = { + asm: "OP_RETURN 5361746f736869204e616b616d6f746f", + type: "nulldata", + p2sh: "bitcoincash:prswx5965nfumux9qng5kj8hw603vcne7q08t8c6jp" + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + const result = await bchjs.RawTransactions.decodeScript( + "6a105361746f736869204e616b616d6f746f" + ) + //console.log(`result: ${util.inspect(result)}`) + + assert.deepEqual(data, result) + }) + }) + + describe("#getRawTransaction", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should get raw transaction", done => { + const data = + "020000000160d663961c63c7f0a07f22ec07b8f55b3935bfdbed8b1d8454916e8932fbf109010000006b4830450221008479fab4cfdcb111833d250a43f98ac26d43272b7a29cb1b9a0491eae5c44b3502203448b17253632395c29a7d62058bbfe93efb20fc8636ba6837002d464195aec04121029123258f7cdcd45b864066bcaa9b71f24d5ed1fa1dd36eaf107d8432b5014658ffffffff016d180000000000001976a91479d3297d1823149f4ec61df31d19f2fad5390c0288ac00000000" + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.RawTransactions.getRawTransaction( + "808d617eccaad4f1397fe07a06ec5ed15a0821cf22a3e0931c0c92aef9e572b6" + ) + .then(result => { + assert.equal(data, result) + }) + .then(done, done) + }) + }) + + describe("#sendRawTransaction", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should send single raw transaction", async () => { + const data = "Error: transaction already in block chain" + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + const result = await bchjs.RawTransactions.sendRawTransaction( + "020000000160d663961c63c7f0a07f22ec07b8f55b3935bfdbed8b1d8454916e8932fbf109010000006b4830450221008479fab4cfdcb111833d250a43f98ac26d43272b7a29cb1b9a0491eae5c44b3502203448b17253632395c29a7d62058bbfe93efb20fc8636ba6837002d464195aec04121029123258f7cdcd45b864066bcaa9b71f24d5ed1fa1dd36eaf107d8432b5014658ffffffff016d180000000000001976a91479d3297d1823149f4ec61df31d19f2fad5390c0288ac00000000" + ) + + assert.equal(data, result) + }) + + it("should send an array of raw transactions", async () => { + const data = "Error: transaction already in block chain" + + // Mock the http call to rest.bitcoin.com + nock(`${bchjs.RawTransactions.restURL}`) + .post(uri => uri.includes(`/`)) + .reply(200, { data: data }) + + const result = await bchjs.RawTransactions.sendRawTransaction([ + "020000000160d663961c63c7f0a07f22ec07b8f55b3935bfdbed8b1d8454916e8932fbf109010000006b4830450221008479fab4cfdcb111833d250a43f98ac26d43272b7a29cb1b9a0491eae5c44b3502203448b17253632395c29a7d62058bbfe93efb20fc8636ba6837002d464195aec04121029123258f7cdcd45b864066bcaa9b71f24d5ed1fa1dd36eaf107d8432b5014658ffffffff016d180000000000001976a91479d3297d1823149f4ec61df31d19f2fad5390c0288ac00000000" + ]) + + assert.equal(data, result.data) + }) + }) +}) diff --git a/test/unit/scripts.js b/test/unit/scripts.js new file mode 100644 index 0000000..f327a14 --- /dev/null +++ b/test/unit/scripts.js @@ -0,0 +1,476 @@ +const fixtures = require("./fixtures/script.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const Buffer = require("safe-buffer").Buffer + +describe("#Script", () => { + describe("#decode", () => { + describe("P2PKH scriptSig", () => { + fixtures.decodeScriptSig.forEach(fixture => { + it(`should decode scriptSig buffer`, () => { + const decodedScriptSig = bchjs.Script.decode( + Buffer.from(fixture.scriptSigHex, "hex") + ) + assert.equal(typeof decodedScriptSig, "object") + }) + + it(`should decode scriptSig buffer to cash address ${fixture.cashAddress}`, () => { + const decodedScriptSig = bchjs.Script.decode( + Buffer.from(fixture.scriptSigHex, "hex") + ) + const address = bchjs.HDNode.toCashAddress( + bchjs.ECPair.fromPublicKey(decodedScriptSig[1]) + ) + assert.equal(address, fixture.cashAddress) + }) + + it(`should decode scriptSig buffer to legacy address ${fixture.legacyAddress}`, () => { + const decodedScriptSig = bchjs.Script.decode( + Buffer.from(fixture.scriptSigHex, "hex") + ) + const address = bchjs.HDNode.toLegacyAddress( + bchjs.ECPair.fromPublicKey(decodedScriptSig[1]) + ) + assert.equal(address, fixture.legacyAddress) + }) + }) + }) + + describe("P2PKH scriptPubKey", () => { + fixtures.decodeScriptPubKey.forEach(fixture => { + it(`should decode scriptSig buffer`, () => { + const decodedScriptPubKey = bchjs.Script.decode( + Buffer.from(fixture.scriptPubKeyHex, "hex") + ) + assert.equal(decodedScriptPubKey.length, 5) + }) + + it(`should match hashed pubKey ${fixture.pubKeyHex}`, () => { + const decodedScriptPubKey = bchjs.Script.decode( + Buffer.from(fixture.scriptPubKeyHex, "hex") + ) + const data = Buffer.from(fixture.pubKeyHex, "hex") + const hash160 = bchjs.Crypto.hash160(data).toString("hex") + assert.equal(decodedScriptPubKey[2].toString("hex"), hash160) + }) + }) + }) + }) + + describe("#encode", () => { + describe("P2PKH scriptSig", () => { + fixtures.encodeScriptSig.forEach(fixture => { + it(`should encode scriptSig chunks to buffer`, () => { + const arr = [ + Buffer.from(fixture.scriptSigChunks[0], "hex"), + Buffer.from(fixture.scriptSigChunks[1], "hex") + ] + const encodedScriptSig = bchjs.Script.encode(arr) + assert.equal(typeof encodedScriptSig, "object") + }) + }) + }) + + describe("P2PKH scriptPubKey", () => { + fixtures.encodeScriptPubKey.forEach(fixture => { + it(`should encode scriptPubKey buffer`, () => { + const decodedScriptPubKey = bchjs.Script.decode( + Buffer.from(fixture.scriptPubKeyHex, "hex") + ) + const compiledScriptPubKey = bchjs.Script.encode(decodedScriptPubKey) + assert.equal( + compiledScriptPubKey.toString("hex"), + fixture.scriptPubKeyHex + ) + }) + }) + }) + + describe("Encode SLP SEND OP_RETURN properly", () => { + it("should correctly compile OP_RETURN SLP SEND transaction", () => { + const scriptArr = [ + bchjs.Script.opcodes.OP_RETURN, + Buffer.from("534c5000", "hex"), + Buffer.from("01", "hex"), + Buffer.from(`SEND`), + Buffer.from( + "73db55368981e4878440637e448d4abe7f661be5c3efdcbcb63bd86a01a76b5a", + "hex" + ), + Buffer.from("00000001", "hex") + ] + + const data = bchjs.Script.encode2(scriptArr) + + // convert data to a hex string + let str = "" + for (let i = 0; i < data.length; i++) { + let hex = Number(data[i]).toString(16) + + // zero pad when its a single digit. + hex = `0${hex}` + hex = hex.slice(-2) + //console.log(`hex: ${hex}`) + + str += hex + } + console.log(`Hex string: ${str}`) + + //console.log(`scriptArr: ${JSON.stringify(data,null,2)}`) + + const correctStr = + "6a04534c500001010453454e442073db55368981e4878440637e448d4abe7f661be5c3efdcbcb63bd86a01a76b5a0400000001" + + assert.equal(str, correctStr) + }) + }) + }) + + describe("#toASM", () => { + describe("P2PKH scriptSig", () => { + fixtures.scriptSigToASM.forEach(fixture => { + it(`should encode scriptSig buffer to ${fixture.asm}`, () => { + const arr = [ + Buffer.from(fixture.scriptSigChunks[0], "hex"), + Buffer.from(fixture.scriptSigChunks[1], "hex") + ] + const compiledScriptSig = bchjs.Script.encode(arr) + const asm = bchjs.Script.toASM(compiledScriptSig) + assert.equal(asm, fixture.asm) + }) + }) + }) + + describe("P2PKH scriptPubKey", () => { + fixtures.scriptPubKeyToASM.forEach(fixture => { + it(`should compile scriptPubKey buffer to ${fixture.asm}`, () => { + const asm = bchjs.Script.toASM( + Buffer.from(fixture.scriptPubKeyHex, "hex") + ) + assert.equal(asm, fixture.asm) + }) + }) + }) + }) + + describe("#fromASM", () => { + describe("P2PKH scriptSig", () => { + fixtures.scriptSigFromASM.forEach(fixture => { + it(`should decode scriptSig asm to buffer`, () => { + const buf = bchjs.Script.fromASM(fixture.asm) + assert.equal(typeof buf, "object") + }) + }) + }) + + describe("P2PKH scriptPubKey", () => { + fixtures.scriptPubKeyFromASM.forEach(fixture => { + it(`should decode scriptPubKey asm to buffer`, () => { + const buf = bchjs.Script.fromASM(fixture.asm) + assert.equal(typeof buf, "object") + }) + }) + }) + }) + + describe("#OPCodes", () => { + for (const opcode in fixtures.opcodes) { + it(`should have OP Code ${opcode}`, () => { + assert.equal(bchjs.Script.opcodes[opcode], fixtures.opcodes[opcode]) + }) + } + }) + + describe("#classifyInput", () => { + fixtures.classifyInput.forEach(fixture => { + it(`should classify input type ${fixture.type}`, () => { + const type = bchjs.Script.classifyInput( + bchjs.Script.fromASM(fixture.script) + ) + assert.equal(type, fixture.type) + }) + }) + }) + + describe("#classifyOutput", () => { + fixtures.classifyOutput.forEach(fixture => { + it(`should classify ouput type ${fixture.type}`, () => { + const type = bchjs.Script.classifyOutput( + bchjs.Script.fromASM(fixture.script) + ) + assert.equal(type, fixture.type) + }) + }) + }) + + describe("#nullDataTemplate", () => { + fixtures.nullDataTemplate.forEach(fixture => { + it(`should encode nulldata output`, () => { + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(`${fixture.data}`, "ascii") + ) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode nulldata output`, () => { + const buf = bchjs.Script.nullData.output.decode( + Buffer.from(`${fixture.hex}`, "hex") + ) + assert.equal(buf.toString("ascii"), fixture.data) + }) + + it(`should confirm correctly formatted nulldata output`, () => { + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(`${fixture.data}`, "ascii") + ) + const valid = bchjs.Script.nullData.output.check(buf) + assert.equal(valid, true) + }) + }) + }) + + describe("#pubKeyTemplate", () => { + describe("#pubKeyInputTemplate", () => { + fixtures.pubKeyInputTemplate.forEach(fixture => { + it(`should encode pubKey input`, () => { + const buf = bchjs.Script.pubKey.input.encode( + Buffer.from(fixture.signature, "hex") + ) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode pubKey input`, () => { + const buf = bchjs.Script.pubKey.input.decode( + Buffer.from(fixture.hex, "hex") + ) + assert.equal(buf.toString("hex"), fixture.signature) + }) + + it(`should confirm correctly formatted pubKeyHash input`, () => { + const buf = bchjs.Script.pubKey.input.encode( + Buffer.from(fixture.signature, "hex") + ) + const valid = bchjs.Script.pubKey.input.check(buf) + assert.equal(valid, true) + }) + }) + }) + + describe("#pubKeyOutputTemplate", () => { + fixtures.pubKeyOutputTemplate.forEach(fixture => { + it(`should encode pubKey output`, () => { + const buf = bchjs.Script.pubKey.output.encode( + Buffer.from(fixture.pubKey, "hex") + ) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode pubKey output`, () => { + const buf = bchjs.Script.pubKey.output.decode( + Buffer.from(`${fixture.hex}`, "hex") + ) + assert.equal(buf.toString("hex"), fixture.pubKey) + }) + + it(`should confirm correctly formatted pubKey output`, () => { + const buf = bchjs.Script.pubKey.output.encode( + Buffer.from(fixture.pubKey, "hex") + ) + const valid = bchjs.Script.pubKey.output.check(buf) + assert.equal(valid, true) + }) + }) + }) + }) + + describe("#pubKeyHashTemplate", () => { + describe("#pubKeyHashInputTemplate", () => { + fixtures.pubKeyHashInputTemplate.forEach(fixture => { + it(`should encode pubKeyHash input`, () => { + const buf = bchjs.Script.pubKeyHash.input.encode( + Buffer.from(fixture.signature, "hex"), + Buffer.from(fixture.pubKey, "hex") + ) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode pubKeyHash input signature`, () => { + const buf = bchjs.Script.pubKeyHash.input.decode( + Buffer.from(fixture.hex, "hex") + ) + assert.equal(buf.signature.toString("hex"), fixture.signature) + }) + + it(`should decode pubKeyHash input pubkey`, () => { + const buf = bchjs.Script.pubKeyHash.input.decode( + Buffer.from(fixture.hex, "hex") + ) + assert.equal(buf.pubKey.toString("hex"), fixture.pubKey) + }) + + it(`should confirm correctly formatted pubKeyHash input`, () => { + const buf = bchjs.Script.pubKeyHash.input.encode( + Buffer.from(fixture.signature, "hex"), + Buffer.from(fixture.pubKey, "hex") + ) + const valid = bchjs.Script.pubKeyHash.input.check(buf) + assert.equal(valid, true) + }) + }) + }) + + describe("#pubKeyHashOutputTemplate", () => { + fixtures.pubKeyHashOutputTemplate.forEach(fixture => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const identifier = bchjs.HDNode.toIdentifier(node) + it(`should encode pubKeyHash output`, () => { + const buf = bchjs.Script.pubKeyHash.output.encode(identifier) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode pubKeyHash output`, () => { + const buf = bchjs.Script.pubKeyHash.output.decode( + Buffer.from(`${fixture.hex}`, "hex") + ) + assert.equal(buf.toString("hex"), identifier.toString("hex")) + }) + + it(`should confirm correctly formatted pubKeyHash output`, () => { + const buf = bchjs.Script.pubKeyHash.output.encode(identifier) + const valid = bchjs.Script.pubKeyHash.output.check(buf) + assert.equal(valid, true) + }) + }) + }) + }) + + describe("#multisigTemplate", () => { + describe("#multisigInputTemplate", () => { + fixtures.multisigInputTemplate.forEach(fixture => { + it(`should encode multisig input`, () => { + const signatures = fixture.signatures.map(signature => + signature + ? Buffer.from(signature, "hex") + : bchjs.Script.opcodes.OP_0 + ) + + const buf = bchjs.Script.multisig.input.encode(signatures) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode multisig input`, () => { + const buf = bchjs.Script.multisig.input.decode( + Buffer.from(fixture.hex, "hex") + ) + assert.equal(buf[0].toString("hex"), fixture.signatures[0]) + }) + + it(`should confirm correctly formatted multisig input`, () => { + const signatures = fixture.signatures.map(signature => + signature + ? Buffer.from(signature, "hex") + : bchjs.Script.opcodes.OP_0 + ) + + const buf = bchjs.Script.multisig.input.encode(signatures) + const valid = bchjs.Script.multisig.input.check(buf) + assert.equal(valid, true) + }) + }) + }) + + describe("#multisigOutputTemplate", () => { + fixtures.multisigOutputTemplate.forEach(fixture => { + it(`should encode multisig output`, () => { + const pubKeys = fixture.pubKeys.map(p => Buffer.from(p, "hex")) + const m = pubKeys.length + const buf = bchjs.Script.multisig.output.encode(m, pubKeys) + + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode multisig output`, () => { + const output = bchjs.Script.multisig.output.decode( + Buffer.from(`${fixture.hex}`, "hex") + ) + assert.equal(output.m, fixture.pubKeys.length) + }) + + it(`should confirm correctly formatted multisig output`, () => { + const pubKeys = fixture.pubKeys.map(p => Buffer.from(p, "hex")) + const m = pubKeys.length + const buf = bchjs.Script.multisig.output.encode(m, pubKeys) + const valid = bchjs.Script.multisig.output.check(buf) + assert.equal(valid, true) + }) + }) + }) + }) + + describe("#scriptHashTemplate", () => { + describe("#scriptHashInputTemplate", () => { + fixtures.scriptHashInputTemplate.forEach(fixture => { + it(`should encode scriptHash input`, () => { + const buf = bchjs.Script.scriptHash.input.encode( + bchjs.Script.fromASM(fixture.redeemScriptSig), + bchjs.Script.fromASM(fixture.redeemScript) + ) + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode scriptHash input`, () => { + const redeemScriptSig = bchjs.Script.fromASM(fixture.redeemScriptSig) + const redeemScript = bchjs.Script.fromASM(fixture.redeemScript) + assert.deepEqual( + bchjs.Script.scriptHash.input.decode( + Buffer.from(fixture.hex, "hex") + ), + { + redeemScriptSig: redeemScriptSig, + redeemScript: redeemScript + } + ) + }) + + it(`should confirm correctly formatted scriptHash input`, () => { + const buf = bchjs.Script.scriptHash.input.encode( + bchjs.Script.fromASM(fixture.redeemScriptSig), + bchjs.Script.fromASM(fixture.redeemScript) + ) + const valid = bchjs.Script.scriptHash.input.check(buf) + assert.equal(valid, true) + }) + }) + }) + + describe("#scriptHashOutputTemplate", () => { + fixtures.scriptHashOutputTemplate.forEach(fixture => { + it(`should encode scriptHash output`, () => { + const redeemScript = bchjs.Script.fromASM(fixture.output) + const scriptHash = bchjs.Crypto.hash160(redeemScript) + const buf = bchjs.Script.scriptHash.output.encode(scriptHash) + + assert.equal(buf.toString("hex"), fixture.hex) + }) + + it(`should decode scriptHash output`, () => { + const redeemScript = bchjs.Script.fromASM(fixture.output) + const scriptHash = bchjs.Crypto.hash160(redeemScript) + const buf = bchjs.Script.scriptHash.output.decode( + Buffer.from(`${fixture.hex}`, "hex") + ) + assert.deepEqual(buf, scriptHash) + }) + + it(`should confirm correctly formatted scriptHash output`, () => { + const redeemScript = bchjs.Script.fromASM(fixture.output) + const scriptHash = bchjs.Crypto.hash160(redeemScript) + const buf = bchjs.Script.scriptHash.output.encode(scriptHash) + const valid = bchjs.Script.scriptHash.output.check(buf) + assert.equal(valid, true) + }) + }) + }) + }) +}) diff --git a/test/unit/slp-address.js b/test/unit/slp-address.js new file mode 100644 index 0000000..6e68bcd --- /dev/null +++ b/test/unit/slp-address.js @@ -0,0 +1,838 @@ +const assert = require("assert") + +const slp = require("../../src/slp/slp") +const SLP = new slp({ restURL: "http://fakeurl.com/" }) + +const fixtures = require("./fixtures/slp/address.json") +//const axios = require("axios") +//const sinon = require("sinon") + +function flatten(arrays) { + return [].concat.apply([], arrays) +} + +const LEGACY_MAINNET_ADDRESSES = flatten([ + fixtures.mainnet.legacyP2PKH, + fixtures.mainnet.legacyP2SH +]) + +const CASH_MAINNET_ADDRESSES = flatten([ + fixtures.mainnet.cashAddressP2PKH, + fixtures.mainnet.cashAddressP2SH +]) + +const SLP_MAINNET_ADDRESSES = flatten([ + fixtures.mainnet.slpAddressP2PKH, + fixtures.mainnet.slpAddressP2SH +]) + +const LEGACY_TESTNET_ADDRESSES = flatten([ + fixtures.testnet.legacyP2PKH, + fixtures.testnet.legacyP2SH +]) + +const CASH_TESTNET_ADDRESSES = flatten([ + fixtures.testnet.cashAddressP2PKH, + fixtures.testnet.cashAddressP2SH +]) + +const SLP_TESTNET_ADDRESSES = flatten([ + fixtures.testnet.slpAddressP2PKH, + fixtures.testnet.slpAddressP2SH +]) + +const MAINNET_P2PKH_ADDRESSES = flatten([ + fixtures.mainnet.legacyP2PKH, + fixtures.mainnet.cashAddressP2PKH, + fixtures.mainnet.slpAddressP2PKH +]) + +const TESTNET_P2PKH_ADDRESSES = flatten([ + fixtures.testnet.legacyP2PKH, + fixtures.testnet.cashAddressP2PKH, + fixtures.testnet.slpAddressP2PKH +]) + +const MAINNET_P2SH_ADDRESSES = flatten([ + fixtures.mainnet.legacyP2SH, + fixtures.mainnet.cashAddressP2SH, + fixtures.mainnet.slpAddressP2SH +]) + +const TESTNET_P2SH_ADDRESSES = flatten([ + fixtures.testnet.legacyP2SH, + fixtures.testnet.cashAddressP2SH, + fixtures.testnet.slpAddressP2SH +]) +/* +const CASH_MAINNET_ADDRESSES_NO_PREFIX = CASH_MAINNET_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) + +const CASH_TESTNET_ADDRESSES_NO_PREFIX = CASH_TESTNET_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) + +const SLP_MAINNET_ADDRESSES_NO_PREFIX = SLP_MAINNET_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) + +const SLP_TESTNET_ADDRESSES_NO_PREFIX = SLP_TESTNET_ADDRESSES.map(address => { + const parts = address.split(":") + return parts[1] +}) +*/ +describe("#SLP Address", () => { + describe("#mainnet", () => { + describe("#toLegacyAddress", () => { + it("should convert mainnet legacy address format to itself correctly", () => { + assert.deepEqual( + LEGACY_MAINNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_MAINNET_ADDRESSES + ) + }) + + it(`should convert cashAddr to legacyAddr`, async () => { + assert.deepEqual( + CASH_MAINNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_MAINNET_ADDRESSES + ) + }) + + it(`should convert slpAddr to legacyAddr`, async () => { + assert.deepEqual( + SLP_MAINNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_MAINNET_ADDRESSES + ) + }) + }) + + describe("#toCashAddress", () => { + it("should convert mainnet cash address format to itself correctly", () => { + assert.deepEqual( + CASH_MAINNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_MAINNET_ADDRESSES + ) + }) + + it(`should convert legacyAddr to cashAddr`, async () => { + assert.deepEqual( + LEGACY_MAINNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_MAINNET_ADDRESSES + ) + }) + + it(`should convert slpAddr to cashAddr`, async () => { + assert.deepEqual( + SLP_MAINNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_MAINNET_ADDRESSES + ) + }) + }) + + describe("#toSLPAddress", () => { + it("should convert mainnet slp address format to itself correctly", () => { + assert.deepEqual( + SLP_MAINNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_MAINNET_ADDRESSES + ) + }) + + it(`should convert legacyAddr to slpAddr`, async () => { + assert.deepEqual( + LEGACY_MAINNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_MAINNET_ADDRESSES + ) + }) + + it(`should convert cashAddr to slpAddr`, async () => { + assert.deepEqual( + CASH_MAINNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_MAINNET_ADDRESSES + ) + }) + }) + + describe("#isLegacyAddress", () => { + describe("is legacy addr", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, true) + }) + }) + }) + + describe("cashaddr is not legacy addr", () => { + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, false) + }) + }) + }) + + describe("slpaddr is not legacy addr", () => { + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, false) + }) + }) + }) + }) + + describe("#isCashAddress", () => { + describe("is cashaddr", () => { + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cashaddr address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, true) + }) + }) + }) + + describe("legacy is not cash addr", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a cash address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, false) + }) + }) + }) + + describe("slpaddr is not cash addr", () => { + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a cash address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, false) + }) + }) + }) + }) + + describe("#isSLPAddress", () => { + describe("is slpaddr", () => { + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, true) + }) + }) + }) + + describe("legacy is not slp addr", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, false) + }) + }) + }) + + describe("cash is not slp addr", () => { + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, false) + }) + }) + }) + }) + + describe("#isMainnetAddress", () => { + describe("mainnet legacy addr", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, true) + }) + }) + }) + + describe("mainnet cash addr", () => { + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, true) + }) + }) + }) + + describe("mainnet slp addr", () => { + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, true) + }) + }) + }) + + describe("testnet legacy addr", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, false) + }) + }) + }) + + describe("testnet cash addr", () => { + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, false) + }) + }) + }) + + describe("testnet slp addr", () => { + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a mainnet address`, () => { + const isMainnetaddr = SLP.Address.isMainnetAddress(address) + assert.equal(isMainnetaddr, false) + }) + }) + }) + }) + + describe("#isP2PKHAddress", () => { + describe("mainnet legacy addr", () => { + MAINNET_P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2PKH address`, () => { + const isP2PKHaddr = SLP.Address.isP2PKHAddress(address) + assert.equal(isP2PKHaddr, true) + }) + }) + }) + }) + + describe("#isP2SHAddress", () => { + describe("mainnet legacy addr", () => { + MAINNET_P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2SH address`, () => { + const isP2SHaddr = SLP.Address.isP2SHAddress(address) + assert.equal(isP2SHaddr, true) + }) + }) + }) + }) + + describe("#detectAddressFormat", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy address`, () => { + const isLegacy = SLP.Address.detectAddressFormat(address) + assert.equal(isLegacy, "legacy") + }) + }) + + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cash address`, () => { + const isCashaddr = SLP.Address.detectAddressFormat(address) + assert.equal(isCashaddr, "cashaddr") + }) + }) + + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is an slp address`, () => { + const isSlpaddr = SLP.Address.detectAddressFormat(address) + assert.equal(isSlpaddr, "slpaddr") + }) + }) + }) + + describe("#detectAddressNetwork", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isMainnet, "mainnet") + }) + }) + + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isMainnet, "mainnet") + }) + }) + + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a mainnet address`, () => { + const isMainnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isMainnet, "mainnet") + }) + }) + }) + + describe("#detectAddressType", () => { + MAINNET_P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a p2pkh address`, () => { + const isp2pkh = SLP.Address.detectAddressType(address) + assert.equal(isp2pkh, "p2pkh") + }) + }) + + MAINNET_P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a p2sh address`, () => { + const isp2sh = SLP.Address.detectAddressType(address) + assert.equal(isp2sh, "p2sh") + }) + }) + }) + }) + + describe("#testnet", () => { + describe("#toLegacyAddress", () => { + it("should convert testnet legacy address format to itself correctly", () => { + assert.deepEqual( + LEGACY_TESTNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_TESTNET_ADDRESSES + ) + }) + + it(`should convert cashAddr to legacyAddr`, async () => { + assert.deepEqual( + CASH_TESTNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_TESTNET_ADDRESSES + ) + }) + + it(`should convert slpAddr to legacyAddr`, async () => { + assert.deepEqual( + SLP_TESTNET_ADDRESSES.map(address => + SLP.Address.toLegacyAddress(address) + ), + LEGACY_TESTNET_ADDRESSES + ) + }) + }) + + describe("#toCashAddress", () => { + it("should convert testnet cash address format to itself correctly", () => { + assert.deepEqual( + CASH_TESTNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_TESTNET_ADDRESSES + ) + }) + + it(`should convert legacyAddr to cashAddr`, async () => { + assert.deepEqual( + LEGACY_TESTNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_TESTNET_ADDRESSES + ) + }) + + it(`should convert slpAddr to cashAddr`, async () => { + assert.deepEqual( + SLP_TESTNET_ADDRESSES.map(address => + SLP.Address.toCashAddress(address) + ), + CASH_TESTNET_ADDRESSES + ) + }) + }) + + describe("#toSLPAddress", () => { + it("should convert testnet slp address format to itself correctly", () => { + assert.deepEqual( + SLP_TESTNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_TESTNET_ADDRESSES + ) + }) + + it(`should convert legacyAddr to slpAddr`, async () => { + assert.deepEqual( + LEGACY_TESTNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_TESTNET_ADDRESSES + ) + }) + + it(`should convert cashAddr to slpAddr`, async () => { + assert.deepEqual( + CASH_TESTNET_ADDRESSES.map(address => + SLP.Address.toSLPAddress(address) + ), + SLP_TESTNET_ADDRESSES + ) + }) + }) + + describe("#isLegacyAddress", () => { + describe("is legacy addr", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, true) + }) + }) + }) + + describe("cashaddr is not legacy addr", () => { + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, false) + }) + }) + }) + + describe("slpaddr is not legacy addr", () => { + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a legacy address`, () => { + const isLegacyaddr = SLP.Address.isLegacyAddress(address) + assert.equal(isLegacyaddr, false) + }) + }) + }) + }) + + describe("#isCashAddress", () => { + describe("is cashaddr", () => { + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cashaddr address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, true) + }) + }) + }) + + describe("legacy is not cash addr", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a cash address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, false) + }) + }) + }) + + describe("slpaddr is not cash addr", () => { + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a cash address`, () => { + const isCashaddr = SLP.Address.isCashAddress(address) + assert.equal(isCashaddr, false) + }) + }) + }) + }) + + describe("#isSLPAddress", () => { + describe("is slpaddr", () => { + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, true) + }) + }) + }) + + describe("legacy is not slp addr", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, false) + }) + }) + }) + + describe("cash is not slp addr", () => { + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not an slp address`, () => { + const isSLPaddr = SLP.Address.isSLPAddress(address) + assert.equal(isSLPaddr, false) + }) + }) + }) + }) + + describe("#isTestnetAddress", () => { + describe("testnet legacy addr", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, true) + }) + }) + }) + + describe("testnet cash addr", () => { + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, true) + }) + }) + }) + + describe("testnet slp addr", () => { + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, true) + }) + }) + }) + + describe("mainnet legacy addr", () => { + LEGACY_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, false) + }) + }) + }) + + describe("mainnet cash addr", () => { + CASH_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, false) + }) + }) + }) + + describe("mainnet slp addr", () => { + SLP_MAINNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is not a testnet address`, () => { + const isTestnetaddr = SLP.Address.isTestnetAddress(address) + assert.equal(isTestnetaddr, false) + }) + }) + }) + }) + + describe("#isP2PKHAddress", () => { + describe("testnet legacy addr", () => { + TESTNET_P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2PKH address`, () => { + const isP2PKHaddr = SLP.Address.isP2PKHAddress(address) + assert.equal(isP2PKHaddr, true) + }) + }) + }) + }) + + describe("#isP2SHAddress", () => { + describe("testnet legacy addr", () => { + TESTNET_P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a P2SH address`, () => { + const isP2SHaddr = SLP.Address.isP2SHAddress(address) + assert.equal(isP2SHaddr, true) + }) + }) + }) + }) + + describe("#detectAddressFormat", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a legacy address`, () => { + const isLegacy = SLP.Address.detectAddressFormat(address) + assert.equal(isLegacy, "legacy") + }) + }) + + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a cash address`, () => { + const isCashaddr = SLP.Address.detectAddressFormat(address) + assert.equal(isCashaddr, "cashaddr") + }) + }) + + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is an slp address`, () => { + const isSlpaddr = SLP.Address.detectAddressFormat(address) + assert.equal(isSlpaddr, "slpaddr") + }) + }) + }) + + describe("#detectAddressNetwork", () => { + LEGACY_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isTestnet, "testnet") + }) + }) + + CASH_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isTestnet, "testnet") + }) + }) + + SLP_TESTNET_ADDRESSES.forEach(address => { + it(`should detect ${address} is a testnet address`, () => { + const isTestnet = SLP.Address.detectAddressNetwork(address) + assert.equal(isTestnet, "testnet") + }) + }) + }) + + describe("#detectAddressType", () => { + TESTNET_P2PKH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a p2pkh address`, () => { + const isp2pkh = SLP.Address.detectAddressType(address) + assert.equal(isp2pkh, "p2pkh") + }) + }) + }) + + describe("#detectAddressType", () => { + TESTNET_P2SH_ADDRESSES.forEach(address => { + it(`should detect ${address} is a p2sh address`, () => { + const isp2sh = SLP.Address.detectAddressType(address) + assert.equal(isp2sh, "p2sh") + }) + }) + }) + }) +}) +/* +describe("#details", () => { + let sandbox + beforeEach(() => (sandbox = sinon.sandbox.create())) + afterEach(() => sandbox.restore()) + + it("should get details", done => { + const data = { + balance: 0.00000546, + balanceSat: 546, + totalReceived: 0.00426132, + totalReceivedSat: 426132, + totalSent: 0.00425586, + totalSentSat: 425586, + unconfirmedBalance: 0, + unconfirmedBalanceSat: 0, + unconfirmedTxApperances: 0, + txApperances: 3, + transactions: [ + "902fe6ed7a19570c032b3ba4c4d7af92804662b486a15c8ca2d284166c658dd4", + "467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5", + "c1d9f3490e96a1fe4f77195067f1ab12c787f79b39d107424e0c4c810098e11b" + ], + legacyAddress: "1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS", + cashAddress: "bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y", + slpAddress: "simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6", + currentPage: 0, + pagesTotal: 1 + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + SLP.Address.details( + "simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) +}) + +describe("#utxo", () => { + let sandbox + beforeEach(() => (sandbox = sinon.sandbox.create())) + afterEach(() => sandbox.restore()) + + it("should get utxo", done => { + const data = { + utxos: [ + { + txid: + "467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 568215, + confirmations: 24 + } + ], + legacyAddress: "1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS", + cashAddress: "bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y", + scriptPubKey: "76a914ea2478cbb9f44100b547cf264d34155dce044b8a88ac" + } + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + SLP.Address.utxo("simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6") + .then(result => { + assert.deepEqual( + "467969e067f5612863d0bf2daaa70dede2c6be03abb6fd401c5ef6e1e1f1f5c5", + result.utxos[0].txid + ) + }) + .then(done, done) + }) +}) + +describe("#unconfirmed", () => { + let sandbox + beforeEach(() => (sandbox = sinon.sandbox.create())) + afterEach(() => sandbox.restore()) + + it("should get unconfirmed transactions", done => { + const data = { + utxos: [], + legacyAddress: "1NM2ozrXVSnMRm66ua6aGeXgMsU7yqwqLS", + cashAddress: "bitcoincash:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3g4hl47n9y", + slpAddress: "simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6", + scriptPubKey: "76a914ea2478cbb9f44100b547cf264d34155dce044b8a88ac" + } + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + SLP.Address.unconfirmed( + "simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) +}) +*/ + +// (async () => { +// try { +// const details = await SLP.Address.unconfirmed( +// "simpleledger:qr4zg7xth86yzq94gl8jvnf5z4wuupzt3gev5wtnm6" +// ) +// console.log(details) +// } catch (error) { +// console.error(error) +// } +// })() diff --git a/test/unit/slp-ecpair.js b/test/unit/slp-ecpair.js new file mode 100644 index 0000000..3b77972 --- /dev/null +++ b/test/unit/slp-ecpair.js @@ -0,0 +1,17 @@ +const fixtures = require("./fixtures/slp/ecpair.json") +const assert = require("assert") + +const SLP = require("../../src/slp/slp") +const slp = new SLP({ restURL: "http://fakeurl.com/" }) + +describe("#SLP ECPair", () => { + describe("#toSLPAddress", () => { + it(`should return slp address for ecpair`, async () => { + fixtures.wif.forEach((wif, index) => { + const ecpair = slp.ECPair.fromWIF(wif) + const slpAddr = slp.ECPair.toSLPAddress(ecpair) + assert.equal(slpAddr, fixtures.address[index]) + }) + }) + }) +}) diff --git a/test/unit/slp-nft1.js b/test/unit/slp-nft1.js new file mode 100644 index 0000000..9e9640b --- /dev/null +++ b/test/unit/slp-nft1.js @@ -0,0 +1,264 @@ +/* + Unit tests for the TokenType1 library. +*/ + +const assert = require("chai").assert +const nock = require("nock") // http call mocking +const sinon = require("sinon") +// const axios = require("axios") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" +// const SERVER = bchjs.restURL + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +// Mock data used for unit tests +// const mockData = require("./fixtures/slp/mock-utils") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" + +describe("#SLP NFT1", () => { + let sandbox + + beforeEach(() => { + // Activate nock if it's inactive. + if (!nock.isActive()) nock.activate() + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + // Clean up HTTP mocks. + nock.cleanAll() // clear interceptor list. + nock.restore() + + sandbox.restore() + }) + + describe("#newNFTGroupOpReturn", () => { + it("should generate new NFT Group OP_RETURN code", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + documentHash: "", + initialQty: 5 + } + + const result = bchjs.SLP.NFT1.newNFTGroupOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#mintNFTGroupOpReturn", () => { + it("should generate NFT Group Mint OP_RETURN code", () => { + const tokenUtxoData = [ + { + txid: + "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76", + vout: 2, + value: "546", + height: 638207, + confirmations: 63, + satoshis: 546, + utxoType: "minting-baton", + transactionType: "mint", + tokenId: + "680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e", + tokenType: 129, + tokenTicker: "NFTP", + tokenName: "NFT Parent", + tokenDocumentUrl: "FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + }, + { + txid: + "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76", + vout: 1, + value: "546", + height: 638207, + confirmations: 63, + satoshis: 546, + utxoType: "token", + tokenQty: 10, + transactionType: "mint", + tokenId: + "680967b3f6fe080dbca8dbd370665bd29742e3490db24e2f28d08b424511807e", + tokenType: 129, + tokenTicker: "NFTP", + tokenName: "NFT Parent", + tokenDocumentUrl: "FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.mintNFTGroupOpReturn(tokenUtxoData, 10) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#generateNFTChildOpReturn", () => { + it("should generate NFT Genesis OP_RETURN code", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + documentHash: "" + } + + const result = bchjs.SLP.NFT1.generateNFTChildGenesisOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#generateNFTChildSendOpReturn", () => { + it("should generate send OP_RETURN code for no change", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3", + vout: 1, + value: "546", + height: 638273, + confirmations: 42, + satoshis: 546, + utxoType: "token", + tokenQty: 1, + tokenId: + "81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3", + tokenTicker: "NFTC", + tokenName: "NFT Child", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + tokenType: 129, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.generateNFTChildSendOpReturn(tokenUtxos, 1) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["script", "outputs"]) + assert.isNumber(result.outputs) + }) + + it("should generate send OP_RETURN code with change", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3", + vout: 1, + value: "546", + height: 638273, + confirmations: 42, + satoshis: 546, + utxoType: "token", + tokenQty: 4, + tokenId: + "81955624a8eb7011769ff5faa607f78f84b0f8152b9145e7db8b1e521c895ee3", + tokenTicker: "NFTC", + tokenName: "NFT Child", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + tokenType: 129, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.generateNFTChildSendOpReturn(tokenUtxos, 1) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["script", "outputs"]) + assert.isNumber(result.outputs) + }) + }) + + describe("#generateNFTGroupSendOpReturn", () => { + it("should generate send OP_RETURN with change", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + vout: 1, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546, + utxoType: "token", + tokenQty: 10, + transactionType: "mint", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + tokenType: 129, + tokenTicker: "NFTTT", + tokenName: "NFT Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.generateNFTGroupSendOpReturn(tokenUtxos, 1) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["script", "outputs"]) + assert.isNumber(result.outputs) + }) + + it("should generate send OP_RETURN with no change", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + vout: 1, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546, + utxoType: "token", + tokenQty: 10, + transactionType: "mint", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + tokenType: 129, + tokenTicker: "NFTTT", + tokenName: "NFT Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 0, + mintBatonVout: 2, + isValid: true + } + ] + + const result = bchjs.SLP.NFT1.generateNFTGroupSendOpReturn(tokenUtxos, 10) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["script", "outputs"]) + assert.isNumber(result.outputs) + }) + }) +}) diff --git a/test/unit/slp-tokentype1.js b/test/unit/slp-tokentype1.js new file mode 100644 index 0000000..23fa94f --- /dev/null +++ b/test/unit/slp-tokentype1.js @@ -0,0 +1,319 @@ +/* + Unit tests for the TokenType1 library. +*/ + +const assert = require("chai").assert +const nock = require("nock") // http call mocking +const sinon = require("sinon") +// const axios = require("axios") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" +// const SERVER = bchjs.restURL + +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() + +// Mock data used for unit tests +// const mockData = require("./fixtures/slp/mock-utils") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" + +describe("#SLP TokenType1", () => { + let sandbox + + beforeEach(() => { + // Activate nock if it's inactive. + if (!nock.isActive()) nock.activate() + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + // Clean up HTTP mocks. + nock.cleanAll() // clear interceptor list. + nock.restore() + + sandbox.restore() + }) + + describe("#generateSendOpReturn", () => { + it("should generate send OP_RETURN code", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", + vout: 2, + value: "546", + confirmations: 0, + satoshis: 546, + utxoType: "token", + transactionType: "send", + tokenId: + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + tokenTicker: "TOK-CH", + tokenName: "TokyoCash", + tokenDocumentUrl: "", + tokenDocumentHash: "", + decimals: 8, + tokenQty: 7 + } + ] + + const result = bchjs.SLP.TokenType1.generateSendOpReturn(tokenUtxos, 1) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.hasAllKeys(result, ["script", "outputs"]) + assert.isNumber(result.outputs) + }) + }) + + describe("#generateBurnOpReturn", () => { + it("should generate burn OP_RETURN code", () => { + // Mock UTXO. + const tokenUtxos = [ + { + txid: + "a8eb788b8ddda6faea00e6e2756624b8feb97655363d0400dd66839ea619d36e", + vout: 2, + value: "546", + confirmations: 0, + satoshis: 546, + utxoType: "token", + transactionType: "send", + tokenId: + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + tokenTicker: "TOK-CH", + tokenName: "TokyoCash", + tokenDocumentUrl: "", + tokenDocumentHash: "", + decimals: 8, + tokenQty: 7 + } + ] + + const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 1) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#generateGenesisOpReturn", () => { + it("should generate genesis OP_RETURN code", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + documentHash: "", + decimals: 8, + initialQty: 10 + } + + const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + + it("should work if user does not specify doc hash", () => { + const configObj = { + name: "SLP Test Token", + ticker: "SLPTEST", + documentUrl: "https://bchjs.cash", + decimals: 8, + initialQty: 10 + } + + const result = bchjs.SLP.TokenType1.generateGenesisOpReturn(configObj) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#generateMintOpReturn", () => { + it("should throw error if tokenUtxos is not an array.", () => { + try { + bchjs.SLP.TokenType1.generateMintOpReturn({}, 100) + + assert.equal(true, false, "Unexpected result.") + } catch (err) { + assert.include( + err.message, + `tokenUtxos must be an array`, + "Expected error message." + ) + } + }) + + it("should throw error if minting baton is not in UTXOs.", () => { + try { + const utxos = [ + { + txid: + "ccc6d336399e26d98afcd3821b41fb1535cd50f57063ed7593eaed5108659606", + vout: 1, + value: "546", + height: 637618, + confirmations: 239, + satoshis: 546, + utxoType: "token", + tokenQty: 100, + tokenId: + "ccc6d336399e26d98afcd3821b41fb1535cd50f57063ed7593eaed5108659606", + tokenTicker: "SLPTEST", + tokenName: "SLP Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 8, + isValid: true + } + ] + + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + + assert.equal(true, false, "Unexpected result.") + } catch (err) { + assert.include( + err.message, + `Minting baton could not be found in tokenUtxos array`, + "Expected error message." + ) + } + }) + + it("should throw error if tokenId is not included in minting-baton UTXO.", () => { + try { + const utxos = [ + { + txid: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + vout: 2, + value: "546", + height: 637625, + confirmations: 207, + satoshis: 546, + utxoType: "minting-baton", + tokenTicker: "SLPTEST", + tokenName: "SLP Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 8, + isValid: true + } + ] + + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + + assert.equal(true, false, "Unexpected result.") + } catch (err) { + assert.include( + err.message, + `tokenId property not found in mint-baton UTXO`, + "Expected error message." + ) + } + }) + + it("should throw error if decimals is not included in minting-baton UTXO.", () => { + try { + const utxos = [ + { + txid: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + vout: 2, + value: "546", + height: 637625, + confirmations: 207, + satoshis: 546, + utxoType: "minting-baton", + tokenId: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + tokenTicker: "SLPTEST", + tokenName: "SLP Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + isValid: true + } + ] + + bchjs.SLP.TokenType1.generateMintOpReturn(utxos, 100) + + assert.equal(true, false, "Unexpected result.") + } catch (err) { + assert.include( + err.message, + `decimals property not found in mint-baton UTXO`, + "Expected error message." + ) + } + }) + + it("should generate genesis OP_RETURN code", () => { + tokenUtxo = [ + { + txid: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + vout: 2, + value: "546", + height: 637625, + confirmations: 207, + satoshis: 546, + utxoType: "minting-baton", + tokenId: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + tokenTicker: "SLPTEST", + tokenName: "SLP Test Token", + tokenDocumentUrl: "https://FullStack.cash", + tokenDocumentHash: "", + decimals: 8, + isValid: true + } + ] + + const result = bchjs.SLP.TokenType1.generateMintOpReturn(tokenUtxo, 100) + // console.log(`result: `, result) + + assert.equal(Buffer.isBuffer(result), true) + }) + }) + + describe("#getHexOpReturn", () => { + it("should return OP_RETURN object ", async () => { + const tokenUtxos = [ + { + tokenId: + "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1", + decimals: 0, + tokenQty: 2 + } + ] + + const sendQty = 1.5 + + sandbox.stub(bchjs.SLP.TokenType1.axios, "post").resolves({ + data: { + script: + "6a04534c500001010453454e44200a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1080000000000000001080000000000000000", + outputs: 2 + } + }) + + const result = await bchjs.SLP.TokenType1.getHexOpReturn( + tokenUtxos, + sendQty + ) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, "script") + assert.isString(result.script) + + assert.property(result, "outputs") + assert.isNumber(result.outputs) + }) + }) +}) diff --git a/test/unit/slp-utils.js b/test/unit/slp-utils.js new file mode 100644 index 0000000..aed1812 --- /dev/null +++ b/test/unit/slp-utils.js @@ -0,0 +1,1779 @@ +const assert = require("assert") +const assert2 = require("chai").assert + +const SLP = require("../../src/slp/slp") +//const slp = new SLP("http://decatur.hopto.org:12400/v3/") +//const slp = new SLP("https://rest.bitcoin.com/v2/") +const slp = new SLP({ + restURL: "https://api.fullstack.cash/v3/", + apiToken: process.env.BCHJSTOKEN +}) + +const nock = require("nock") // http call mocking +const sinon = require("sinon") +const axios = require("axios") + +// Mock data used for unit tests +const mockData = require("./fixtures/slp/mock-utils") + +// Default to unit tests unless some other value for TEST is passed. +if (!process.env.TEST) process.env.TEST = "unit" +const SERVER = slp.restURL + +// Used for debugging and iterrogating JS objects. +const util = require("util") +util.inspect.defaultOptions = { depth: 1 } + +describe("#SLP Utils", () => { + let sandbox + + beforeEach(() => { + // Activate nock if it's inactive. + if (!nock.isActive()) nock.activate() + + sandbox = sinon.createSandbox() + }) + + afterEach(() => { + // Clean up HTTP mocks. + nock.cleanAll() // clear interceptor list. + nock.restore() + + sandbox.restore() + }) + + describe("#list", () => { + it(`should list all SLP tokens`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockList) + } + + const list = await slp.Utils.list() + //console.log(`list: ${JSON.stringify(list, null, 2)}`) + + assert2.isArray(list) + assert2.hasAllKeys(list[0], [ + "blockCreated", + "blockLastActiveMint", + "blockLastActiveSend", + "circulatingSupply", + "containsBaton", + "decimals", + "documentHash", + "documentUri", + "id", + "initialTokenQty", + "mintingBatonStatus", + "name", + "symbol", + "timestamp", + "timestampUnix", + "totalBurned", + "totalMinted", + "txnsSinceGenesis", + "validAddresses", + "versionType" + ]) + }) + + it(`should list single SLP token by id: 4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockToken) + } + + const tokenId = + "4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84" + + const list = await slp.Utils.list(tokenId) + //console.log(`list: ${JSON.stringify(list, null, 2)}`) + + assert2.hasAllKeys(list, [ + "blockCreated", + "blockLastActiveMint", + "blockLastActiveSend", + "circulatingSupply", + "containsBaton", + "decimals", + "documentHash", + "documentUri", + "id", + "initialTokenQty", + "mintingBatonStatus", + "name", + "symbol", + "timestamp", + "timestampUnix", + "totalBurned", + "totalMinted", + "txnsSinceGenesis", + "validAddresses", + "versionType" + ]) + assert.equal(list.id, tokenId) + }) + + it(`should list multiple SLP tokens by array of ids`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .post(uri => uri.includes("/")) + .reply(200, mockData.mockTokens) + } + + const tokenIds = [ + "4276533bb702e7f8c9afd8aa61ebf016e95011dc3d54e55faa847ac1dd461e84", + "b3f4f132dc3b9c8c96316346993a8d54d729715147b7b11aa6c8cd909e955313" + ] + + const list = await slp.Utils.list(tokenIds) + // console.log(`list: ${JSON.stringify(list, null, 2)}`) + + assert2.hasAllKeys(list[0], [ + "blockCreated", + "blockLastActiveMint", + "blockLastActiveSend", + "circulatingSupply", + "containsBaton", + "decimals", + "documentHash", + "documentUri", + "id", + "initialTokenQty", + "mintingBatonStatus", + "name", + "symbol", + "timestamp", + "timestampUnix", + "totalBurned", + "totalMinted", + "txnsSinceGenesis", + "validAddresses", + "versionType" + ]) + assert.equal(list[0].id, tokenIds[0]) + }) + }) + + describe("#balancesForAddress", () => { + it(`should throw an error if input is not a string or array of strings`, async () => { + try { + const address = 1234 + + await slp.Utils.balancesForAddress(address) + + assert2.equal(true, false, "Uh oh. Code path should not end here.") + } catch (err) { + //console.log(`Error: `, err) + assert2.include( + err.message, + `Input address must be a string or array of strings` + ) + } + }) + + it(`should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.balancesForAddress }) + } + //sandbox + // .stub(slp.Utils, "balancesForAddress") + // .resolves(mockData.balancesForAddress) + + const balances = await slp.Utils.balancesForAddress( + "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9" + ) + // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert2.isArray(balances) + assert2.hasAllKeys(balances[0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + + it(`should fetch balances for multiple addresses.`, async () => { + const addresses = [ + "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + "simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57" + ] + + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "post") + .resolves({ data: mockData.balancesForAddresses }) + } + + const balances = await slp.Utils.balancesForAddress(addresses) + //console.log(`balances: ${JSON.stringify(balances, null, 2)}`) + + assert2.isArray(balances) + assert2.isArray(balances[0]) + assert2.hasAllKeys(balances[0][0], [ + "tokenId", + "balanceString", + "balance", + "decimalCount", + "slpAddress" + ]) + }) + }) + + describe("#balance", () => { + it(`should fetch balance of single token for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") + sandbox.stub(axios, "get").resolves({ data: mockData.mockBalance }) + + const balance = await slp.Utils.balance( + "simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9", + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + ) + //console.log(`balance: ${JSON.stringify(balance, null, 2)}`) + + assert2.hasAllKeys(balance, ["tokenId", "balance", "balanceString"]) + }) + }) + + describe("#validateTxid", () => { + it(`should validate slp txid`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .post(uri => uri.includes("/")) + .reply(200, mockData.mockIsValidTxid) + } + + const isValid = await slp.Utils.validateTxid( + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + ) + assert.deepEqual(isValid, [ + { + txid: + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb", + valid: true + } + ]) + }) + }) + + describe("#balancesForToken", () => { + it(`should retrieve token balances for a given tokenId`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockBalancesForToken) + } + + const balances = await slp.Utils.balancesForToken( + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + ) + assert2.hasAnyKeys(balances[0], ["tokenBalance", "slpAddress"]) + }) + }) + + describe("#tokenStats", () => { + it(`should retrieve stats for a given tokenId`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockTokenStats) + } + + const tokenStats = await slp.Utils.tokenStats( + "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" + ) + assert2.hasAnyKeys(tokenStats, [ + "circulatingSupply", + "decimals", + "documentUri", + "name", + "satoshisLockedUp", + "symbol", + "tokenId", + "totalBurned", + "totalMinted", + "txnsSinceGenesis", + "validAddresses", + "validUtxos" + ]) + }) + }) + + describe("#transactions", () => { + it(`should retrieve transactions for a given tokenId and address`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockTransactions) + } + + const transactions = await slp.Utils.transactions( + "495322b37d6b2eae81f045eda612b95870a0c2b6069c58f70cf8ef4e6a9fd43a", + "simpleledger:qrhvcy5xlegs858fjqf8ssl6a4f7wpstaqnt0wauwu" + ) + assert2.hasAnyKeys(transactions[0], ["txid", "tokenDetails"]) + }) + }) + + describe("#burnTotal", () => { + it(`should retrieve input, output and burn totals`, async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + nock(SERVER) + .get(uri => uri.includes("/")) + .reply(200, mockData.mockBurnTotal) + } + + const burnTotal = await slp.Utils.burnTotal( + "c7078a6c7400518a513a0bde1f4158cf740d08d3b5bfb19aa7b6657e2f4160de" + ) + //console.log(`burnTotal: ${JSON.stringify(burnTotal, null, 2)}`) + + assert2.hasAnyKeys(burnTotal, [ + "transactionId", + "inputTotal", + "outputTotal", + "burnTotal" + ]) + }) + }) + + describe("#decodeOpReturn", () => { + it("should throw an error for a non-string input", async () => { + try { + const txid = 53423 // Not a string. + + await slp.Utils.decodeOpReturn(txid) + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + //console.log(`err: ${util.inspect(err)}`) + assert2.include(err.message, `txid string must be included`) + } + }) + + it("should throw an error for non-SLP transaction", async () => { + try { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) + } + + const txid = + "3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec" + + await slp.Utils.decodeOpReturn(txid) + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + // console.log(`err: ${util.inspect(err)}`) + assert2.include(err.message, `scriptpubkey not op_return`) + } + }) + + it("should throw an error for non-SLP transaction with OP_RETURN", async () => { + try { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.nonSLPTxDetailsWithOpReturn }) + } + + const txid = + "2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f" + + await slp.Utils.decodeOpReturn(txid) + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + // console.log(`err: ${util.inspect(err)}`) + assert2.include(err.message, `SLP not in first chunk`) + } + }) + + it("should decode a genesis transaction", async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.txDetailsSLPGenesis }) + } + + const txid = + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90" + + const result = await slp.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.hasAllKeys(result, [ + "tokenType", + "txType", + "tokenId", + "ticker", + "name", + "documentUri", + "documentHash", + "decimals", + "mintBatonVout", + "qty" + ]) + }) + + it("should decode a mint transaction", async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") + sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPMint }) + + const txid = + "65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4" + + const result = await slp.Utils.decodeOpReturn(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.hasAllKeys(result, [ + "tokenType", + "txType", + "tokenId", + "mintBatonVout", + "qty" + ]) + }) + + it("should decode a send transaction", async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") + sandbox.stub(axios, "get").resolves({ data: mockData.txDetailsSLPSend }) + + const txid = + "4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa" + + const result = await slp.Utils.decodeOpReturn(txid) + //console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.hasAllKeys(result, ["tokenType", "txType", "tokenId", "amounts"]) + }) + + it("should properly decode a Genesis transaction with no minting baton", async () => { + // Mock the call to the REST API. + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.txDetailsSLPGenesisNoBaton }) + } + + const txid = + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" + + const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.equal(data.mintBatonVout, 0) + }) + + it("should decode a send transaction with alternate encoding", async () => { + // Mock the call to rest.bitcoin.com + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.txDetailsSLPSendAlt }) + } + + const txid = + "d94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae" + + const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.hasAnyKeys(data, [ + "transactionType", + "txType", + "tokenId", + "amounts" + ]) + }) + + // Note: This TX is interpreted as valid by the original decodeOpReturn(). + // Fixing this issue and related issues was the reason for creating the + // decodeOpReturn2() method using the slp-parser library. + it("should throw error for invalid SLP transaction", async () => { + try { + // Mock the call to rest.bitcoin.com + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + .resolves({ data: mockData.mockInvalidSlpSend }) + } + + const txid = + "a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415" + + await slp.Utils.decodeOpReturn(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + } catch (err) { + // console.log(`err: `, err) + assert2.include(err.message, "amount string size not 8 bytes") + } + }) + + it("should decode a NFT Parent transaction", async () => { + // Mock the call to the REST API. + sandbox + .stub(axios, "get") + .resolves({ data: mockData.txDetailsSLPNftGenesis }) + + const txid = + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4" + + const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.property(data, "tokenType") + assert2.property(data, "txType") + assert2.property(data, "ticker") + assert2.property(data, "name") + assert2.property(data, "tokenId") + assert2.property(data, "documentUri") + assert2.property(data, "documentHash") + assert2.property(data, "decimals") + assert2.property(data, "mintBatonVout") + assert2.property(data, "qty") + + assert2.equal(data.tokenType, 129) + assert2.equal(data.mintBatonVout, 2) + assert2.equal(data.qty, 1) + }) + + // it("should decode a NFT Child transaction", async () => { + // // Mock the call to the REST API. + // // sandbox + // // .stub(axios, "get") + // // .resolves({ data: mockData.txDetailsSLPNftGenesis }) + // + // const txid = + // "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76" + // + // const data = await slp.Utils.decodeOpReturn(txid) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + // + // // assert2.property(data, "tokenType") + // // assert2.property(data, "txType") + // // assert2.property(data, "ticker") + // // assert2.property(data, "name") + // // assert2.property(data, "tokenId") + // // assert2.property(data, "documentUri") + // // assert2.property(data, "documentHash") + // // assert2.property(data, "decimals") + // // assert2.property(data, "mintBatonVout") + // // assert2.property(data, "qty") + // // + // // assert2.equal(data.tokenType, 129) + // // assert2.equal(data.mintBatonVout, 2) + // // assert2.equal(data.qty, 1) + // }) + }) + + describe("#tokenUtxoDetails", () => { + it("should throw error if input is not an array.", async () => { + try { + await slp.Utils.tokenUtxoDetails("test") + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + assert2.include( + err.message, + `Input must be an array`, + "Expected error message." + ) + } + }) + + it("should throw error if utxo does not have satoshis or value property.", async () => { + try { + const utxos = [ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 3, + amount: 0.00002015, + satoshis: 2015, + height: 594892, + confirmations: 5 + }, + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 2, + amount: 0.00000546, + height: 594892, + confirmations: 5 + } + ] + + await slp.Utils.tokenUtxoDetails(utxos) + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + assert2.include( + err.message, + `utxo 1 does not have a satoshis or value property`, + "Expected error message." + ) + } + }) + + it("should throw error if utxo does not have txid or tx_hash property.", async () => { + try { + const utxos = [ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 3, + amount: 0.00002015, + satoshis: 2015, + height: 594892, + confirmations: 5 + }, + { + vout: 2, + amount: 0.00000546, + satoshis: 546, + height: 594892, + confirmations: 5 + } + ] + + await slp.Utils.tokenUtxoDetails(utxos) + + assert2.equal(true, false, "Unexpected result.") + } catch (err) { + assert2.include( + err.message, + `utxo 1 does not have a txid or tx_hash property`, + "Expected error message." + ) + } + }) + + // // This captures an important corner-case. When an SLP token is created, the + // // change UTXO will contain the same SLP txid, but it is not an SLP UTXO. + it("should return details on minting baton from genesis transaction", async () => { + // Mock the call to REST API + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves([ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + valid: true + } + ]) + + // Stub the calls to decodeOpReturn. + sandbox.stub(slp.Utils, "decodeOpReturn").resolves({ + tokenType: 1, + txType: "GENESIS", + ticker: "SLPSDK", + name: "SLP SDK example using BITBOX", + tokenId: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + documentUri: "developer.bitcoin.com", + documentHash: "", + decimals: 8, + mintBatonVout: 2, + qty: "50700000000" + }) + + const utxos = [ + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 3, + amount: 0.00002015, + satoshis: 2015, + height: 594892, + confirmations: 5 + }, + { + txid: + "bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90", + vout: 2, + amount: 0.00000546, + satoshis: 546, + height: 594892, + confirmations: 5 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + // assert2.equal(data[0], false, "Change UTXO marked as false.") + assert2.property(data[0], "txid") + assert2.property(data[0], "vout") + assert2.property(data[0], "amount") + assert2.property(data[0], "satoshis") + assert2.property(data[0], "height") + assert2.property(data[0], "confirmations") + assert2.property(data[0], "isValid") + assert2.equal(data[0].isValid, false) + + assert2.property(data[1], "txid") + assert2.property(data[1], "vout") + assert2.property(data[1], "amount") + assert2.property(data[1], "satoshis") + assert2.property(data[1], "height") + assert2.property(data[1], "confirmations") + assert2.property(data[1], "utxoType") + assert2.property(data[1], "tokenId") + assert2.property(data[1], "tokenTicker") + assert2.property(data[1], "tokenName") + assert2.property(data[1], "tokenDocumentUrl") + assert2.property(data[1], "tokenDocumentHash") + assert2.property(data[1], "decimals") + assert2.property(data[1], "isValid") + assert2.equal(data[1].isValid, true) + }) + + it("should return details for a MINT token utxo", async () => { + // Mock the call to REST API + + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .onCall(0) + .resolves({ + tokenType: 1, + txType: "MINT", + tokenId: + "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", + mintBatonVout: 2, + qty: "1000000000000" + }) + .onCall(1) + .resolves({ + tokenType: 1, + txType: "GENESIS", + ticker: "PSF", + name: "Permissionless Software Foundation", + tokenId: + "38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0", + documentUri: "psfoundation.cash", + documentHash: "", + decimals: 8, + mintBatonVout: 2, + qty: "1988209163133" + }) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves([ + { + txid: + "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + valid: true + } + ]) + + const utxos = [ + { + txid: + "cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 600297, + confirmations: 76 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.property(data[0], "txid") + assert2.property(data[0], "vout") + assert2.property(data[0], "amount") + assert2.property(data[0], "satoshis") + assert2.property(data[0], "height") + assert2.property(data[0], "confirmations") + assert2.property(data[0], "utxoType") + assert2.property(data[0], "transactionType") + assert2.property(data[0], "tokenId") + assert2.property(data[0], "tokenTicker") + assert2.property(data[0], "tokenName") + assert2.property(data[0], "tokenDocumentUrl") + assert2.property(data[0], "tokenDocumentHash") + assert2.property(data[0], "decimals") + assert2.property(data[0], "mintBatonVout") + assert2.property(data[0], "tokenQty") + assert2.property(data[0], "isValid") + assert.equal(data[0].isValid, true) + }) + + it("should return details for a simple SEND SLP token utxo", async () => { + // Mock the call to REST API + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .onCall(0) + .resolves({ + tokenType: 1, + txType: "SEND", + tokenId: + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + amounts: ["200000000", "99887500000000"] + }) + .onCall(1) + .resolves({ + tokenType: 1, + txType: "GENESIS", + ticker: "TOK-CH", + name: "TokyoCash", + tokenId: + "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7", + documentUri: "", + documentHash: "", + decimals: 8, + mintBatonVout: 0, + qty: "2100000000000000" + }) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves([ + { + txid: + "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + valid: true + } + ]) + + const utxos = [ + { + txid: + "fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 596089, + confirmations: 748 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.property(data[0], "txid") + assert2.property(data[0], "vout") + assert2.property(data[0], "amount") + assert2.property(data[0], "satoshis") + assert2.property(data[0], "height") + assert2.property(data[0], "confirmations") + assert2.property(data[0], "utxoType") + assert2.property(data[0], "tokenId") + assert2.property(data[0], "tokenTicker") + assert2.property(data[0], "tokenName") + assert2.property(data[0], "tokenDocumentUrl") + assert2.property(data[0], "tokenDocumentHash") + assert2.property(data[0], "decimals") + assert2.property(data[0], "tokenQty") + assert2.property(data[0], "isValid") + assert.equal(data[0].isValid, true) + }) + + it("should handle BCH and SLP utxos in the same TX", async () => { + // Mock external dependencies. + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(mockData.mockDualValidation) + + sandbox + .stub(slp.Utils, "decodeOpReturn") + .onCall(0) + .resolves({ + tokenType: 1, + txType: "SEND", + tokenId: + "dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d", + amounts: ["1", "5"] + }) + .onCall(1) + .resolves({ + tokenType: 1, + txType: "SEND", + tokenId: + "dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d", + amounts: ["1", "5"] + }) + .onCall(2) + .resolves({ + tokenType: 1, + txType: "GENESIS", + ticker: "TAP", + name: "Thoughts and Prayers", + tokenId: + "dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d", + documentUri: "", + documentHash: "", + decimals: 0, + mintBatonVout: 2, + qty: "1000000" + }) + + const utxos = [ + { + txid: + "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + vout: 3, + value: "6816", + height: 606848, + confirmations: 13, + satoshis: 6816 + }, + { + txid: + "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56", + vout: 2, + value: "546", + height: 606848, + confirmations: 13, + satoshis: 546 + } + ] + + const result = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.isArray(result) + assert2.equal(result.length, 2) + + assert2.property(result[0], "txid") + assert2.property(result[0], "vout") + assert2.property(result[0], "value") + assert2.property(result[0], "satoshis") + assert2.property(result[0], "height") + assert2.property(result[0], "confirmations") + assert2.property(result[0], "isValid") + assert2.equal(result[0].isValid, false) + + assert.equal(result[1].isValid, true) + assert.equal(result[1].utxoType, "token") + assert.equal(result[1].transactionType, "send") + }) + + it("should handle problematic utxos", async () => { + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .onCall(0) + .throws({ message: "scriptpubkey not op_return" }) + .onCall(1) + .resolves({ + tokenType: 1, + txType: "SEND", + tokenId: + "f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f", + amounts: ["5000000", "395010942"] + }) + .onCall(2) + .resolves({ + tokenType: 1, + txType: "GENESIS", + ticker: "AUDC", + name: "AUD Coin", + tokenId: + "f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f", + documentUri: "audcoino@gmail.com", + documentHash: "", + decimals: 6, + mintBatonVout: 0, + qty: "2000000000000000000" + }) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves([ + { + txid: + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", + valid: true + } + ]) + + const utxos = [ + { + txid: + "0e3a217fc22612002031d317b4cecd9b692b66b52951a67b23c43041aefa3959", + vout: 0, + amount: 0.00018362, + satoshis: 18362, + height: 613483, + confirmations: 124 + }, + { + txid: + "67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19", + vout: 1, + amount: 0.00000546, + satoshis: 546, + height: 612075, + confirmations: 1532 + } + ] + + const result = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.isArray(result) + assert2.equal(result.length, 2) + + assert2.property(result[0], "txid") + assert2.property(result[0], "vout") + assert2.property(result[0], "amount") + assert2.property(result[0], "satoshis") + assert2.property(result[0], "height") + assert2.property(result[0], "confirmations") + assert2.property(result[0], "isValid") + assert2.equal(result[0].isValid, false) + + assert2.equal(result[1].isValid, true) + assert.equal(result[1].utxoType, "token") + assert.equal(result[1].transactionType, "send") + }) + + it("should return isValid=false for BCH-only UTXOs", async () => { + // Mock live network calls + + sandbox + .stub(slp.Utils, "decodeOpReturn") + .throws(new Error("scriptpubkey not op_return")) + + const utxos = [ + { + txid: + "a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b", + vout: 0, + amount: 0.00001, + satoshis: 1000, + confirmations: 0, + ts: 1578424704 + }, + { + txid: + "53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44", + vout: 0, + amount: 0.00001, + satoshis: 1000, + confirmations: 0, + ts: 1578424634 + } + ] + + const result = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.isArray(result) + + assert2.property(result[0], "txid") + assert2.property(result[0], "vout") + assert2.property(result[0], "amount") + assert2.property(result[0], "satoshis") + assert2.property(result[0], "confirmations") + assert2.property(result[0], "isValid") + assert2.equal(result[0].isValid, false) + + assert2.property(result[1], "txid") + assert2.property(result[1], "vout") + assert2.property(result[1], "amount") + assert2.property(result[1], "satoshis") + assert2.property(result[1], "confirmations") + assert2.property(result[1], "isValid") + assert2.equal(result[1].isValid, false) + }) + + it("should decode a Genesis transaction", async () => { + const slpData = { + tokenType: 1, + txType: "GENESIS", + ticker: "SLPTEST", + name: "SLP Test Token", + tokenId: + "d2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 8, + mintBatonVout: 2, + qty: "10000000000" + } + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox.stub(slp.Utils, "decodeOpReturn").resolves(slpData) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves([ + { + txid: + "d2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954", + valid: true + } + ]) + + const utxos = [ + { + txid: + "d2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954", + vout: 1, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "d2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954", + vout: 2, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "d2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954", + vout: 3, + value: "12178", + confirmations: 0, + satoshis: 12178 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].utxoType, "token") + assert2.equal(data[0].tokenQty, 100) + assert2.equal(data[0].isValid, true) + assert2.equal(data[0].tokenType, 1) + + assert2.equal(data[1].utxoType, "minting-baton") + assert2.equal(data[1].isValid, true) + assert2.equal(data[1].tokenType, 1) + + assert2.equal(data[2].isValid, false) + }) + + it("should decode a Mint transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 1, + txType: "MINT", + tokenId: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + mintBatonVout: 2, + qty: "10000000000" + } + + const genesisData = { + tokenType: 1, + txType: "GENESIS", + ticker: "SLPTEST", + name: "SLP Test Token", + tokenId: + "9d35c1803ed3ab8bd23c198b027f7b3b530586494dc265de6391b74a6b090136", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 8, + mintBatonVout: 2, + qty: "10000000000" + } + + const stubValid = [ + { + txid: + "880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(genesisData) + .onCall(2) + .resolves(slpData) + .onCall(3) + .resolves(genesisData) + .onCall(4) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(stubValid) + .onCall(1) + .resolves(stubValid) + + const utxos = [ + { + txid: + "880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04", + vout: 1, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04", + vout: 2, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04", + vout: 3, + value: "10552", + confirmations: 0, + satoshis: 10552 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].utxoType, "token") + assert2.equal(data[0].tokenQty, 100) + assert2.equal(data[0].isValid, true) + assert2.equal(data[0].tokenType, 1) + + assert2.equal(data[1].utxoType, "minting-baton") + assert2.equal(data[1].isValid, true) + assert2.equal(data[1].tokenType, 1) + + assert2.equal(data[2].isValid, false) + }) + + it("should decode a NFT Group Genesis transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 129, + txType: "GENESIS", + ticker: "NFTTT", + name: "NFT Test Token", + tokenId: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 2, + qty: "1" + } + + const stubValid = [ + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(slpData) + .onCall(2) + .resolves(slpData) + .onCall(3) + .resolves(slpData) + .onCall(4) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(stubValid) + .onCall(1) + .resolves(stubValid) + .onCall(2) + .resolves(stubValid) + + const utxos = [ + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 3, + value: "15620", + height: 638207, + confirmations: 3, + satoshis: 15620 + }, + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 2, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + }, + { + txid: + "4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4", + vout: 1, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].isValid, false) + + assert2.equal(data[1].utxoType, "minting-baton") + assert2.equal(data[1].isValid, true) + assert2.equal(data[1].tokenType, 129) + + assert2.equal(data[2].utxoType, "token") + assert2.equal(data[2].tokenType, 129) + assert2.equal(data[1].isValid, true) + }) + + it("should decode a NFT Group Mint transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 129, + txType: "MINT", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + mintBatonVout: 2, + qty: "10" + } + + const genesisData = { + tokenType: 129, + txType: "GENESIS", + ticker: "NFTTT", + name: "NFT Test Token", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 2, + qty: "1" + } + + const stubValid = [ + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(slpData) + .onCall(2) + .resolves(genesisData) + .onCall(3) + .resolves(slpData) + .onCall(4) + .resolves(genesisData) + + // Stub the call to validateTxid + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(stubValid) + .onCall(1) + .resolves(stubValid) + .onCall(2) + .resolves(stubValid) + + const utxos = [ + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + vout: 3, + value: "15620", + height: 638207, + confirmations: 3, + satoshis: 15620 + }, + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + vout: 2, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + }, + { + txid: + "35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919", + vout: 1, + value: "546", + height: 638207, + confirmations: 3, + satoshis: 546 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].isValid, false) + + assert2.equal(data[1].utxoType, "minting-baton") + assert2.equal(data[1].tokenType, 129) + assert2.equal(data[1].isValid, true) + + assert2.equal(data[2].utxoType, "token") + assert2.equal(data[2].tokenType, 129) + assert2.equal(data[2].isValid, true) + }) + + it("should decode a NFT Child Genesis transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 65, + txType: "GENESIS", + ticker: "NFTC", + name: "NFT Child", + tokenId: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 0, + qty: "1" + } + + const stubValid = [ + { + txid: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves(stubValid) + + const utxos = [ + { + txid: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + vout: 1, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + vout: 2, + value: "13478", + confirmations: 0, + satoshis: 13478 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].utxoType, "token") + assert2.equal(data[0].tokenType, 65) + assert2.equal(data[0].isValid, true) + + assert2.equal(data[1].isValid, false) + }) + + it("should decode an NFT Child Send transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 65, + txType: "SEND", + tokenId: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + amounts: ["1"] + } + + const genesisData = { + tokenType: 65, + txType: "GENESIS", + ticker: "NFTC", + name: "NFT Child", + tokenId: + "9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 0, + qty: "1" + } + + const stubValid = [ + { + txid: + "6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(genesisData) + .onCall(2) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox.stub(slp.Utils, "validateTxid").resolves(stubValid) + + const utxos = [ + { + txid: + "6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a", + vout: 1, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a", + vout: 2, + value: "12136", + confirmations: 0, + satoshis: 12136 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].utxoType, "token") + assert2.equal(data[0].transactionType, "send") + assert2.equal(data[0].tokenType, 65) + assert2.equal(data[0].isValid, true) + + assert2.equal(data[1].isValid, false) + }) + + it("should decode an NFT Group Send transaction", async () => { + // Define stubbed data. + const slpData = { + tokenType: 129, + txType: "SEND", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + amounts: ["1", "8"] + } + + const genesisData = { + tokenType: 129, + txType: "GENESIS", + ticker: "NFTTT", + name: "NFT Test Token", + tokenId: + "eee4b82e4bb7113eca433829144363fc45f110693c286494fbf5b5c8043cc981", + documentUri: "https://FullStack.cash", + documentHash: "", + decimals: 0, + mintBatonVout: 2, + qty: "1" + } + + const stubValid = [ + { + txid: + "57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766", + valid: true + } + ] + + // Mock external dependencies. + // Stub the calls to decodeOpReturn. + sandbox + .stub(slp.Utils, "decodeOpReturn") + .resolves(slpData) + .onCall(1) + .resolves(genesisData) + .onCall(2) + .resolves(slpData) + .onCall(3) + .resolves(genesisData) + .onCall(4) + .resolves(slpData) + + // Stub the call to validateTxid + sandbox + .stub(slp.Utils, "validateTxid") + .resolves(stubValid) + .onCall(1) + .resolves(stubValid) + + const utxos = [ + { + txid: + "57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766", + vout: 1, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766", + vout: 2, + value: "546", + confirmations: 0, + satoshis: 546 + }, + { + txid: + "57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766", + vout: 3, + value: "10794", + confirmations: 0, + satoshis: 10794 + } + ] + + const data = await slp.Utils.tokenUtxoDetails(utxos) + // console.log(`data: ${JSON.stringify(data, null, 2)}`) + + assert2.isArray(data) + + assert2.equal(data[0].utxoType, "token") + assert2.equal(data[0].transactionType, "send") + assert2.equal(data[0].tokenType, 129) + assert2.equal(data[0].isValid, true) + + assert2.equal(data[1].utxoType, "token") + assert2.equal(data[1].transactionType, "send") + assert2.equal(data[1].tokenType, 129) + assert2.equal(data[1].isValid, true) + + assert2.equal(data[2].isValid, false) + }) + }) + + describe("#txDetails", () => { + it("should throw an error if txid is not included", async () => { + try { + await slp.Utils.txDetails() + } catch (err) { + assert2.include( + err.message, + `txid string must be included`, + "Expected error message." + ) + } + }) + + it("should throw error for non-existent txid", async () => { + try { + // Mock the call to the REST API + if (process.env.TEST === "unit") { + sandbox + .stub(axios, "get") + //.resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn }) + .throws({ error: `TXID not found` }) + } + + const txid = `d284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e` + + const result = await slp.Utils.txDetails(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + } catch (err) { + // console.log(`err: `, err) + assert2.include(err.error, `TXID not found`, "Expected error message.") + } + }) + + it("should return details for an SLP txid", async () => { + // Mock the call to the REST API + if (process.env.TEST === "unit") + sandbox.stub(axios, "get").resolves({ data: mockData.mockTxDetails }) + + const txid = `9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0` + + const result = await slp.Utils.txDetails(txid) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert2.hasAnyKeys(result, [ + "txid", + "version", + "locktime", + "vin", + "vout", + "blockhash", + "blockheight", + "confirmations", + "time", + "blocktime", + "valueOut", + "size", + "valueIn", + "fees", + "tokenInfo", + "tokenIsValid" + ]) + }) + }) +}) diff --git a/test/unit/transaction-builder.js b/test/unit/transaction-builder.js new file mode 100644 index 0000000..aa5f29f --- /dev/null +++ b/test/unit/transaction-builder.js @@ -0,0 +1,2039 @@ +const fixtures = require("./fixtures/transaction-builder.json") +const assert = require("assert") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const Buffer = require("safe-buffer").Buffer + +describe("#TransactionBuilder", () => { + describe("#hashTypes", () => { + const transactionBuilder = new bchjs.TransactionBuilder("mainnet") + fixtures.hashTypes.forEach(fixture => { + it(`should match hash type`, () => { + assert.equal( + fixture[Object.keys(fixture)[0]], + transactionBuilder.hashTypes[Object.keys(fixture)[0]] + ) + }) + }) + }) + + describe("#P2PK", () => { + describe("#toOne", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pk.toOne.mainnet.forEach(fixture => { + it(`should create 1-to-1 P2PK transaction on mainnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey = bchjs.HDNode.toPublicKey(node) + const buf = bchjs.Script.pubKey.output.encode(pubKey) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf, sendAmount) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pk.toOne.testnet.forEach(fixture => { + it(`should create 1-to-1 P2PK transaction on testnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey = bchjs.HDNode.toPublicKey(node) + const buf = bchjs.Script.pubKey.output.encode(pubKey) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf, sendAmount) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + }) + + describe("#toMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pk.toMany.mainnet.forEach(fixture => { + it(`should create 1-to-many P2PK transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder() + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf2, Math.floor(sendAmount / 2)) + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + const keyPair = bchjs.HDNode.toKeyPair(node1) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pk.toMany.testnet.forEach(fixture => { + it(`should create 1-to-many P2PK transaction on testnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf2, Math.floor(sendAmount / 2)) + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + const keyPair = bchjs.HDNode.toKeyPair(node1) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + }) + + describe("#manyToMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pk.manyToMany.mainnet.forEach(fixture => { + it(`should create many-to-many P2PK transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const transactionBuilder = new bchjs.TransactionBuilder() + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + const buf4 = bchjs.Script.pubKey.output.encode(pubKey4) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + transactionBuilder.addOutput(buf4, Math.floor(sendAmount / 2)) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pk.manyToMany.testnet.forEach(fixture => { + it(`should create many-to-many P2PK transaction on testnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + const buf4 = bchjs.Script.pubKey.output.encode(pubKey4) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + transactionBuilder.addOutput(buf4, Math.floor(sendAmount / 2)) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + }) + + describe("#fromMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pk.fromMany.mainnet.forEach(fixture => { + it(`should create many-to-1 P2PK transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder() + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf3, sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pk.fromMany.testnet.forEach(fixture => { + it(`should create many-to-1 P2PK transaction on testnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const originalAmount = fixture.amount + const txid = fixture.txHash + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const buf1 = bchjs.Script.pubKey.output.encode(pubKey1) + const buf2 = bchjs.Script.pubKey.output.encode(pubKey2) + const buf3 = bchjs.Script.pubKey.output.encode(pubKey3) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf3, sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + }) + }) + + describe("#P2PKH", () => { + describe("#toOne", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pkh.toOne.mainnet.forEach(fixture => { + it(`should create 1-to-1 P2PKH transaction on mainnet`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 1 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount + // add output w/ address and amount to send + let redeemScript + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pkh.toOne.testnet.forEach(fixture => { + it(`should create 1-to-1 P2PKH transaction on testnet`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv, "testnet") + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 1 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount * 15 + // add output w/ address and amount to send + let redeemScript + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.scripts.p2pkh.toOne.regtest.forEach(fixture => { + it(`should create 1-to-1 P2PKH transaction on regtest`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 1 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount * 15 + // add output w/ address and amount to send + let redeemScript + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + */ + }) + + describe("#toMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pkh.toMany.mainnet.forEach(fixture => { + it(`should create 1-to-2 P2PKH transaction on mainnet`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount + // add output w/ address and amount to send + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pkh.toMany.testnet.forEach(fixture => { + // TODO pass in tesnet network config + it(`should create 1-to-2 P2PKH transaction on testnet`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount * 15 + // add output w/ address and amount to send + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.scripts.p2pkh.toMany.regtest.forEach(fixture => { + // TODO pass in tesnet network config + it(`should create 1-to-2 P2PKH transaction on regtest`, () => { + const hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const keyPair = bchjs.HDNode.toKeyPair(hdnode) + const txHash = fixture.txHash + // original amount of satoshis in vin + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, fixture.vout) + // get byte count to calculate fee. paying 1 sat/byte + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 2 } + ) + // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + const sendAmount = originalAmount - byteCount * 15 + // add output w/ address and amount to send + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + // build tx + const tx = transactionBuilder.build() + // output rawhex + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + })*/ + }) + + describe("#manyToMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pkh.manyToMany.mainnet.forEach(fixture => { + it(`should create 2-to-2 P2PKH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 2 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pkh.manyToMany.testnet.forEach(fixture => { + it(`should create 2-to-2 P2PKH transaction on testnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 2 } + ) + const sendAmount = originalAmount - byteCount * 15 + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.scripts.p2pkh.manyToMany.regtest.forEach(fixture => { + it(`should create 2-to-2 P2PKH transaction on regtest`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 2 } + ) + const sendAmount = originalAmount - byteCount * 15 + transactionBuilder.addOutput( + fixture.outputs[0], + Math.floor(sendAmount / 2) + ) + transactionBuilder.addOutput( + fixture.outputs[1], + Math.floor(sendAmount / 2) + ) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + */ + }) + + describe("#fromMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2pkh.fromMany.mainnet.forEach(fixture => { + it(`should create 2-to-1 P2PKH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 1 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.scripts.p2pkh.fromMany.testnet.forEach(fixture => { + it(`should create 2-to-1 P2PKH transaction on testnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 1 } + ) + const sendAmount = originalAmount - byteCount * 15 + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.scripts.p2pkh.fromMany.regtest.forEach(fixture => { + it(`should create 2-to-1 P2PKH transaction on regtest`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const txHash = fixture.txHash + const originalAmount = fixture.amounts[0] + fixture.amounts[1] + transactionBuilder.addInput(txHash, 0) + transactionBuilder.addInput(txHash, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 1 } + ) + const sendAmount = originalAmount - byteCount * 15 + transactionBuilder.addOutput(fixture.outputs[0], sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + let redeemScript + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[0] + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amounts[1] + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + */ + }) + }) + + describe("#op_return", () => { + describe("#Mainnet", () => { + fixtures.nulldata.mainnet.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on mainnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.nulldata.testnet.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on testnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.nulldata.regtest.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on regtest`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + */ + }) + + describe("#P2MS", () => { + describe("#toOne", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2ms.toOne.mainnet.forEach(fixture => { + it(`should create 1-to-1 1-of-2 P2MS transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const buf1 = bchjs.Script.multisig.output.encode(1, [ + pubKey1, + pubKey2 + ]) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf2 = bchjs.Script.multisig.output.encode(1, [ + pubKey3, + pubKey4 + ]) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(buf2, sendAmount) + let redeemScript + const keyPair = bchjs.HDNode.toKeyPair(node1) + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2ms.toOne.testnet.forEach((fixture) => { + // it(`should create 1-to-1 P2MS transaction on testnet`, () => { + // let hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv, 'testnet'); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let keyPair = bchjs.HDNode.toKeyPair(hdnode); + // let txHash = fixture.txHash; + // // original amount of satoshis in vin + // let originalAmount = fixture.amount; + // transactionBuilder.addInput(txHash, fixture.vout); + // // get byte count to calculate fee. paying 1 sat/byte + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); + // // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + // let sendAmount = originalAmount - (byteCount * 15); + // // add output w/ address and amount to send + // let redeemScript + // transactionBuilder.addOutput(fixture.outputs[0], sendAmount); + // transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount); + // + // // build tx + // let tx = transactionBuilder.build(); + // // output rawhex + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#toMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2ms.toMany.mainnet.forEach(fixture => { + it(`should create 1-to-2 P2MS transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const node5 = bchjs.HDNode.fromXPriv(fixture.xprivs[4]) + const node6 = bchjs.HDNode.fromXPriv(fixture.xprivs[5]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const buf1 = bchjs.Script.multisig.output.encode(1, [ + pubKey1, + pubKey2 + ]) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf2 = bchjs.Script.multisig.output.encode(1, [ + pubKey3, + pubKey4 + ]) + transactionBuilder.addOutput(buf2, Math.floor(sendAmount / 2)) + const pubKey5 = bchjs.HDNode.toPublicKey(node5) + const pubKey6 = bchjs.HDNode.toPublicKey(node6) + const buf3 = bchjs.Script.multisig.output.encode(1, [ + pubKey5, + pubKey6 + ]) + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + let redeemScript + const keyPair = bchjs.HDNode.toKeyPair(node1) + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2ms.toMany.testnet.forEach((fixture) => { + // // TODO pass in tesnet network config + // it(`should create 1-to-2 P2MS transaction on testnet`, () => { + // let hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let keyPair = bchjs.HDNode.toKeyPair(hdnode); + // let txHash = fixture.txHash; + // // original amount of satoshis in vin + // let originalAmount = fixture.amount; + // transactionBuilder.addInput(txHash, fixture.vout); + // // get byte count to calculate fee. paying 1 sat/byte + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 }); + // // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + // let sendAmount = originalAmount - (byteCount * 15); + // // add output w/ address and amount to send + // transactionBuilder.addOutput(fixture.outputs[0], Math.floor(sendAmount / 2)); + // transactionBuilder.addOutput(fixture.outputs[1], Math.floor(sendAmount / 2)); + // let redeemScript + // transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount); + // // build tx + // let tx = transactionBuilder.build(); + // // output rawhex + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#manyToMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2ms.manyToMany.mainnet.forEach(fixture => { + it(`should create 2-to-2 P2MS transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const node5 = bchjs.HDNode.fromXPriv(fixture.xprivs[4]) + const node6 = bchjs.HDNode.fromXPriv(fixture.xprivs[5]) + const node7 = bchjs.HDNode.fromXPriv(fixture.xprivs[6]) + const node8 = bchjs.HDNode.fromXPriv(fixture.xprivs[7]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const buf1 = bchjs.Script.multisig.output.encode(1, [ + pubKey1, + pubKey2 + ]) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf2 = bchjs.Script.multisig.output.encode(1, [ + pubKey3, + pubKey4 + ]) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + const pubKey5 = bchjs.HDNode.toPublicKey(node5) + const pubKey6 = bchjs.HDNode.toPublicKey(node6) + const buf3 = bchjs.Script.multisig.output.encode(1, [ + pubKey5, + pubKey6 + ]) + transactionBuilder.addOutput(buf3, Math.floor(sendAmount / 2)) + const pubKey7 = bchjs.HDNode.toPublicKey(node7) + const pubKey8 = bchjs.HDNode.toPublicKey(node8) + const buf4 = bchjs.Script.multisig.output.encode(1, [ + pubKey7, + pubKey8 + ]) + transactionBuilder.addOutput(buf4, Math.floor(sendAmount / 2)) + let redeemScript + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node3) + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2ms.manyToMany.testnet.forEach((fixture) => { + // it(`should create 2-to-2 P2MS transaction on testnet`, () => { + // let node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]); + // let node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let txHash = fixture.txHash; + // let originalAmount = fixture.amounts[0] + fixture.amounts[1]; + // transactionBuilder.addInput(txHash, 0); + // transactionBuilder.addInput(txHash, 1); + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 2 }); + // let sendAmount = originalAmount - (byteCount * 15); + // transactionBuilder.addOutput(fixture.outputs[0], Math.floor(sendAmount / 2)); + // transactionBuilder.addOutput(fixture.outputs[1], Math.floor(sendAmount / 2)); + // let keyPair1 = bchjs.HDNode.toKeyPair(node1); + // let keyPair2 = bchjs.HDNode.toKeyPair(node2); + // let redeemScript; + // transactionBuilder.sign(0, keyPair1, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[0]); + // transactionBuilder.sign(1, keyPair2, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[1]); + // let tx = transactionBuilder.build(); + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#fromMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2ms.fromMany.mainnet.forEach(fixture => { + it(`should create 2-to-1 P2MS transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const node5 = bchjs.HDNode.fromXPriv(fixture.xprivs[4]) + const node6 = bchjs.HDNode.fromXPriv(fixture.xprivs[5]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const pubKey1 = bchjs.HDNode.toPublicKey(node1) + const pubKey2 = bchjs.HDNode.toPublicKey(node2) + const buf1 = bchjs.Script.multisig.output.encode(1, [ + pubKey1, + pubKey2 + ]) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + buf1 + ) + const pubKey3 = bchjs.HDNode.toPublicKey(node3) + const pubKey4 = bchjs.HDNode.toPublicKey(node4) + const buf2 = bchjs.Script.multisig.output.encode(1, [ + pubKey3, + pubKey4 + ]) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + buf2 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 3 } + ) + const sendAmount = originalAmount - byteCount + const pubKey5 = bchjs.HDNode.toPublicKey(node5) + const pubKey6 = bchjs.HDNode.toPublicKey(node6) + const buf3 = bchjs.Script.multisig.output.encode(1, [ + pubKey5, + pubKey6 + ]) + transactionBuilder.addOutput(buf3, sendAmount) + let redeemScript + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node3) + transactionBuilder.sign( + 0, + keyPair1, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2ms.fromMany.testnet.forEach((fixture) => { + // it(`should create 2-to-1 P2MS transaction on testnet`, () => { + // let node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]); + // let node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let txHash = fixture.txHash; + // let originalAmount = fixture.amounts[0] + fixture.amounts[1]; + // transactionBuilder.addInput(txHash, 0); + // transactionBuilder.addInput(txHash, 1); + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 1 }); + // let sendAmount = originalAmount - (byteCount * 15); + // transactionBuilder.addOutput(fixture.outputs[0], sendAmount); + // let keyPair1 = bchjs.HDNode.toKeyPair(node1); + // let keyPair2 = bchjs.HDNode.toKeyPair(node2); + // let redeemScript; + // transactionBuilder.sign(0, keyPair1, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[0]); + // transactionBuilder.sign(1, keyPair2, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[1]); + // let tx = transactionBuilder.build(); + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + }) + + describe("#P2SH", () => { + describe("#toOne", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2sh.toOne.mainnet.forEach(fixture => { + it(`should create 1-to-1 P2SH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const identifier1 = bchjs.HDNode.toIdentifier(node1) + const buf1 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier1, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash1 = bchjs.Crypto.hash160(buf1) + const data1 = bchjs.Script.scriptHash.output.encode(scriptHash1) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + data1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 2 }, + { P2PKH: 1 } + ) + const sendAmount = originalAmount - byteCount + const identifier2 = bchjs.HDNode.toIdentifier(node2) + const buf2 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier2, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + + const scriptHash2 = bchjs.Crypto.hash160(buf2) + const data2 = bchjs.Script.scriptHash.output.encode(scriptHash2) + transactionBuilder.addOutput(data2, sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + transactionBuilder.sign( + 0, + keyPair1, + buf1, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2sh.toOne.testnet.forEach((fixture) => { + // it(`should create 1-to-1 P2SH transaction on testnet`, () => { + // let hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv, 'testnet'); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let keyPair = bchjs.HDNode.toKeyPair(hdnode); + // let txHash = fixture.txHash; + // // original amount of satoshis in vin + // let originalAmount = fixture.amount; + // transactionBuilder.addInput(txHash, fixture.vout); + // // get byte count to calculate fee. paying 1 sat/byte + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); + // // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + // let sendAmount = originalAmount - (byteCount * 15); + // // add output w/ address and amount to send + // let redeemScript + // transactionBuilder.addOutput(fixture.outputs[0], sendAmount); + // transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount); + // + // // build tx + // let tx = transactionBuilder.build(); + // // output rawhex + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#toMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2sh.toMany.mainnet.forEach(fixture => { + it(`should create 1-to-2 P2SH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const identifier1 = bchjs.HDNode.toIdentifier(node1) + const buf1 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier1, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash1 = bchjs.Crypto.hash160(buf1) + const data1 = bchjs.Script.scriptHash.output.encode(scriptHash1) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + data1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 2 } + ) + const sendAmount = originalAmount - byteCount + const identifier2 = bchjs.HDNode.toIdentifier(node2) + const buf2 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier2, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash2 = bchjs.Crypto.hash160(buf2) + const data2 = bchjs.Script.scriptHash.output.encode(scriptHash2) + transactionBuilder.addOutput(data2, Math.floor(sendAmount / 2)) + const identifier3 = bchjs.HDNode.toIdentifier(node3) + const buf3 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier3, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash3 = bchjs.Crypto.hash160(buf3) + const data3 = bchjs.Script.scriptHash.output.encode(scriptHash3) + transactionBuilder.addOutput(data3, Math.floor(sendAmount / 2)) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + transactionBuilder.sign( + 0, + keyPair1, + buf1, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2sh.toMany.testnet.forEach((fixture) => { + // // TODO pass in tesnet network config + // it(`should create 1-to-2 P2SH transaction on testnet`, () => { + // let hdnode = bchjs.HDNode.fromXPriv(fixture.xpriv); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let keyPair = bchjs.HDNode.toKeyPair(hdnode); + // let txHash = fixture.txHash; + // // original amount of satoshis in vin + // let originalAmount = fixture.amount; + // transactionBuilder.addInput(txHash, fixture.vout); + // // get byte count to calculate fee. paying 1 sat/byte + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 2 }); + // // amount to send to receiver. It's the original amount - 1 sat/byte for tx size + // let sendAmount = originalAmount - (byteCount * 15); + // // add output w/ address and amount to send + // transactionBuilder.addOutput(fixture.outputs[0], Math.floor(sendAmount / 2)); + // transactionBuilder.addOutput(fixture.outputs[1], Math.floor(sendAmount / 2)); + // let redeemScript + // transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount); + // // build tx + // let tx = transactionBuilder.build(); + // // output rawhex + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#manyToMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2sh.manyToMany.mainnet.forEach(fixture => { + it(`should create 2-to-2 P2SH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const node4 = bchjs.HDNode.fromXPriv(fixture.xprivs[3]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const identifier1 = bchjs.HDNode.toIdentifier(node1) + const buf1 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier1, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash1 = bchjs.Crypto.hash160(buf1) + const data1 = bchjs.Script.scriptHash.output.encode(scriptHash1) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + data1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 5 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + const identifier2 = bchjs.HDNode.toIdentifier(node2) + const buf2 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier2, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash2 = bchjs.Crypto.hash160(buf2) + const data2 = bchjs.Script.scriptHash.output.encode(scriptHash2) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + data2 + ) + const identifier3 = bchjs.HDNode.toIdentifier(node3) + const buf3 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier3, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash3 = bchjs.Crypto.hash160(buf3) + const data3 = bchjs.Script.scriptHash.output.encode(scriptHash3) + transactionBuilder.addOutput(data3, Math.floor(sendAmount / 2)) + const identifier4 = bchjs.HDNode.toIdentifier(node4) + const buf4 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier4, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash4 = bchjs.Crypto.hash160(buf4) + const data4 = bchjs.Script.scriptHash.output.encode(scriptHash4) + transactionBuilder.addOutput(data4, Math.floor(sendAmount / 2)) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + transactionBuilder.sign( + 0, + keyPair1, + buf1, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + buf2, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2sh.manyToMany.testnet.forEach((fixture) => { + // it(`should create 2-to-2 P2SH transaction on testnet`, () => { + // let node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]); + // let node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let txHash = fixture.txHash; + // let originalAmount = fixture.amounts[0] + fixture.amounts[1]; + // transactionBuilder.addInput(txHash, 0); + // transactionBuilder.addInput(txHash, 1); + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 2 }); + // let sendAmount = originalAmount - (byteCount * 15); + // transactionBuilder.addOutput(fixture.outputs[0], Math.floor(sendAmount / 2)); + // transactionBuilder.addOutput(fixture.outputs[1], Math.floor(sendAmount / 2)); + // let keyPair1 = bchjs.HDNode.toKeyPair(node1); + // let keyPair2 = bchjs.HDNode.toKeyPair(node2); + // let redeemScript; + // transactionBuilder.sign(0, keyPair1, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[0]); + // transactionBuilder.sign(1, keyPair2, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[1]); + // let tx = transactionBuilder.build(); + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + + describe("#fromMany", () => { + describe("#Mainnet", () => { + fixtures.scripts.p2sh.fromMany.mainnet.forEach(fixture => { + it(`should create 2-to-1 P2SH transaction on mainnet`, () => { + const node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]) + const node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]) + const node3 = bchjs.HDNode.fromXPriv(fixture.xprivs[2]) + const transactionBuilder = new bchjs.TransactionBuilder() + const txid = fixture.txHash + const originalAmount = fixture.amount + const identifier1 = bchjs.HDNode.toIdentifier(node1) + const buf1 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier1, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash1 = bchjs.Crypto.hash160(buf1) + const data1 = bchjs.Script.scriptHash.output.encode(scriptHash1) + transactionBuilder.addInput( + txid, + 0, + transactionBuilder.DEFAULT_SEQUENCE, + data1 + ) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 3 }, + { P2PKH: 2 } + ) + const sendAmount = originalAmount - byteCount + const identifier2 = bchjs.HDNode.toIdentifier(node2) + const buf2 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier2, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash2 = bchjs.Crypto.hash160(buf2) + const data2 = bchjs.Script.scriptHash.output.encode(scriptHash2) + transactionBuilder.addInput( + txid, + 1, + transactionBuilder.DEFAULT_SEQUENCE, + data2 + ) + const identifier3 = bchjs.HDNode.toIdentifier(node3) + const buf3 = bchjs.Script.encode([ + bchjs.Script.opcodes.OP_DUP, + bchjs.Script.opcodes.OP_HASH160, + identifier3, + bchjs.Script.opcodes.OP_EQUALVERIFY, + bchjs.Script.opcodes.OP_CHECKSIG + ]) + const scriptHash3 = bchjs.Crypto.hash160(buf3) + const data3 = bchjs.Script.scriptHash.output.encode(scriptHash3) + transactionBuilder.addOutput(data3, sendAmount) + const keyPair1 = bchjs.HDNode.toKeyPair(node1) + const keyPair2 = bchjs.HDNode.toKeyPair(node2) + transactionBuilder.sign( + 0, + keyPair1, + buf1, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + transactionBuilder.sign( + 1, + keyPair2, + buf2, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount / 2 + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + // describe('#Testnet', () => { + // fixtures.scripts.p2sh.fromMany.testnet.forEach((fixture) => { + // it(`should create 2-to-1 P2SH transaction on testnet`, () => { + // let node1 = bchjs.HDNode.fromXPriv(fixture.xprivs[0]); + // let node2 = bchjs.HDNode.fromXPriv(fixture.xprivs[1]); + // let transactionBuilder = new bchjs.TransactionBuilder('testnet'); + // let txHash = fixture.txHash; + // let originalAmount = fixture.amounts[0] + fixture.amounts[1]; + // transactionBuilder.addInput(txHash, 0); + // transactionBuilder.addInput(txHash, 1); + // let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 1 }); + // let sendAmount = originalAmount - (byteCount * 15); + // transactionBuilder.addOutput(fixture.outputs[0], sendAmount); + // let keyPair1 = bchjs.HDNode.toKeyPair(node1); + // let keyPair2 = bchjs.HDNode.toKeyPair(node2); + // let redeemScript; + // transactionBuilder.sign(0, keyPair1, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[0]); + // transactionBuilder.sign(1, keyPair2, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, fixture.amounts[1]); + // let tx = transactionBuilder.build(); + // let hex = tx.toHex(); + // assert.equal(hex, fixture.hex); + // }); + // }); + // }); + }) + }) + + describe("#op_return", () => { + describe("#Mainnet", () => { + fixtures.nulldata.mainnet.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on mainnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + + describe("#Testnet", () => { + fixtures.nulldata.testnet.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on testnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("testnet") + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + /* + describe("#RegTest", () => { + fixtures.nulldata.regtest.forEach(fixture => { + it(`should create transaction w/ OP_RETURN output on regtest`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder("regtest") + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 5 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const data = fixture.data + const buf = bchjs.Script.nullData.output.encode( + Buffer.from(data, "ascii") + ) + transactionBuilder.addOutput(buf, 0) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) +*/ + }) + + describe("#bip66", () => { + fixtures.bip66.forEach(fixture => { + it(`should bip66 encode as ${fixture.DER}`, () => { + const transactionBuilder = new bchjs.TransactionBuilder() + const r = Buffer.from(fixture.r, "hex") + const s = Buffer.from(fixture.s, "hex") + const DER = transactionBuilder.bip66.encode(r, s) + assert.equal(DER.toString("hex"), fixture.DER) + }) + }) + + fixtures.bip66.forEach(fixture => { + it(`should bip66 decode ${fixture.DER}`, () => { + const transactionBuilder = new bchjs.TransactionBuilder() + const buffer = Buffer.from(fixture.DER, "hex") + const signature = transactionBuilder.bip66.decode(buffer) + assert.equal(signature.r.toString("hex"), fixture.r) + assert.equal(signature.s.toString("hex"), fixture.s) + }) + }) + + fixtures.bip66.forEach(fixture => { + it(`should bip66 check ${fixture.DER}`, () => { + const transactionBuilder = new bchjs.TransactionBuilder() + const buffer = Buffer.from(fixture.DER, "hex") + assert.equal(transactionBuilder.bip66.check(buffer), true) + }) + }) + }) + + describe("#bip68", () => { + fixtures.bip68.encode.forEach(fixture => { + it(`should bip68 encode as ${fixture.result}`, () => { + const transactionBuilder = new bchjs.TransactionBuilder() + const obj = {} + obj[fixture.type] = fixture.value + const encode = transactionBuilder.bip68.encode(obj) + assert.equal(encode, fixture.result) + }) + }) + + fixtures.bip68.decode.forEach(fixture => { + it(`should bip68 decode ${fixture.result}`, () => { + const transactionBuilder = new bchjs.TransactionBuilder() + const obj = {} + const decode = transactionBuilder.bip68.decode(fixture.result) + assert.equal(Object.keys(decode)[0], fixture.type) + assert.deepEqual(decode[Object.keys(decode)[0]], fixture.value) + }) + }) + }) + + describe("#LockTime", () => { + describe("#Mainnet", () => { + fixtures.locktime.mainnet.forEach(fixture => { + it(`should create transaction with nLockTime on mainnet`, () => { + const node = bchjs.HDNode.fromXPriv(fixture.xpriv) + const transactionBuilder = new bchjs.TransactionBuilder() + + const txHash = fixture.txHash + const originalAmount = fixture.amount + transactionBuilder.addInput(txHash, 0, 1) + const byteCount = bchjs.BitcoinCash.getByteCount( + { P2PKH: 1 }, + { P2PKH: 1 } + ) + const sendAmount = originalAmount - byteCount + transactionBuilder.addOutput(fixture.output, sendAmount) + const lockTime = fixture.lockTime + transactionBuilder.setLockTime(lockTime) + const keyPair = bchjs.HDNode.toKeyPair(node) + let redeemScript + transactionBuilder.sign( + 0, + keyPair, + redeemScript, + transactionBuilder.hashTypes.SIGHASH_ALL, + fixture.amount + ) + const tx = transactionBuilder.build() + const hex = tx.toHex() + assert.equal(hex, fixture.hex) + }) + }) + }) + }) +}) diff --git a/test/unit/util.js b/test/unit/util.js new file mode 100644 index 0000000..7a17955 --- /dev/null +++ b/test/unit/util.js @@ -0,0 +1,35 @@ +const assert = require("assert") +const axios = require("axios") +const BCHJS = require("../../src/bch-js") +const bchjs = new BCHJS() +const sinon = require("sinon") + +describe("#Util", () => { + describe("#validateAddress", () => { + let sandbox + beforeEach(() => (sandbox = sinon.createSandbox())) + afterEach(() => sandbox.restore()) + + it("should validate address", done => { + const data = { + isvalid: true, + address: "bitcoincash:qpz7qtkuyhrsz4qmnnrvf8gz9zd0u9v7eqsewyk4w5", + scriptPubKey: "76a91445e02edc25c701541b9cc6c49d02289afe159ec888ac", + ismine: false, + iswatchonly: false, + isscript: false + } + + const resolved = new Promise(r => r({ data: data })) + sandbox.stub(axios, "get").returns(resolved) + + bchjs.Util.validateAddress( + "bitcoincash:qpz7qtkuyhrsz4qmnnrvf8gz9zd0u9v7eqsewyk4w5" + ) + .then(result => { + assert.deepEqual(data, result) + }) + .then(done, done) + }) + }) +})