├── .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 | [](https://www.npmjs.com/package/@psf/bch-js)
4 | [](https://npmjs.org/package/@psf/bch-js)
5 | [](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
6 | [](https://github.com/feross/standard) [](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 |
--------------------------------------------------------------------------------