├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .on-save.json ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── deploy-production.sh ├── examples └── README.md ├── img └── donation-qr.png ├── package-lock.json ├── package.json ├── src ├── address.js ├── bch-js.js ├── bitcoincash.js ├── blockchain.js ├── control.js ├── crypto.js ├── dsproof.js ├── ecash.js ├── ecpair.js ├── electrumx.js ├── encryption.js ├── generating.js ├── hdnode.js ├── mining.js ├── mnemonic.js ├── price.js ├── psf-slp-indexer.js ├── raw-transactions.js ├── schnorr.js ├── script.js ├── slp │ ├── address.js │ ├── ecpair.js │ ├── nft1.js │ ├── slp.js │ ├── tokentype1.js │ └── utils.js ├── transaction-builder.js ├── transaction.js ├── util.js └── utxo.js └── test ├── e2e ├── bch-js-e2e-tests.js ├── ipfs │ └── ipfs-e2e.js ├── rate-limits │ ├── anonymous-rate-limits.js │ ├── basic-auth-rate-limits.js │ ├── free-rate-limits.js │ ├── full-node-rate-limits.js │ └── indexer-rate-limits.js ├── send-raw-transaction-bulk │ ├── package.json │ └── sendrawtransaction.js ├── send-raw-transaction-single │ ├── package.json │ └── sendrawtransaction.js ├── send-token │ └── send-token.js ├── util │ └── e2e-util.js ├── utxo │ └── unsynced-indexer.js ├── wallet1.json └── wallet2.json ├── integration ├── blockchain.js ├── chains │ ├── abc │ │ ├── psf-slp-indexer-integration.js │ │ ├── rawtransaction.js │ │ └── utxo-integration.js │ ├── bchn │ │ ├── dsproof.js │ │ ├── psf-slp-indexer.integration.js │ │ ├── rawtransaction.js │ │ ├── slp.js │ │ ├── transaction-integration.js │ │ ├── util.js │ │ └── utxo-integration.js │ └── testnet │ │ ├── blockchain.js │ │ ├── control.js │ │ ├── electrumx.js │ │ ├── rawtransaction.js │ │ ├── slp.js │ │ ├── test-free-tier.sh │ │ └── util.js ├── control.js ├── electrumx.js ├── encryption.js ├── price.js ├── rawtransaction.js ├── slp.js └── transaction-integration.js └── unit ├── address.js ├── bitcoin-cash.js ├── blockchain.js ├── control.js ├── crypto.js ├── dsproof.js ├── ecash.js ├── ecpairs.js ├── electrumx.js ├── encryption.js ├── fixtures ├── address.json ├── bitcoincash.json ├── bitcore-mock.js ├── block-mock.js ├── blockchain-mock.js ├── blockchain.json ├── crypto.json ├── dsproof-mock.js ├── ecpair.json ├── electrumx-mock.js ├── encryption-mock.js ├── hdnode.json ├── ipfs-mock.js ├── mnemonic.json ├── openbazaar-mock.js ├── price-mocks.js ├── psf-slp-indexer-mock.js ├── rawtransaction-mock.js ├── script.json ├── slp │ ├── address.json │ ├── ecpair.json │ └── mock-utils.js ├── transaction-builder.json ├── transaction-mock.js └── utxo-mocks.js ├── generating.js ├── hdnode.js ├── mining.js ├── mnemonic.js ├── price.js ├── psf-slp-indexer.js ├── raw-tranactions.js ├── scripts.js ├── slp-address.js ├── slp-ecpair.js ├── slp-nft1.js ├── slp-tokentype1.js ├── slp-utils.js ├── transaction-builder.js ├── transaction-unit.js ├── util.js └── utxo-unit.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | # A special property that should be specified at the top of the file outside of 4 | # any sections. Set to true to stop .editor config file search on current file 5 | root = true 6 | 7 | [*] 8 | # Indentation style 9 | # Possible values - tab, space 10 | indent_style = space 11 | 12 | # Indentation size in single-spaced characters 13 | # Possible values - an integer, tab 14 | indent_size = 2 15 | 16 | # Line ending file format 17 | # Possible values - lf, crlf, cr 18 | end_of_line = lf 19 | 20 | # File character encoding 21 | # Possible values - latin1, utf-8, utf-16be, utf-16le 22 | charset = utf-8 23 | 24 | # Denotes whether to trim whitespace at the end of lines 25 | # Possible values - true, false 26 | trim_trailing_whitespace = true 27 | 28 | # Denotes whether file should end with a newline 29 | # Possible values - true, false 30 | insert_final_newline = true 31 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "env": { 4 | "node": true, 5 | "mocha": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 8 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .nyc_output/* 3 | wallet-info.txt 4 | wallet.json 5 | integration-test-sweet.sh 6 | integration-test-ss.sh 7 | integration-test-bitfinex.sh 8 | integration-test-pearson.sh 9 | 10 | docs/ 11 | coverage/ 12 | 13 | # This paragraph comes last. Force includes specific files. 14 | !docs/README.md 15 | -------------------------------------------------------------------------------- /.on-save.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "srcDir": "", 4 | "destDir": "", 5 | "files": "**/*.js", 6 | "command": "npm run lint" 7 | } 8 | ] 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This is a node.js v8+ JavaScript project 2 | language: node_js 3 | node_js: 4 | - "10" 5 | 6 | # Build on Ubuntu Xenial (16.04) 7 | # https://docs.travis-ci.com/user/reference/trusty/#javascript-and-nodejs-images 8 | dist: xenial 9 | sudo: required 10 | 11 | # Use Docker 12 | services: 13 | - docker 14 | 15 | before_install: 16 | #- ./install-mongo 17 | #- npm install -g mocha 18 | 19 | # https://github.com/greenkeeperio/greenkeeper-lockfile/issues/156 20 | install: case $TRAVIS_BRANCH in greenkeeper*) npm i;; *) npm ci;; esac; 21 | 22 | # Send coverage data to Coveralls 23 | after_success: 24 | - npm run coverage 25 | 26 | deploy: 27 | provider: script 28 | skip_cleanup: true 29 | script: 30 | - npx semantic-release && ./deploy-production.sh 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Permissionless Software Foundation Community Contributing Guide 1.0 2 | 3 | This document describes a very simple process suitable for most projects under 4 | 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). 5 | Projects are encouraged to adopt this whether they 6 | are hosted under the PSF or not. 7 | 8 | The goal of this document is to create a contribution process that: 9 | 10 | * Encourages new contributions. 11 | * Encourages contributors to remain involved. 12 | * Avoids unnecessary processes and bureaucracy whenever possible. 13 | * Creates a transparent decision making process which makes it clear how 14 | contributors can be involved in decision making. 15 | 16 | This document is based on much prior art in the Node.js community, io.js, 17 | and the Node.js project. 18 | 19 | Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software). 20 | 21 | ## Vocabulary 22 | 23 | * A **Contributor** is any individual creating or commenting on an issue or pull request. 24 | * A **Committer** is a subset of contributors who have been given write access to the repository. 25 | * A **TC (Technical Committee)** is a group of committers representing the required technical 26 | expertise to resolve rare disputes. 27 | 28 | # Logging Issues 29 | 30 | Log an issue for any question or problem you might have. When in doubt, log an issue, 31 | any additional policies about what to include will be provided in the responses. The only 32 | exception is security disclosures which should be sent privately. 33 | 34 | Committers may direct you to another repository, ask for additional clarifications, and 35 | add appropriate metadata before the issue is addressed. 36 | 37 | Please be courteous, respectful, and every participant is expected to follow the 38 | project's Code of Conduct. 39 | 40 | # Contributions 41 | 42 | Any change to resources in this repository must be through pull requests. This applies to all changes 43 | to documentation, code, binary files, etc. Even long term committers and TC members must use 44 | pull requests. 45 | 46 | No pull request can be merged without being reviewed. 47 | 48 | For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that 49 | contributors in other timezones have time to review. Consideration should also be given to 50 | weekends and other holiday periods to ensure active committers all have reasonable time to 51 | become involved in the discussion and review process if they wish. 52 | 53 | The default for each contribution is that it is accepted once no committer has an objection. 54 | During review committers may also request that a specific contributor who is most versed in a 55 | particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off" 56 | process for contributions to land. Once all issues brought by committers are addressed it can 57 | be landed by any committer. 58 | 59 | In the case of an objection being raised in a pull request by another committer, all involved 60 | committers should seek to arrive at a consensus by way of addressing concerns being expressed 61 | by discussion, compromise on the proposed change, or withdrawal of the proposed change. 62 | 63 | If a contribution is controversial and committers cannot agree about how to get it to land 64 | or if it should land then it should be escalated to the TC. TC members should regularly 65 | discuss pending contributions in order to find a resolution. It is expected that only a 66 | small minority of issues be brought to the TC for resolution and that discussion and 67 | compromise among committers be the default resolution mechanism. 68 | 69 | # Becoming a Committer 70 | 71 | All contributors who land a non-trivial contribution should be on-boarded in a timely manner, 72 | and added as a committer, and be given write access to the repository. 73 | 74 | Committers are expected to follow this policy and continue to send pull requests, go through 75 | proper review, and have other committers merge their pull requests. 76 | 77 | # TC Process 78 | 79 | The TC uses a "consensus seeking" process for issues that are escalated to the TC. 80 | The group tries to find a resolution that has no open objections among TC members. 81 | If a consensus cannot be reached that has no objections then a majority wins vote 82 | is called. It is also expected that the majority of decisions made by the TC are via 83 | a consensus seeking process and that voting is only used as a last-resort. 84 | 85 | Resolution may involve returning the issue to committers with suggestions on how to 86 | move forward towards a consensus. It is not expected that a meeting of the TC 87 | will resolve all issues on its agenda during that meeting and may prefer to continue 88 | the discussion happening among the committers. 89 | 90 | Members can be added to the TC at any time. Any committer can nominate another committer 91 | to the TC and the TC uses its standard consensus seeking process to evaluate whether or 92 | not to add this new member. Members who do not participate consistently at the level of 93 | a majority of the other members are expected to resign. 94 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2022 Permissionless Software Foundation contributors 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bch-js 2 | 3 | [![Version](https://img.shields.io/npm/v/@psf/bch-js)](https://www.npmjs.com/package/@psf/bch-js) 4 | [![Downloads/week](https://img.shields.io/npm/dw/@psf/bch-js)](https://npmjs.org/package/@psf/bch-js) 5 | [![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md) 6 | [![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard) [![Join the chat at https://gitter.im/Permissionless-Software-Foundation/bch-js](https://badges.gitter.im/Permissionless-Software-Foundation/bch-js.svg)](https://gitter.im/Permissionless-Software-Foundation/bch-js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | [bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) and eCash (XEC) blockchains. bch-js contains a toolbox of handy tools, and an easy API for talking with [bch-api REST API](https://github.com/Permissionless-Software-Foundation/bch-api). [FullStack.cash](https://fullstack.cash) offers paid cloud access to bch-api. You can run your own infrastructure by following documentation on [CashStack.info](https://cashstack.info). 9 | 10 | ### Quick Start Videos: 11 | 12 | Here are two YouTube walk-through videos to help you get started: 13 | 14 | - [Introduction to bch-js and the bch-js-examples repository](https://youtu.be/GD2i1ZUiyrk) 15 | - [Working with the FullStack.cash JWT token](https://youtu.be/GD2i1ZUiyrk) 16 | 17 | ### Quick Links 18 | 19 | - [npm Library](https://www.npmjs.com/package/@psf/bch-js) 20 | - [Documentation](https://bchjs.fullstack.cash/) 21 | - [Examples](https://github.com/Permissionless-Software-Foundation/bch-js-examples) 22 | - [bchn.fullstack.cash](https://bchn.fullstack.cash) - The REST API this library talks to by default. 23 | - [FullStack.cash](https://fullstack.cash) - cloud-based infrastructure for application developers. 24 | - [FullStack.cash Account](https://fullstack.cash/login) - Get your API key to unlock increased rate limits. 25 | - [Permissionless Software Foundation](https://psfoundation.cash) - The organization that maintains this library. 26 | - [CashStack.info](https://cashstack.info) - bch-js is part of the Cash Stack, a JavaScript framework for writing web 2 and web 3 business applications. 27 | 28 | ### Quick Notes 29 | 30 | - Install library: `npm install @psf/bch-js` 31 | 32 | - Instantiate the library in your code: 33 | 34 | ``` 35 | const BCHJS = require("@psf/bch-js") 36 | let bchjs = new BCHJS() // Defaults to BCHN network. 37 | ``` 38 | 39 | This library is intended to be paired with 40 | the [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API, and the infrastructure provided by [FullStack.cash](https://fullstack.cash). The `restURL` property can be changed to work with different Bitcoin Cash networks: 41 | 42 | - BCHN Mainnet REST API server: https://bchn.fullstack.cash/v5/ 43 | - ABC Mainnet REST API server: https://abc.fullstack.cash/v5/ 44 | - Check server status: https://metrics.fullstack.cash 45 | 46 | ### API Key (JWT Token) 47 | 48 | The [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API hosted by [FullStack.cash](https://fullstack.cash) uses JWT tokens to pay for increased 49 | rate limits when interacting with the back end server. See [this article](https://cashstack.info) if you want to understand the system-as-a-whole. The JWT token can be fed to bch-js _implicitly_ or _explicitly_. 50 | 51 | - Implicitly: bch-js will detect your JWT token if you set the `BCHJSTOKEN` environment variable. 52 | - Explicitly: You can directly feed in the JWT token with the `apiToken` property when instantiating the library. Here is an example: 53 | 54 | ``` 55 | const BCHJS = require("@psf/bch-js") 56 | let bchjs = new BCHJS({ 57 | restURL: 'https://bchn.fullstack.cash/v5/', 58 | apiToken: 'eyJhbGciO...' // Your JWT token here. 59 | }) 60 | ``` 61 | 62 | ### Gatsby & Web Apps 63 | 64 | [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is a minimal wallet 'engine' that incorporates bch-js. It's compiled with Browserify for front end apps. 65 | 66 | [gatsby-theme-bch-wallet](https://github.com/Permissionless-Software-Foundation/gatsby-theme-bch-wallet) is a Gatsby Theme and [bch-wallet-starter](https://github.com/Permissionless-Software-Foundation/bch-wallet-starter) is a Gatsby Starter for building web wallets using minimal-slp-wallet. 67 | 68 | [This gist](https://gist.github.com/christroutner/6cb9d1b615f3f9363af79723157bc434) shows how to include minimal-slp-wallet into a basic web page without using a framework. 69 | 70 | ## Features 71 | 72 | - [ECMAScript 2017 standard JavaScript](https://en.wikipedia.org/wiki/ECMAScript#8th_Edition_-_ECMAScript_2017) used instead of TypeScript. Works 73 | natively with node.js v10 or higher. 74 | 75 | - Full SLP tokens support: bch-js has full support for all SLP token functionality, including send, mint, and genesis transactions. It also fully supports all aspects of [non-fugible tokans (NFTs)](https://www.youtube.com/watch?v=vvlpYUx6HRs). 76 | 77 | - [Semantic Release](https://github.com/semantic-release/semantic-release) for 78 | continuous delivery using semantic versioning. 79 | 80 | - [IPFS](https://ipfs.io) and [Radicle](https://radicle.xyz) uploads of all files and dependencies, to backup 81 | dependencies in case they are ever inaccessible from GitHub or npm. 82 | 83 | ## Documentation: 84 | 85 | Full documentation for this library can be found here: 86 | 87 | - [Documentation](https://bchjs.fullstack.cash/) 88 | 89 | bch-js uses [APIDOC](http://apidocjs.com/) so that documentation and working code 90 | live in the same repository. To generate the documentation: 91 | 92 | - `npm run docs` 93 | - Open the generated `docs/index.html` file in a web browser. 94 | 95 | ## Support 96 | 97 | Have questions? Need help? Join our community support 98 | [Telegram channel](https://t.me/bch_js_toolkit) 99 | 100 | ## Donate 101 | 102 | This open source software is developed and maintained by the [Permissionless Software Foundation](https://psfoundation.cash). If this library provides value to you, please consider making a donation to support the PSF developers: 103 | 104 |
105 | 106 |

bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr

107 |
108 | 109 | 110 | ## IPFS & Radicle Releases 111 | 112 | Copies of this repository are also published on [IPFS](https://ipfs.io). 113 | 114 | - v6.2.10: `bafybeifsioj3ba77u2763nsyuzq53gtbdxsnqpoipvdl4immj6ytznjaoy` 115 | - (with dependencies, node v14.18.2 and npm v8.8.0): `bafybeihfendd4oj6uxvvecm7sluobwwhpb5wdcxhvhmx56e667nxdncd4a` 116 | 117 | They are also posted to the Radicle: 118 | - v6.2.10: `rad:git:hnrkkroqnbfwj6uxpfjuhspoxnfm4i8e6oqwy` 119 | 120 | ## License 121 | 122 | [MIT](LICENSE.md) 123 | -------------------------------------------------------------------------------- /deploy-production.sh: -------------------------------------------------------------------------------- 1 | # Triggers a webhook to update the staging server at staging.fullstack.cash. 2 | 3 | #!/bin/bash 4 | #echo $DEPLOY_SECRET 5 | 6 | echo "Deploy to production...." 7 | 8 | export DATA="{\"ref\":\"$DEPLOY_SECRET\"}" 9 | 10 | #echo $DATA 11 | 12 | curl -X POST http://fullstack.cash:9000/hooks/bch-js -H "Content-Type: application/json" -d $DATA 13 | 14 | echo "...Finished deploying to production." 15 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Examples have been moved to this repository 2 | 3 | https://github.com/Permissionless-Software-Foundation/bch-js-examples 4 | -------------------------------------------------------------------------------- /img/donation-qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Permissionless-Software-Foundation/bch-js/01625abb37e2f54c0588f1676252c2892c918b8a/img/donation-qr.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@psf/bch-js", 3 | "version": "6.0.0", 4 | "description": "A JavaScript library for working with Bitcoin Cash, eCash, and SLP Tokens", 5 | "author": "Chris Troutner ", 6 | "contributors": [ 7 | "Gabriel Cardona ", 8 | "Stoyan Zhekov " 9 | ], 10 | "main": "src/bch-js", 11 | "scripts": { 12 | "test": "nyc mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/", 13 | "test:integration": "npm run test:integration:bchn", 14 | "test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js", 15 | "test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/", 16 | "test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", 17 | "test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", 18 | "test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/", 19 | "test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", 20 | "test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet", 21 | "test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/", 22 | "test:integration:decatur:abc": "export RESTURL=http://192.168.2.142:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/", 23 | "test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/", 24 | "test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/", 25 | "test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/", 26 | "test:temp3": "export RESTURL=https://bchn.fullstack.cash/v5/ && mocha --timeout 30000 -g '#DSProof' test/integration/chains/", 27 | "test:temp4": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/", 28 | "coverage": "nyc report --reporter=text-lcov | coveralls", 29 | "coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/", 30 | "docs": "./node_modules/.bin/apidoc -i src/ -o docs", 31 | "lint": "standard --env mocha --fix" 32 | }, 33 | "license": "MIT", 34 | "homepage": "https://github.com/Permissionless-Software-Foundation/bch-js", 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git" 38 | }, 39 | "dependencies": { 40 | "@chris.troutner/bip32-utils": "1.0.5", 41 | "@psf/bip21": "2.0.1", 42 | "@psf/bitcoincash-ops": "2.0.0", 43 | "@psf/bitcoincashjs-lib": "4.0.3", 44 | "@psf/coininfo": "4.0.0", 45 | "axios": "0.26.1", 46 | "bc-bip68": "1.0.5", 47 | "bchaddrjs-slp": "0.2.5", 48 | "bigi": "1.4.2", 49 | "bignumber.js": "9.0.0", 50 | "bip-schnorr": "0.3.0", 51 | "bip38": "2.0.2", 52 | "bip39": "3.0.2", 53 | "bip66": "1.1.5", 54 | "bitcoinjs-message": "2.0.0", 55 | "bs58": "4.0.1", 56 | "ecashaddrjs": "1.0.7", 57 | "ini": "1.3.8", 58 | "randombytes": "2.0.6", 59 | "safe-buffer": "5.1.2", 60 | "satoshi-bitcoin": "1.0.4", 61 | "slp-mdm": "0.0.6", 62 | "slp-parser": "0.0.4", 63 | "wif": "2.0.6" 64 | }, 65 | "devDependencies": { 66 | "apidoc": "0.50.5", 67 | "assert": "2.0.0", 68 | "chai": "4.1.2", 69 | "coveralls": "3.0.2", 70 | "eslint": "7.17.0", 71 | "eslint-config-prettier": "7.1.0", 72 | "eslint-config-standard": "16.0.3", 73 | "eslint-plugin-node": "11.1.0", 74 | "eslint-plugin-prettier": "3.3.1", 75 | "eslint-plugin-standard": "4.0.0", 76 | "husky": "4.3.8", 77 | "lodash.clonedeep": "4.5.0", 78 | "mocha": "9.2.1", 79 | "node-mocks-http": "1.7.0", 80 | "nyc": "15.1.0", 81 | "semantic-release": "^19.0.2", 82 | "sinon": "9.2.2", 83 | "standard": "^16.0.4" 84 | }, 85 | "apidoc": { 86 | "title": "bch-js", 87 | "url": "bchjs." 88 | }, 89 | "husky": { 90 | "hooks": { 91 | "pre-commit": "npm run lint" 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/bch-js.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the primary library file for bch-js. This file combines all the other 3 | libraries in order to create the BCHJS class. 4 | 5 | The primary server used has switched to fullstack.cash. Go there to sign up 6 | for an account that gives you increased rate limits. 7 | */ 8 | 9 | // bch-api mainnet. 10 | const DEFAULT_REST_API = 'https://api.fullstack.cash/v5/' 11 | // const DEFAULT_REST_API = "http://localhost:3000/v5/" 12 | 13 | // local deps 14 | const BitcoinCash = require('./bitcoincash') 15 | const Crypto = require('./crypto') 16 | const Util = require('./util') 17 | const Blockchain = require('./blockchain') 18 | const Control = require('./control') 19 | const Generating = require('./generating') 20 | const Mining = require('./mining') 21 | const RawTransactions = require('./raw-transactions') 22 | const Mnemonic = require('./mnemonic') 23 | const Address = require('./address') 24 | const HDNode = require('./hdnode') 25 | const TransactionBuilder = require('./transaction-builder') 26 | const ECPair = require('./ecpair') 27 | const Script = require('./script') 28 | const Price = require('./price') 29 | const Schnorr = require('./schnorr') 30 | const SLP = require('./slp/slp') 31 | const Encryption = require('./encryption') 32 | const Utxo = require('./utxo') 33 | const Transaction = require('./transaction') 34 | const DSProof = require('./dsproof') 35 | const Ecash = require('./ecash') 36 | 37 | // Indexers 38 | const Electrumx = require('./electrumx') 39 | const PsfSlpIndexer = require('./psf-slp-indexer') 40 | 41 | class BCHJS { 42 | constructor (config) { 43 | // Try to retrieve the REST API URL from different sources. 44 | if (config && config.restURL && config.restURL !== '') { 45 | this.restURL = config.restURL 46 | } else if (process.env.RESTURL && process.env.RESTURL !== '') { 47 | this.restURL = process.env.RESTURL 48 | } else this.restURL = DEFAULT_REST_API 49 | 50 | // Retrieve the apiToken 51 | this.apiToken = '' // default value. 52 | if (config && config.apiToken && config.apiToken !== '') { 53 | this.apiToken = config.apiToken 54 | } else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') { 55 | this.apiToken = process.env.BCHJSTOKEN 56 | } 57 | 58 | // Retrieve the Basic Authentication password. 59 | this.authPass = '' // default value. 60 | if (config && config.authPass && config.authPass !== '') { 61 | this.authPass = config.authPass 62 | } else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') { 63 | this.authPass = process.env.BCHJSAUTHPASS 64 | } 65 | 66 | // Generate a Basic Authentication token from an auth password 67 | this.authToken = '' 68 | if (this.authPass) { 69 | // console.log(`bch-js initialized with authPass: ${this.authPass}`) 70 | // Generate the header for Basic Authentication. 71 | const combined = `fullstackcash:${this.authPass}` 72 | const base64Credential = Buffer.from(combined).toString('base64') 73 | this.authToken = `Basic ${base64Credential}` 74 | } 75 | 76 | const libConfig = { 77 | restURL: this.restURL, 78 | apiToken: this.apiToken, 79 | authToken: this.authToken 80 | } 81 | 82 | // console.log(`apiToken: ${this.apiToken}`) 83 | 84 | // ElectrumX indexer 85 | this.Electrumx = new Electrumx(libConfig) 86 | 87 | // Populate Full Node 88 | this.Control = new Control(libConfig) 89 | this.Mining = new Mining(libConfig) 90 | this.RawTransactions = new RawTransactions(libConfig) 91 | 92 | // Populate utility functions 93 | this.Address = new Address(libConfig) 94 | this.BitcoinCash = new BitcoinCash(this.Address) 95 | this.Blockchain = new Blockchain(libConfig) 96 | this.Crypto = Crypto 97 | this.ECPair = ECPair 98 | this.ECPair.setAddress(this.Address) 99 | this.encryption = new Encryption(libConfig) 100 | this.Generating = new Generating(libConfig) 101 | this.HDNode = new HDNode(this.Address) 102 | this.Mnemonic = new Mnemonic(this.Address) 103 | this.Price = new Price(libConfig) 104 | this.Script = new Script() 105 | this.TransactionBuilder = TransactionBuilder 106 | this.TransactionBuilder.setAddress(this.Address) 107 | this.Util = new Util(libConfig) 108 | this.Schnorr = new Schnorr(libConfig) 109 | 110 | this.SLP = new SLP(libConfig) 111 | this.SLP.HDNode = this.HDNode 112 | 113 | this.Utxo = new Utxo(libConfig) 114 | this.Transaction = new Transaction(libConfig) 115 | 116 | this.DSProof = new DSProof(libConfig) 117 | this.eCash = new Ecash() 118 | 119 | this.PsfSlpIndexer = new PsfSlpIndexer(libConfig) 120 | } 121 | } 122 | 123 | module.exports = BCHJS 124 | -------------------------------------------------------------------------------- /src/control.js: -------------------------------------------------------------------------------- 1 | /* 2 | API endpoints for basic control and information of the full node. 3 | */ 4 | 5 | const axios = require('axios') 6 | 7 | // let _this // Global reference to the instance of this class. 8 | 9 | class Control { 10 | constructor (config) { 11 | this.restURL = config.restURL 12 | this.apiToken = config.apiToken 13 | this.authToken = config.authToken 14 | 15 | if (this.authToken) { 16 | // Add Basic Authentication token to the authorization header. 17 | this.axiosOptions = { 18 | headers: { 19 | authorization: this.authToken 20 | } 21 | } 22 | } else { 23 | // Add JWT token to the authorization header. 24 | this.axiosOptions = { 25 | headers: { 26 | authorization: `Token ${this.apiToken}` 27 | } 28 | } 29 | } 30 | 31 | // _this = this 32 | } 33 | 34 | /** 35 | * @api Control.getNetworkInfo() getNetworkInfo() 36 | * @apiName getNetworkInfo 37 | * @apiGroup Control 38 | * @apiDescription Returns an object containing various network info. 39 | * 40 | * @apiExample Example usage: 41 | * (async () => { 42 | * try { 43 | * let getInfo = await bchjs.Control.getNetworkInfo(); 44 | * console.log(getInfo); 45 | * } catch(error) { 46 | * console.error(error) 47 | * } 48 | * })() 49 | * 50 | * // returns 51 | * { version: 190500, 52 | * subversion: '/Bitcoin ABC:0.19.5(EB32.0)/', 53 | * protocolversion: 70015, 54 | * localservices: '0000000000000425', 55 | * localrelay: true, 56 | * timeoffset: 0, 57 | * networkactive: true, 58 | * connections: 17, 59 | * networks: 60 | * [ { name: 'ipv4', 61 | * limited: false, 62 | * reachable: true, 63 | * proxy: '', 64 | * proxy_randomize_credentials: false }, 65 | * { name: 'ipv6', 66 | * limited: false, 67 | * reachable: true, 68 | * proxy: '', 69 | * proxy_randomize_credentials: false }, 70 | * { name: 'onion', 71 | * limited: true, 72 | * reachable: false, 73 | * proxy: '', 74 | * proxy_randomize_credentials: false } ], 75 | * relayfee: 0.00001, 76 | * excessutxocharge: 0, 77 | * warnings: 78 | * 'Warning: Unknown block versions being mined! It\'s possible unknown rules are in effect' }} 79 | */ 80 | async getNetworkInfo () { 81 | try { 82 | const response = await axios.get( 83 | `${this.restURL}control/getNetworkInfo`, 84 | this.axiosOptions 85 | ) 86 | return response.data 87 | } catch (error) { 88 | if (error.response && error.response.data) throw error.response.data 89 | else throw error 90 | } 91 | } 92 | 93 | async getMemoryInfo () { 94 | try { 95 | const response = await axios.get( 96 | `${this.restURL}control/getMemoryInfo`, 97 | this.axiosOptions 98 | ) 99 | return response.data 100 | } catch (error) { 101 | if (error.response && error.response.data) throw error.response.data 102 | else throw error 103 | } 104 | } 105 | // 106 | // stop() { 107 | // // Stop Bitcoin Cash server. 108 | // return axios.post(`${this.restURL}control/stop`) 109 | // .then((response) => { 110 | // return response.data; 111 | // }) 112 | // .catch((error) => { 113 | // return JSON.stringify(error.response.data.error.message); 114 | // }); 115 | // } 116 | } 117 | 118 | module.exports = Control 119 | -------------------------------------------------------------------------------- /src/crypto.js: -------------------------------------------------------------------------------- 1 | const randomBytes = require('randombytes') 2 | const Bitcoin = require('@psf/bitcoincashjs-lib') 3 | 4 | class Crypto { 5 | /** 6 | * @api Crypto.sha256() sha256() 7 | * @apiName sha256 8 | * @apiGroup Crypto 9 | * @apiDescription Utility for creating sha256 hash digests of data 10 | * 11 | * @apiExample Example usage: 12 | * // buffer from hex 13 | * let buffer = Buffer.from('0101010101010101', 'hex') 14 | * bchjs.Crypto.sha256(buffer) 15 | * // 16 | * 17 | * // buffer from hex 18 | * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') 19 | * bchjs.Crypto.sha256(buffer) 20 | * // 21 | * 22 | * // buffer from hex 23 | * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') 24 | * bchjs.Crypto.sha256(buffer) 25 | * // 26 | * 27 | * */ 28 | // Translate address from any address format into a specific format. 29 | static sha256 (buffer) { 30 | return Bitcoin.crypto.sha256(buffer) 31 | } 32 | 33 | /** 34 | * @api Crypto.ripemd160() ripemd160() 35 | * @apiName ripemd160 36 | * @apiGroup Crypto 37 | * @apiDescription Utility for creating ripemd160 hash digests of data 38 | * 39 | * @apiExample Example usage: 40 | * // buffer from hex 41 | * let buffer = Buffer.from('0101010101010101', 'hex') 42 | * bchjs.Crypto.ripemd160(buffer) 43 | * // 44 | * 45 | * // buffer from hex 46 | * let buffer = Buffer.from('75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab', 'hex') 47 | * bchjs.Crypto.ripemd160(buffer) 48 | * // 49 | * 50 | * // buffer from hex 51 | * let buffer = Buffer.from('978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de', 'hex') 52 | * bchjs.Crypto.ripemd160(buffer) 53 | * // 54 | * */ 55 | static ripemd160 (buffer) { 56 | return Bitcoin.crypto.ripemd160(buffer) 57 | } 58 | 59 | /** 60 | * @api Crypto.hash256() hash256() 61 | * @apiName hash256 62 | * @apiGroup Crypto 63 | * @apiDescription Utility for creating double sha256 hash digests of buffer encoded data. 64 | * 65 | * @apiExample Example usage: 66 | * // buffer from hex 67 | * let buffer = Buffer.from('0101010101010101', 'hex') 68 | * bchjs.Crypto.hash256(buffer) 69 | * // 70 | * 71 | * // buffer from hex 72 | * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') 73 | * bchjs.Crypto.hash256(buffer) 74 | * // 75 | * 76 | * // buffer from hex 77 | * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') 78 | * bchjs.Crypto.hash256(buffer) 79 | * // 80 | * */ 81 | static hash256 (buffer) { 82 | return Bitcoin.crypto.hash256(buffer) 83 | } 84 | 85 | /** 86 | * @api Crypto.hash160() hash160() 87 | * @apiName hash160 88 | * @apiGroup Crypto 89 | * @apiDescription Utility for creating ripemd160(sha256()) hash digests of buffer encoded data. 90 | * 91 | * @apiExample Example usage: 92 | * // buffer from hex 93 | * let buffer = Buffer.from('0101010101010101', 'hex') 94 | * bchjs.Crypto.hash160(buffer) 95 | * // 96 | * 97 | * // buffer from hex 98 | * let buffer = Buffer.from('031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b', 'hex') 99 | * bchjs.Crypto.hash160(buffer) 100 | * // 101 | * 102 | * // buffer from hex 103 | * let buffer = Buffer.from('03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354', 'hex') 104 | * bchjs.Crypto.hash160(buffer) 105 | * 106 | * */ 107 | static hash160 (buffer) { 108 | return Bitcoin.crypto.hash160(buffer) 109 | } 110 | 111 | /** 112 | * @api Crypto.randomBytes() randomBytes() 113 | * @apiName randomBytes 114 | * @apiGroup Crypto 115 | * @apiDescription Generates cryptographically strong pseudo-random data. The size argument is a number indicating the number of bytes to generate. 116 | * 117 | * @apiExample Example usage: 118 | * bchjs.Crypto.randomBytes(16) 119 | * // 120 | * 121 | * bchjs.Crypto.randomBytes(20) 122 | * // 123 | * 124 | * bchjs.Crypto.randomBytes(24) 125 | * // 126 | * 127 | * bchjs.Crypto.randomBytes(28) 128 | * // 129 | * 130 | * bchjs.Crypto.randomBytes(32) 131 | * // 132 | * */ 133 | static randomBytes (size = 16) { 134 | return randomBytes(size) 135 | } 136 | } 137 | 138 | module.exports = Crypto 139 | -------------------------------------------------------------------------------- /src/dsproof.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | let _this 4 | 5 | class DSProof { 6 | constructor (config) { 7 | this.restURL = config.restURL 8 | this.apiToken = config.apiToken 9 | this.authToken = config.authToken 10 | this.axios = axios 11 | 12 | if (this.authToken) { 13 | // Add Basic Authentication token to the authorization header. 14 | this.axiosOptions = { 15 | headers: { 16 | authorization: this.authToken 17 | } 18 | } 19 | } else { 20 | // Add JWT token to the authorization header. 21 | this.axiosOptions = { 22 | headers: { 23 | authorization: `Token ${this.apiToken}` 24 | } 25 | } 26 | } 27 | 28 | _this = this 29 | } 30 | 31 | /** 32 | * @api DSProof.getDSProof() getDSProof() 33 | * @apiName getDSProof 34 | * @apiGroup DSProof 35 | * @apiDescription Checks if a transaction generated a double-spend proof. 36 | * 37 | * If a double-spend is attempted, one of the transactions will generate a 38 | * 'double spend proof'. This call can be used to check if a transaction 39 | * generated such a proof. 40 | * 41 | * Merchants should wait 3-5 seconds after receiving notification of a 42 | * transaction before calling this endpoint, to see if the TXID generated a 43 | * proof. If this method returns no data, then the TX can be considered 'safe' 44 | * and not a double spend. If proof data is returned by this method, then 45 | * the transaction generated a proof and can be considered a 'double spend'. 46 | * 47 | * @apiExample Example usage: 48 | * (async () => { 49 | * try { 50 | * const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e' 51 | * const result = await bchjs.DSProof.getDSProof(txid) 52 | * console.log(result); 53 | * } catch(error) { 54 | * console.error(error) 55 | * } 56 | * })() 57 | * 58 | * // returns 59 | * null 60 | */ 61 | async getDSProof (txid) { 62 | try { 63 | if (!txid) { 64 | throw new Error('txid is required') 65 | } 66 | if (txid.length !== 64) { 67 | throw new Error(`txid must be of length 64 (not ${txid.length})`) 68 | } 69 | const response = await _this.axios.get( 70 | `${this.restURL}dsproof/getdsproof/${txid}`, 71 | this.axiosOptions 72 | ) 73 | return response.data 74 | } catch (error) { 75 | if (error.response && error.response.data) throw error.response.data 76 | else throw error 77 | } 78 | } 79 | } 80 | 81 | module.exports = DSProof 82 | -------------------------------------------------------------------------------- /src/ecash.js: -------------------------------------------------------------------------------- 1 | /* 2 | Utility library for converting units with eCash. 3 | */ 4 | 5 | class eCash { 6 | /** 7 | * @api eCash.toSatoshi() toSatoshi() 8 | * @apiName toSatoshi 9 | * @apiGroup eCash 10 | * @apiDescription 11 | * Convert XEC units into satoshi units 12 | * 13 | * @apiExample Example usage: 14 | * // convert 10,704.35 XEC to satoshis: 15 | * bchjs.eCash.toSatoshi(10704.35) 16 | * // 1070435 17 | */ 18 | toSatoshi (xec) { 19 | if (typeof xec !== 'number') { 20 | throw new Error('input must be a floating number representing XEC') 21 | } 22 | 23 | return Math.floor(xec * 100) 24 | } 25 | 26 | /** 27 | * @api eCash.toXec() toXec() 28 | * @apiName toXec 29 | * @apiGroup eCash 30 | * @apiDescription 31 | * Convert satoshi units to XEC units 32 | * 33 | * @apiExample Example usage: 34 | * // convert 1,070,435 satoshis to XEC: 35 | * bchjs.eCash.toSatoshi(1070435) 36 | * // 10704.35 37 | */ 38 | toXec (sats) { 39 | if (typeof sats !== 'number') { 40 | throw new Error('input must be a floating number representing satoshis') 41 | } 42 | 43 | return sats / 100 44 | } 45 | } 46 | 47 | module.exports = eCash 48 | -------------------------------------------------------------------------------- /src/ecpair.js: -------------------------------------------------------------------------------- 1 | const Bitcoin = require('@psf/bitcoincashjs-lib') 2 | const coininfo = require('@psf/coininfo') 3 | 4 | class ECPair { 5 | static setAddress (address) { 6 | ECPair._address = address 7 | } 8 | 9 | /** 10 | * @api Ecpair.fromWIF() fromWIF() 11 | * @apiName fromWIF 12 | * @apiGroup ECPair 13 | * @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. 14 | * 15 | * @apiExample Example usage: 16 | * // mainnet WIF 17 | * let wif = 'L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1'; 18 | * bchjs.ECPair.fromWIF(wif); 19 | * 20 | * // testnet WIF 21 | * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW' 22 | * bchjs.ECPair.fromWIF(wif) 23 | * */ 24 | static fromWIF (privateKeyWIF) { 25 | let network 26 | if (privateKeyWIF[0] === 'L' || privateKeyWIF[0] === 'K') { network = 'mainnet' } else if (privateKeyWIF[0] === 'c') network = 'testnet' 27 | 28 | let bitcoincash 29 | if (network === 'mainnet') bitcoincash = coininfo.bitcoincash.main 30 | else bitcoincash = coininfo.bitcoincash.test 31 | 32 | const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() 33 | 34 | return Bitcoin.ECPair.fromWIF(privateKeyWIF, bitcoincashBitcoinJSLib) 35 | } 36 | 37 | /** 38 | * @api Ecpair.toWIF() toWIF() 39 | * @apiName toWIF 40 | * @apiGroup ECPair 41 | * @apiDescription Gets a private key in wallet import format from an ECPair. 42 | * 43 | * @apiExample Example usage: 44 | * // mainnet wif 45 | * let wif = 'L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1'; 46 | * // ecpair from wif 47 | * let ecpair = bchjs.ECPair.fromWIF(wif); 48 | * // wif from ecpair 49 | * bchjs.ECPair.toWIF(ecpair); 50 | * // L4vmKsStbQaCvaKPnCzdRArZgdAxTqVx8vjMGLW5nHtWdRguiRi1 51 | * 52 | * // testnet wif 53 | * let wif = 'cT3tJP7BnjFJSAHbooMXrY8E9t2AFj37amSBAYFMeHfqPqPgD4ZA'; 54 | * // ecpair from wif 55 | * let ecpair = bchjs.ECPair.fromWIF(wif); 56 | * // wif from ecpair 57 | * bchjs.ECPair.toWIF(ecpair); 58 | * // cT3tJP7BnjFJSAHbooMXrY8E9t2AFj37amSBAYFMeHfqPqPgD4ZA 59 | * */ 60 | static toWIF (ecpair) { 61 | return ecpair.toWIF() 62 | } 63 | 64 | static sign (ecpair, buffer) { 65 | return ecpair.sign(buffer) 66 | } 67 | 68 | static verify (ecpair, buffer, signature) { 69 | return ecpair.verify(buffer, signature) 70 | } 71 | 72 | /** 73 | * @api Ecpair.fromPublicKey() fromPublicKey() 74 | * @apiName fromPublicKey 75 | * @apiGroup ECPair 76 | * @apiDescription Generates an ECPair from a public key buffer. 77 | * 78 | * @apiExample Example usage: 79 | * // create ECPair from mainnet pubkeyBuffer 80 | * let pubkeyBuffer = Buffer.from("02fb721b92025e775b1b84774e65d568d24645cb633275f5c26f5c3101b214a8fb", 'hex'); 81 | * bchjs.ECPair.fromPublicKey(pubkeyBuffer); 82 | * 83 | * // create ECPair from testnet pubkeyBuffer 84 | * let pubkeyBuffer = Buffer.from("024a6d0737a23c472d078d78c1cbc3c2bbf8767b48e72684ff03a911b463da7fa6", 'hex'); 85 | * bchjs.ECPair.fromPublicKey(pubkeyBuffer); 86 | * */ 87 | static fromPublicKey (pubkeyBuffer) { 88 | return Bitcoin.ECPair.fromPublicKeyBuffer(pubkeyBuffer) 89 | } 90 | 91 | /** 92 | * @api Ecpair.toPublicKey() toPublicKey() 93 | * @apiName toPublicKey 94 | * @apiGroup ECPair 95 | * @apiDescription Get the public key of an ECPair as a buffer. 96 | * 97 | * @apiExample Example usage: 98 | * // create ecpair from mainnet public key buffer 99 | * let ecpair = bchjs.ECPair.fromPublicKey(Buffer.from('02d305772e0873fba6c1c7ff353ce374233316eb5820acd7ff3d7d9b82d514126b', 'hex')); 100 | * // create public key buffer 101 | * bchjs.ECPair.toPublicKey(ecpair); 102 | * // 103 | * 104 | * // create ecpair from testnet public key buffer 105 | * let ecpair = bchjs.ECPair.fromPublicKey(Buffer.from('024a6d0737a23c472d078d78c1cbc3c2bbf8767b48e72684ff03a911b463da7fa6', 'hex')); 106 | * // create public key buffer 107 | * bchjs.ECPair.toPublicKey(ecpair); 108 | * // 109 | * */ 110 | static toPublicKey (ecpair) { 111 | return ecpair.getPublicKeyBuffer() 112 | } 113 | 114 | /** 115 | * @api Ecpair.toLegacyAddress() toLegacyAddress() 116 | * @apiName toLegacyAddress 117 | * @apiGroup ECPair 118 | * @apiDescription Get legacy address of ECPair. 119 | * 120 | * @apiExample Example usage: 121 | * // mainnet wif 122 | * let wif = 'L5GPEGxCmojgzFoBLUUqT2GegLGqobiYhTZzfLtpkLTfTb9E9NRn'; 123 | * // ecpair from wif 124 | * let ecpair = bchjs.ECPair.fromWIF(wif); 125 | * // to legacy address 126 | * bchjs.ECPair.toLegacyAddress(ecpair); 127 | * // 1DgxdA5bbMcCNWg3yB2MgKqFazV92BXgxK 128 | * 129 | * // testnet wif 130 | * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW'; 131 | * // ecpair from wif 132 | * let ecpair = bchjs.ECPair.fromWIF(wif); 133 | * // to legacy address 134 | * bchjs.ECPair.toLegacyAddress(ecpair); 135 | * // mg4PygFcXoyNJGJkM2Dcpe25av9wXzz1My 136 | * */ 137 | static toLegacyAddress (ecpair) { 138 | return ecpair.getAddress() 139 | } 140 | 141 | /** 142 | * @api Ecpair.toCashAddress() toCashAddress() 143 | * @apiName toCashAddress 144 | * @apiGroup ECPair 145 | * @apiDescription Get cash address of ECPair. 146 | * 147 | * @apiExample Example usage: 148 | * // mainnet wif 149 | * let wif = 'L5GPEGxCmojgzFoBLUUqT2GegLGqobiYhTZzfLtpkLTfTb9E9NRn'; 150 | * // ecpair from wif 151 | * let ecpair = bchjs.ECPair.fromWIF(wif); 152 | * // to legacy address 153 | * bchjs.ECPair.toCashAddress(ecpair); 154 | * // bitcoincash:qz9nq206kteyv2t7trhdr4vzzkej60kqtytn7sxkxm 155 | * 156 | * // testnet wif 157 | * let wif = 'cSNLj6xeg3Yg2rfcgKoWNx4MiAgn9ugCUUro37UDEhn6CzeYqjWW'; 158 | * // ecpair from wif 159 | * let ecpair = bchjs.ECPair.fromWIF(wif); 160 | * // to legacy address 161 | * bchjs.ECPair.toCashAddress(ecpair); 162 | * // bchtest:qqzly4vrcxcjw62u4yq4nv86ltk2mc9v0yvq8mvj6m 163 | * */ 164 | static toCashAddress (ecpair, regtest = false) { 165 | return ECPair._address.toCashAddress(ecpair.getAddress(), true, regtest) 166 | } 167 | } 168 | 169 | module.exports = ECPair 170 | -------------------------------------------------------------------------------- /src/encryption.js: -------------------------------------------------------------------------------- 1 | /* 2 | This library contains useful functions that deal with encryption. 3 | */ 4 | 5 | const axios = require('axios') 6 | 7 | let _this 8 | 9 | class Encryption { 10 | constructor (config) { 11 | this.restURL = config.restURL 12 | this.apiToken = config.apiToken 13 | this.axios = axios 14 | this.authToken = config.authToken 15 | 16 | if (this.authToken) { 17 | // Add Basic Authentication token to the authorization header. 18 | this.axiosOptions = { 19 | headers: { 20 | authorization: this.authToken 21 | } 22 | } 23 | } else { 24 | // Add JWT token to the authorization header. 25 | this.axiosOptions = { 26 | headers: { 27 | authorization: `Token ${this.apiToken}` 28 | } 29 | } 30 | } 31 | 32 | _this = this 33 | } 34 | 35 | /** 36 | * @api encryption.getPubKey() getPubKey() 37 | * @apiName Encryption getPubKey() 38 | * @apiGroup Encryption 39 | * @apiDescription Get the public key for an address 40 | * Given an address, the command will search the blockchain for a public 41 | * key associated with that address. The address needs to have made at least 42 | * one spend transaction, in order for its public key to be retrievable. 43 | * 44 | * @apiExample Example usage: 45 | *(async () => { 46 | * try { 47 | * const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d' 48 | * const pubkey = await bchjs.encryption.getPubKey(addr); 49 | * console.log(pubkey); 50 | * } catch(err) { 51 | * console.error(err) 52 | * } 53 | *})() 54 | * 55 | */ 56 | 57 | // Search the blockchain for a public key associated with a BCH address. 58 | async getPubKey (addr) { 59 | try { 60 | if (!addr || typeof addr !== 'string') { 61 | throw new Error('Input must be a valid Bitcoin Cash address.') 62 | } 63 | 64 | const response = await _this.axios.get( 65 | `${this.restURL}encryption/publickey/${addr}`, 66 | this.axiosOptions 67 | ) 68 | return response.data 69 | } catch (error) { 70 | if (error.response && error.response.data) throw error.response.data 71 | else throw error 72 | } 73 | } 74 | } 75 | 76 | module.exports = Encryption 77 | -------------------------------------------------------------------------------- /src/generating.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | // let _this 4 | 5 | class Generating { 6 | constructor (config) { 7 | this.restURL = config.restURL 8 | this.apiToken = config.apiToken 9 | this.authToken = config.authToken 10 | 11 | if (this.authToken) { 12 | // Add Basic Authentication token to the authorization header. 13 | this.axiosOptions = { 14 | headers: { 15 | authorization: this.authToken 16 | } 17 | } 18 | } else { 19 | // Add JWT token to the authorization header. 20 | this.axiosOptions = { 21 | headers: { 22 | authorization: `Token ${this.apiToken}` 23 | } 24 | } 25 | } 26 | 27 | // _this = this 28 | } 29 | 30 | async generateToAddress (blocks, address, maxtries = 1000000) { 31 | try { 32 | const response = await axios.post( 33 | `${this.restURL}generating/generateToAddress/${blocks}/${address}?maxtries=${maxtries}`, 34 | this.axiosOptions 35 | ) 36 | return response.data 37 | } catch (error) { 38 | if (error.response && error.response.data) throw error.response.data 39 | else throw error 40 | } 41 | } 42 | } 43 | 44 | module.exports = Generating 45 | -------------------------------------------------------------------------------- /src/mining.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | // let _this 4 | 5 | class Mining { 6 | constructor (config) { 7 | this.restURL = config.restURL 8 | this.apiToken = config.apiToken 9 | this.authToken = config.authToken 10 | 11 | if (this.authToken) { 12 | // Add Basic Authentication token to the authorization header. 13 | this.axiosOptions = { 14 | headers: { 15 | authorization: this.authToken 16 | } 17 | } 18 | } else { 19 | // Add JWT token to the authorization header. 20 | this.axiosOptions = { 21 | headers: { 22 | authorization: `Token ${this.apiToken}` 23 | } 24 | } 25 | } 26 | 27 | // _this = this 28 | } 29 | 30 | async getBlockTemplate (templateRequest) { 31 | try { 32 | const response = await axios.get( 33 | `${this.restURL}mining/getBlockTemplate/${templateRequest}`, 34 | this.axiosOptions 35 | ) 36 | return response.data 37 | } catch (error) { 38 | if (error.response && error.response.data) throw error.response.data 39 | else throw error 40 | } 41 | } 42 | 43 | async getMiningInfo () { 44 | try { 45 | const response = await axios.get( 46 | `${this.restURL}mining/getMiningInfo`, 47 | this.axiosOptions 48 | ) 49 | return response.data 50 | } catch (error) { 51 | if (error.response && error.response.data) throw error.response.data 52 | else throw error 53 | } 54 | } 55 | 56 | async getNetworkHashps (nblocks = 120, height = 1) { 57 | try { 58 | const response = await axios.get( 59 | `${this.restURL}mining/getNetworkHashps?nblocks=${nblocks}&height=${height}`, 60 | this.axiosOptions 61 | ) 62 | return response.data 63 | } catch (error) { 64 | if (error.response && error.response.data) throw error.response.data 65 | else throw error 66 | } 67 | } 68 | 69 | async submitBlock (hex, parameters) { 70 | let path = `${this.restURL}mining/submitBlock/${hex}` 71 | if (parameters) path = `${path}?parameters=${parameters}` 72 | 73 | try { 74 | const response = await axios.post(path, this.axiosOptions) 75 | return response.data 76 | } catch (error) { 77 | if (error.response && error.response.data) throw error.response.data 78 | else throw error 79 | } 80 | } 81 | } 82 | 83 | module.exports = Mining 84 | -------------------------------------------------------------------------------- /src/slp/ecpair.js: -------------------------------------------------------------------------------- 1 | // const BCHJS = require("../bch-js") 2 | // const bchjs = new BCHJS() 3 | 4 | const BCHJSECPair = require('../ecpair') 5 | 6 | const bchaddrjs = require('bchaddrjs-slp') 7 | 8 | class ECPair extends BCHJSECPair { 9 | /* 10 | constructor(restURL) { 11 | super(restURL) 12 | this.restURL = restURL 13 | } 14 | */ 15 | /** 16 | * @api SLP.ECPair.toSLPAddress() toSLPAddress() 17 | * @apiName toSLPAddress 18 | * @apiGroup SLP 19 | * @apiDescription Get slp address of ECPair. 20 | * 21 | * @apiExample Example usage: 22 | * // create ecpair from wif 23 | * let ecpair = bchjs.SLP.ECPair.fromWIF('cUCSrdhu7mCzx4sWqL6irqzprkofxPmLHYgkSnG2WaWVqJDXtWRS') 24 | * // to slp address 25 | * bchjs.SLP.ECPair.toSLPAddress(ecpair); 26 | * // slptest:qq835u5srlcqwrtwt6xm4efwan30fxg9hcqag6fk03 27 | */ 28 | static toSLPAddress (ecpair) { 29 | const slpAddress = bchaddrjs.toSlpAddress(this.toCashAddress(ecpair)) 30 | return slpAddress 31 | } 32 | } 33 | 34 | module.exports = ECPair 35 | -------------------------------------------------------------------------------- /src/slp/slp.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is the parent library for the SLP class. It was originally forked from slp-sdk. 3 | 4 | TODO: Create an SLP fee calculator like slpjs: 5 | https://github.com/simpleledger/slpjs/blob/master/lib/slp.ts#L921 6 | */ 7 | 8 | // imports 9 | // require deps 10 | // const BCHJS = require("../bch-js") 11 | const Address = require('./address') 12 | const ECPair = require('./ecpair') 13 | // const HDNode = require("./hdnode") 14 | const TokenType1 = require('./tokentype1') 15 | const NFT1 = require('./nft1') 16 | const Utils = require('./utils') 17 | 18 | // SLP is a superset of BITBOX 19 | class SLP { 20 | constructor (config) { 21 | this.restURL = config.restURL 22 | this.apiToken = config.apiToken 23 | this.authToken = config.authToken 24 | 25 | if (this.authToken) { 26 | // Add Basic Authentication token to the authorization header. 27 | this.axiosOptions = { 28 | headers: { 29 | authorization: this.authToken 30 | } 31 | } 32 | } else { 33 | // Add JWT token to the authorization header. 34 | this.axiosOptions = { 35 | headers: { 36 | authorization: `Token ${this.apiToken}` 37 | } 38 | } 39 | } 40 | 41 | this.Address = new Address(config) 42 | this.ECPair = ECPair 43 | this.TokenType1 = new TokenType1(config) 44 | this.NFT1 = new NFT1(config) 45 | this.Utils = new Utils(config) 46 | } 47 | } 48 | 49 | module.exports = SLP 50 | -------------------------------------------------------------------------------- /src/slp/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-catch */ 2 | 3 | // Public npm libraries 4 | const axios = require('axios') 5 | const slpParser = require('slp-parser') 6 | 7 | // Local libraries 8 | const Util = require('../util') 9 | 10 | let _this 11 | 12 | class Utils { 13 | constructor (config = {}) { 14 | this.restURL = config.restURL 15 | this.apiToken = config.apiToken 16 | this.slpParser = slpParser 17 | this.authToken = config.authToken 18 | this.axios = axios 19 | 20 | if (this.authToken) { 21 | // Add Basic Authentication token to the authorization header. 22 | this.axiosOptions = { 23 | headers: { 24 | authorization: this.authToken 25 | } 26 | } 27 | } else { 28 | // Add JWT token to the authorization header. 29 | this.axiosOptions = { 30 | headers: { 31 | authorization: `Token ${this.apiToken}` 32 | } 33 | } 34 | } 35 | 36 | _this = this 37 | 38 | this.whitelist = [] 39 | 40 | this.util = new Util(config) 41 | } 42 | 43 | /** 44 | * @api SLP.Utils.decodeOpReturn() decodeOpReturn() 45 | * @apiName decodeOpReturn 46 | * @apiGroup SLP Utils 47 | * @apiDescription 48 | * Retrieves transactions data from a txid and decodes the SLP OP_RETURN data. 49 | * 50 | * Throws an error if given a non-SLP txid. 51 | * 52 | * If optional associative array parameter cache is used, will cache and 53 | * reuse responses for the same input. 54 | * 55 | * A third optional input, `usrObj`, is used by bch-api for managing rate limits. 56 | * It can be safely ignored when writing apps using this call. 57 | * 58 | * 59 | * @apiExample Example usage: 60 | * 61 | * (async () => { 62 | * try { 63 | * const txid = 64 | * "266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1" 65 | * 66 | * const data = await bchjs.SLP.Utils.decodeOpReturn(txid) 67 | * 68 | * console.log(`Decoded OP_RETURN data: ${JSON.stringify(data,null,2)}`) 69 | * } catch (error) { 70 | * console.error(error) 71 | * } 72 | * })() 73 | * 74 | * // returns 75 | * { 76 | * "tokenType": 1, 77 | * "txType": "SEND", 78 | * "tokenId": "497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7" 79 | * "amounts": [ 80 | * "100000000", 81 | * "99883300000000" 82 | * ] 83 | * } 84 | * 85 | */ 86 | // Reimplementation of decodeOpReturn() using slp-parser. 87 | async decodeOpReturn (txid, cache = null, usrObj = null) { 88 | // The cache object is an in-memory cache (JS Object) that can be passed 89 | // into this function. It helps if multiple vouts from the same TXID are 90 | // being evaluated. In that case, it can significantly reduce the number 91 | // of API calls. 92 | // To use: add the output of this function to the cache object: 93 | // cache[txid] = returnValue 94 | // Then pass that cache object back into this function every time its called. 95 | if (cache) { 96 | if (!(cache instanceof Object)) { 97 | throw new Error('decodeOpReturn cache parameter must be Object') 98 | } 99 | 100 | const cachedVal = cache[txid] 101 | if (cachedVal) return cachedVal 102 | } 103 | 104 | // console.log(`decodeOpReturn usrObj: ${JSON.stringify(usrObj, null, 2)}`) 105 | 106 | try { 107 | // Validate the txid input. 108 | if (!txid || txid === '' || typeof txid !== 'string') { 109 | throw new Error('txid string must be included.') 110 | } 111 | 112 | // CT: 2/24/21 Deprected GET in favor of POST, to pass IP address. 113 | // Retrieve the transaction object from the full node. 114 | const path = `${this.restURL}rawtransactions/getRawTransaction` 115 | // console.log('decodeOpReturn() this.axiosOptions: ', this.axiosOptions) 116 | const response = await this.axios.post( 117 | path, 118 | { 119 | verbose: true, 120 | txids: [txid], 121 | usrObj // pass user data when making an internal call. 122 | }, 123 | this.axiosOptions 124 | ) 125 | const txDetails = response.data[0] 126 | // console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`) 127 | 128 | // SLP spec expects OP_RETURN to be the first output of the transaction. 129 | const opReturn = txDetails.vout[0].scriptPubKey.hex 130 | // console.log(`opReturn hex: ${opReturn}`) 131 | 132 | const parsedData = _this.slpParser.parseSLP(Buffer.from(opReturn, 'hex')) 133 | // console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`) 134 | 135 | // Convert Buffer data to hex strings or utf8 strings. 136 | let tokenData = {} 137 | if (parsedData.transactionType === 'SEND') { 138 | tokenData = { 139 | tokenType: parsedData.tokenType, 140 | txType: parsedData.transactionType, 141 | tokenId: parsedData.data.tokenId.toString('hex'), 142 | amounts: parsedData.data.amounts 143 | } 144 | } else if (parsedData.transactionType === 'GENESIS') { 145 | tokenData = { 146 | tokenType: parsedData.tokenType, 147 | txType: parsedData.transactionType, 148 | ticker: parsedData.data.ticker.toString(), 149 | name: parsedData.data.name.toString(), 150 | tokenId: txid, 151 | documentUri: parsedData.data.documentUri.toString(), 152 | documentHash: parsedData.data.documentHash.toString(), 153 | decimals: parsedData.data.decimals, 154 | mintBatonVout: parsedData.data.mintBatonVout, 155 | qty: parsedData.data.qty 156 | } 157 | } else if (parsedData.transactionType === 'MINT') { 158 | tokenData = { 159 | tokenType: parsedData.tokenType, 160 | txType: parsedData.transactionType, 161 | tokenId: parsedData.data.tokenId.toString('hex'), 162 | mintBatonVout: parsedData.data.mintBatonVout, 163 | qty: parsedData.data.qty 164 | } 165 | } 166 | // console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`) 167 | 168 | if (cache) cache[txid] = tokenData 169 | 170 | return tokenData 171 | } catch (error) { 172 | // Used for debugging 173 | // console.log('decodeOpReturn error: ', error) 174 | // console.log(`decodeOpReturn error.message: ${error.message}`) 175 | // if (error.response && error.response.data) { 176 | // console.log( 177 | // `decodeOpReturn error.response.data: ${JSON.stringify( 178 | // error.response.data 179 | // )}` 180 | // ) 181 | // } 182 | throw error 183 | } 184 | } 185 | } 186 | 187 | module.exports = Utils 188 | -------------------------------------------------------------------------------- /src/transaction-builder.js: -------------------------------------------------------------------------------- 1 | const Bitcoin = require('@psf/bitcoincashjs-lib') 2 | const coininfo = require('@psf/coininfo') 3 | const bip66 = require('bip66') 4 | const bip68 = require('bc-bip68') 5 | 6 | class TransactionBuilder { 7 | static setAddress (address) { 8 | TransactionBuilder._address = address 9 | } 10 | 11 | constructor (network = 'mainnet') { 12 | let bitcoincash 13 | if (network === 'bitcoincash' || network === 'mainnet') { bitcoincash = coininfo.bitcoincash.main } else bitcoincash = coininfo.bitcoincash.test 14 | 15 | const bitcoincashBitcoinJSLib = bitcoincash.toBitcoinJS() 16 | this.transaction = new Bitcoin.TransactionBuilder(bitcoincashBitcoinJSLib) 17 | this.DEFAULT_SEQUENCE = 0xffffffff 18 | this.hashTypes = { 19 | SIGHASH_ALL: 0x01, 20 | SIGHASH_NONE: 0x02, 21 | SIGHASH_SINGLE: 0x03, 22 | SIGHASH_ANYONECANPAY: 0x80, 23 | SIGHASH_BITCOINCASH_BIP143: 0x40, 24 | ADVANCED_TRANSACTION_MARKER: 0x00, 25 | ADVANCED_TRANSACTION_FLAG: 0x01 26 | } 27 | this.signatureAlgorithms = { 28 | ECDSA: Bitcoin.ECSignature.ECDSA, 29 | SCHNORR: Bitcoin.ECSignature.SCHNORR 30 | } 31 | this.bip66 = bip66 32 | this.bip68 = bip68 33 | this.p2shInput = false 34 | // this.tx 35 | } 36 | 37 | /** 38 | * @api Transaction-Builder.addInput() addInput() 39 | * @apiName AddInput 40 | * @apiGroup TransactionBuilder 41 | * @apiDescription Add input to transaction. 42 | * 43 | * @apiExample Example usage: 44 | * // txid of vout 45 | * let txid = 'f7890915febe580920df2681d2bac0909ae89bd0cc1d3ed763e5eeba7f337f0e'; 46 | * // add input with txid and index of vout 47 | * transactionBuilder.addInput(txid, 0); 48 | */ 49 | addInput (txHash, vout, sequence = this.DEFAULT_SEQUENCE, prevOutScript) { 50 | this.transaction.addInput(txHash, vout, sequence, prevOutScript) 51 | } 52 | 53 | addInputScript (vout, script) { 54 | this.tx = this.transaction.buildIncomplete() 55 | this.tx.setInputScript(vout, script) 56 | this.p2shInput = true 57 | } 58 | 59 | addInputScripts (scripts) { 60 | this.tx = this.transaction.buildIncomplete() 61 | scripts.forEach(script => { 62 | this.tx.setInputScript(script.vout, script.script) 63 | }) 64 | this.p2shInput = true 65 | } 66 | 67 | /** 68 | * @api Transaction-Builder.addOutput() addOutput() 69 | * @apiName AddOutput 70 | * @apiGroup TransactionBuilder 71 | * @apiDescription Add output to transaction. 72 | * 73 | * @apiExample Example usage: 74 | * let originalAmount = 100000; 75 | * let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); 76 | * // amount to send to receiver. It's the original amount - 1 sat/byte for tx size 77 | * let sendAmount = originalAmount - byteCount; 78 | * // add output w/ address and amount to send 79 | * transactionBuilder.addOutput('bitcoincash:qpuax2tarq33f86wccwlx8ge7tad2wgvqgjqlwshpw', sendAmount); 80 | */ 81 | addOutput (scriptPubKey, amount) { 82 | try { 83 | this.transaction.addOutput( 84 | TransactionBuilder._address.toLegacyAddress(scriptPubKey), 85 | amount 86 | ) 87 | } catch (error) { 88 | this.transaction.addOutput(scriptPubKey, amount) 89 | } 90 | } 91 | 92 | /** 93 | * @api Transaction-Builder.setLockTime() setLockTime() 94 | * @apiName SetLockTime 95 | * @apiGroup TransactionBuilder 96 | * @apiDescription Set locktime. 97 | * 98 | * @apiExample Example usage: 99 | * let originalAmount = 100000; 100 | * let byteCount = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 }); 101 | * // amount to send to receiver. It's the original amount - 1 sat/byte for tx size 102 | * let sendAmount = originalAmount - byteCount; 103 | * // add output w/ address and amount to send 104 | * transactionBuilder.addOutput('bitcoincash:qpuax2tarq33f86wccwlx8ge7tad2wgvqgjqlwshpw', sendAmount); 105 | * transactionBuilder.setLockTime(50000) 106 | */ 107 | setLockTime (locktime) { 108 | this.transaction.setLockTime(locktime) 109 | } 110 | 111 | /** 112 | * @api Transaction-Builder.sign() sign() 113 | * @apiName Sign. 114 | * @apiGroup TransactionBuilder 115 | * @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.. 116 | * 117 | * @apiExample Example usage: 118 | * let originalAmount = 100000; 119 | * // node of address which is going to spend utxo 120 | * let hdnode = bchjs.HDNode.fromXPriv("xprvA3eaDg64MwDr72PVGJ7CkvshNAzCDRz7rn98sYrZVAtDSWCAmNGQhEQeCLDcnmcpSkfjhHevXmu4ZL8ZcT9D4vEbG8LpiToZETrHZttw9Yw"); 121 | * // keypair 122 | * let keyPair = bchjs.HDNode.toKeyPair(hdnode); 123 | * // empty redeemScript variable 124 | * let redeemScript; 125 | * // sign w/ keyPair 126 | * transactionBuilder.sign(0, keyPair, redeemScript, transactionBuilder.hashTypes.SIGHASH_ALL, originalAmount, transactionBuilder.signatureAlgorithms.SCHNORR); 127 | */ 128 | sign ( 129 | vin, 130 | keyPair, 131 | redeemScript, 132 | hashType = this.hashTypes.SIGHASH_ALL, 133 | value, 134 | signatureAlgorithm 135 | ) { 136 | let witnessScript 137 | 138 | this.transaction.sign( 139 | vin, 140 | keyPair, 141 | redeemScript, 142 | hashType, 143 | value, 144 | witnessScript, 145 | signatureAlgorithm 146 | ) 147 | } 148 | 149 | /** 150 | * @api Transaction-Builder.build() build() 151 | * @apiName Build. 152 | * @apiGroup TransactionBuilder 153 | * @apiDescription Build transaction. 154 | * 155 | * @apiExample Example usage: 156 | * // build tx 157 | * let tx = bchjs.transactionBuilder.build(); 158 | */ 159 | build () { 160 | if (this.p2shInput === true) return this.tx 161 | 162 | return this.transaction.build() 163 | } 164 | } 165 | 166 | module.exports = TransactionBuilder 167 | -------------------------------------------------------------------------------- /src/transaction.js: -------------------------------------------------------------------------------- 1 | /* 2 | High-level functions for working with Transactions 3 | */ 4 | 5 | // Global npm libraries 6 | // const BigNumber = require('bignumber.js') 7 | 8 | // Local libraries 9 | const RawTransaction = require('./raw-transactions') 10 | const SlpUtils = require('./slp/utils') 11 | const Blockchain = require('./blockchain') 12 | const PsfSlpIndexer = require('./psf-slp-indexer') 13 | 14 | class Transaction { 15 | constructor (config = {}) { 16 | // Encapsulate dependencies 17 | this.slpUtils = new SlpUtils(config) 18 | this.rawTransaction = new RawTransaction(config) 19 | this.blockchain = new Blockchain(config) 20 | this.psfSlpIndexer = new PsfSlpIndexer(config) 21 | } 22 | 23 | /** 24 | * @api Transaction.get() get() 25 | * @apiName get 26 | * @apiGroup Transaction 27 | * @apiDescription 28 | * Returns an object of transaction data, including addresses for input UTXOs. 29 | * If it is a SLP token transaction, the token information for inputs and 30 | * outputs will also be included. 31 | * 32 | * 33 | * @apiExample Example usage: 34 | * (async () => { 35 | * try { 36 | * let txData = await bchjs.Transaction.get("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); 37 | * console.log(txData); 38 | * } catch(error) { 39 | * console.error(error) 40 | * } 41 | * })() 42 | */ 43 | async get (txid) { 44 | // console.log('transaction.get() txid: ', txid) 45 | return await this.psfSlpIndexer.tx(txid) 46 | } 47 | 48 | /** 49 | * @api Transaction.getTokenInfo() getTokenInfo() 50 | * @apiName getTokenInfo 51 | * @apiGroup Transaction 52 | * @apiDescription 53 | * Given the TXID of a token transaction, it will return data about that 54 | * token by retrieving the data from the Genesis transaction and docoding 55 | * the OP_RETURN. 56 | * 57 | * 58 | * @apiExample Example usage: 59 | * (async () => { 60 | * try { 61 | * let txData = await bchjs.Transaction.getTokenInfo("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098"); 62 | * console.log(txData); 63 | * } catch(error) { 64 | * console.error(error) 65 | * } 66 | * })() 67 | */ 68 | // A wrapper for decodeOpReturn(). Returns false if txid is not an SLP tx. 69 | // Returns the token data if the txid is an SLP tx. 70 | async getTokenInfo (txid) { 71 | try { 72 | const tokenData = await this.slpUtils.decodeOpReturn(txid) 73 | return tokenData 74 | } catch (err) { 75 | return false 76 | } 77 | } 78 | } 79 | 80 | module.exports = Transaction 81 | -------------------------------------------------------------------------------- /test/e2e/bch-js-e2e-tests.js: -------------------------------------------------------------------------------- 1 | /* 2 | A Mocha test file for running end-to-end (e2e) tests. 3 | */ 4 | 5 | // const mocha = require("mocha") 6 | const assert = require('chai').assert 7 | 8 | const sendToken = require('./send-token/send-token') 9 | 10 | describe('#end-to-end tests', () => { 11 | describe('#send-tokens', () => { 12 | it('SLPDB should update balances in less than 10 seconds', async () => { 13 | const result = await sendToken.sendTokenTest() 14 | 15 | assert(result, true, 'True expected if test passed successfully.') 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/e2e/ipfs/ipfs-e2e.js: -------------------------------------------------------------------------------- 1 | /* 2 | An end-to-end test for testing the functionality of uploading a file to 3 | IPFS is working. 4 | */ 5 | 6 | process.env.IPFS_API = 'http://localhost:5001' 7 | // process.env.IPFS_API = `https://ipfs-api.fullstack.cash` 8 | 9 | const BCHJS = require('../../../src/bch-js') 10 | const bchjs = new BCHJS() 11 | 12 | describe('#IPFS', () => { 13 | it('should upload a file to the server', async () => { 14 | const path = `${__dirname.toString()}/ipfs-e2e.js` 15 | 16 | const fileModel = await bchjs.IPFS.createFileModel(path) 17 | console.log(`fileModel: ${JSON.stringify(fileModel, null, 2)}`) 18 | 19 | const fileId = fileModel.file._id 20 | 21 | const fileObj = await bchjs.IPFS.uploadFile(path, fileId) 22 | console.log(`fileObj: ${JSON.stringify(fileObj, null, 2)}`) 23 | }).timeout(30000) 24 | }) 25 | -------------------------------------------------------------------------------- /test/e2e/rate-limits/anonymous-rate-limits.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mocha test that verifies that the bch-api server is enforcing its 3 | anonymous access rules 4 | 5 | To Run Test: 6 | - Update the RESTURL for bch-api you want to test against. 7 | - Ensure the BCHJSTOKEN environment variable is set to an empty string. 8 | - If working with bch-api locally, eliminate the local IP address from the 9 | whitelist in bch-api/src/middleware/route-ratelimit.js 10 | 11 | Run this test with this command: 12 | mocha --timeout=30000 anonymous-rate-limits.js 13 | */ 14 | 15 | const assert = require('chai').assert 16 | 17 | // const RESTURL = `https://abc.fullstack.cash/v5/` 18 | const RESTURL = 'https://bchn.fullstack.cash/v5/' 19 | // const RESTURL = `http://localhost:3000/v5/` 20 | 21 | const BCHJS = require('../../../src/bch-js') 22 | const bchjs = new BCHJS({ restURL: RESTURL }) 23 | 24 | describe('#anonymous rate limits', () => { 25 | it('should allow an anonymous call to a full node endpoint', async () => { 26 | const result = await bchjs.Control.getNetworkInfo() 27 | 28 | assert.property(result, 'version') 29 | }).timeout(5000) 30 | 31 | it('should allow an anonymous call to an indexer', async () => { 32 | const addr = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 33 | const result = await bchjs.Electrumx.balance(addr) 34 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 35 | 36 | assert.property(result, 'balance') 37 | }).timeout(5000) 38 | 39 | it('should throw error when rate limit exceeded', async () => { 40 | try { 41 | for (let i = 0; i < 35; i++) await bchjs.Control.getNetworkInfo() 42 | 43 | assert.fail('Unexpected result') 44 | } catch (err) { 45 | assert.include(err.error, 'Too many requests') 46 | } 47 | }).timeout(20000) 48 | }) 49 | -------------------------------------------------------------------------------- /test/e2e/rate-limits/basic-auth-rate-limits.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mocha test that verifies that the bch-api server is enforcing its 3 | access rules for Basic Authentication. 4 | 5 | To Run Test: 6 | - Update the restURL for bch-api you want to test against. 7 | - Update the PRO_PASS value with a current PRO_PASSES string used by bch-api. 8 | */ 9 | 10 | const assert = require('chai').assert 11 | 12 | const PRO_PASS = 'testpassword' 13 | 14 | const BCHJS = require('../../../src/bch-js') 15 | const bchjs = new BCHJS({ 16 | // restURL: `https://bchn.fullstack.cash/v5/`, 17 | restURL: 'https://abc.fullstack.cash/v5/', 18 | // restURL: `http://localhost:3000/v5/`, 19 | authPass: PRO_PASS 20 | }) 21 | 22 | describe('#Basic Authentication rate limits', () => { 23 | it('should allow more than 20 RPM to full node', async () => { 24 | for (let i = 0; i < 22; i++) { 25 | const result = await bchjs.Control.getNetworkInfo() 26 | 27 | if (i === 5) { 28 | // console.log(`validating 5th call: ${i}`) 29 | assert.property(result, 'version', 'more than 3 calls allowed') 30 | } 31 | 32 | if (i === 15) { 33 | // console.log(`validating 5th call: ${i}`) 34 | assert.property(result, 'version', 'more than 10 calls allowed') 35 | } 36 | } 37 | }).timeout(45000) 38 | 39 | it('should allow more than 20 RPM to an indexer', async () => { 40 | const addr = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 41 | 42 | for (let i = 0; i < 22; i++) { 43 | const result = await bchjs.Blockbook.balance(addr) 44 | 45 | if (i === 5) { 46 | // console.log(`validating 5th call: ${i}`) 47 | assert.property(result, 'balance', 'more than 3 calls allowed') 48 | } 49 | 50 | if (i === 15) { 51 | // console.log(`validating 5th call: ${i}`) 52 | assert.property(result, 'balance', 'more than 10 calls allowed') 53 | } 54 | } 55 | }).timeout(45000) 56 | }) 57 | -------------------------------------------------------------------------------- /test/e2e/rate-limits/free-rate-limits.js: -------------------------------------------------------------------------------- 1 | /* 2 | CT 11/11/2020: 3 | There is no longer a free tier. These tests have been deprecated. 4 | 5 | -------- 6 | Mocha test that verifies that the bch-api server is enforcing its 7 | FREE access rules 8 | 9 | To Run Test: 10 | - Update the restURL for bch-api you want to test against. 11 | - Update the JWT_TOKEN value with a current free-level JWT token. 12 | */ 13 | 14 | const assert = require('chai').assert 15 | 16 | const JWT_TOKEN = 17 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2YyMDIyMWMxMDAxMmFkOTQwMSIsImVtYWlsIjoiZGVtb0BkZW1vLmNvbSIsImFwaUxldmVsIjoxMCwicmF0ZUxpbWl0IjozLCJpYXQiOjE2MDQ4NTQ2OTEsImV4cCI6MTYwNzQ0NjY5MX0.iwse0z0KDKHx9graCxcOwj6lSlfKQAb1zLhmjvygvts' 18 | 19 | const BCHJS = require('../../../src/bch-js') 20 | const bchjs = new BCHJS({ 21 | restURL: 'https://api.fullstack.cash/v5/', 22 | // restURL: `http://localhost:3000/v5/`, 23 | apiToken: JWT_TOKEN 24 | }) 25 | 26 | describe('#free rate limits', () => { 27 | it('should allow an free call to an indexer', async () => { 28 | const addr = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 29 | const result = await bchjs.Blockbook.balance(addr) 30 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 31 | 32 | assert.property(result, 'balance') 33 | }).timeout(5000) 34 | 35 | it('should allow up to 10 RPM to indexer, then throw error', async () => { 36 | try { 37 | const addr = 'bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0' 38 | 39 | for (let i = 0; i < 15; i++) { 40 | const result = await await bchjs.Blockbook.balance(addr) 41 | 42 | if (i === 5) { 43 | // console.log(`validating 5th call: ${i}`) 44 | assert.property(result, 'address', 'more than 3 calls allowed') 45 | } 46 | } 47 | } catch (err) { 48 | // console.log(`err: `, err) 49 | assert.include( 50 | err.error, 51 | 'currently 10 requests', 52 | 'more than 10 not allowed' 53 | ) 54 | assert.include(err.error, 10) 55 | assert.notInclude(err.error, 3) 56 | } 57 | }).timeout(20000) 58 | 59 | it('should allow up to 10 RPM to full node, then throw error', async () => { 60 | try { 61 | for (let i = 0; i < 15; i++) { 62 | const result = await bchjs.Control.getNetworkInfo() 63 | 64 | if (i === 5) { 65 | // console.log(`validating 5th call: ${i}`) 66 | assert.property(result, 'version', 'more than 3 calls allowed') 67 | } 68 | } 69 | } catch (err) { 70 | // console.log(`validating after 10th call`) 71 | // console.log(`err: `, err) 72 | assert.include( 73 | err.error, 74 | 'currently 10 requests', 75 | 'more than 10 not allowed' 76 | ) 77 | assert.include(err.error, 10) 78 | assert.notInclude(err.error, 3) 79 | } 80 | }).timeout(20000) 81 | 82 | it('should throw error when rate limit exceeded 20 RPM for indexer endpoints', async () => { 83 | try { 84 | for (let i = 0; i < 22; i++) await bchjs.Control.getNetworkInfo() 85 | 86 | assert.fail('unexpected result') 87 | } catch (err) { 88 | // console.log(`err: `, err) 89 | assert.include(err.error, 'Too many requests') 90 | } 91 | }).timeout(20000) 92 | }) 93 | -------------------------------------------------------------------------------- /test/e2e/rate-limits/full-node-rate-limits.js: -------------------------------------------------------------------------------- 1 | /* 2 | CT 11/11/2020: 3 | There is no longer a free tier. These tests have been deprecated. 4 | 5 | ---- 6 | Mocha test that verifies that the bch-api server is enforcing its 7 | full-node access rules 8 | 9 | To Run Test: 10 | - Update the restURL for bch-api you want to test against. 11 | - Update the JWT_TOKEN value with a paid tier JWT token. 12 | */ 13 | 14 | const assert = require('chai').assert 15 | 16 | const JWT_TOKEN = 17 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMzIyNzEwNCwiZXhwIjoxNjA1ODE5MTA0fQ.CV36grzdD36Ht3BwZGHG4XU40CVDzMRw9Ars1x1r27M' 18 | 19 | const BCHJS = require('../../../src/bch-js') 20 | const bchjs = new BCHJS({ 21 | // restURL: `https://bchn.fullstack.cash/v5/`, 22 | restURL: 'https:/abc.fullstack.cash/v5/', 23 | // restURL: `http://localhost:3000/v5/`, 24 | apiToken: JWT_TOKEN 25 | }) 26 | 27 | describe('#full node rate limits', () => { 28 | it('should allow an free call to an indexer', async () => { 29 | const addr = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 30 | const result = await bchjs.Electrumx.balance(addr) 31 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 32 | 33 | assert.property(result, 'balance') 34 | }).timeout(5000) 35 | 36 | // CT 11/8/20: New rate limits make this test invalid. 37 | // it("should throw error when rate limit exceeded 20 RPM for indexer endpoints", async () => { 38 | // const addr = "bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" 39 | // 40 | // try { 41 | // for (let i = 0; i < 22; i++) await bchjs.Electrumx.balance(addr) 42 | // 43 | // assert.fail("unexpected result") 44 | // } catch (err) { 45 | // // console.log(`err: `, err) 46 | // assert.include(err.error, "Too many requests") 47 | // } 48 | // }).timeout(10000) 49 | 50 | it('should allow more than 20 RPM to full node', async () => { 51 | for (let i = 0; i < 22; i++) { 52 | const result = await bchjs.Control.getNetworkInfo() 53 | 54 | if (i === 5) { 55 | // console.log(`validating 5th call: ${i}`) 56 | assert.property(result, 'version', 'more than 3 calls allowed') 57 | } 58 | 59 | if (i === 15) { 60 | // console.log(`validating 5th call: ${i}`) 61 | assert.property(result, 'version', 'more than 10 calls allowed') 62 | } 63 | } 64 | }).timeout(20000) 65 | 66 | it('should throw error for more than 100 RPM to fullnode', async () => { 67 | try { 68 | console.log("This test usually doesn't pass, because of latency.") 69 | for (let i = 0; i < 100; i++) await bchjs.Control.getNetworkInfo() 70 | } catch (err) { 71 | // console.log(`validating after 10th call`) 72 | // console.log(`err: `, err) 73 | assert.include(err.error, 'Too many requests') 74 | assert.include(err.error, 100) 75 | } 76 | }).timeout(45000) 77 | }) 78 | -------------------------------------------------------------------------------- /test/e2e/rate-limits/indexer-rate-limits.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mocha test that verifies that the bch-api server is enforcing its 3 | indexer access rules 4 | 5 | To Run Test: 6 | - Update the restURL for bch-api you want to test against. 7 | - Update the JWT_TOKEN value with a current indexer-level JWT token. 8 | */ 9 | 10 | const assert = require('chai').assert 11 | 12 | const JWT_TOKEN = 13 | 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMzIyNzEwNCwiZXhwIjoxNjA1ODE5MTA0fQ.CV36grzdD36Ht3BwZGHG4XU40CVDzMRw9Ars1x1r27M' 14 | 15 | const BCHJS = require('../../../src/bch-js') 16 | const bchjs = new BCHJS({ 17 | // restURL: `https://bchn.fullstack.cash/v5/`, 18 | restURL: 'https://abc.fullstack.cash/v5/', 19 | // restURL: `http://localhost:3000/v5/`, 20 | apiToken: JWT_TOKEN 21 | }) 22 | 23 | describe('#Indexer rate limits', () => { 24 | it('should allow more than 20 RPM to full node', async () => { 25 | for (let i = 0; i < 22; i++) { 26 | const result = await bchjs.Control.getNetworkInfo() 27 | 28 | if (i === 5) { 29 | // console.log(`validating 5th call: ${i}`) 30 | assert.property(result, 'version', 'more than 3 calls allowed') 31 | } 32 | 33 | if (i === 15) { 34 | // console.log(`validating 5th call: ${i}`) 35 | assert.property(result, 'version', 'more than 10 calls allowed') 36 | } 37 | } 38 | }).timeout(45000) 39 | 40 | it('should allow more than 20 RPM to an indexer', async () => { 41 | const addr = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 42 | 43 | for (let i = 0; i < 22; i++) { 44 | const result = await bchjs.Electrumx.balance(addr) 45 | 46 | if (i === 5) { 47 | // console.log(`validating 5th call: ${i}`) 48 | assert.property(result, 'balance', 'more than 3 calls allowed') 49 | } 50 | 51 | if (i === 15) { 52 | // console.log(`validating 5th call: ${i}`) 53 | assert.property(result, 'balance', 'more than 10 calls allowed') 54 | } 55 | } 56 | }).timeout(45000) 57 | }) 58 | -------------------------------------------------------------------------------- /test/e2e/send-raw-transaction-bulk/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendrawtransaction", 3 | "version": "1.0.0", 4 | "description": "e2e test - Send two transactions in parallel.", 5 | "main": "sendrawtransaction.js", 6 | "scripts": { 7 | "test": "echo no tests yet", 8 | "start": "node sendrawtransaction.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Bitcoin-com/bitbox-javascript-sdk.git" 13 | }, 14 | "keywords": [ 15 | "bitbox", 16 | "bch", 17 | "bitcoin", 18 | "bitcoin cash", 19 | "bitcoin.com", 20 | "javascript" 21 | ], 22 | "author": "Chris Troutner ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/issues" 26 | }, 27 | "homepage": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/blob/master/README.md" 28 | } 29 | -------------------------------------------------------------------------------- /test/e2e/send-raw-transaction-single/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sendrawtransaction", 3 | "version": "1.0.0", 4 | "description": "e2e test - Send two transactions in parallel.", 5 | "main": "sendrawtransaction.js", 6 | "scripts": { 7 | "test": "echo no tests yet", 8 | "start": "node sendrawtransaction.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/Bitcoin-com/bitbox-javascript-sdk.git" 13 | }, 14 | "keywords": [ 15 | "bitbox", 16 | "bch", 17 | "bitcoin", 18 | "bitcoin cash", 19 | "bitcoin.com", 20 | "javascript" 21 | ], 22 | "author": "Chris Troutner ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/issues" 26 | }, 27 | "homepage": "https://github.com/Bitcoin-com/bitbox-javascript-sdk/blob/master/README.md" 28 | } 29 | -------------------------------------------------------------------------------- /test/e2e/send-raw-transaction-single/sendrawtransaction.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is an end-to-end test adapted from the send-bch example. It's purpose 3 | is to test the sendRawTransaction endpoint. 4 | 5 | This version tests the single send call. 6 | 7 | Instructions: 8 | - Ensure the address in the wallet.json file has some tBCH. 9 | - Ensure the address in the wallet.json file has a UTXOs between 0.1 and 10 | 0.001 tBCH 11 | - This test will generate a single transaction from a UTXOs and broadcast it 12 | using the sendrawtransaction single GET endpoint. 13 | */ 14 | 15 | // Replace the address below with the address you want to send the BCH to. 16 | const RECV_ADDR1 = 'bchtest:qzfn2mly05t6fjsh5kjj0dqq0jjtct27ng089dgg05' 17 | const SATOSHIS_TO_SEND = 1000 18 | 19 | // Instantiate BITBOX. 20 | const bitboxLib = '../../../lib/BITBOX' 21 | const BITBOXSDK = require(bitboxLib) 22 | const BITBOX = new BITBOXSDK({ restURL: 'https://trest.bitcoin.com/v2/' }) 23 | // const BITBOX = new BITBOXSDK({ restURL: "http://localhost:3000/v2/" }) 24 | 25 | // const util = require('util') 26 | 27 | // Open the wallet generated with create-wallet. 28 | let walletInfo = {} 29 | try { 30 | walletInfo = require('./wallet.json') 31 | } catch (err) { 32 | console.log( 33 | 'Could not open wallet.json. Generate a wallet with create-wallet first.' 34 | ) 35 | process.exit(0) 36 | } 37 | 38 | const SEND_ADDR = walletInfo.cashAddress 39 | const SEND_MNEMONIC = walletInfo.mnemonic 40 | 41 | async function testSend () { 42 | try { 43 | const hex1 = await buildTx1(RECV_ADDR1) 44 | // const hex2 = await buildTx2(RECV_ADDR2) 45 | 46 | console.log(`hex1: ${hex1}\n\n`) 47 | // console.log(`hex2: ${hex2}\n\n`) 48 | 49 | const broadcast = await BITBOX.RawTransactions.sendRawTransaction(hex1) 50 | 51 | console.log(`Transaction IDs: ${JSON.stringify(broadcast, null, 2)}`) 52 | console.log('Should return a TXID string.') 53 | } catch (err) { 54 | console.log('Error in testSend: ', err) 55 | } 56 | } 57 | testSend() 58 | 59 | // Build a TX hex with the largest UTXO. 60 | async function buildTx1 (recAddr) { 61 | try { 62 | // Get the balance of the sending address. 63 | const balance = await getBCHBalance(SEND_ADDR, false) 64 | console.log(`balance: ${JSON.stringify(balance, null, 2)}`) 65 | console.log(`Balance of sending address ${SEND_ADDR} is ${balance} BCH.`) 66 | 67 | // Exit if the balance is zero. 68 | if (balance <= 0.0) { 69 | console.log('Balance of sending address is zero. Exiting.') 70 | process.exit(0) 71 | } 72 | 73 | const SEND_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(SEND_ADDR) 74 | const RECV_ADDR_LEGACY = BITBOX.Address.toLegacyAddress(recAddr) 75 | console.log(`Sender Legacy Address: ${SEND_ADDR_LEGACY}`) 76 | console.log(`Receiver Legacy Address: ${RECV_ADDR_LEGACY}`) 77 | 78 | // const balance2 = await getBCHBalance(recAddr, false) 79 | // console.log(`Balance of recieving address ${recAddr} is ${balance2} BCH.`) 80 | await getBCHBalance(recAddr, false) 81 | 82 | const u = await BITBOX.Address.utxo(SEND_ADDR) 83 | // console.log(`u: ${JSON.stringify(u, null, 2)}`) 84 | const utxo = findBiggestUtxo(u.utxos) 85 | console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) 86 | 87 | // instance of transaction builder 88 | const transactionBuilder = new BITBOX.TransactionBuilder('testnet') 89 | 90 | const satoshisToSend = SATOSHIS_TO_SEND 91 | const originalAmount = utxo.satoshis 92 | const vout = utxo.vout 93 | const txid = utxo.txid 94 | 95 | // add input with txid and index of vout 96 | transactionBuilder.addInput(txid, vout) 97 | 98 | // get byte count to calculate fee. paying 1.2 sat/byte 99 | const byteCount = BITBOX.BitcoinCash.getByteCount( 100 | { P2PKH: 1 }, 101 | { P2PKH: 2 } 102 | ) 103 | console.log(`byteCount: ${byteCount}`) 104 | const satoshisPerByte = 1.0 105 | const txFee = Math.floor(satoshisPerByte * byteCount) 106 | console.log(`txFee: ${txFee}`) 107 | 108 | // amount to send back to the sending address. 109 | // It's the original amount - 1 sat/byte for tx size 110 | const remainder = originalAmount - satoshisToSend - txFee 111 | 112 | // add output w/ address and amount to send 113 | transactionBuilder.addOutput(recAddr, satoshisToSend) 114 | transactionBuilder.addOutput(SEND_ADDR, remainder) 115 | 116 | // Generate a change address from a Mnemonic of a private key. 117 | const change = changeAddrFromMnemonic(SEND_MNEMONIC) 118 | 119 | // Generate a keypair from the change address. 120 | const keyPair = BITBOX.HDNode.toKeyPair(change) 121 | 122 | // Sign the transaction with the HD node. 123 | let redeemScript 124 | transactionBuilder.sign( 125 | 0, 126 | keyPair, 127 | redeemScript, 128 | transactionBuilder.hashTypes.SIGHASH_ALL, 129 | originalAmount 130 | ) 131 | 132 | // build tx 133 | const tx = transactionBuilder.build() 134 | // output rawhex 135 | const hex = tx.toHex() 136 | 137 | return hex 138 | } catch (err) { 139 | console.log('Error in buildTx().') 140 | throw err 141 | } 142 | } 143 | 144 | // Generate a change address from a Mnemonic of a private key. 145 | function changeAddrFromMnemonic (mnemonic) { 146 | // root seed buffer 147 | const rootSeed = BITBOX.Mnemonic.toSeed(mnemonic) 148 | 149 | // master HDNode 150 | const masterHDNode = BITBOX.HDNode.fromSeed(rootSeed, 'testnet') 151 | 152 | // HDNode of BIP44 account 153 | const account = BITBOX.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") 154 | 155 | // derive the first external change address HDNode which is going to spend utxo 156 | const change = BITBOX.HDNode.derivePath(account, '0/0') 157 | 158 | return change 159 | } 160 | 161 | // Get the balance in BCH of a BCH address. 162 | async function getBCHBalance (addr, verbose) { 163 | try { 164 | const result = await BITBOX.Address.details(addr) 165 | 166 | if (verbose) console.log(result) 167 | 168 | const bchBalance = result 169 | 170 | return bchBalance.balance 171 | } catch (err) { 172 | console.error('Error in getBCHBalance: ', err) 173 | console.log(`addr: ${addr}`) 174 | throw err 175 | } 176 | } 177 | 178 | // Returns the utxo with the biggest balance from an array of utxos. 179 | function findBiggestUtxo (utxos) { 180 | // Sort the utxos by the amount of satoshis, largest first. 181 | utxos.sort(function (a, b) { 182 | return b.satoshis - a.satoshis 183 | }) 184 | 185 | return utxos[0] 186 | } 187 | -------------------------------------------------------------------------------- /test/e2e/send-token/send-token.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is an end-to-end test which verified the happy-path of sending an SLP 3 | token. It's really a test of the speed of SLPDB to process new token 4 | transactions. 5 | 6 | This program expects two wallets. Wallet 1 must have a small amount of BCH 7 | and an inventory of SLP test tokens. Wallet 2 is the reieving wallet. 8 | */ 9 | 10 | // Inspect utility used for debugging. 11 | const util = require('util') 12 | util.inspect.defaultOptions = { 13 | showHidden: true, 14 | colors: true, 15 | depth: 1 16 | } 17 | 18 | // const SLPSDK = require("../../../lib/SLP") 19 | // const slpsdk = new SLPSDK() 20 | 21 | const WALLET1 = '../wallet1.json' 22 | const WALLET2 = '../wallet2.json' 23 | 24 | const lib = require('../util/e2e-util') 25 | 26 | // The main test function. 27 | // Sends a token and reports on how long it takes to show up in SLPDB production. 28 | async function sendTokenTest () { 29 | try { 30 | // Open the sending wallet. 31 | const sendWallet = await lib.openWallet(WALLET1) 32 | // console.log(`sendWallet: ${JSON.stringify(walletInfo, null, 2)}`) 33 | 34 | // Open the recieving wallet. 35 | const recvWallet = await lib.openWallet(WALLET2) 36 | // console.log(`recvWallet: ${JSON.stringify(walletInfo, null, 2)}`) 37 | 38 | // Get the balance of the recieving wallet. 39 | // const testTokens = recvWallet.tokenBalance.filter( 40 | // x => testTokenId === x.tokenId 41 | // ) 42 | // const startBalance = testTokens[0].balance 43 | const startBalance = await lib.getTestTokenBalance(recvWallet) 44 | console.log(`Starting balance: ${startBalance} test tokens.`) 45 | let newBalance = startBalance 46 | 47 | // Send a token to the recieving wallet. 48 | await lib.sendToken(sendWallet, recvWallet) 49 | console.log('Sent test token.') 50 | 51 | // Track the time until the balance for the recieving wallet has been updated. 52 | const startTime = new Date() 53 | const waitTime = 10000 // time in milliseconds 54 | 55 | // Loop with a definite exit point, so we don't loop forever. 56 | for (let i = 0; i < 50; i++) { 57 | await sleep(waitTime) // Wait for a while before checking 58 | 59 | console.log('Checking token balance...') 60 | newBalance = await lib.getTestTokenBalance(recvWallet) 61 | 62 | // Break out of the loop once a new balance is detected. 63 | if (newBalance > startBalance) break 64 | 65 | // Provide high-level warnings. 66 | const secondsPassed = (i * waitTime) / 1000 67 | if (secondsPassed > 60 * 10) { 68 | console.log('More than 10 minutes passed.') 69 | return false // Fail the test. 70 | } else if (secondsPassed > 60 * 5) { 71 | console.log('More than 5 minutes passed.') 72 | } else if (secondsPassed > 60) { 73 | console.log('More than 1 minute passed.') 74 | } 75 | } 76 | 77 | // Calculate the amount of time that has passed. 78 | const endTime = new Date() 79 | let deltaTime = (endTime.getTime() - startTime.getTime()) / 60000 80 | deltaTime = lib.threeDecimals(deltaTime) 81 | console.log(`SLPDB updated token balance in ${deltaTime} minutes.`) 82 | 83 | // Consolidate the SLP UTXOs on the recieve wallet. 84 | await lib.sendToken(recvWallet, recvWallet) 85 | 86 | return deltaTime // Return the time in minutes it took for SLPDB to update. 87 | } catch (err) { 88 | console.log('Error in e2e/send-token.js/sendTokenTest(): ', err) 89 | return false 90 | } 91 | } 92 | 93 | // Promise based sleep function. 94 | function sleep (ms) { 95 | return new Promise(resolve => setTimeout(resolve, ms)) 96 | } 97 | 98 | module.exports = { 99 | sendTokenTest 100 | } 101 | -------------------------------------------------------------------------------- /test/e2e/util/e2e-util.js: -------------------------------------------------------------------------------- 1 | /* 2 | This is a utility library for common SLP and BCH actions needed by the e2e 3 | tests. 4 | */ 5 | 6 | module.exports = { 7 | openWallet, 8 | sendToken, 9 | getTestTokenBalance, 10 | threeDecimals 11 | } 12 | 13 | const SLPSDK = require('../../../lib/SLP') 14 | const slpsdk = new SLPSDK() 15 | 16 | const testTokenId = 17 | 'cc1b2084a9c43bb5a633df7f38201adde5c5f5cef2fed945d12f8dcd4e505c67' 18 | 19 | // Open a wallet and return an object with its address, BCH balance, and SLP 20 | // token balance. 21 | async function openWallet (filename) { 22 | try { 23 | const walletInfo = require(filename) 24 | 25 | // const walletBalance = await getBalance(walletInfo) 26 | // const walletBalance = await slpsdk.Utils.balancesForAddress( 27 | // walletInfo.slpAddress 28 | // ) 29 | // // console.log(`walletBalance: ${JSON.stringify(walletBalance, null, 2)}`) 30 | // walletInfo.tokenBalance = walletBalance 31 | 32 | return walletInfo 33 | } catch (err) { 34 | console.log( 35 | `Could not open ${filename}. Generate a wallet with create-wallet first.`, 36 | err 37 | ) 38 | process.exit(0) 39 | } 40 | } 41 | 42 | // Send a token from wallet1 to wallet2. 43 | async function sendToken (wallet1, wallet2) { 44 | try { 45 | const mnemonic = wallet1.mnemonic 46 | 47 | // root seed buffer 48 | const rootSeed = slpsdk.Mnemonic.toSeed(mnemonic) 49 | 50 | // master HDNode 51 | const masterHDNode = slpsdk.HDNode.fromSeed(rootSeed) 52 | 53 | // HDNode of BIP44 account 54 | const account = slpsdk.HDNode.derivePath(masterHDNode, "m/44'/145'/0'") 55 | 56 | const change = slpsdk.HDNode.derivePath(account, '0/0') 57 | 58 | // get the cash address 59 | // const cashAddress = slpsdk.HDNode.toCashAddress(change) 60 | // const slpAddress = slpsdk.HDNode.toSLPAddress(change) 61 | 62 | const fundingAddress = wallet1.slpAddress 63 | const fundingWif = slpsdk.HDNode.toWIF(change) // <-- compressed WIF format 64 | const tokenReceiverAddress = wallet2.slpAddress 65 | const bchChangeReceiverAddress = wallet1.cashAddress 66 | 67 | // Create a config object for minting 68 | const sendConfig = { 69 | fundingAddress, 70 | fundingWif, 71 | tokenReceiverAddress, 72 | bchChangeReceiverAddress, 73 | tokenId: 74 | 'cc1b2084a9c43bb5a633df7f38201adde5c5f5cef2fed945d12f8dcd4e505c67', 75 | amount: 1 76 | } 77 | 78 | // console.log(`createConfig: ${util.inspect(createConfig)}`) 79 | 80 | // Generate, sign, and broadcast a hex-encoded transaction for sending 81 | // the tokens. 82 | // const sendTxId = await slpsdk.TokenType1.send(sendConfig) 83 | await slpsdk.TokenType1.send(sendConfig) 84 | 85 | // console.log(`sendTxId: ${sendTxId}`) 86 | } catch (err) { 87 | console.log('Error in e2e-util.js/sendToken()') 88 | throw err 89 | } 90 | } 91 | 92 | // Returns just the test token balance for a wallet. 93 | async function getTestTokenBalance (walletData) { 94 | try { 95 | const tokenBalance = await slpsdk.Util.balancesForAddress( 96 | walletData.slpAddress 97 | ) 98 | // console.log(`tokenBalance: ${JSON.stringify(tokenBalance, null, 2)}`) 99 | 100 | const testTokens = tokenBalance.filter(x => testTokenId === x.tokenId) 101 | 102 | return testTokens[0].balance 103 | } catch (err) { 104 | console.log('Error in e2e-util.js/getTestTokenBalance()') 105 | throw err 106 | } 107 | } 108 | 109 | // Round a number to three decimal places. 110 | function threeDecimals (inNum) { 111 | try { 112 | let tempNum = inNum * 1000 113 | tempNum = Math.round(tempNum) 114 | tempNum = tempNum / 1000 115 | return tempNum 116 | } catch (err) { 117 | console.log('Error in e2e-util.js/threeDecimals()') 118 | throw err 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /test/e2e/utxo/unsynced-indexer.js: -------------------------------------------------------------------------------- 1 | /* 2 | This test is used to interrogate the behavior of bch-js when a psf-slp-indexer 3 | panicks and starts indexing from SLP genesis. In this corner-case, bch-js 4 | should detect the indexer is out of sync, and protect any token UTXOs by 5 | moving any UTXO under 600 sats into the null array. 6 | 7 | TO RUN THIS TEST: 8 | - Reset a local instance of psf-slp-indexer to sync from genesis. 9 | - Start a local copy of bch-api 10 | */ 11 | 12 | const BCHJS = require('../../../src/bch-js.js') 13 | const bchjs = new BCHJS({ 14 | restURL: 'http://localhost:3000/v5/' 15 | }) 16 | 17 | async function startTest () { 18 | try { 19 | const addr = 'bitcoincash:qzkvas58zag9tjry693mflkjze2m20ps7vx7uw7z9d' 20 | 21 | const result = await bchjs.Utxo.get(addr) 22 | console.log(`result: ${JSON.stringify(result, null, 2)}`) 23 | } catch (err) { 24 | console.error('Error in startTest(): ', err) 25 | } 26 | } 27 | startTest() 28 | -------------------------------------------------------------------------------- /test/e2e/wallet1.json: -------------------------------------------------------------------------------- 1 | { 2 | "mnemonic": "capital cushion ostrich later educate rubber thank resist alter hollow way dragon", 3 | "cashAddress": "bitcoincash:qrjnvnvsukcvc59a7v28pzmtue6aes4qgy5zmp72sr", 4 | "slpAddress": "simpleledger:qrjnvnvsukcvc59a7v28pzmtue6aes4qgyces6t2wa", 5 | "legacyAddress": "1Mtxny6hMUifjE9hNFoVbUYPS3wDkroLzH" 6 | } -------------------------------------------------------------------------------- /test/e2e/wallet2.json: -------------------------------------------------------------------------------- 1 | { 2 | "mnemonic": "obscure volume zone abstract humor wisdom panther economy upset nation choose latin", 3 | "cashAddress": "bitcoincash:qpqv8cqlgkzzh2ed372g9l5vxur7f39y6vq7hac00m", 4 | "slpAddress": "simpleledger:qpqv8cqlgkzzh2ed372g9l5vxur7f39y6vv9uxd039", 5 | "legacyAddress": "16uStwkG1nGC1HB2tSWGqLQN2bDjXeeeRk" 6 | } -------------------------------------------------------------------------------- /test/integration/chains/abc/psf-slp-indexer-integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the psf-slp-indexer.js library, specific to the eCash 3 | blockchain. 4 | */ 5 | 6 | // Global npm libraries 7 | const assert = require('chai').assert 8 | 9 | // Local libraries 10 | const BCHJS = require('../../../../src/bch-js') 11 | let bchjs 12 | 13 | describe('#psf-slp-indexer', () => { 14 | beforeEach(async () => { 15 | // Introduce a delay so that the BVT doesn't trip the rate limits. 16 | if (process.env.IS_USING_FREE_TIER) await sleep(3000) 17 | 18 | bchjs = new BCHJS() 19 | }) 20 | 21 | describe('#balance', () => { 22 | it('should get token balance for an ecash address', async () => { 23 | const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' 24 | 25 | const result = await bchjs.PsfSlpIndexer.balance(addr) 26 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 27 | 28 | assert.property(result.balance, 'utxos') 29 | assert.property(result.balance, 'txs') 30 | assert.property(result.balance, 'balances') 31 | }) 32 | }) 33 | }) 34 | 35 | // Promise-based sleep function 36 | function sleep (ms) { 37 | return new Promise(resolve => setTimeout(resolve, ms)) 38 | } 39 | -------------------------------------------------------------------------------- /test/integration/chains/abc/rawtransaction.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the bchjs. Only covers calls made to 3 | rest.bitcoin.com. 4 | 5 | TODO 6 | */ 7 | 8 | const chai = require('chai') 9 | const assert = chai.assert 10 | const BCHJS = require('../../../../src/bch-js') 11 | const bchjs = new BCHJS({ restURL: process.env.RESTURL }) 12 | 13 | // Inspect utility used for debugging. 14 | const util = require('util') 15 | util.inspect.defaultOptions = { 16 | showHidden: true, 17 | colors: true, 18 | depth: 3 19 | } 20 | 21 | describe('#rawtransaction', () => { 22 | beforeEach(async () => { 23 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 24 | }) 25 | 26 | /* 27 | Testing sentRawTransaction isn't really possible with an integration test, 28 | as the endpoint really needs an e2e test to be properly tested. The tests 29 | below expect error messages returned from the server, but at least test 30 | that the server is responding on those endpoints, and responds consistently. 31 | */ 32 | describe('sendRawTransaction', () => { 33 | it('should send a single transaction hex', async () => { 34 | try { 35 | const hex = 36 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000' 37 | 38 | await bchjs.RawTransactions.sendRawTransaction(hex) 39 | // console.log(`result ${JSON.stringify(result, null, 2)}`) 40 | 41 | assert.equal(true, false, 'Unexpected result!') 42 | } catch (err) { 43 | // console.log(`err: ${util.inspect(err)}`) 44 | 45 | assert.hasAllKeys(err, ['error']) 46 | assert.include(err.error, 'bad-txns-inputs-missingorspent') 47 | // assert.include(err.error, 'Missing inputs') 48 | } 49 | }) 50 | 51 | it('should send an array of tx hexes', async () => { 52 | try { 53 | const hexes = [ 54 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000', 55 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000' 56 | ] 57 | 58 | const result = await bchjs.RawTransactions.sendRawTransaction(hexes) 59 | console.log(`result ${JSON.stringify(result, null, 2)}`) 60 | } catch (err) { 61 | // console.log(`err: ${util.inspect(err)}`) 62 | 63 | assert.hasAllKeys(err, ['error']) 64 | assert.include(err.error, 'bad-txns-inputs-missingorspent') 65 | // assert.include(err.error, 'Missing inputs') 66 | } 67 | }) 68 | }) 69 | }) 70 | 71 | function sleep (ms) { 72 | return new Promise(resolve => setTimeout(resolve, ms)) 73 | } 74 | -------------------------------------------------------------------------------- /test/integration/chains/abc/utxo-integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the utxo.js library. 3 | */ 4 | 5 | const assert = require('chai').assert 6 | 7 | const BCHJS = require('../../../../src/bch-js') 8 | const bchjs = new BCHJS() 9 | 10 | describe('#UTXO', () => { 11 | beforeEach(async () => { 12 | // sandbox = sinon.createSandbox() 13 | 14 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 15 | }) 16 | 17 | describe('#get', () => { 18 | it('should get hydrated and filtered UTXOs for an address', async () => { 19 | const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09' 20 | // const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' 21 | 22 | const result = await bchjs.Utxo.get(addr) 23 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 24 | 25 | // assert.isArray(result) 26 | assert.property(result, 'address') 27 | assert.property(result, 'bchUtxos') 28 | assert.property(result, 'nullUtxos') 29 | assert.property(result, 'slpUtxos') 30 | assert.isArray(result.bchUtxos) 31 | assert.isArray(result.nullUtxos) 32 | }) 33 | }) 34 | }) 35 | 36 | function sleep (ms) { 37 | return new Promise(resolve => setTimeout(resolve, ms)) 38 | } 39 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/dsproof.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for bchjs dsproof library. 3 | */ 4 | 5 | const assert = require('chai').assert 6 | 7 | const BCHJS = require('../../../../src/bch-js') 8 | const bchjs = new BCHJS() 9 | 10 | describe('#DSProof', () => { 11 | beforeEach(async () => { 12 | if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000) 13 | }) 14 | 15 | describe('#getDSProof', () => { 16 | it('should get TX info from the full node', async () => { 17 | const txid = 18 | 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e' 19 | const result = await bchjs.DSProof.getDSProof(txid) 20 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 21 | 22 | assert.equal(result, null) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/psf-slp-indexer.integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the psf-slp-indexer.js library, specific to the BCH 3 | blockchain. 4 | */ 5 | 6 | // Global npm libraries 7 | const assert = require('chai').assert 8 | 9 | // Local libraries 10 | const BCHJS = require('../../../../src/bch-js') 11 | let bchjs 12 | 13 | describe('#psf-slp-indexer', () => { 14 | beforeEach(async () => { 15 | // Introduce a delay so that the BVT doesn't trip the rate limits. 16 | if (process.env.IS_USING_FREE_TIER) await sleep(3000) 17 | 18 | bchjs = new BCHJS() 19 | }) 20 | 21 | describe('#status', () => { 22 | it('should return the status of the indexer.', async () => { 23 | const result = await bchjs.PsfSlpIndexer.status() 24 | // console.log('result: ', result) 25 | 26 | assert.property(result.status, 'startBlockHeight') 27 | assert.property(result.status, 'syncedBlockHeight') 28 | assert.property(result.status, 'chainBlockHeight') 29 | }) 30 | }) 31 | 32 | describe('#balance', () => { 33 | it('should get balance data for an address.', async () => { 34 | const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n' 35 | 36 | const result = await bchjs.PsfSlpIndexer.balance(addr) 37 | // console.log('result: ', result) 38 | 39 | assert.property(result.balance, 'utxos') 40 | assert.property(result.balance, 'txs') 41 | assert.property(result.balance, 'balances') 42 | }) 43 | }) 44 | 45 | describe('#tokenStats', () => { 46 | it('should get stats on a token, without tx history', async () => { 47 | const tokenId = 48 | '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' 49 | 50 | const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId) 51 | // console.log('result: ', result) 52 | 53 | assert.property(result.tokenData, 'documentUri') 54 | assert.property(result.tokenData, 'totalBurned') 55 | }) 56 | 57 | it('should get stats on a token, with tx history', async () => { 58 | const tokenId = 59 | '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0' 60 | 61 | const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId, true) 62 | // console.log('result: ', result) 63 | 64 | assert.property(result.tokenData, 'documentUri') 65 | assert.property(result.tokenData, 'txs') 66 | assert.property(result.tokenData, 'totalBurned') 67 | }) 68 | }) 69 | 70 | describe('#tx', () => { 71 | it('should get hydrated tx data for an SLP transaction', async () => { 72 | const txid = 73 | '947ccb2a0d62ca287bc4b0993874ab0f9f6afd454193e631e2bf84dca66731fc' 74 | 75 | const result = await bchjs.PsfSlpIndexer.tx(txid) 76 | // console.log('result: ', result) 77 | 78 | assert.property(result.txData, 'vin') 79 | assert.property(result.txData, 'vout') 80 | assert.property(result.txData, 'isValidSlp') 81 | assert.equal(result.txData.isValidSlp, true) 82 | }) 83 | 84 | it('should mark non-SLP transaction as false', async () => { 85 | const txid = 86 | '03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e' 87 | 88 | const result = await bchjs.PsfSlpIndexer.tx(txid) 89 | // console.log('result: ', result) 90 | 91 | assert.property(result.txData, 'vin') 92 | assert.property(result.txData, 'vout') 93 | assert.property(result.txData, 'isValidSlp') 94 | assert.equal(result.txData.isValidSlp, false) 95 | }) 96 | 97 | // FlexUSD transactions 98 | // Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means 99 | // the wallet will burn them. There is a TODO in the code. This test will 100 | // need to be changed when it is done. 101 | it('should mark blacklisted token as null', async () => { 102 | const txid = 103 | '302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f' 104 | 105 | const result = await bchjs.PsfSlpIndexer.tx(txid) 106 | // console.log('result: ', result) 107 | 108 | assert.property(result.txData, 'vin') 109 | assert.property(result.txData, 'vout') 110 | assert.property(result.txData, 'isValidSlp') 111 | assert.equal(result.txData.isValidSlp, null) 112 | }) 113 | 114 | it('should throw error for non-existent txid', async () => { 115 | try { 116 | const txid = 117 | '302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85e' 118 | 119 | await bchjs.PsfSlpIndexer.tx(txid) 120 | // console.log('result: ', result) 121 | 122 | assert.fail('Unexpected code path') 123 | } catch (err) { 124 | // console.log(err) 125 | 126 | assert.include(err.message, 'No such mempool or blockchain transaction') 127 | } 128 | }) 129 | }) 130 | 131 | describe('#getTokenData', () => { 132 | it('should get token data', async () => { 133 | const tokenId = 134 | 'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b' 135 | 136 | // bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/' 137 | const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId) 138 | // console.log('result: ', result) 139 | 140 | assert.property(result, 'genesisData') 141 | assert.property(result, 'immutableData') 142 | assert.property(result, 'mutableData') 143 | 144 | assert.isObject(result.genesisData) 145 | assert.isString(result.immutableData) 146 | assert.isString(result.mutableData) 147 | }) 148 | 149 | it('should get token data with a transaction history', async () => { 150 | const tokenId = '43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d' 151 | 152 | const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId, true) 153 | // console.log('result: ', result) 154 | 155 | assert.isArray(result.genesisData.txs) 156 | }) 157 | }) 158 | 159 | // This test is commented out because it can not succeed in the BVT without 160 | // tripping rate limits. 161 | // describe('#getTokenData2', () => { 162 | // it('should get token data', async () => { 163 | // const tokenId = 164 | // 'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b' 165 | // 166 | // // bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/' 167 | // const result = await bchjs.PsfSlpIndexer.getTokenData2(tokenId) 168 | // // console.log('result: ', result) 169 | // 170 | // assert.property(result, 'tokenStats') 171 | // assert.property(result, 'mutableData') 172 | // assert.property(result, 'immutableData') 173 | // assert.property(result, 'tokenIcon') 174 | // assert.property(result, 'fullSizedUrl') 175 | // assert.property(result, 'optimizedTokenIcon') 176 | // assert.property(result, 'optimizedFullSizedUrl') 177 | // assert.property(result, 'iconRepoCompatible') 178 | // assert.property(result, 'ps002Compatible') 179 | // }) 180 | // }) 181 | }) 182 | 183 | // Promise-based sleep function 184 | function sleep (ms) { 185 | return new Promise(resolve => setTimeout(resolve, ms)) 186 | } 187 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/rawtransaction.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the bchjs. Only covers calls made to 3 | rest.bitcoin.com. 4 | 5 | TODO 6 | */ 7 | 8 | const chai = require('chai') 9 | const assert = chai.assert 10 | const BCHJS = require('../../../../src/bch-js') 11 | const bchjs = new BCHJS() 12 | 13 | // Inspect utility used for debugging. 14 | const util = require('util') 15 | util.inspect.defaultOptions = { 16 | showHidden: true, 17 | colors: true, 18 | depth: 3 19 | } 20 | 21 | describe('#rawtransaction', () => { 22 | beforeEach(async () => { 23 | if (process.env.IS_USING_FREE_TIER) await sleep(3000) 24 | }) 25 | 26 | /* 27 | Testing sentRawTransaction isn't really possible with an integration test, 28 | as the endpoint really needs an e2e test to be properly tested. The tests 29 | below expect error messages returned from the server, but at least test 30 | that the server is responding on those endpoints, and responds consistently. 31 | */ 32 | describe('sendRawTransaction', () => { 33 | it('should send a single transaction hex', async () => { 34 | try { 35 | const hex = 36 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000' 37 | 38 | await bchjs.RawTransactions.sendRawTransaction(hex) 39 | // console.log(`result ${JSON.stringify(result, null, 2)}`) 40 | 41 | assert.equal(true, false, 'Unexpected result!') 42 | } catch (err) { 43 | // console.log(`err: ${util.inspect(err)}`) 44 | 45 | assert.hasAllKeys(err, ['error']) 46 | assert.include(err.error, 'Missing inputs') 47 | } 48 | }) 49 | 50 | it('should send an array of tx hexes', async () => { 51 | try { 52 | const hexes = [ 53 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000', 54 | '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000' 55 | ] 56 | 57 | const result = await bchjs.RawTransactions.sendRawTransaction(hexes) 58 | console.log(`result ${JSON.stringify(result, null, 2)}`) 59 | } catch (err) { 60 | // console.log(`err: ${util.inspect(err)}`) 61 | 62 | assert.hasAllKeys(err, ['error']) 63 | assert.include(err.error, 'Missing inputs') 64 | } 65 | }) 66 | }) 67 | }) 68 | 69 | function sleep (ms) { 70 | return new Promise(resolve => setTimeout(resolve, ms)) 71 | } 72 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/transaction-integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the transaction.js library. 3 | */ 4 | 5 | const assert = require('chai').assert 6 | const BCHJS = require('../../../../src/bch-js') 7 | const bchjs = new BCHJS() 8 | 9 | describe('#Transaction', () => { 10 | beforeEach(async () => { 11 | if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000) 12 | }) 13 | 14 | describe('#get', () => { 15 | it('should get a tx details for a non-SLP TX with an OP_RETURN', async () => { 16 | const txid = 17 | '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b' 18 | 19 | const result = await bchjs.Transaction.get(txid) 20 | // console.log('result: ', result) 21 | 22 | assert.property(result.txData, 'txid') 23 | assert.property(result.txData, 'vin') 24 | assert.property(result.txData, 'vout') 25 | assert.equal(result.txData.isValidSlp, false) 26 | }) 27 | 28 | it('should handle a coinbase transaction', async () => { 29 | const txid = 'cca1d2dd3a533d2f501448dec03face2cb2814afd59a533a611e9a2909f2302b' 30 | 31 | const details = await bchjs.Transaction.get(txid) 32 | // console.log(`details: ${JSON.stringify(details, null, 2)}`) 33 | 34 | assert.property(details.txData, 'txid') 35 | }) 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the bchjs. Only covers calls made to 3 | rest.bitcoin.com. 4 | */ 5 | 6 | const chai = require('chai') 7 | const assert = chai.assert 8 | const BCHJS = require('../../../../src/bch-js') 9 | const bchjs = new BCHJS() 10 | 11 | // Inspect utility used for debugging. 12 | const util = require('util') 13 | util.inspect.defaultOptions = { 14 | showHidden: true, 15 | colors: true, 16 | depth: 3 17 | } 18 | 19 | describe('#util', () => { 20 | beforeEach(async () => { 21 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 22 | }) 23 | 24 | describe('#validateAddress', () => { 25 | it('should return false for testnet addr on mainnet', async () => { 26 | const address = 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' 27 | 28 | const result = await bchjs.Util.validateAddress(address) 29 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 30 | 31 | assert.hasAllKeys(result, ['isvalid']) 32 | assert.equal(result.isvalid, false) 33 | }) 34 | 35 | it('should return false for bad address', async () => { 36 | const address = 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peu' 37 | 38 | const result = await bchjs.Util.validateAddress(address) 39 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 40 | 41 | assert.hasAllKeys(result, ['isvalid']) 42 | assert.equal(result.isvalid, false) 43 | }) 44 | 45 | it('should validate valid address', async () => { 46 | const address = 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z' 47 | 48 | const result = await bchjs.Util.validateAddress(address) 49 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 50 | 51 | assert.hasAnyKeys(result, [ 52 | 'isvalid', 53 | 'address', 54 | 'scriptPubKey', 55 | // "ismine", 56 | // "iswatchonly", 57 | 'isscript' 58 | ]) 59 | assert.equal(result.isvalid, true) 60 | }) 61 | 62 | it('should validate an array of addresses', async () => { 63 | const address = [ 64 | 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z', 65 | 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z' 66 | ] 67 | 68 | const result = await bchjs.Util.validateAddress(address) 69 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 70 | 71 | assert.isArray(result) 72 | assert.hasAnyKeys(result[0], [ 73 | 'isvalid', 74 | 'address', 75 | 'scriptPubKey', 76 | // "ismine", 77 | // "iswatchonly", 78 | 'isscript' 79 | ]) 80 | }) 81 | 82 | it('should throw error on array size rate limit', async () => { 83 | try { 84 | const dataMock = 85 | 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peuwupzan2vcnwrmpy0z' 86 | const data = [] 87 | for (let i = 0; i < 25; i++) data.push(dataMock) 88 | 89 | const result = await bchjs.Util.validateAddress(data) 90 | 91 | console.log(`result: ${util.inspect(result)}`) 92 | assert.equal(true, false, 'Unexpected result!') 93 | } catch (err) { 94 | assert.hasAnyKeys(err, ['error']) 95 | assert.include(err.error, 'Array too large') 96 | } 97 | }) 98 | }) 99 | }) 100 | 101 | function sleep (ms) { 102 | return new Promise(resolve => setTimeout(resolve, ms)) 103 | } 104 | -------------------------------------------------------------------------------- /test/integration/chains/bchn/utxo-integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the utxo.js library. 3 | */ 4 | 5 | const assert = require('chai').assert 6 | 7 | const BCHJS = require('../../../../src/bch-js') 8 | const bchjs = new BCHJS() 9 | // const bchjs = new BCHJS({ restURL: 'http://192.168.2.129:3000/v5/' }) 10 | 11 | describe('#UTXO', () => { 12 | beforeEach(async () => { 13 | // sandbox = sinon.createSandbox() 14 | 15 | if (process.env.IS_USING_FREE_TIER) await sleep(3000) 16 | }) 17 | 18 | describe('#hydrateTokenData', () => { 19 | it('should hydrate token UTXOs', async () => { 20 | const utxos = [ 21 | { 22 | txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c', 23 | vout: 1, 24 | type: 'token', 25 | qty: '10000000', 26 | tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37', 27 | address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m' 28 | }, 29 | { 30 | txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d', 31 | vout: 1, 32 | type: 'token', 33 | qty: '100000000', 34 | tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb', 35 | address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m' 36 | }, 37 | { 38 | txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93', 39 | vout: 5, 40 | type: 'token', 41 | qty: '4764', 42 | tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', 43 | address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m' 44 | }, 45 | { 46 | txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861', 47 | vout: 5, 48 | type: 'token', 49 | qty: '238', 50 | tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f', 51 | address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m' 52 | } 53 | ] 54 | 55 | const result = await bchjs.Utxo.hydrateTokenData(utxos) 56 | // console.log('result: ', result) 57 | 58 | assert.property(result[0], 'ticker') 59 | assert.property(result[0], 'name') 60 | assert.property(result[0], 'qtyStr') 61 | assert.property(result[0], 'documentUri') 62 | assert.property(result[0], 'documentHash') 63 | }) 64 | }) 65 | 66 | describe('#get', () => { 67 | it('should hydrate address with BCH and SLP UTXOs', async () => { 68 | const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' 69 | 70 | const result = await bchjs.Utxo.get(addr) 71 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 72 | 73 | // Assert expected properties exist. 74 | assert.property(result, 'address') 75 | assert.property(result, 'bchUtxos') 76 | assert.property(result, 'slpUtxos') 77 | assert.property(result.slpUtxos, 'type1') 78 | assert.property(result.slpUtxos, 'nft') 79 | assert.property(result, 'nullUtxos') 80 | 81 | assert.isAbove(result.bchUtxos.length, 0) 82 | assert.isAbove(result.infoUtxos.length, 0) 83 | assert.isAbove(result.slpUtxos.type1.tokens.length, 0) 84 | assert.equal(result.slpUtxos.type1.mintBatons.length, 0) 85 | }) 86 | 87 | it('should handle Type1 minting batons', async () => { 88 | const addr = 'simpleledger:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpv4awvv7m7' 89 | 90 | const result = await bchjs.Utxo.get(addr) 91 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 92 | 93 | // Assert that minting batons are correctly identified. 94 | assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0) 95 | }) 96 | 97 | it('should return UTXOs for address with no SLP tokens', async () => { 98 | const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' 99 | 100 | const result = await bchjs.Utxo.get(addr) 101 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 102 | 103 | assert.isAbove(result.bchUtxos.length, 0) 104 | assert.equal(result.slpUtxos.type1.tokens.length, 0) 105 | }) 106 | 107 | it('should filter Group tokens and mint batons', async () => { 108 | const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q' 109 | 110 | const result = await bchjs.Utxo.get(addr) 111 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 112 | 113 | assert.isAbove(result.slpUtxos.group.tokens.length, 0) 114 | assert.isAbove(result.slpUtxos.group.mintBatons.length, 0) 115 | }) 116 | 117 | it('should filter NFTs', async () => { 118 | const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q' 119 | 120 | const result = await bchjs.Utxo.get(addr) 121 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 122 | 123 | assert.isAbove(result.slpUtxos.nft.tokens.length, 0) 124 | }) 125 | }) 126 | 127 | describe('#isValid', () => { 128 | it('should return true for valid UTXO with fullnode properties', async () => { 129 | const utxo = { 130 | txid: '260d4fb4006a330660c805327c840d569d7547b7e3e9659fb423b3a041c2e254', 131 | vout: 0 132 | } 133 | 134 | const result = await bchjs.Utxo.isValid(utxo) 135 | // console.log('result: ', result) 136 | 137 | assert.equal(result, true) 138 | }) 139 | 140 | it('should return true for valid UTXO with fulcrum properties', async () => { 141 | const utxo = { 142 | tx_hash: '260d4fb4006a330660c805327c840d569d7547b7e3e9659fb423b3a041c2e254', 143 | tx_pos: 0 144 | } 145 | 146 | const result = await bchjs.Utxo.isValid(utxo) 147 | // console.log('result: ', result) 148 | 149 | assert.equal(result, true) 150 | }) 151 | 152 | it('should return true for valid UTXO with fullnode properties', async () => { 153 | const utxo = { 154 | txid: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a', 155 | vout: 0 156 | } 157 | 158 | const result = await bchjs.Utxo.isValid(utxo) 159 | // console.log('result: ', result) 160 | 161 | assert.equal(result, false) 162 | }) 163 | 164 | it('should return true for valid UTXO with fulcrum properties', async () => { 165 | const utxo = { 166 | tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a', 167 | tx_pos: 0 168 | } 169 | 170 | const result = await bchjs.Utxo.isValid(utxo) 171 | // console.log('result: ', result 172 | 173 | assert.equal(result, false) 174 | }) 175 | 176 | it('should process output of Utxo.get()', async () => { 177 | const utxo = await bchjs.Utxo.get('bitcoincash:qqgahqa5zkknmhmvsc98jndpwn3r77gqgc02x03jce') 178 | // console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`) 179 | 180 | const result = await bchjs.Utxo.isValid(utxo.bchUtxos[0]) 181 | 182 | assert.equal(result, true) 183 | }) 184 | }) 185 | }) 186 | 187 | function sleep (ms) { 188 | return new Promise(resolve => setTimeout(resolve, ms)) 189 | } 190 | -------------------------------------------------------------------------------- /test/integration/chains/testnet/control.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for bchjs control library. 3 | */ 4 | 5 | const chai = require('chai') 6 | const assert = chai.assert 7 | 8 | const RESTURL = process.env.RESTURL 9 | ? process.env.RESTURL 10 | : 'https://testnet3.fullstack.cash/v5/' 11 | 12 | const BCHJS = require('../../../../src/bch-js') 13 | const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) 14 | 15 | describe('#control', () => { 16 | beforeEach(async () => { 17 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 18 | }) 19 | 20 | describe('#getNetworkInfo', () => { 21 | it('should get info on the full node', async () => { 22 | const result = await bchjs.Control.getNetworkInfo() 23 | console.log(`result: ${JSON.stringify(result, null, 2)}`) 24 | 25 | assert.property(result, 'version') 26 | }) 27 | }) 28 | }) 29 | 30 | function sleep (ms) { 31 | return new Promise(resolve => setTimeout(resolve, ms)) 32 | } 33 | -------------------------------------------------------------------------------- /test/integration/chains/testnet/slp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the bchjs covering SLP tokens. 3 | */ 4 | 5 | const chai = require('chai') 6 | const assert = chai.assert 7 | 8 | const RESTURL = process.env.RESTURL 9 | ? process.env.RESTURL 10 | : 'https://testnet3.fullstack.cash/v5/' 11 | // if (process.env.RESTURL) RESTURL = process.env.RESTURL 12 | 13 | const BCHJS = require('../../../../src/bch-js') 14 | // const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` }) 15 | const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) 16 | 17 | // Inspect utility used for debugging. 18 | const util = require('util') 19 | util.inspect.defaultOptions = { 20 | showHidden: true, 21 | colors: true, 22 | depth: 1 23 | } 24 | 25 | describe('#SLP', () => { 26 | beforeEach(async () => { 27 | // Introduce a delay so that the BVT doesn't trip the rate limits. 28 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 29 | }) 30 | 31 | describe('#util', () => { 32 | it('should get information on the Oasis token', async () => { 33 | const tokenId = 34 | 'a371e9934c7695d08a5eb7f31d3bceb4f3644860cc67520cda1e149423b9ec39' 35 | 36 | const result = await bchjs.SLP.Utils.list(tokenId) 37 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 38 | 39 | assert.hasAnyKeys(result, [ 40 | 'decimals', 41 | 'timestamp', 42 | 'timestamp_unix', 43 | 'versionType', 44 | 'documentUri', 45 | 'symbol', 46 | 'name', 47 | 'containsBaton', 48 | 'id', 49 | 'documentHash', 50 | 'initialTokenQty', 51 | 'blockCreated', 52 | 'blockLastActiveSend', 53 | 'blockLastActiveMint', 54 | 'txnsSinceGenesis', 55 | 'validAddress', 56 | 'totalMinted', 57 | 'totalBurned', 58 | 'circulatingSupply', 59 | 'mintingBatonStatus' 60 | ]) 61 | }) 62 | }) 63 | 64 | describe('#decodeOpReturn', () => { 65 | it('should decode the OP_RETURN for a SEND txid', async () => { 66 | const txid = 67 | 'ad28116e0818339342dddfc5f58ca8a5379ceb9679b4e4cbd72f4de905415ec1' 68 | 69 | const result = await bchjs.SLP.Utils.decodeOpReturn(txid) 70 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 71 | 72 | assert.hasAllKeys(result, ['amounts', 'tokenType', 'tokenId', 'txType']) 73 | }) 74 | }) 75 | 76 | describe('#balancesForAddress', () => { 77 | it('should fetch all balances for address: slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9', async () => { 78 | const balances = await bchjs.SLP.Utils.balancesForAddress( 79 | 'slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9' 80 | ) 81 | // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) 82 | 83 | assert.isArray(balances) 84 | assert.hasAllKeys(balances[0], [ 85 | 'tokenId', 86 | 'balanceString', 87 | 'balance', 88 | 'decimalCount', 89 | 'slpAddress' 90 | ]) 91 | }) 92 | 93 | it('should fetch balances for multiple addresses', async () => { 94 | const addresses = [ 95 | 'slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9', 96 | 'slptest:qr5uy4h8ysyhwkp9s245scckdnc7teyjqc5ft2z43h' 97 | ] 98 | 99 | const balances = await bchjs.SLP.Utils.balancesForAddress(addresses) 100 | // console.log(`balances: ${JSON.stringify(balances, null, 2)}`) 101 | 102 | assert.isArray(balances) 103 | assert.isArray(balances[0]) 104 | assert.hasAllKeys(balances[0][0], [ 105 | 'tokenId', 106 | 'balanceString', 107 | 'balance', 108 | 'decimalCount', 109 | 'slpAddress' 110 | ]) 111 | }) 112 | }) 113 | 114 | describe('#tokenUtxoDetails', () => { 115 | it('should hydrate UTXOs', async () => { 116 | const bchAddr = bchjs.SLP.Address.toCashAddress( 117 | 'slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9' 118 | ) 119 | 120 | const utxos = await bchjs.Electrumx.utxo([bchAddr]) 121 | // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) 122 | 123 | const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails( 124 | utxos.utxos[0].utxos 125 | ) 126 | // console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`) 127 | 128 | assert.isArray(utxoInfo) 129 | 130 | // first UTXO should be a PSF test token. 131 | assert.equal(utxoInfo[0].isValid, true) 132 | }) 133 | }) 134 | 135 | describe('#hydrateUtxos', () => { 136 | // This test will error out if the LOCAL_RESTURL settings is not set properly 137 | // in bch-api. 138 | it('should hydrate UTXOs', async () => { 139 | const bchAddr = bchjs.SLP.Address.toCashAddress( 140 | 'slptest:qz0kc67pm4emjyr3gaaa2wstdaykg9m4yqwlzpj3w9' 141 | ) 142 | 143 | const utxos = await bchjs.Electrumx.utxo([bchAddr]) 144 | // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) 145 | 146 | const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos) 147 | // console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`) 148 | 149 | assert.isArray(utxoInfo.slpUtxos[0].utxos) 150 | 151 | // first UTXO should be a PSF test token. 152 | assert.equal(utxoInfo.slpUtxos[0].utxos[0].isValid, true) 153 | }) 154 | }) 155 | 156 | describe('#validateTxid2', () => { 157 | it('should validate a token txid', async () => { 158 | const txid = 159 | 'ad28116e0818339342dddfc5f58ca8a5379ceb9679b4e4cbd72f4de905415ec1' 160 | 161 | const validated = await bchjs.SLP.Utils.validateTxid(txid) 162 | // console.log(validated) 163 | 164 | assert.equal(validated[0].valid, true) 165 | }) 166 | }) 167 | }) 168 | 169 | // Promise-based sleep function 170 | function sleep (ms) { 171 | return new Promise(resolve => setTimeout(resolve, ms)) 172 | } 173 | -------------------------------------------------------------------------------- /test/integration/chains/testnet/test-free-tier.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export RESTURL=https://free-test.fullstack.cash/v5/ 4 | export NETWORK=testnet 5 | export IS_USING_FREE_TIER=true 6 | 7 | cd test/integration/testnet/ 8 | mocha --timeout 30000 blockchain.js control.js electrumx.js rawtransaction.js slp.js util.js 9 | #mocha --timeout 30000 blockchain.js 10 | -------------------------------------------------------------------------------- /test/integration/chains/testnet/util.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the bchjs. Only covers calls made to 3 | rest.bitcoin.com. 4 | */ 5 | 6 | const chai = require('chai') 7 | const assert = chai.assert 8 | 9 | const RESTURL = process.env.RESTURL 10 | ? process.env.RESTURL 11 | : 'https://testnet3.fullstack.cash/v5/' 12 | // if (process.env.RESTURL) RESTURL = process.env.RESTURL 13 | 14 | const BCHJS = require('../../../../src/bch-js') 15 | // const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` }) 16 | const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN }) 17 | 18 | // Inspect utility used for debugging. 19 | const util = require('util') 20 | util.inspect.defaultOptions = { 21 | showHidden: true, 22 | colors: true, 23 | depth: 3 24 | } 25 | 26 | describe('#util', () => { 27 | beforeEach(async () => { 28 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 29 | }) 30 | 31 | describe('#validateAddress', () => { 32 | it('should return false for testnet addr on mainnet', async () => { 33 | const address = 'bitcoincash:qp4k8fjtgunhdr7yq30ha4peu' 34 | 35 | const result = await bchjs.Util.validateAddress(address) 36 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 37 | 38 | assert.hasAllKeys(result, ['isvalid']) 39 | assert.equal(result.isvalid, false) 40 | }) 41 | 42 | it('should return false for bad address', async () => { 43 | const address = 'bchtest:qqqk4y6lsl5da64sg53xezmplyu5kmpyz2ysaa5y' 44 | 45 | const result = await bchjs.Util.validateAddress(address) 46 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 47 | 48 | assert.hasAllKeys(result, ['isvalid']) 49 | assert.equal(result.isvalid, false) 50 | }) 51 | 52 | it('should validate valid address', async () => { 53 | const address = 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y' 54 | 55 | const result = await bchjs.Util.validateAddress(address) 56 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 57 | 58 | assert.hasAnyKeys(result, [ 59 | 'isvalid', 60 | 'address', 61 | 'scriptPubKey', 62 | // "ismine", 63 | // "iswatchonly", 64 | 'isscript' 65 | ]) 66 | assert.equal(result.isvalid, true) 67 | }) 68 | 69 | it('should validate an array of addresses', async () => { 70 | const address = [ 71 | 'bchtest:qqqk4y6lsl5da64sg5qc3xezmplyu5kmpyz2ysaa5y', 72 | 'bchtest:pq6k9969f6v6sg7a75jkru4n7wn9sknv5cztcp0dnh' 73 | ] 74 | 75 | const result = await bchjs.Util.validateAddress(address) 76 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 77 | 78 | assert.isArray(result) 79 | assert.hasAnyKeys(result[0], [ 80 | 'isvalid', 81 | 'address', 82 | 'scriptPubKey', 83 | // "ismine", 84 | // "iswatchonly", 85 | 'isscript' 86 | ]) 87 | }) 88 | 89 | it('should throw error on array size rate limit', async () => { 90 | try { 91 | const dataMock = 'bchtest:pq6k9969f6v6sg7a75jkru4n7wn9sknv5cztcp0dnh' 92 | const data = [] 93 | for (let i = 0; i < 25; i++) data.push(dataMock) 94 | 95 | const result = await bchjs.Util.validateAddress(data) 96 | 97 | console.log(`result: ${util.inspect(result)}`) 98 | assert.equal(true, false, 'Unexpected result!') 99 | } catch (err) { 100 | assert.hasAnyKeys(err, ['error']) 101 | assert.include(err.error, 'Array too large') 102 | } 103 | }) 104 | }) 105 | }) 106 | 107 | function sleep (ms) { 108 | return new Promise(resolve => setTimeout(resolve, ms)) 109 | } 110 | -------------------------------------------------------------------------------- /test/integration/control.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for bchjs control library. 3 | */ 4 | 5 | const chai = require('chai') 6 | const assert = chai.assert 7 | const BCHJS = require('../../src/bch-js') 8 | const bchjs = new BCHJS() 9 | 10 | describe('#control', () => { 11 | beforeEach(async () => { 12 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 13 | }) 14 | 15 | describe('#getNetworkInfo', () => { 16 | it('should get info on the full node', async () => { 17 | const result = await bchjs.Control.getNetworkInfo() 18 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 19 | 20 | assert.property(result, 'version') 21 | }) 22 | }) 23 | }) 24 | 25 | function sleep (ms) { 26 | return new Promise(resolve => setTimeout(resolve, ms)) 27 | } 28 | -------------------------------------------------------------------------------- /test/integration/encryption.js: -------------------------------------------------------------------------------- 1 | // const assert = require('chai').assert 2 | 3 | // const BCHJS = require('../../src/bch-js') 4 | // const bchjs = new BCHJS() 5 | 6 | describe('#Encryption', () => { 7 | beforeEach(async () => { 8 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 9 | }) 10 | 11 | describe('#getPubKey', () => { 12 | // Commenting out these tests since they are failing in BVT due to 429 errors 13 | /* 14 | it('should get a public key', async () => { 15 | // Add delay for this endpoint. 16 | await sleep(8000) 17 | 18 | const addr = 'bitcoincash:qpf8jv9hmqcda0502gjp7nm3g24y5h5s4unutghsxq' 19 | 20 | const result = await bchjs.encryption.getPubKey(addr) 21 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 22 | 23 | assert.property(result, 'success') 24 | assert.equal(result.success, true) 25 | assert.property(result, 'publicKey') 26 | }) 27 | 28 | it('should report when public key can not be found', async () => { 29 | // Add delay for this endpoint. 30 | await sleep(8000) 31 | 32 | const addr = 'bitcoincash:qrgqqkky28jdkv3w0ctrah0mz3jcsnsklc34gtukrh' 33 | 34 | const result = await bchjs.encryption.getPubKey(addr) 35 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 36 | 37 | assert.property(result, 'success') 38 | assert.equal(result.success, false) 39 | assert.property(result, 'publicKey') 40 | assert.equal(result.publicKey, 'not found') 41 | }) 42 | */ 43 | }) 44 | }) 45 | 46 | function sleep (ms) { 47 | return new Promise(resolve => setTimeout(resolve, ms)) 48 | } 49 | -------------------------------------------------------------------------------- /test/integration/price.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert 2 | const BCHJS = require('../../src/bch-js') 3 | const bchjs = new BCHJS() 4 | 5 | describe('#price', () => { 6 | beforeEach(async () => { 7 | if (process.env.IS_USING_FREE_TIER) await sleep(1500) 8 | }) 9 | 10 | // describe('#current', () => { 11 | // describe('#single currency', () => { 12 | // it('should get current price for single currency', async () => { 13 | // const result = await bchjs.Price.current('usd') 14 | // assert.notEqual(0, result) 15 | // }) 16 | // }) 17 | // }) 18 | 19 | describe('#getUsd', () => { 20 | it('should get the USD price of BCH', async () => { 21 | const result = await bchjs.Price.getUsd() 22 | // console.log(result) 23 | 24 | assert.isNumber(result) 25 | }) 26 | }) 27 | 28 | describe('#getBchaUsd', () => { 29 | it('should get the USD price of BCHA', async () => { 30 | const result = await bchjs.Price.getBchaUsd() 31 | console.log(result) 32 | 33 | assert.isNumber(result) 34 | }) 35 | }) 36 | 37 | describe('#getBchUsd', () => { 38 | it('should get the USD price of BCH', async () => { 39 | const result = await bchjs.Price.getBchUsd() 40 | console.log(result) 41 | 42 | assert.isNumber(result) 43 | }) 44 | }) 45 | 46 | describe('#rates', () => { 47 | it('should get the price of BCH in several currencies', async () => { 48 | const result = await bchjs.Price.rates() 49 | // console.log(result) 50 | 51 | assert.property(result, 'USD') 52 | assert.property(result, 'CAD') 53 | }) 54 | }) 55 | 56 | describe('#getPsffppPrice', () => { 57 | it('should get the price of BCH in several currencies', async () => { 58 | const result = await bchjs.Price.getPsffppPrice() 59 | // console.log(result) 60 | 61 | assert.isNumber(result) 62 | }) 63 | }) 64 | }) 65 | 66 | function sleep (ms) { 67 | return new Promise(resolve => setTimeout(resolve, ms)) 68 | } 69 | -------------------------------------------------------------------------------- /test/integration/transaction-integration.js: -------------------------------------------------------------------------------- 1 | /* 2 | Integration tests for the transaction.js library. 3 | */ 4 | 5 | const assert = require('chai').assert 6 | const BCHJS = require('../../src/bch-js') 7 | const bchjs = new BCHJS() 8 | 9 | describe('#Transaction', () => { 10 | beforeEach(async () => { 11 | if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000) 12 | }) 13 | 14 | if (process.env.TESTSLP) { 15 | describe('#getOld', () => { 16 | it('should get details about a non-SLP transaction', async () => { 17 | const txid = 18 | '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' 19 | 20 | const result = await bchjs.Transaction.getOld(txid) 21 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 22 | 23 | // Assert that there are stanardized properties. 24 | assert.property(result, 'txid') 25 | assert.property(result, 'vin') 26 | assert.property(result, 'vout') 27 | assert.property(result.vout[0], 'value') 28 | assert.property(result.vout[0].scriptPubKey, 'addresses') 29 | 30 | // Assert that added properties exist. 31 | assert.property(result.vin[0], 'address') 32 | assert.property(result.vin[0], 'value') 33 | assert.property(result, 'isValidSLPTx') 34 | assert.equal(result.isValidSLPTx, false) 35 | }) 36 | 37 | it('should get details about a SLP transaction', async () => { 38 | const txid = 39 | '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1' 40 | 41 | const result = await bchjs.Transaction.getOld(txid) 42 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 43 | 44 | // Assert that there are stanardized properties. 45 | assert.property(result, 'txid') 46 | assert.property(result, 'vin') 47 | assert.property(result, 'vout') 48 | assert.property(result.vout[0], 'value') 49 | assert.property(result.vout[1].scriptPubKey, 'addresses') 50 | 51 | // Assert that added properties exist. 52 | assert.property(result.vout[0], 'tokenQty') 53 | assert.equal(result.vout[0].tokenQty, null) 54 | assert.property(result.vin[0], 'address') 55 | assert.property(result.vin[0], 'value') 56 | assert.property(result.vin[0], 'tokenQty') 57 | assert.property(result, 'isValidSLPTx') 58 | assert.equal(result.isValidSLPTx, true) 59 | }) 60 | 61 | // it('should get problematic transaction', async () => { 62 | // const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1' 63 | // 64 | // const result = await bchjs.Transaction.get(txid) 65 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 66 | // }) 67 | 68 | // TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has 69 | // an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6) 70 | // that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation, 71 | // but for separate tokens. 72 | it('should get problematic transaction', async () => { 73 | const txid = 74 | 'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe' 75 | 76 | const result = await bchjs.Transaction.getOld(txid) 77 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 78 | 79 | // The token ID should equal the txid for this Vin. 80 | assert.equal(result.vin[2].txid, result.vin[2].tokenId) 81 | }) 82 | }) 83 | } 84 | }) 85 | -------------------------------------------------------------------------------- /test/unit/control.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const axios = require('axios') 3 | const BCHJS = require('../../src/bch-js') 4 | const bchjs = new BCHJS() 5 | const sinon = require('sinon') 6 | 7 | describe('#Control', () => { 8 | let sandbox 9 | beforeEach(() => (sandbox = sinon.createSandbox())) 10 | afterEach(() => sandbox.restore()) 11 | 12 | describe('#getNetworkInfo', () => { 13 | it('should get info', done => { 14 | const data = { 15 | version: 170000, 16 | protocolversion: 70015, 17 | blocks: 527813, 18 | timeoffset: 0, 19 | connections: 21, 20 | proxy: '', 21 | difficulty: 581086703759.5878, 22 | testnet: false, 23 | paytxfee: 0, 24 | relayfee: 0.00001, 25 | errors: '' 26 | } 27 | const resolved = new Promise(resolve => resolve({ data: data })) 28 | sandbox.stub(axios, 'get').returns(resolved) 29 | 30 | bchjs.Control.getNetworkInfo() 31 | .then(result => { 32 | assert.deepStrictEqual(data, result) 33 | }) 34 | .then(done, done) 35 | }) 36 | }) 37 | 38 | describe('#getMemoryInfo', () => { 39 | it('should get memory info', done => { 40 | const data = { 41 | locked: { 42 | used: 0, 43 | free: 65536, 44 | total: 65536, 45 | locked: 65536, 46 | chunks_used: 0, 47 | chunks_free: 1 48 | } 49 | } 50 | const resolved = new Promise(resolve => resolve({ data: data })) 51 | sandbox.stub(axios, 'get').returns(resolved) 52 | 53 | bchjs.Control.getMemoryInfo() 54 | .then(result => { 55 | assert.deepStrictEqual(data, result) 56 | }) 57 | .then(done, done) 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/unit/crypto.js: -------------------------------------------------------------------------------- 1 | const fixtures = require('./fixtures/crypto.json') 2 | const assert = require('assert') 3 | const BCHJS = require('../../src/bch-js') 4 | const bchjs = new BCHJS() 5 | const Buffer = require('safe-buffer').Buffer 6 | 7 | describe('#Crypto', () => { 8 | describe('#sha256', () => { 9 | fixtures.sha256.forEach(fixture => { 10 | it(`should create SHA256Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { 11 | const data = Buffer.from(fixture.hex, 'hex') 12 | const sha256Hash = bchjs.Crypto.sha256(data).toString('hex') 13 | assert.equal(sha256Hash, fixture.hash) 14 | }) 15 | 16 | it('should create 64 character SHA256Hash hex encoded', () => { 17 | const data = Buffer.from(fixture.hex, 'hex') 18 | const sha256Hash = bchjs.Crypto.sha256(data).toString('hex') 19 | assert.equal(sha256Hash.length, 64) 20 | }) 21 | }) 22 | }) 23 | 24 | describe('#ripemd160', () => { 25 | fixtures.ripemd160.forEach(fixture => { 26 | it(`should create RIPEMD160Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { 27 | const data = Buffer.from(fixture.hex, 'hex') 28 | const ripemd160 = bchjs.Crypto.ripemd160(data).toString('hex') 29 | assert.equal(ripemd160, fixture.hash) 30 | }) 31 | 32 | it('should create 64 character RIPEMD160Hash hex encoded', () => { 33 | const data = Buffer.from(fixture.hex, 'hex') 34 | const ripemd160 = bchjs.Crypto.ripemd160(data).toString('hex') 35 | assert.equal(ripemd160.length, 40) 36 | }) 37 | }) 38 | }) 39 | 40 | describe('#hash256', () => { 41 | fixtures.hash256.forEach(fixture => { 42 | it(`should create double SHA256 Hash hex encoded ${fixture.hash} from ${fixture.hex}`, () => { 43 | const data = Buffer.from(fixture.hex, 'hex') 44 | const hash256 = bchjs.Crypto.hash256(data).toString('hex') 45 | assert.equal(hash256, fixture.hash) 46 | }) 47 | 48 | it('should create 64 character SHA256 Hash hex encoded', () => { 49 | const data = Buffer.from(fixture.hex, 'hex') 50 | const hash256 = bchjs.Crypto.hash256(data).toString('hex') 51 | assert.equal(hash256.length, 64) 52 | }) 53 | }) 54 | }) 55 | 56 | describe('#hash160', () => { 57 | fixtures.hash160.forEach(fixture => { 58 | it(`should create RIPEMD160(SHA256()) hex encoded ${fixture.hash} from ${fixture.hex}`, () => { 59 | const data = Buffer.from(fixture.hex, 'hex') 60 | const hash160 = bchjs.Crypto.hash160(data).toString('hex') 61 | assert.equal(hash160, fixture.hash) 62 | }) 63 | 64 | it('should create 64 character SHA256Hash hex encoded', () => { 65 | const data = Buffer.from(fixture.hex, 'hex') 66 | const hash160 = bchjs.Crypto.hash160(data).toString('hex') 67 | assert.equal(hash160.length, 40) 68 | }) 69 | }) 70 | }) 71 | 72 | describe('#randomBytes', () => { 73 | for (let i = 0; i < 6; i++) { 74 | it('should return 16 bytes of entropy hex encoded', () => { 75 | const entropy = bchjs.Crypto.randomBytes(16) 76 | assert.equal(Buffer.byteLength(entropy), 16) 77 | }) 78 | 79 | it('should return 20 bytes of entropy hex encoded', () => { 80 | const entropy = bchjs.Crypto.randomBytes(20) 81 | assert.equal(Buffer.byteLength(entropy), 20) 82 | }) 83 | 84 | it('should return 24 bytes of entropy hex encoded', () => { 85 | const entropy = bchjs.Crypto.randomBytes(24) 86 | assert.equal(Buffer.byteLength(entropy), 24) 87 | }) 88 | 89 | it('should return 28 bytes of entropy hex encoded', () => { 90 | const entropy = bchjs.Crypto.randomBytes(28) 91 | assert.equal(Buffer.byteLength(entropy), 28) 92 | }) 93 | 94 | it('should return 32 bytes of entropy hex encoded', () => { 95 | const entropy = bchjs.Crypto.randomBytes(32) 96 | assert.equal(Buffer.byteLength(entropy), 32) 97 | }) 98 | } 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /test/unit/dsproof.js: -------------------------------------------------------------------------------- 1 | // Public npm libraries 2 | const assert = require('chai').assert 3 | const sinon = require('sinon') 4 | 5 | // Unit under test (uut) 6 | const BCHJS = require('../../src/bch-js') 7 | const mockData = require('./fixtures/dsproof-mock') 8 | 9 | let bchjs 10 | const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e' 11 | describe('#DSProof', () => { 12 | let sandbox 13 | beforeEach(() => { 14 | bchjs = new BCHJS() 15 | sandbox = sinon.createSandbox() 16 | }) 17 | afterEach(() => sandbox.restore()) 18 | 19 | describe('#getDSProof', () => { 20 | it('should throw error if input is not provided', async () => { 21 | try { 22 | await bchjs.DSProof.getDSProof() 23 | assert.equal(false, true, 'unexpected error') 24 | } catch (err) { 25 | assert.include(err.message, 'txid is required') 26 | } 27 | }) 28 | it('should throw error txid is invalid', async () => { 29 | try { 30 | await bchjs.DSProof.getDSProof('txid') 31 | assert.equal(false, true, 'unexpected error') 32 | } catch (err) { 33 | assert.include(err.message, 'txid must be of length 64') 34 | } 35 | }) 36 | it('should handle error', async () => { 37 | try { 38 | sandbox.stub(bchjs.DSProof.axios, 'get').throws(new Error('test error')) 39 | await bchjs.DSProof.getDSProof(txid) 40 | 41 | assert.equal(false, true, 'unexpected error') 42 | } catch (err) { 43 | assert.include(err.message, 'test error') 44 | } 45 | }) 46 | it('should get double spend proof', async () => { 47 | try { 48 | sandbox 49 | .stub(bchjs.DSProof.axios, 'get') 50 | .resolves({ data: mockData.dsproof }) 51 | const result = await bchjs.DSProof.getDSProof(txid) 52 | 53 | assert.property(result, 'dspid') 54 | assert.property(result, 'txid') 55 | assert.property(result, 'outpoint') 56 | assert.property(result, 'path') 57 | assert.property(result, 'descendants') 58 | 59 | assert.property(result.outpoint, 'txid') 60 | assert.property(result.outpoint, 'vout') 61 | } catch (error) { 62 | assert.equal(false, true, 'unexpected error') 63 | } 64 | }) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/unit/ecash.js: -------------------------------------------------------------------------------- 1 | /* 2 | Unit tests for eCash library. 3 | */ 4 | 5 | // Global npm libraries 6 | const assert = require('chai').assert 7 | const Ecash = require('../../src/ecash') 8 | const uut = new Ecash() 9 | 10 | describe('#eCash', () => { 11 | describe('#toSatoshi', () => { 12 | it('should convert XEC to satoshis', () => { 13 | const xec = 10704.35 14 | 15 | const result = uut.toSatoshi(xec) 16 | 17 | assert.equal(result, 1070435) 18 | }) 19 | 20 | it('should throw an error if input is not a number', () => { 21 | try { 22 | uut.toSatoshi('test') 23 | 24 | assert.fail('Unexpected code path') 25 | } catch (err) { 26 | assert.equal( 27 | err.message, 28 | 'input must be a floating number representing XEC' 29 | ) 30 | } 31 | }) 32 | }) 33 | 34 | describe('#toXec', () => { 35 | it('should convert satoshis to XEC', () => { 36 | const sats = 1070435 37 | 38 | const result = uut.toXec(sats) 39 | 40 | assert.equal(result, 10704.35) 41 | }) 42 | 43 | it('should throw an error if input is not a number', () => { 44 | try { 45 | uut.toXec('test') 46 | 47 | assert.fail('Unexpected code path') 48 | } catch (err) { 49 | assert.equal( 50 | err.message, 51 | 'input must be a floating number representing satoshis' 52 | ) 53 | } 54 | }) 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /test/unit/ecpairs.js: -------------------------------------------------------------------------------- 1 | // Public npm libraries. 2 | const assert = require('assert') 3 | const Buffer = require('safe-buffer').Buffer 4 | 5 | // Mocks 6 | const fixtures = require('./fixtures/ecpair.json') 7 | 8 | // Unit under test (uut) 9 | const BCHJS = require('../../src/bch-js') 10 | // const bchjs = new BCHJS() 11 | let bchjs 12 | 13 | describe('#ECPair', () => { 14 | beforeEach(() => { 15 | bchjs = new BCHJS() 16 | }) 17 | 18 | describe('#fromWIF', () => { 19 | fixtures.fromWIF.forEach(fixture => { 20 | it(`should create ECPair from WIF ${fixture.privateKeyWIF}`, () => { 21 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 22 | assert.equal(typeof ecpair, 'object') 23 | }) 24 | 25 | it(`should get ${fixture.legacy} legacy address`, () => { 26 | const legacy = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 27 | assert.equal(bchjs.HDNode.toLegacyAddress(legacy), fixture.legacy) 28 | }) 29 | 30 | it(`should get ${fixture.cashAddr} cash address`, () => { 31 | const cashAddr = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 32 | assert.equal(bchjs.HDNode.toCashAddress(cashAddr), fixture.cashAddr) 33 | }) 34 | 35 | it(`should get ${fixture.regtestAddr} cash address`, () => { 36 | const cashAddr = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 37 | assert.equal( 38 | bchjs.HDNode.toCashAddress(cashAddr, true), 39 | fixture.regtestAddr 40 | ) 41 | }) 42 | }) 43 | }) 44 | 45 | describe('#toWIF', () => { 46 | fixtures.toWIF.forEach(fixture => { 47 | it(`should get WIF ${fixture.privateKeyWIF} from ECPair`, () => { 48 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 49 | const wif = bchjs.ECPair.toWIF(ecpair) 50 | assert.equal(wif, fixture.privateKeyWIF) 51 | }) 52 | }) 53 | }) 54 | 55 | describe('#fromPublicKey', () => { 56 | fixtures.fromPublicKey.forEach(fixture => { 57 | it('should create ECPair from public key buffer', () => { 58 | const ecpair = bchjs.ECPair.fromPublicKey( 59 | Buffer.from(fixture.pubkeyHex, 'hex') 60 | ) 61 | assert.equal(typeof ecpair, 'object') 62 | }) 63 | 64 | it(`should get ${fixture.legacy} legacy address`, () => { 65 | const ecpair = bchjs.ECPair.fromPublicKey( 66 | Buffer.from(fixture.pubkeyHex, 'hex') 67 | ) 68 | assert.equal(bchjs.HDNode.toLegacyAddress(ecpair), fixture.legacy) 69 | }) 70 | 71 | it(`should get ${fixture.cashAddr} cash address`, () => { 72 | const ecpair = bchjs.ECPair.fromPublicKey( 73 | Buffer.from(fixture.pubkeyHex, 'hex') 74 | ) 75 | assert.equal(bchjs.HDNode.toCashAddress(ecpair), fixture.cashAddr) 76 | }) 77 | 78 | it(`should get ${fixture.regtestAddr} cash address`, () => { 79 | const ecpair = bchjs.ECPair.fromPublicKey( 80 | Buffer.from(fixture.pubkeyHex, 'hex') 81 | ) 82 | assert.equal( 83 | bchjs.HDNode.toCashAddress(ecpair, true), 84 | fixture.regtestAddr 85 | ) 86 | }) 87 | }) 88 | }) 89 | 90 | describe('#toPublicKey', () => { 91 | fixtures.toPublicKey.forEach(fixture => { 92 | it('should create a public key buffer from an ECPair', () => { 93 | const ecpair = bchjs.ECPair.fromPublicKey( 94 | Buffer.from(fixture.pubkeyHex, 'hex') 95 | ) 96 | const pubkeyBuffer = bchjs.ECPair.toPublicKey(ecpair) 97 | assert.equal(typeof pubkeyBuffer, 'object') 98 | }) 99 | }) 100 | }) 101 | 102 | describe('#toLegacyAddress', () => { 103 | fixtures.toLegacyAddress.forEach(fixture => { 104 | it(`should create legacy address ${fixture.legacy} from an ECPair`, () => { 105 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 106 | const legacyAddress = bchjs.ECPair.toLegacyAddress(ecpair) 107 | assert.equal(legacyAddress, fixture.legacy) 108 | }) 109 | }) 110 | }) 111 | 112 | describe('#toCashAddress', () => { 113 | fixtures.toCashAddress.forEach(fixture => { 114 | it(`should create cash address ${fixture.cashAddr} from an ECPair`, () => { 115 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 116 | const cashAddr = bchjs.ECPair.toCashAddress(ecpair) 117 | assert.equal(cashAddr, fixture.cashAddr) 118 | }) 119 | }) 120 | 121 | fixtures.toCashAddress.forEach(fixture => { 122 | it(`should create regtest cash address ${fixture.regtestAddr} from an ECPair`, () => { 123 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 124 | const regtestAddr = bchjs.ECPair.toCashAddress(ecpair, true) 125 | assert.equal(regtestAddr, fixture.regtestAddr) 126 | }) 127 | }) 128 | }) 129 | 130 | describe('#sign', () => { 131 | fixtures.sign.forEach(fixture => { 132 | it('should sign 32 byte hash buffer', () => { 133 | const ecpair = bchjs.ECPair.fromWIF(fixture.privateKeyWIF) 134 | const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), 'hex') 135 | const signatureBuf = bchjs.ECPair.sign(ecpair, buf) 136 | assert.equal(typeof signatureBuf, 'object') 137 | }) 138 | }) 139 | }) 140 | 141 | describe('#verify', () => { 142 | fixtures.verify.forEach(fixture => { 143 | it('should verify signed 32 byte hash buffer', () => { 144 | const ecpair1 = bchjs.ECPair.fromWIF(fixture.privateKeyWIF1) 145 | // const ecpair2 = bchjs.ECPair.fromWIF(fixture.privateKeyWIF2) 146 | const buf = Buffer.from(bchjs.Crypto.sha256(fixture.data), 'hex') 147 | const signature = bchjs.ECPair.sign(ecpair1, buf) 148 | const verify = bchjs.ECPair.verify(ecpair1, buf, signature) 149 | assert.equal(verify, true) 150 | }) 151 | }) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /test/unit/encryption.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert 2 | const sinon = require('sinon') 3 | 4 | const BCHJS = require('../../src/bch-js') 5 | // const bchjs = new BCHJS() 6 | let bchjs 7 | 8 | const mockData = require('./fixtures/encryption-mock') 9 | 10 | describe('#Encryption', () => { 11 | let sandbox 12 | 13 | beforeEach(() => { 14 | bchjs = new BCHJS() 15 | sandbox = sinon.createSandbox() 16 | }) 17 | 18 | afterEach(() => sandbox.restore()) 19 | 20 | describe('#getPubKey', () => { 21 | it('should throw error if BCH address is not provided.', async () => { 22 | try { 23 | await bchjs.encryption.getPubKey() 24 | 25 | assert.equal(true, false, 'Unexpected result!') 26 | } catch (err) { 27 | // console.log(`err: `, err) 28 | assert.include( 29 | err.message, 30 | 'Input must be a valid Bitcoin Cash address' 31 | ) 32 | } 33 | }) 34 | 35 | it('should report when public key can not be found', async () => { 36 | // Stub the network call. 37 | sandbox 38 | .stub(bchjs.encryption.axios, 'get') 39 | .resolves({ data: mockData.failureMock }) 40 | 41 | const addr = 'bitcoincash:qpxqr2pmcverj4vukgjqssvk2zju8tp9xsgz2nqagx' 42 | 43 | const result = await bchjs.encryption.getPubKey(addr) 44 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 45 | 46 | assert.property(result, 'success') 47 | assert.equal(result.success, false) 48 | assert.property(result, 'publicKey') 49 | assert.equal(result.publicKey, 'not found') 50 | }) 51 | 52 | it('should get a public key', async () => { 53 | // Stub the network call. 54 | sandbox 55 | .stub(bchjs.encryption.axios, 'get') 56 | .resolves({ data: mockData.successMock }) 57 | 58 | const addr = 'bitcoincash:qpf8jv9hmqcda0502gjp7nm3g24y5h5s4unutghsxq' 59 | 60 | const result = await bchjs.encryption.getPubKey(addr) 61 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 62 | 63 | assert.property(result, 'success') 64 | assert.equal(result.success, true) 65 | assert.property(result, 'publicKey') 66 | }) 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/unit/fixtures/bitcore-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Unit test mocks for bitcore endpoints. 3 | */ 4 | 5 | const balance = { confirmed: 230000, unconfirmed: 0, balance: 230000 } 6 | 7 | const utxo = [ 8 | { 9 | _id: '5cecdd39a9f1235e2a3d409a', 10 | chain: 'BCH', 11 | network: 'mainnet', 12 | coinbase: false, 13 | mintIndex: 0, 14 | spentTxid: '', 15 | mintTxid: 16 | '27ec8512c1a9ee9e9ae9b98eb60375f1d2bd60e2e76a1eff5a45afdbc517cf9c', 17 | mintHeight: 560430, 18 | spentHeight: -2, 19 | address: 'qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 20 | script: '76a914db6ea94fa26b7272dc5e1487c35f258391e0f38788ac', 21 | value: 100000, 22 | confirmations: -1 23 | }, 24 | { 25 | _id: '5cecdd1ca9f1235e2a3b6349', 26 | chain: 'BCH', 27 | network: 'mainnet', 28 | coinbase: false, 29 | mintIndex: 0, 30 | spentTxid: '', 31 | mintTxid: 32 | '6e1ae1bf7db6de799ec1c05ab2816ac65549bd80141567af088e6f291385b07d', 33 | mintHeight: 560039, 34 | spentHeight: -2, 35 | address: 'qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf', 36 | script: '76a914db6ea94fa26b7272dc5e1487c35f258391e0f38788ac', 37 | value: 130000, 38 | confirmations: -1 39 | } 40 | ] 41 | 42 | module.exports = { 43 | balance, 44 | utxo 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/fixtures/block-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data used for unit testing. 3 | */ 4 | 5 | module.exports = { 6 | details: { 7 | hash: '000000001c6aeec19265e9cc3ded8ba5ef5e63fae7747f30bf9c02c7bc8883f0', 8 | size: 216, 9 | height: 507, 10 | version: 1, 11 | merkleroot: 12 | 'a85fa3d831ab6b0305e7ff88d2d4941e25a810d4461635df51490653822071a8', 13 | tx: ['a85fa3d831ab6b0305e7ff88d2d4941e25a810d4461635df51490653822071a8'], 14 | time: 1231973656, 15 | nonce: 330467862, 16 | bits: '1d00ffff', 17 | difficulty: 1, 18 | chainwork: 19 | '000000000000000000000000000000000000000000000000000001fc01fc01fc', 20 | confirmations: 585104, 21 | previousblockhash: 22 | '00000000a99525c043fd7e323414b60add43c254c44860094048f9c01e9a5fdd', 23 | nextblockhash: 24 | '000000000d550f4161f2702165fdd782ec72ff9c541f864ebb8256b662b7e51a', 25 | reward: 50, 26 | isMainChain: true, 27 | poolInfo: {} 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/unit/fixtures/blockchain-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data used for unit testing. 3 | */ 4 | 5 | module.exports = { 6 | bestBlockHash: 7 | '0000000000000000008e1f65f875703872544aa888c7ca6587f055f8f5fbd4bf', 8 | 9 | blockHeader: { 10 | hash: '000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201', 11 | confirmations: 85727, 12 | height: 500000, 13 | version: 536870912, 14 | versionHex: '20000000', 15 | merkleroot: 16 | '4af279645e1b337e655ae3286fc2ca09f58eb01efa6ab27adedd1e9e6ec19091', 17 | time: 1509343584, 18 | mediantime: 1509336533, 19 | nonce: 3604508752, 20 | bits: '1809b91a', 21 | difficulty: 113081236211.4533, 22 | chainwork: 23 | '0000000000000000000000000000000000000000007ae48aca46e3b449ad9714', 24 | previousblockhash: 25 | '0000000000000000043831d6ebb013716f0580287ee5e5687e27d0ed72e6e523', 26 | nextblockhash: 27 | '00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3' 28 | }, 29 | 30 | txOutProof: 31 | '0000002086a4a3161f9ba2174883ec0b93acceac3b2f37b36ed1f90000000000000000009cb02406d1094ecf3e0b4c0ca7c585125e721147c39daf6b48c90b512741e13a12333e5cb38705180f441d8c7100000008fee9b60f1edb57e5712839186277ed39e0a004a32be9096ee47472efde8eae62f789f9d7a9f59d0ea7093dea1e0c65ff0b953f1d8cf3d47f92e732ca0295f603c272d5f4a63509f7a887f2549d78af7444aa0ecbb4f66d9cbe13bc6a89f59e05a199df8325d490818ffefe6b6321d32d7496a68580459836c0183f89082fc1b491cc91b23ecdcaa4c347bf599a62904d61f1c15b400ebbd5c90149010c139d9c1e31b774b796977393a238080ab477e1d240d0c4f155d36f519668f49bae6bd8cd5b8e40522edf76faa09cca6188d83ff13af6967cc6a569d1a5e9aeb1fdb7f531ddd2d0cbb81879741d5f38166ac1932136264366a4065cc96a42e41f96294f02df01', 32 | 33 | verifiedProof: 34 | '03f69502ca32e7927fd4f38c1d3f950bff650c1eea3d09a70e9df5a9d7f989f7', 35 | 36 | txOutUnspent: { 37 | bestblock: 38 | '000000000000000000b441e02f5b1b9f5b3def961047afcc6f2f5636c952705e', 39 | confirmations: 2, 40 | value: 0.00006, 41 | scriptPubKey: { 42 | asm: 43 | 'OP_DUP OP_HASH160 d19fae66b685f5c3633c0db0600313918347225f OP_EQUALVERIFY OP_CHECKSIG', 44 | hex: '76a914d19fae66b685f5c3633c0db0600313918347225f88ac', 45 | reqSigs: 1, 46 | type: 'pubkeyhash', 47 | addresses: ['bitcoincash:qrgeltnxk6zltsmr8sxmqcqrzwgcx3eztusrwgf0x3'] 48 | }, 49 | coinbase: false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/unit/fixtures/blockchain.json: -------------------------------------------------------------------------------- 1 | { 2 | "getBestBlockHash": [ 3 | { "data": 4 | { "result": "0000000000000000005f1f550d3d8b142b684277016ebd00fa29c668606ae52d"} 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/fixtures/crypto.json: -------------------------------------------------------------------------------- 1 | { 2 | "sha256": [ 3 | { 4 | "hash": "04abc8821a06e5a30937967d11ad10221cb5ac3b5273e434f1284ee87129a061", 5 | "hex": "0101010101010101" 6 | }, 7 | { 8 | "hash": "75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab", 9 | "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" 10 | }, 11 | { 12 | "hash": "978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de", 13 | "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" 14 | }, 15 | { 16 | "hash": "243e5ec9798d6ac435b30661528d8d83745543f517b8adac421a76fbe7f08105", 17 | "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" 18 | }, 19 | { 20 | "hash": "1304a815512047082dba9a9414ff0db643dbbf9eeb68527195f79cc26b021e10", 21 | "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" 22 | } 23 | ], 24 | "ripemd160": [ 25 | { 26 | "hash": "5825701b4b9767fd35063b286dca3582853e0630", 27 | "hex": "0101010101010101" 28 | }, 29 | { 30 | "hash": "8874ef888a9bcbd83b87d06ff7bc213c51497362", 31 | "hex": "75618d82d1f6251f2ef1f42f5f0d5040330948a707ff6d69720dbdcb00b48aab" 32 | }, 33 | { 34 | "hash": "5f956a88863051ea5215d8970ced8e218eb615cf", 35 | "hex": "978c09dd46091d1922fa01e9f4a975b91a371f26ba8399de27d53801152121de" 36 | }, 37 | { 38 | "hash": "1fc790f399d3064cdf917c6b22bb0ef534fe35c9", 39 | "hex": "243e5ec9798d6ac435b30661528d8d83745543f517b8adac421a76fbe7f08105" 40 | }, 41 | { 42 | "hash": "abd7091d8c4bd1e2c074f125870427f3dc0c0ce9", 43 | "hex": "1304a815512047082dba9a9414ff0db643dbbf9eeb68527195f79cc26b021e10" 44 | } 45 | ], 46 | "hash256": [ 47 | { 48 | "hash": "728338d99f356175c4945ef5cccfa61b7b56143cbbf426ddd0e0fc7cfe8c3c23", 49 | "hex": "0101010101010101" 50 | }, 51 | { 52 | "hash": "7ad2a74bd59698714a2991a82b71736f3542b2828b6ac24de427c440da89d01a", 53 | "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" 54 | }, 55 | { 56 | "hash": "688f1d029ed54c34d0320b838bf6fc64f62f38a6e930a0af5bdb4e27d1a684cd", 57 | "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" 58 | }, 59 | { 60 | "hash": "46f3c4ddfa908cf8a3cda8ce3a676d98fec149d97db834a6e8f071790d839c52", 61 | "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" 62 | }, 63 | { 64 | "hash": "72cfe7b6b9402a4463dfc8bc1c08080578b1b82a2e3e632062e2cce2c9327c1f", 65 | "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" 66 | } 67 | ], 68 | "hash160": [ 69 | { 70 | "hash": "abaf1119f83e384210fe8e222eac76e2f0da39dc", 71 | "hex": "0101010101010101" 72 | }, 73 | { 74 | "hash": "8874ef888a9bcbd83b87d06ff7bc213c51497362", 75 | "hex": "031ad329b3117e1d1e2974406868e575d48cff88e8128ba0eedb10da053785033b" 76 | }, 77 | { 78 | "hash": "5f956a88863051ea5215d8970ced8e218eb615cf", 79 | "hex": "03123464075c7a5fa6b8680afa2c962a02e7bf071c6b2395b0ac711d462cac9354" 80 | }, 81 | { 82 | "hash": "1fc790f399d3064cdf917c6b22bb0ef534fe35c9", 83 | "hex": "03ea9277ebf3d6edd26847fb109494def7d458f05e6e4fba381c094ce703c62248" 84 | }, 85 | { 86 | "hash": "abd7091d8c4bd1e2c074f125870427f3dc0c0ce9", 87 | "hex": "020379254cc10ef94976192ab42cab25f65ba4438e4b2b4610debd145d2bdb8d53" 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /test/unit/fixtures/dsproof-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data used for unit testing. 3 | */ 4 | const dsproof = { 5 | dspid: '6234fff2a9dbcaa50e2c50d74e1d725a2bed1757d7a05a2db7a82df659554397', 6 | txid: 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e', 7 | outpoint: { 8 | txid: '17ebef34f7e9a0692317dd6fbb43ffccff45b7d688a2a754dbccfc5e6d93ce3e', 9 | vout: 1 10 | }, 11 | path: ['ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'], 12 | descendants: [ 13 | 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e' 14 | ] 15 | } 16 | 17 | module.exports = { 18 | dsproof 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/fixtures/encryption-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data used for unit tests against the Encryption library. 3 | */ 4 | 5 | const successMock = { 6 | success: true, 7 | publicKey: 8 | '03fcc37586d93af1e146238217989924e0ab1f011c34e1a23aec529354d5b28eb4' 9 | } 10 | 11 | const failureMock = { 12 | success: false, 13 | publicKey: 'not found' 14 | } 15 | 16 | module.exports = { 17 | successMock, 18 | failureMock 19 | } 20 | -------------------------------------------------------------------------------- /test/unit/fixtures/ipfs-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mock data for IPFS unit tests. 3 | */ 4 | 5 | const uploadData = { 6 | successful: [ 7 | { 8 | source: 'Local', 9 | id: 'uppy-ipfs/js-1e-application/octet-stream', 10 | name: 'ipfs.js', 11 | extension: 'js', 12 | meta: { 13 | test: 'avatar', 14 | name: 'ipfs.js', 15 | type: 'application/octet-stream', 16 | fileModelId: '5ec562319bfacc745e8d8a52' 17 | }, 18 | type: 'application/octet-stream', 19 | progress: { 20 | uploadStarted: 1589596706524, 21 | uploadComplete: true, 22 | percentage: 100, 23 | bytesUploaded: 2374, 24 | bytesTotal: 2374 25 | }, 26 | size: null, 27 | isRemote: false, 28 | remote: '', 29 | tus: { 30 | uploadUrl: 31 | 'http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33' 32 | }, 33 | response: { 34 | uploadURL: 35 | 'http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33' 36 | }, 37 | uploadURL: 38 | 'http://localhost:5001/uppy-files/6acd261f494c3375dc522e27880efe33', 39 | isPaused: false 40 | } 41 | ], 42 | failed: [], 43 | uploadID: 'cka90u4m300000fi0cldg6lrf' 44 | } 45 | 46 | const paymentInfo = { 47 | success: true, 48 | hostingCostBCH: 0.00004201, 49 | hostingCostUSD: 0.01, 50 | file: { 51 | payloadLink: '', 52 | hasBeenPaid: false, 53 | _id: '5ebf5d04cba8b038394e0d62', 54 | schemaVersion: 1, 55 | size: 2374, 56 | fileId: 'uppy-ipfs/js-1e-application/octet-stream', 57 | fileName: 'ipfs.js', 58 | fileExtension: 'js', 59 | createdTimestamp: '1589599492.952', 60 | hostingCost: 4201, 61 | walletIndex: 36, 62 | bchAddr: 'bchtest:qqymyk4zfvnw8rh6hcvl922ewudkyrn9jvk6gtuae6', 63 | __v: 0 64 | } 65 | } 66 | 67 | const mockNewFileModel = { 68 | success: true, 69 | hostingCostBCH: 0.00004197, 70 | hostingCostUSD: 0.01, 71 | file: { 72 | payloadLink: '', 73 | hasBeenPaid: false, 74 | _id: '5ec562319bfacc745e8d8a52', 75 | schemaVersion: 1, 76 | size: 4458, 77 | fileName: 'ipfs.js', 78 | fileExtension: 'js', 79 | createdTimestamp: '1589994033.655', 80 | hostingCost: 4196, 81 | walletIndex: 49, 82 | bchAddr: 'bchtest:qzrpkevu7h2ayfa4rjx08r5elvpfu72dg567x3mh3c', 83 | __v: 0 84 | } 85 | } 86 | 87 | const unpaidFileData = { 88 | file: { 89 | payloadLink: '', 90 | hasBeenPaid: false, 91 | _id: '5ec7392c2acfe57aa62e945a', 92 | schemaVersion: 1, 93 | size: 726, 94 | fileName: 'ipfs-e2e.js', 95 | fileExtension: 'js', 96 | createdTimestamp: '1590114604.986', 97 | hostingCost: 4403, 98 | walletIndex: 56, 99 | bchAddr: 'bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk', 100 | __v: 0 101 | } 102 | } 103 | 104 | const paidFileData = { 105 | file: { 106 | payloadLink: 'QmRDHPhY5hCNVRMVQvS2H9uty8P1skdwgLaHpUAkEvsjcE', 107 | hasBeenPaid: true, 108 | _id: '5ec7392c2acfe57aa62e945a', 109 | schemaVersion: 1, 110 | size: 726, 111 | fileName: 'ipfs-e2e.js', 112 | fileExtension: 'js', 113 | createdTimestamp: '1590114604.986', 114 | hostingCost: 4403, 115 | walletIndex: 56, 116 | bchAddr: 'bchtest:qz5z82u0suqh80x5tfx4ht8kdrkkw664vcy44uz0wk', 117 | __v: 0 118 | } 119 | } 120 | 121 | module.exports = { 122 | uploadData, 123 | paymentInfo, 124 | mockNewFileModel, 125 | unpaidFileData, 126 | paidFileData 127 | } 128 | -------------------------------------------------------------------------------- /test/unit/fixtures/openbazaar-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Unit test mocks for OpenBazaar endpoints. 3 | */ 4 | 5 | const balance = { 6 | page: 1, 7 | totalPages: 1, 8 | itemsOnPage: 1000, 9 | addrStr: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9', 10 | balance: '0.00001', 11 | totalReceived: '0.00001', 12 | totalSent: '0', 13 | unconfirmedBalance: '0', 14 | unconfirmedTxApperances: 0, 15 | txApperances: 1, 16 | transactions: [ 17 | '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7' 18 | ] 19 | } 20 | 21 | const utxo = [ 22 | { 23 | txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', 24 | vout: 0, 25 | amount: '0.00001', 26 | satoshis: 1000, 27 | height: 602405, 28 | confirmations: 11 29 | } 30 | ] 31 | 32 | const tx = { 33 | txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7', 34 | version: 2, 35 | vin: [ 36 | { 37 | txid: '5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac', 38 | vout: 1, 39 | sequence: 4294967295, 40 | n: 0, 41 | scriptSig: { 42 | hex: 43 | '473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9' 44 | }, 45 | addresses: ['bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe'], 46 | value: '0.00047504' 47 | } 48 | ], 49 | vout: [ 50 | { 51 | value: '0.00001', 52 | n: 0, 53 | scriptPubKey: { 54 | hex: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac', 55 | addresses: ['bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'] 56 | }, 57 | spent: false 58 | }, 59 | { 60 | value: '0.00046256', 61 | n: 1, 62 | scriptPubKey: { 63 | hex: '76a9142dbf5e1804c39a497b908c876097d63210c8490288ac', 64 | addresses: ['bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e'] 65 | }, 66 | spent: false 67 | } 68 | ], 69 | blockhash: '0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5', 70 | blockheight: 602405, 71 | confirmations: 11, 72 | blocktime: 1569792892, 73 | valueOut: '0.00047256', 74 | valueIn: '0.00047504', 75 | fees: '0.00000248', 76 | hex: 77 | '0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000' 78 | } 79 | 80 | module.exports = { 81 | balance, 82 | utxo, 83 | tx 84 | } 85 | -------------------------------------------------------------------------------- /test/unit/fixtures/price-mocks.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mocks for price. 3 | */ 4 | 5 | const mockRates = { 6 | AED: '915.049218', 7 | AFN: '19144.48874646', 8 | ALGO: '826.6633482661356600405', 9 | ALL: '26221.96098759', 10 | AMD: '119977.82663822', 11 | ANG: '446.841810455', 12 | AOA: '162269.774275', 13 | ARS: '19322.704621', 14 | ATOM: '45.5920571010248851205', 15 | AUD: '353.78913716', 16 | AWG: '448.407', 17 | AZN: '424.1182875', 18 | BAL: '18.9361216125975755781', 19 | BAM: '413.75759465', 20 | BAND: '40.84187228461349099275', 21 | BAT: '1169.75444148879972951', 22 | BBD: '498.23', 23 | BCH: '1.0', 24 | BDT: '21107.92000745', 25 | BGN: '413.93197515', 26 | BHD: '93.93578597', 27 | BIF: '481540.76228735', 28 | BMD: '249.115', 29 | BND: '337.84677362', 30 | BOB: '1718.851399565', 31 | BRL: '1396.737982', 32 | BSD: '249.115', 33 | BSV: '1.555324933590611026515', 34 | BTC: '0.021265', 35 | BTN: '18249.513962505', 36 | BWP: '2848.17713393', 37 | BYN: '637.963336685', 38 | BYR: '6379633.36685000049823', 39 | BZD: '501.752236985', 40 | CAD: '328.80838319', 41 | CDF: '488848.679106575', 42 | CGLD: '121.0412516398620018305', 43 | CHF: '226.8989243', 44 | CLF: '7.110489445', 45 | CLP: '196202.978234955', 46 | CNH: '1664.25510705', 47 | CNY: '1666.429881', 48 | COMP: '2.4706436576415750709', 49 | COP: '958730.858397465', 50 | CRC: '150173.845981', 51 | CUC: '248.997666835', 52 | CVE: '23541.3675', 53 | CZK: '5770.848621', 54 | DAI: '247.0463573269230002455', 55 | DASH: '3.327367317363110236645', 56 | DJF: '44315.31795969', 57 | DKK: '1575.7370741', 58 | DOP: '14529.76988648', 59 | DZD: '32107.28592277', 60 | EEK: '3640.489633465', 61 | EGP: '3911.404438', 62 | EOS: '96.6873665825732601419', 63 | ERN: '3736.727740265', 64 | ETB: '9302.454572035', 65 | ETC: '44.91795888928957612395', 66 | ETH: '0.6574429621419051422355', 67 | EUR: '211.595', 68 | FJD: '533.11855575', 69 | FKP: '192.55642863', 70 | GBP: '192.87', 71 | GEL: '804.64145', 72 | GGP: '192.55642863', 73 | GHS: '1448.776859925', 74 | GIP: '192.55642863', 75 | GMD: '12891.70125', 76 | GNF: '2438630.43574976', 77 | GTQ: '1936.689014855', 78 | GYD: '52047.88891278', 79 | HKD: '1930.67861725', 80 | HNL: '6126.43736492', 81 | HRK: '1605.40119007', 82 | HTG: '15682.48428085', 83 | HUF: '77261.419177275', 84 | IDR: '3666848.2425', 85 | ILS: '843.29662455', 86 | IMP: '192.55642863', 87 | INR: '18275.43761675', 88 | IQD: '297176.65680577', 89 | ISK: '34639.44075', 90 | JEP: '192.55642863', 91 | JMD: '36261.061070375', 92 | JOD: '176.622535', 93 | JPY: '26304.9247525', 94 | KES: '27093.7474', 95 | KGS: '20240.23303148', 96 | KHR: '1021526.66327067', 97 | KMF: '104678.164103975', 98 | KNC: '275.5392102643512742545', 99 | KRW: '284115.6575', 100 | KWD: '76.196555935', 101 | KYD: '207.44852333', 102 | KZT: '106578.169689505', 103 | LAK: '2300372.70137523', 104 | LBP: '376426.60931701', 105 | LINK: '22.8704181013371177476', 106 | LKR: '45903.840861165', 107 | LRC: '1428.4116972477063306', 108 | LRD: '48689.521020355', 109 | LSL: '4093.68786226', 110 | LTC: '5.182877353583689269445', 111 | LTL: '803.357262175', 112 | LVL: '163.484459015', 113 | LYD: '340.470203685', 114 | MAD: '2290.86403115', 115 | MDL: '4231.74490411', 116 | MGA: '978918.42727237', 117 | MKD: '13034.71219274', 118 | MKR: '0.43727565410331105846', 119 | MMK: '321367.16219401', 120 | MNT: '706980.17312004', 121 | MOP: '1987.157222705', 122 | MRO: '88934.055', 123 | MTL: '170.32939187', 124 | MUR: '9942.18014823', 125 | MVR: '3836.371', 126 | MWK: '187533.350248305', 127 | MXN: '5285.574344805', 128 | MYR: '1033.2044625', 129 | MZN: '18168.45667469', 130 | NAD: '4120.3621', 131 | NGN: '95465.8503', 132 | NIO: '8675.07065117', 133 | NMR: '8.6886689628111785348', 134 | NOK: '2328.95371465', 135 | NPR: '29198.52701022', 136 | NZD: '378.934057915', 137 | OMG: '74.27621574882972595255', 138 | OMR: '95.913509955', 139 | OXT: '1023.479868529169993565', 140 | PAB: '249.115', 141 | PEN: '892.41413087', 142 | PGK: '870.976539545', 143 | PHP: '12100.979598855', 144 | PKR: '40413.26181118', 145 | PLN: '968.71705891', 146 | PYG: '1750259.579863715', 147 | QAR: '907.08999375', 148 | REN: '758.802924154736515935', 149 | REP: '18.2168190127970744169', 150 | REPV2: '18.225711080673967324', 151 | RON: '1032.33256', 152 | RSD: '24895.307525', 153 | RUB: '19351.4774035', 154 | RWF: '243426.179717085', 155 | SAR: '934.309544225', 156 | SBD: '2013.0584566', 157 | SCR: '4561.388071665', 158 | SEK: '2201.10291435', 159 | SGD: '338.356712025', 160 | SHP: '192.55642863', 161 | SLL: '2482430.971263275', 162 | SOS: '144000.839307095', 163 | SRD: '3525.97371', 164 | SSP: '32449.7199', 165 | STD: '5232524.31108792', 166 | SVC: '2178.147216215', 167 | SZL: '4091.741526765', 168 | THB: '7785.4665375', 169 | TJS: '2568.96954016', 170 | TMT: '871.9025', 171 | TND: '686.00043125', 172 | TOP: '578.0165522', 173 | TRY: '1963.587954325', 174 | TTD: '1689.460313635', 175 | TWD: '7158.9423125', 176 | TZS: '577510.41827928', 177 | UAH: '7061.44019619', 178 | UGX: '931508.2111739', 179 | UMA: '30.09543944427665349095', 180 | UNI: '79.18593747516647884725', 181 | USD: '249.115', 182 | USDC: '249.115', 183 | UYU: '10688.478117885', 184 | UZS: '2582693.606121005', 185 | VEF: '61901998.996866715', 186 | VES: '112695476.713406465', 187 | VND: '5773814.12282902', 188 | VUV: '28486.73470656', 189 | WST: '653.35042289', 190 | XAF: '138884.3079243', 191 | XAG: '10.22661168355', 192 | XAU: '0.1312935696', 193 | XCD: '673.24574325', 194 | XDR: '176.50246157', 195 | XLM: '2936.20532162536435083', 196 | XOF: '138884.3079243', 197 | XPD: '0.1061030608', 198 | XPF: '25265.84267069', 199 | XPT: '0.2910211253', 200 | XRP: '1014.931757995518373565', 201 | XTZ: '113.8135051169590628124', 202 | YER: '62365.930534515', 203 | YFI: '0.0178703370267443543872', 204 | ZAR: '4122.31765275', 205 | ZEC: '3.87305659203980154283', 206 | ZMK: '1308619.842149325', 207 | ZMW: '5027.95231667', 208 | ZRX: '644.844402797695193656', 209 | ZWL: '80215.03' 210 | } 211 | 212 | module.exports = { 213 | mockRates 214 | } 215 | -------------------------------------------------------------------------------- /test/unit/fixtures/rawtransaction-mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | Mocking data for the RawTransactions unit tests. 3 | */ 4 | 5 | const mockTx = { 6 | txid: '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3', 7 | vin: [ 8 | { 9 | txid: '8a6b3b70569270f0bdbf68fd12a410aef8f7bf044bc88ab02386a1572024b2bd', 10 | vout: 0, 11 | scriptSig: { 12 | asm: 13 | '3044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba0[ALL|FORKID] 030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9', 14 | hex: 15 | '473044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba04121030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9' 16 | }, 17 | sequence: 4294967295 18 | } 19 | ] 20 | } 21 | 22 | const mockParentTx1 = { 23 | vout: [ 24 | { 25 | value: 0.04199959, 26 | n: 0, 27 | scriptPubKey: { 28 | asm: 29 | 'OP_DUP OP_HASH160 d5258a73b5c8f94c939d7fe96f78ce97906083be OP_EQUALVERIFY OP_CHECKSIG', 30 | hex: '76a914d5258a73b5c8f94c939d7fe96f78ce97906083be88ac', 31 | reqSigs: 1, 32 | type: 'pubkeyhash', 33 | addresses: ['bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt'] 34 | } 35 | }, 36 | { 37 | value: 1.66296161, 38 | n: 1, 39 | scriptPubKey: { 40 | asm: 41 | 'OP_DUP OP_HASH160 5e95c308c25c74c64c5ffe44a60a4d9b35743e90 OP_EQUALVERIFY OP_CHECKSIG', 42 | hex: '76a9145e95c308c25c74c64c5ffe44a60a4d9b35743e9088ac', 43 | reqSigs: 1, 44 | type: 'pubkeyhash', 45 | addresses: ['bitcoincash:qp0ftscgcfw8f3jvtllyffs2fkdn2ap7jqreakn4ye'] 46 | } 47 | } 48 | ] 49 | } 50 | 51 | const mockGetInputAddrsOutput = [ 52 | { 53 | vin: 0, 54 | address: 'bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt' 55 | } 56 | ] 57 | 58 | module.exports = { 59 | mockTx, 60 | mockParentTx1, 61 | mockGetInputAddrsOutput 62 | } 63 | -------------------------------------------------------------------------------- /test/unit/fixtures/slp/address.json: -------------------------------------------------------------------------------- 1 | { 2 | "mainnet": { 3 | "legacyP2PKH": [ 4 | "18xHZ8g2feo4ceejGpvzHkvXT79fi2ZdTG", 5 | "1K7Qb1dWkiYwPrZhLws3uUKhxKEU7dRnbQ", 6 | "1Au7gWXS2zAgFSdWwT5QnTNeasye16kxoG", 7 | "174uecjfRgh1XkVBYFzZymkV1JwmFQ91s9", 8 | "1KLXUPMdq4vaU3VoQzSvLUx2mub5qzFkTc" 9 | ], 10 | "legacyP2SH": [ 11 | "3DA6RBcFgLwLTpnF6BRAee8w6a9H6JQLCm", 12 | "3AbtU1JSaiQijyGT21stxpBRZj1hixWcGB", 13 | "3KfgmLeczB525pV2tJLQ6RM5qFMLaB2Kn1", 14 | "3Bsr5dvAJ2Q8CpHxJSZkNdgD12Tkb9TaR7", 15 | "3J78iYD4i4ht8Btp8pyRx71jg5yrRTkQaM" 16 | ], 17 | "cashAddressP2PKH": [ 18 | "bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh", 19 | "bitcoincash:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsn9h5vuvr", 20 | "bitcoincash:qpkfg4kck99wksyss6nvaqtafeahfnyrpsj0ed372t", 21 | "bitcoincash:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvy0c2jvx7", 22 | "bitcoincash:qryj8x4s7vfsc864jm0xaak9qfe8qgk245y9ska57l" 23 | ], 24 | "cashAddressP2SH": [ 25 | "bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug", 26 | "bitcoincash:ppsup5akyvql5w46q9eszd9fxpx970acpyqkw79vq2", 27 | "bitcoincash:prznrkhnf6zqsap6l664ayzu2xue67ue4gv686sjyu", 28 | "bitcoincash:pphmm80pznl6pnkmzakz3ahafydmvhwzcslea4v5mz", 29 | "bitcoincash:pz6pr25g6mulp0kes9xnmsda0u4rf442ase2un89pl" 30 | ], 31 | "slpAddressP2PKH": [ 32 | "simpleledger:qptnmya5wkly7xf97wm5ak23yqdsz3l2cy79a7ey6f", 33 | "simpleledger:qrr2suh9yjsrkl2qp3p967uhfg6u0r6xxsl7u0euja", 34 | "simpleledger:qpkfg4kck99wksyss6nvaqtafeahfnyrps75jky754", 35 | "simpleledger:qppgmuuwy07g0x39sx2z0x2u8e34tvfdxvg5n38vcq", 36 | "simpleledger:qryj8x4s7vfsc864jm0xaak9qfe8qgk245g7mdg5qp" 37 | ], 38 | "slpAddressP2SH": [ 39 | "simpleledger:pp7ushdxf5we8mcpaa3wqgsuqt639cu59u00d8pfzk", 40 | "simpleledger:ppsup5akyvql5w46q9eszd9fxpx970acpyvd99sv75", 41 | "simpleledger:prznrkhnf6zqsap6l664ayzu2xue67ue4gqpvp9j6z", 42 | "simpleledger:pphmm80pznl6pnkmzakz3ahafydmvhwzcsnzkwe59u", 43 | "simpleledger:pz6pr25g6mulp0kes9xnmsda0u4rf442as43hgj9lp" 44 | ], 45 | "slpTxids": [ 46 | "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb" 47 | ] 48 | }, 49 | "testnet": { 50 | "legacyP2PKH": [ 51 | "mhTg9sgNgvAGfmJs192oUzQWqAXHH5nqLE", 52 | "mmpjQ24UyGGfJ39k44prH6W2A7y1qVaQti", 53 | "muEnSveRwu8cEJSvXzDTJZQWrr7y9i331i", 54 | "n3jZn7rb3Bjepap4TWo8pyqcwuxAw439HW", 55 | "mqHUen3SUNjQkAPjMxZfSnuyd8cmcbL6fq" 56 | ], 57 | "legacyP2SH": [ 58 | "2N7euSnwuJP93QmK5TD4AkWmaTfBbBLrxUs", 59 | "2N38BPRHX4ejA7QJEfYGikw2PM7QRaVnFg3", 60 | "2N46fAZgsFSwKxM5reRdQCmmtAXaotVqakP", 61 | "2MzFSj4mQM3bCFYEKmhxSecuoPA3aziudov", 62 | "2N4Y11CqifLKEDfCFRHa6Dt36X9BKCP2K2o" 63 | ], 64 | "cashAddressP2PKH": [ 65 | "bchtest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u22nzt946", 66 | "bchtest:qpzj67wmlsq8uttddddapjjawyusureca59ug9cak8", 67 | "bchtest:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5m6kyejfd", 68 | "bchtest:qrem2cg43ksmvlampheur8gfgdhhk57mygy26y7f2e", 69 | "bchtest:qp4jf3n740kffkladul96xnq5dtrflg4x5w5rfy22r" 70 | ], 71 | "cashAddressP2SH": [ 72 | "bchtest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqh2hmtpyr", 73 | "bchtest:ppk9ctxq9feg56vmwyznlgqs8sk6astknq75a27y9c", 74 | "bchtest:ppms42pganhr9fqnkfz7xnwcj7emxng3u5526prj59", 75 | "bchtest:ppxd8qmgse5h2gy03vf7swxg4rrfpxckx5mnszkjw8", 76 | "bchtest:ppaat8w24kdu74782fu8jr27k42dwhem45ycrhxgae" 77 | ], 78 | "slpAddressP2PKH": [ 79 | "slptest:qq24rpar9qas3vc9r8d4p0prhwaf7jmx2u375e3j88", 80 | "slptest:qpzj67wmlsq8uttddddapjjawyusureca57g07z2y6", 81 | "slptest:qztg9c4u3ldhg68mqgzrple6ae92hwnfe5qw3lr9ms", 82 | "slptest:qrem2cg43ksmvlampheur8gfgdhhk57mygl7aly7cy", 83 | "slptest:qp4jf3n740kffkladul96xnq5dtrflg4x54qyj7ac7" 84 | ], 85 | "slpAddressP2SH": [ 86 | "slptest:pz0qcslrqn7hr44hsszwl4lw5r6udkg6zqv7sq3kk7", 87 | "slptest:ppk9ctxq9feg56vmwyznlgqs8sk6astknq9q63ynh9", 88 | "slptest:ppms42pganhr9fqnkfz7xnwcj7emxng3u507a6e9xc", 89 | "slptest:ppxd8qmgse5h2gy03vf7swxg4rrfpxckx5q8hev9u6", 90 | "slptest:ppaat8w24kdu74782fu8jr27k42dwhem45lvyvul0y" 91 | ] 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /test/unit/fixtures/slp/ecpair.json: -------------------------------------------------------------------------------- 1 | { 2 | "wif": [ 3 | "cUCSrdhu7mCzx4sWqL6irqzprkofxPmLHYgkSnG2WaWVqJDXtWRS", 4 | "cNVP2nTzUMFerfpjrTuDgoFGnKAfjZznKomknUVKQSdFHqK5cRc5" 5 | ], 6 | "address": [ 7 | "slptest:qq835u5srlcqwrtwt6xm4efwan30fxg9hcqag6fk03", 8 | "slptest:qrj9k49drcsk4al8wxn53hnkfvts6ew5jvv32952nh" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/unit/generating.js: -------------------------------------------------------------------------------- 1 | // Public npm libraries 2 | const assert = require('assert') 3 | const axios = require('axios') 4 | const sinon = require('sinon') 5 | 6 | // Unit under test (uut) 7 | const BCHJS = require('../../src/bch-js') 8 | // const bchjs = new BCHJS() 9 | let bchjs 10 | 11 | describe('#Generating', () => { 12 | beforeEach(() => { 13 | bchjs = new BCHJS() 14 | }) 15 | 16 | describe('#generateToAddress', () => { 17 | let sandbox 18 | beforeEach(() => (sandbox = sinon.createSandbox())) 19 | afterEach(() => sandbox.restore()) 20 | 21 | it('should generate', done => { 22 | const data = [] 23 | const resolved = new Promise(resolve => resolve({ data: data })) 24 | sandbox.stub(axios, 'post').returns(resolved) 25 | 26 | bchjs.Generating.generateToAddress( 27 | 1, 28 | 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf' 29 | ) 30 | .then(result => { 31 | assert.deepStrictEqual(data, result) 32 | }) 33 | .then(done, done) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /test/unit/mining.js: -------------------------------------------------------------------------------- 1 | // Public npm libraries 2 | const assert = require('assert') 3 | const axios = require('axios') 4 | const sinon = require('sinon') 5 | 6 | // Unit under test (uut) 7 | const BCHJS = require('../../src/bch-js') 8 | let bchjs 9 | 10 | describe('#Mining', () => { 11 | beforeEach(() => { 12 | bchjs = new BCHJS() 13 | }) 14 | 15 | describe('#getBlockTemplate', () => { 16 | let sandbox 17 | beforeEach(() => (sandbox = sinon.createSandbox())) 18 | afterEach(() => sandbox.restore()) 19 | 20 | it('should get block template', done => { 21 | const data = { 22 | data: 23 | '01000000017f6305e3b0b05f5b57a82f4e6d4187e148bbe56a947208390e488bad36472368000000006a47304402203b0079ff5b896187feb02e2679c87ac2fb8d483b60e0721ed33601e2c0eecc700220590f8a0e1a51b53b368294861fd5fc99db3a6607d0f4e543f6217108e208c1834121024c93c841d7f576584ffbf513b7abd8283e6562669905f6554f788fce4cc67a34ffffffff0228100000000000001976a914af78709a76abc8a28e568c9210c8247dd10cff2c88ac22020000000000001976a914f339927678803f451b41400737e7dc83c6a8682188ac00000000', 24 | txid: 25 | '7f462d71c649a0d8cfbaa2d20d8ff86677966b308f0ac9906ee015bf4453f97a', 26 | hash: 27 | '7f462d71c649a0d8cfbaa2d20d8ff86677966b308f0ac9906ee015bf4453f97a', 28 | depends: [], 29 | fee: 226, 30 | sigops: 2 31 | } 32 | 33 | const resolved = new Promise(resolve => resolve({ data: data })) 34 | sandbox.stub(axios, 'get').returns(resolved) 35 | 36 | bchjs.Mining.getBlockTemplate('') 37 | .then(result => { 38 | assert.deepStrictEqual(data, result) 39 | }) 40 | .then(done, done) 41 | }) 42 | }) 43 | 44 | describe('#getMiningInfo', () => { 45 | let sandbox 46 | beforeEach(() => (sandbox = sinon.createSandbox())) 47 | afterEach(() => sandbox.restore()) 48 | 49 | it('should get mining info', done => { 50 | const data = { 51 | blocks: 527816, 52 | currentblocksize: 89408, 53 | currentblocktx: 156, 54 | difficulty: 568757800682.7649, 55 | blockprioritypercentage: 5, 56 | errors: '', 57 | networkhashps: 4347259225696976000, 58 | pooledtx: 184, 59 | chain: 'main' 60 | } 61 | 62 | const resolved = new Promise(resolve => resolve({ data: data })) 63 | sandbox.stub(axios, 'get').returns(resolved) 64 | 65 | bchjs.Mining.getMiningInfo() 66 | .then(result => { 67 | assert.deepStrictEqual(data, result) 68 | }) 69 | .then(done, done) 70 | }) 71 | }) 72 | 73 | describe('#getNetworkHashps', () => { 74 | let sandbox 75 | beforeEach(() => (sandbox = sinon.createSandbox())) 76 | afterEach(() => sandbox.restore()) 77 | 78 | it('should get network hashps', done => { 79 | const data = 3586365937646890000 80 | 81 | const resolved = new Promise(resolve => resolve({ data: data })) 82 | sandbox.stub(axios, 'get').returns(resolved) 83 | 84 | bchjs.Mining.getNetworkHashps() 85 | .then(result => { 86 | assert.strictEqual(data, result) 87 | }) 88 | .then(done, done) 89 | }) 90 | }) 91 | 92 | describe('#submitBlock', () => { 93 | // TODO finish 94 | let sandbox 95 | beforeEach(() => (sandbox = sinon.createSandbox())) 96 | afterEach(() => sandbox.restore()) 97 | 98 | it('should TODO', done => { 99 | const data = {} 100 | 101 | const resolved = new Promise(resolve => resolve({ data: data })) 102 | sandbox.stub(axios, 'post').returns(resolved) 103 | 104 | bchjs.Mining.submitBlock() 105 | .then(result => { 106 | assert.deepStrictEqual(data, result) 107 | }) 108 | .then(done, done) 109 | }) 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /test/unit/price.js: -------------------------------------------------------------------------------- 1 | const assert = require('chai').assert 2 | const BCHJS = require('../../src/bch-js') 3 | const sinon = require('sinon') 4 | 5 | const mockDataLib = require('./fixtures/price-mocks') 6 | let mockData 7 | 8 | describe('#price', () => { 9 | let sandbox 10 | let bchjs 11 | 12 | beforeEach(() => { 13 | sandbox = sinon.createSandbox() 14 | 15 | bchjs = new BCHJS() 16 | 17 | mockData = Object.assign({}, mockDataLib) 18 | }) 19 | 20 | afterEach(() => sandbox.restore()) 21 | 22 | // describe('#current', () => { 23 | // it('should get current price for single currency', async () => { 24 | // sandbox 25 | // .stub(bchjs.Price.axios, 'get') 26 | // .resolves({ data: { price: 24905 } }) 27 | // 28 | // const result = await bchjs.Price.current('usd') 29 | // // console.log(result) 30 | // 31 | // assert.isNumber(result) 32 | // }) 33 | // }) 34 | 35 | describe('#getUsd', () => { 36 | it('should get the USD price of BCH', async () => { 37 | sandbox.stub(bchjs.Price.axios, 'get').resolves({ data: { usd: 249.87 } }) 38 | 39 | const result = await bchjs.Price.getUsd() 40 | // console.log(result) 41 | 42 | assert.isNumber(result) 43 | }) 44 | }) 45 | 46 | describe('#rates', () => { 47 | it('should get the price of BCH in several currencies', async () => { 48 | sandbox 49 | .stub(bchjs.Price.axios, 'get') 50 | .resolves({ data: mockData.mockRates }) 51 | 52 | const result = await bchjs.Price.rates() 53 | // console.log(result) 54 | 55 | assert.property(result, 'USD') 56 | assert.property(result, 'CAD') 57 | }) 58 | }) 59 | 60 | describe('#getBchaUsd', () => { 61 | it('should get the USD price of BCHA', async () => { 62 | sandbox.stub(bchjs.Price.axios, 'get').resolves({ data: { usd: 18.87 } }) 63 | 64 | const result = await bchjs.Price.getBchaUsd() 65 | // console.log(result) 66 | 67 | assert.isNumber(result) 68 | }) 69 | }) 70 | describe('#getBchUsd', () => { 71 | it('should get the USD price of BCH', async () => { 72 | sandbox.stub(bchjs.Price.axios, 'get').resolves({ data: { usd: 510.39 } }) 73 | 74 | const result = await bchjs.Price.getBchUsd() 75 | // console.log(result) 76 | 77 | assert.isNumber(result) 78 | }) 79 | it('should handle error', async () => { 80 | try { 81 | sandbox.stub(bchjs.Price.axios, 'get').throws(new Error('test error')) 82 | 83 | await bchjs.Price.getBchUsd() 84 | // console.log(result) 85 | 86 | assert.equal(false, true, 'unexpected error') 87 | } catch (err) { 88 | assert.include(err.message, 'test error') 89 | } 90 | }) 91 | }) 92 | }) 93 | -------------------------------------------------------------------------------- /test/unit/slp-ecpair.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | 3 | const fixtures = require('./fixtures/slp/ecpair.json') 4 | 5 | const BCHJS = require('../../src/bch-js') 6 | let slp 7 | 8 | // const SLP = require("../../src/slp/slp") 9 | // const slp = new SLP({ restURL: "http://fakeurl.com/" }) 10 | 11 | describe('#SLP ECPair', () => { 12 | beforeEach(() => { 13 | const bchjs = new BCHJS() 14 | slp = bchjs.SLP 15 | }) 16 | 17 | describe('#toSLPAddress', () => { 18 | it('should return slp address for ecpair', async () => { 19 | fixtures.wif.forEach((wif, index) => { 20 | const ecpair = slp.ECPair.fromWIF(wif) 21 | const slpAddr = slp.ECPair.toSLPAddress(ecpair) 22 | assert.equal(slpAddr, fixtures.address[index]) 23 | }) 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test/unit/transaction-unit.js: -------------------------------------------------------------------------------- 1 | /* 2 | Unit tests for the transaction.js library. 3 | */ 4 | 5 | // Public npm libraries 6 | const assert = require('chai').assert 7 | const sinon = require('sinon') 8 | // const cloneDeep = require('lodash.clonedeep') 9 | 10 | const BCHJS = require('../../src/bch-js') 11 | const bchjs = new BCHJS() 12 | 13 | // const mockDataLib = require('./fixtures/transaction-mock.js') 14 | 15 | describe('#TransactionLib', () => { 16 | let sandbox 17 | 18 | beforeEach(() => { 19 | sandbox = sinon.createSandbox() 20 | 21 | // mockData = cloneDeep(mockDataLib) 22 | }) 23 | afterEach(() => sandbox.restore()) 24 | 25 | describe('#get', () => { 26 | it('should proxy psf-slp-indexer', async () => { 27 | // console.log('bchjs.Transaction: ', bchjs.Transaction) 28 | sandbox.stub(bchjs.Transaction.psfSlpIndexer, 'tx').resolves('test data') 29 | 30 | const result = await bchjs.Transaction.get() 31 | 32 | assert.equal(result, 'test data') 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /test/unit/util.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const assert2 = require('chai').assert 3 | const axios = require('axios') 4 | const BCHJS = require('../../src/bch-js') 5 | const bchjs = new BCHJS() 6 | const sinon = require('sinon') 7 | 8 | describe('#Util', () => { 9 | describe('#validateAddress', () => { 10 | let sandbox 11 | beforeEach(() => (sandbox = sinon.createSandbox())) 12 | afterEach(() => sandbox.restore()) 13 | 14 | it('should validate address', done => { 15 | const data = { 16 | isvalid: true, 17 | address: 'bitcoincash:qpz7qtkuyhrsz4qmnnrvf8gz9zd0u9v7eqsewyk4w5', 18 | scriptPubKey: '76a91445e02edc25c701541b9cc6c49d02289afe159ec888ac', 19 | ismine: false, 20 | iswatchonly: false, 21 | isscript: false 22 | } 23 | 24 | const resolved = new Promise(resolve => resolve({ data: data })) 25 | sandbox.stub(axios, 'get').returns(resolved) 26 | 27 | bchjs.Util.validateAddress( 28 | 'bitcoincash:qpz7qtkuyhrsz4qmnnrvf8gz9zd0u9v7eqsewyk4w5' 29 | ) 30 | .then(result => { 31 | assert.deepStrictEqual(data, result) 32 | }) 33 | .then(done, done) 34 | }) 35 | }) 36 | 37 | describe('#floor8', () => { 38 | it('should floor a number to 8 decimals', () => { 39 | const num = 1.234567891111 40 | 41 | const result = bchjs.Util.floor8(num) 42 | // console.log(result) 43 | 44 | assert2.equal(result, 1.23456789) 45 | }) 46 | 47 | it('should throw an error for non-number input', () => { 48 | try { 49 | const num = 'string' 50 | 51 | bchjs.Util.floor8(num) 52 | 53 | assert2.equal(true, false, 'Unexpected result') 54 | } catch (err) { 55 | // console.log(err) 56 | assert2.include(err.message, 'input must be a number') 57 | } 58 | }) 59 | 60 | it('should not effect a number with less than 8 decimals', () => { 61 | const num = 1.23 62 | 63 | const result = bchjs.Util.floor8(num) 64 | // console.log(result) 65 | 66 | assert2.equal(result, 1.23) 67 | }) 68 | }) 69 | 70 | describe('#floor2', () => { 71 | it('should round a number to 2 decimals', () => { 72 | const num = 1.234567891111 73 | 74 | const result = bchjs.Util.floor2(num) 75 | // console.log(result) 76 | 77 | assert2.equal(result, 1.23) 78 | }) 79 | 80 | it('should throw an error for non-number input', () => { 81 | try { 82 | const num = 'string' 83 | 84 | bchjs.Util.floor2(num) 85 | 86 | assert2.equal(true, false, 'Unexpected result') 87 | } catch (err) { 88 | // console.log(err) 89 | assert2.include(err.message, 'input must be a number') 90 | } 91 | }) 92 | 93 | it('should not effect a number with less than 8 decimals', () => { 94 | const num = 1.2 95 | 96 | const result = bchjs.Util.floor2(num) 97 | // console.log(result) 98 | 99 | assert2.equal(result, 1.2) 100 | }) 101 | }) 102 | 103 | describe('#chunk20', () => { 104 | const thirtyFiveElements = [ 105 | 0, 106 | 1, 107 | 2, 108 | 3, 109 | 4, 110 | 5, 111 | 6, 112 | 7, 113 | 8, 114 | 9, 115 | 10, 116 | 11, 117 | 12, 118 | 13, 119 | 14, 120 | 15, 121 | 16, 122 | 17, 123 | 18, 124 | 19, 125 | 20, 126 | 21, 127 | 22, 128 | 23, 129 | 24, 130 | 25, 131 | 26, 132 | 27, 133 | 28, 134 | 29, 135 | 30, 136 | 31, 137 | 32, 138 | 33, 139 | 34 140 | ] 141 | 142 | it('should split 35 elements into 2 arrays', () => { 143 | const result = bchjs.Util.chunk20(thirtyFiveElements) 144 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 145 | 146 | assert2.isArray(result) 147 | assert2.isArray(result[0]) 148 | assert2.equal(result.length, 2) 149 | assert2.equal(result[0].length, 20) 150 | }) 151 | 152 | it('should return accurately with an array of less than 20 elements', () => { 153 | const elems = [0, 1, 2, 3, 4, 5] 154 | 155 | const result = bchjs.Util.chunk20(elems) 156 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 157 | 158 | assert2.isArray(result) 159 | assert2.isArray(result[0]) 160 | assert2.equal(result.length, 1) 161 | assert2.equal(result[0].length, 6) 162 | }) 163 | 164 | it('should throw an error for non-array input', () => { 165 | try { 166 | bchjs.Util.chunk20('string') 167 | 168 | assert2.equal(true, false, 'Unexpected result') 169 | } catch (err) { 170 | // console.log(err) 171 | assert2.include(err.message, 'input must be an array') 172 | } 173 | }) 174 | }) 175 | }) 176 | -------------------------------------------------------------------------------- /test/unit/utxo-unit.js: -------------------------------------------------------------------------------- 1 | /* 2 | Unit tests for the utxo.js library. 3 | */ 4 | 5 | const sinon = require('sinon') 6 | const assert = require('chai').assert 7 | 8 | const BCHJS = require('../../src/bch-js') 9 | const bchjs = new BCHJS() 10 | 11 | const mockData = require('./fixtures/utxo-mocks') 12 | 13 | describe('#utxo', () => { 14 | let sandbox 15 | beforeEach(() => (sandbox = sinon.createSandbox())) 16 | afterEach(() => sandbox.restore()) 17 | 18 | describe('#findBiggestUtxo', () => { 19 | it('should throw error for non-array input', async () => { 20 | try { 21 | await bchjs.Utxo.findBiggestUtxo({}) 22 | 23 | assert.equal(true, false, 'Unexpected result!') 24 | } catch (err) { 25 | assert.include( 26 | err.message, 27 | 'utxos input to findBiggestUtxo() must be an array' 28 | ) 29 | } 30 | }) 31 | 32 | it('should throw an error if input does not have a value or satoshis property', async () => { 33 | try { 34 | const badUtxos = [ 35 | { 36 | height: 0, 37 | tx_hash: 38 | '192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414', 39 | tx_pos: 0 40 | } 41 | ] 42 | 43 | await bchjs.Utxo.findBiggestUtxo(badUtxos) 44 | } catch (err) { 45 | assert.include( 46 | err.message, 47 | 'UTXOs require a satoshis or value property for findBiggestUtxo()' 48 | ) 49 | } 50 | }) 51 | 52 | it('should sort UTXOs from Electrumx', async () => { 53 | const result = bchjs.Utxo.findBiggestUtxo(mockData.electrumxUtxos.utxos) 54 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 55 | 56 | assert.property(result, 'satoshis') 57 | assert.equal(result.satoshis, 800) 58 | }) 59 | }) 60 | 61 | describe('#hydrateTokenData', () => { 62 | it('should hydrate token UTXOs', async () => { 63 | // Mock dependencies 64 | sandbox 65 | .stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats') 66 | .onCall(0) 67 | .resolves(mockData.genesisData01) 68 | .onCall(1) 69 | .resolves(mockData.genesisData02) 70 | .onCall(2) 71 | .resolves(mockData.genesisData03) 72 | 73 | const result = await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01) 74 | // console.log('result: ', result) 75 | 76 | assert.equal(result.length, 4) 77 | assert.property(result[0], 'qtyStr') 78 | assert.property(result[0], 'ticker') 79 | assert.property(result[0], 'name') 80 | assert.property(result[0], 'documentUri') 81 | assert.property(result[0], 'documentHash') 82 | }) 83 | 84 | it('should should catch and throw errors', async () => { 85 | try { 86 | // Force error 87 | sandbox 88 | .stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats') 89 | .rejects(new Error('test error')) 90 | 91 | await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01) 92 | 93 | assert.fail('Unexpected code path') 94 | } catch (err) { 95 | assert.equal(err.message, 'test error') 96 | } 97 | }) 98 | }) 99 | 100 | describe('#get', () => { 101 | it('should throw an error if input is not a string', async () => { 102 | try { 103 | const addr = 123 104 | 105 | await bchjs.Utxo.get(addr) 106 | 107 | assert.fail('Unexpected code path') 108 | } catch (err) { 109 | assert.include(err.message, 'address input must be a string') 110 | } 111 | }) 112 | 113 | it('should return UTXO information', async () => { 114 | // mock dependencies 115 | sandbox 116 | .stub(bchjs.Utxo.electrumx, 'utxo') 117 | .resolves(mockData.fulcrumUtxos01) 118 | sandbox 119 | .stub(bchjs.Utxo.psfSlpIndexer, 'balance') 120 | .resolves(mockData.psfSlpIndexerUtxos01) 121 | sandbox 122 | .stub(bchjs.Utxo.psfSlpIndexer, 'tx') 123 | .resolves({ txData: { isValidSlp: false } }) 124 | sandbox.stub(bchjs.Utxo.psfSlpIndexer, 'status') 125 | .resolves({ status: { syncedBlockHeight: 600000, chainBlockHeight: 600000 } }) 126 | 127 | // Mock function to return the same input. Good enough for this test. 128 | sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x) 129 | 130 | const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' 131 | 132 | const result = await bchjs.Utxo.get(addr) 133 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 134 | 135 | // Assert expected properties exist 136 | assert.property(result, 'address') 137 | assert.property(result, 'bchUtxos') 138 | assert.property(result, 'slpUtxos') 139 | assert.property(result.slpUtxos, 'type1') 140 | assert.property(result.slpUtxos.type1, 'tokens') 141 | assert.property(result.slpUtxos.type1, 'mintBatons') 142 | assert.property(result.slpUtxos, 'nft') 143 | assert.property(result, 'nullUtxos') 144 | 145 | assert.equal(result.bchUtxos.length, 1) 146 | assert.equal(result.infoUtxos.length, 3) 147 | assert.equal(result.slpUtxos.type1.tokens.length, 1) 148 | assert.equal(result.slpUtxos.type1.mintBatons.length, 1) 149 | assert.equal(result.nullUtxos.length, 0) 150 | }) 151 | 152 | it('should handle an address with no SLP UTXOs', async () => { 153 | // mock dependencies 154 | sandbox 155 | .stub(bchjs.Utxo.electrumx, 'utxo') 156 | .resolves(mockData.fulcrumUtxos02) 157 | sandbox 158 | .stub(bchjs.Utxo.psfSlpIndexer, 'tx') 159 | .resolves({ txData: { isValidSlp: false } }) 160 | sandbox.stub(bchjs.Utxo.psfSlpIndexer, 'status') 161 | .resolves({ status: { syncedBlockHeight: 600000, chainBlockHeight: 600000 } }) 162 | 163 | // Force psf-slp-indexer to return no UTXOs 164 | sandbox 165 | .stub(bchjs.Utxo.psfSlpIndexer, 'balance') 166 | .rejects(mockData.noUtxoErr) 167 | 168 | // Mock function to return the same input. Good enough for this test. 169 | sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => []) 170 | 171 | const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' 172 | 173 | const result = await bchjs.Utxo.get(addr) 174 | // console.log(`result: ${JSON.stringify(result, null, 2)}`) 175 | 176 | assert.equal(result.bchUtxos.length, 1) 177 | assert.equal(result.slpUtxos.type1.tokens.length, 0) 178 | assert.equal(result.slpUtxos.type1.mintBatons.length, 0) 179 | assert.equal(result.nullUtxos.length, 0) 180 | }) 181 | }) 182 | 183 | describe('#isValid', () => { 184 | it('should return false if getTxOut() returns null', async () => { 185 | // Mock dependencies 186 | sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves(null) 187 | 188 | const utxo = { 189 | tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a', 190 | tx_pos: 0 191 | } 192 | 193 | const result = await bchjs.Utxo.isValid(utxo) 194 | // console.log('result: ', result 195 | 196 | assert.equal(result, false) 197 | }) 198 | 199 | it('should return true if getTxOut() returns non-null output', async () => { 200 | // Mock dependencies 201 | sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves({ a: 'b' }) 202 | 203 | const utxo = { 204 | tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40', 205 | tx_pos: 0 206 | } 207 | 208 | const result = await bchjs.Utxo.isValid(utxo) 209 | // console.log('result: ', result 210 | 211 | assert.equal(result, true) 212 | }) 213 | }) 214 | }) 215 | --------------------------------------------------------------------------------