Compare commits

...
124 Commits
Author SHA1 Message Date
Chris Troutner 65314439d7 Merge pull request #108 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Allow users to pass in utxoDelay
2021-03-02 08:55:38 -08:00
Chris Troutner 36a84726af fix(hydrateUtxos): Allow users to pass in utxoDelay 2021-03-02 08:54:07 -08:00
Chris Troutner 78f462c9ec Merge pull request #107 from Permissionless-Software-Foundation/ct-unstable
feat(v3 & blockbook): Deprecating v3 route and blockbook indexer
2021-03-01 15:36:51 -08:00
Chris Troutner b97d94edaf Bumping bch-js to v4.15.19 2021-03-01 11:37:23 -08:00
Chris Troutner b4d29a5856 fix(Util.sweepWif): Replaced blockbook with electrumx 2021-03-01 11:35:07 -08:00
Chris Troutner 64e59f607a Refactored Util route class 2021-03-01 10:29:49 -08:00
Chris Troutner fca0b50a2a Refactored util.js lib 2021-03-01 08:55:16 -08:00
Chris Troutner 47ddc6f186 feat(v3 & blockbook): Deprecating v3 route and blockbook indexer 2021-03-01 08:41:09 -08:00
Chris Troutner 1f1e3c781f Updating to bch-js v4.15.19 2021-03-01 08:28:51 -08:00
Chris Troutner 09f0cb6b6e Merge pull request #106 from Permissionless-Software-Foundation/ct-unstable
Improved rate-limits with bch-js v4.15.18
2021-02-27 16:18:38 -08:00
Chris Troutner fb43b3f390 fix(bch-js): Bumping to v4.15.18 2021-02-27 16:17:28 -08:00
Chris Troutner f76f00d235 Adding decatur script to gitignore 2021-02-27 08:13:44 -08:00
Chris Troutner ac96860c27 Merge pull request #105 from Permissionless-Software-Foundation/ct-unstable
feat(v3 hydrateUxos): Deprecating v3 hydrateUtxos to improve system s…
2021-02-24 18:21:47 -08:00
Chris Troutner c51290acf5 feat(v3 hydrateUxos): Deprecating v3 hydrateUtxos to improve system stability 2021-02-24 18:18:23 -08:00
Chris Troutner 2f520dcaec Merge pull request #104 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumping to v4.15.8
2021-02-24 18:11:26 -08:00
Chris Troutner e0be1d308a fix(bch-js): Bumping to v4.15.8 2021-02-24 18:09:24 -08:00
Chris Troutner 9cd06630ea Merge pull request #103 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Passing JWT token to internal calls
2021-02-24 17:10:17 -08:00
Chris Troutner 6b281e2842 fix(rate limits): Passing JWT token to internal calls 2021-02-24 17:07:24 -08:00
Chris Troutner 2e143d5a9e Merge pull request #102 from Permissionless-Software-Foundation/ct-unstable
More debugging around rate limits
2021-02-24 16:53:40 -08:00
Chris Troutner eb817da044 Merge branch 'master' into ct-unstable 2021-02-24 16:50:50 -08:00
Chris Troutner 1c88aa7d72 fix(bch-js): Bumping to v4.15.7 2021-02-24 16:50:38 -08:00
Chris Troutner a9e976cc24 Merge pull request #101 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Upgrading to v4.14.4
2021-02-24 15:24:01 -08:00
Chris Troutner 5644b53cf8 fix(bch-js): Upgrading to v4.14.4 2021-02-24 15:22:54 -08:00
Chris Troutner 236637f0e0 Renaming 'origin' to 'ip' 2021-02-24 15:12:40 -08:00
Chris Troutner fb1bb0b7ed Merge pull request #100 from Permissionless-Software-Foundation/ct-unstable
fix(rate limit): Fine tuning rate limits for internal calls
2021-02-24 14:42:18 -08:00
Chris Troutner 8a91f3ae3a fix(rate limit): Fine tuning rate limits for internal calls 2021-02-24 14:40:16 -08:00
Chris Troutner 80dc296dee Merge pull request #99 from Permissionless-Software-Foundation/ct-unstable
fix(debugging): Adding debugging console.log
2021-02-24 14:29:35 -08:00
Chris Troutner f01c82631b fix(debugging): Adding debugging console.log 2021-02-24 14:26:10 -08:00
Chris Troutner ef2a681edb Merge pull request #98 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Testing recursive rate limits
2021-02-24 14:15:49 -08:00
Chris Troutner 44ce5f2aa9 fix(rate limits): Testing recursive rate limits 2021-02-24 14:14:36 -08:00
Chris Troutner f52a04a337 Merge pull request #97 from Permissionless-Software-Foundation/ct-unstable
fix(bodyOrigin): Passing the original origin through the POST body
2021-02-24 13:51:53 -08:00
Chris Troutner 9aa3e5b52a fix(bodyOrigin): Passing the original origin through the POST body 2021-02-24 13:50:38 -08:00
Chris Troutner 71452f4834 Merge pull request #96 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v4.15.2 to test rate limit idea
2021-02-24 13:37:57 -08:00
Chris Troutner 96f1cb6ad4 fix(bch-js): Updating to v4.15.2 to test rate limit idea 2021-02-24 13:36:21 -08:00
Chris Troutner 36dd64db22 Updating docker example shell script 2021-02-11 13:54:00 -08:00
Chris Troutner 05de04a569 Merge branch 'ct-unstable' 2021-02-11 12:56:49 -08:00
Chris Troutner c661799095 Commenting out debugging line 2021-02-11 12:56:35 -08:00
Chris Troutner 4f416d6423 Merge pull request #94 from Permissionless-Software-Foundation/ct-unstable
fix(slp): Fixing typo with whitelist env var
2021-02-11 12:51:46 -08:00
Chris Troutner 58dc4da92c fix(slp): Fixing typo with whitelist env var 2021-02-11 12:50:39 -08:00
Chris Troutner 7b8710d3da Merge pull request #93 from Permissionless-Software-Foundation/ct-unstable
feat(SLPDB): Adding separate passwords for GP and WL SLPDBs
2021-02-11 11:26:34 -08:00
Chris Troutner 7976909dbb feat(SLPDB): Adding separate passwords for GP and WL SLPDBs 2021-02-11 11:25:25 -08:00
Chris Troutner 24b85e410d Adding script to gitignore 2021-02-11 10:10:23 -08:00
Chris Troutner ae13d2c42e Merge pull request #92 from Permissionless-Software-Foundation/ct-unstable
fix(CD): Testing continuous deployment
2021-02-10 13:56:25 -08:00
Chris Troutner bf0fc2437a fix(CD): Testing continuous deployment 2021-02-10 13:54:16 -08:00
Chris Troutner 88d3dd12a1 Merge pull request #91 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumping to v4.11.1
2021-02-09 15:17:24 -08:00
Chris Troutner cb8b4a5586 fix(bch-js): Bumping to v4.11.1 2021-02-09 15:15:35 -08:00
Chris Troutner ec0ef24508 fix(comments): Commenting out debug info 2021-02-09 15:07:52 -08:00
Chris Troutner 5a16e69719 Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxosWL): SLPDB with whitelist filter used to hydrate UTXOs
2021-02-09 12:20:11 -08:00
Chris Troutner 154dbe8151 feat(hydrateUtxosWL): SLPDB with whitelist filter used to hydrate UTXOs 2021-02-09 12:18:52 -08:00
Chris Troutner 785d3059c3 Merge pull request #89 from Permissionless-Software-Foundation/joey
fix(logging): Adding env vars for configuring logging
2021-01-13 09:27:55 -08:00
Chris Troutner 21b19b59c9 fix(logging): Adding env vars for configuring logging 2021-01-13 09:26:03 -08:00
Chris Troutner 588feff5db Added minor comments 2021-01-13 09:24:28 -08:00
Chris Troutner 9fafc6fba3 Merge pull request #88 from josephroyking/logging-env-var
Add logging storage params as env variables
2021-01-13 09:19:34 -08:00
Joey King 8412da15db Add logging storage params as env variables 2021-01-13 07:51:08 -06:00
Chris Troutner 54c299e7fe Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
Updating README, shell scripts, and dependencies.
2021-01-11 16:37:10 -08:00
Chris Troutner a06399cfda fix(dependencies): Removed unneeded dependencies. 2021-01-11 16:35:41 -08:00
Chris Troutner e49c488e06 Removing unneeded dependencies 2021-01-11 16:33:49 -08:00
Chris Troutner be07ffab33 Removing unneeded dependencies 2021-01-11 16:29:46 -08:00
Chris Troutner 41a5b24faf Updating README and shell script examples 2021-01-11 16:22:27 -08:00
Chris Troutner 2172d0feff Updating example shell script 2021-01-11 15:33:45 -08:00
Chris Troutner c4664ca173 Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumped to v4.5.4
2021-01-10 15:40:28 -08:00
Chris Troutner 11a7915cb8 fix(bch-js): Bumped to v4.5.4 2021-01-10 15:37:15 -08:00
Chris Troutner 06b944f36a Merge pull request #85 from Permissionless-Software-Foundation/ct-unstable
feat(error handling): update to error handling to pass more information
2021-01-10 15:15:05 -08:00
Chris Troutner 9a8886121d feat(error handling): update to error handling to pass more information 2021-01-10 15:13:33 -08:00
Chris Troutner 89c6d48dfd fix(hydrateUtxos): Passing error message to bch-js 2021-01-09 20:48:10 -08:00
Chris Troutner 4525b99f33 Merge pull request #84 from Permissionless-Software-Foundation/ct-unstable
fix(generateSendOpReturn): Fixed poorly written integration test
2021-01-07 16:42:15 -08:00
Chris Troutner 3d04507948 fixing unit and integration test 2021-01-07 16:40:55 -08:00
Chris Troutner 7e3a447973 fix(generateSendOpReturn): Fixed poorly written integration test 2021-01-07 16:30:12 -08:00
Chris Troutner bdde3a271d Merge pull request #83 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updating to v4.5.2
2021-01-07 13:28:01 -08:00
Chris Troutner 9450c3969b fix(bch-js): Updating to v4.5.2 2021-01-07 13:26:38 -08:00
Chris Troutner 23d66da792 Merge pull request #82 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Fixed bug with rate limits
2021-01-07 10:05:58 -08:00
Chris Troutner cb430c20e2 fix(rate limits): Fixed bug with rate limits 2021-01-07 10:04:25 -08:00
Chris Troutner 58ce0e0604 Merge pull request #81 from Permissionless-Software-Foundation/ct-unstable
Rate limits configured with environment variables
2021-01-07 09:13:52 -08:00
Chris Troutner 61485dfe99 fix(rate limits): Configuring rate limits with env vars 2021-01-07 09:11:35 -08:00
Chris Troutner ab4c80b674 Created rate limit isInWhitelist(). Needs tests 2021-01-03 18:20:49 -08:00
Chris Troutner dbd5dd53c2 Merge pull request #80 from Permissionless-Software-Foundation/ct-unstable
fix(SLPDB status): Adding GET /slp/status to get status on SLPDB
2020-12-23 19:33:48 -08:00
Chris Troutner e2fa671964 fix(SLPDB status): Adding GET /slp/status to get status on SLPDB 2020-12-23 19:30:38 -08:00
Chris Troutner 02818da317 Merge pull request #79 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Updated to v4.2.5 to fix issue with hydrateUtxos()
2020-12-13 09:30:11 -08:00
Chris Troutner 9804ea8c3a Ran npm audit fix 2020-12-13 09:26:58 -08:00
Chris Troutner 4bfe198a62 fix(bch-js): Updated to v4.2.5 to fix issue with hydrateUtxos() 2020-12-13 09:23:41 -08:00
Chris Troutner 453cbacf9d Updating gitignore 2020-12-11 14:03:38 -08:00
Chris Troutner 42d88a149a Merge pull request #78 from Permissionless-Software-Foundation/ct-unstable
Updating bch-js for improvements to tokenUtxoDetails()
2020-12-06 20:01:01 -08:00
Chris Troutner 2e97430856 fix(bitcore-lib-cash): Upgrading to 8.23.1 to fix vulnerability 2020-12-06 19:56:30 -08:00
Chris Troutner 60bf587031 fix(bchjs): Bumping to v4.1.1 2020-12-06 19:50:00 -08:00
Chris Troutner 68d1226ded Merge pull request #77 from Permissionless-Software-Foundation/ct-unstable
fix(unit tests): Updating changing test value
2020-12-05 21:23:45 -08:00
Chris Troutner f6dc788492 fix(unit tests): Updating changing test value 2020-12-05 21:22:18 -08:00
Chris Troutner a515334a94 Merge pull request #76 from Permissionless-Software-Foundation/ct-unstable
fix(bch-js): Bumping to version 4.0.0
2020-12-05 21:02:45 -08:00
Chris Troutner 6be1369182 Updating package-lock 2020-12-05 20:59:05 -08:00
Chris Troutner 6455522ba6 fix(bch-js): Bumping to version 4.0.0 2020-12-05 20:56:35 -08:00
Chris Troutner d9a8999239 Merge pull request #75 from Permissionless-Software-Foundation/ct-unstable
fix(validateBulk): Handling corner case in SLP Utils validateBulk
2020-12-05 20:17:26 -08:00
Chris Troutner ae0f556f75 fix(validateBulk): Handling corner case in SLP Utils validateBulk 2020-12-05 20:16:09 -08:00
Chris Troutner cdca9b84a4 Merge pull request #74 from Permissionless-Software-Foundation/v4
perf(v4): Ported recent SLP validate changes to new v4 route
2020-12-05 18:49:40 -08:00
Chris Troutner 24a6a6d423 Fixing merging conflicts 2020-12-05 18:46:14 -08:00
Chris Troutner 8a7b391486 perf(v4): Ported recent SLP validate changes to new v4 route 2020-12-05 18:37:59 -08:00
Chris Troutner 25bd9ce4c9 Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
fix(docs): Fixing some visual issues with the docs
2020-12-05 18:14:59 -08:00
Chris Troutner b408b048c1 fix(docs): Fixing some visual issues with the docs 2020-12-05 18:12:58 -08:00
Chris Troutner 4ec36358ad Merge pull request #72 from Permissionless-Software-Foundation/ct-unstable
Big change in SLP validate output
2020-12-05 17:50:51 -08:00
Chris Troutner 19b40c54e7 feat(validateSingle): Returning null instead of false as default return value 2020-12-05 17:47:46 -08:00
Chris Troutner 3e09b63a55 Added unit test for validateBulk() 2020-12-05 17:19:14 -08:00
Chris Troutner 9580a30ba5 feat(validateBulk): Changed output. Passing true, null, and false values 2020-12-05 16:55:21 -08:00
Chris Troutner af47a3a37f Merge pull request #71 from Permissionless-Software-Foundation/ct-unstable
SLPDB Whitelist Validate
2020-12-05 11:04:35 -08:00
Chris Troutner d61ea0831a Testing new validation calls against BCHN and BCHA 2020-12-05 11:02:44 -08:00
Chris Troutner f2c0cd6feb feat(validate3Bulk): Adding bulk SLP tx validtion for whitelist endpoint 2020-12-05 09:48:05 -08:00
Chris Troutner d93d20321d feat(validate3Single): Adding third SLP validation option using whitelist server 2020-12-05 09:31:38 -08:00
Chris Troutner ff1c37ddb6 fix(validateSingle): Added unit tests for /slp/validateSingle 2020-12-05 08:46:22 -08:00
Chris Troutner a260f7506a Merge pull request #69 from Permissionless-Software-Foundation/ct-unstable
fix(whitelist): Adusting the domain names on the whitelist
2020-12-03 14:19:45 -08:00
Chris Troutner 9684441d92 fix(whitelist): Adusting the domain names on the whitelist 2020-12-03 14:17:28 -08:00
Chris Troutner e793c2a8a9 Merge pull request #68 from Permissionless-Software-Foundation/ct-unstable
fix(txDetails): Adding additional metadata to GET slp/txDetails
2020-11-29 17:59:10 -08:00
Chris Troutner 1bf8e59097 fix(txDetails): Adding additional metadata to GET slp/txDetails 2020-11-29 17:56:22 -08:00
Chris Troutner 105ecd6305 Merge pull request #67 from josephroyking/psf-add-inputs-to-txDetails
Psf add inputs to tx details
2020-11-29 17:51:13 -08:00
josephroyking 314040453b Support unconfirmed slp txs 2020-11-25 15:31:59 -08:00
josephroyking cbcf5c1d2b Add tx input details to slp/txDetails endpoint 2020-11-25 10:56:10 -08:00
Chris Troutner e881b68ca5 Merge pull request #65 from Permissionless-Software-Foundation/ct-unstable
ix(electrumx): Fixed connect/disconnect bug that was preventing tests
2020-11-21 11:39:05 -08:00
Chris Troutner f54c69a664 Fixing test typo 2020-11-21 11:35:01 -08:00
Chris Troutner 319281c096 fix(electrumx): Fixed connect/disconnect bug that was preventing tests 2020-11-21 11:29:18 -08:00
Chris Troutner 7dee8c0604 Merge branch 'master' into ct-unstable 2020-11-21 11:15:41 -08:00
Chris Troutner efbcbc5deb linting 2020-11-21 11:15:33 -08:00
Chris Troutner c742518612 Merge pull request #64 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Automatically reconnecting when Electrumx disconnects
2020-11-20 20:53:40 -08:00
Chris Troutner 6a0ed46413 fix(electrumx): Automatically reconnecting when Electrumx disconnects 2020-11-20 20:52:22 -08:00
Chris Troutner 5ea71bc89b Updating default Docker run script 2020-11-20 08:36:34 -08:00
Chris Troutner 84701e90dc Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
fix(price): Fixing bug in new price endpoint
2020-11-17 14:29:22 -08:00
Chris Troutner 34ed5ddc4c fix(price): Fixing bug in new price endpoint 2020-11-17 14:27:42 -08:00
Chris Troutner 9a8cd4b664 Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
fix(price): Adding Coinex price feed for BCHA
2020-11-17 14:00:37 -08:00
Chris Troutner 2a0b06ca96 fix(price): Adding Coinex price feed for BCHA 2020-11-17 13:48:10 -08:00
65 changed files with 6331 additions and 9225 deletions
+3
View File
@@ -16,6 +16,7 @@ start-local-testnet.sh
test-fullstack-main.sh
start-fullstack-main.sh
start-fullstack-test.sh
start-fullstack-testnet.sh
start-free-main.sh
start-local-mainnet.sh.save
start-local-testnet.sh.save
@@ -27,6 +28,8 @@ test-fullstack-abc.sh
test-fullstack-bchn.sh
start-fullstack-abc.sh
start-fullstack-bchn.sh
start-ss-main.sh
start-decatur-bchn.sh
coverage
start-my-infra
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Chris Troutner
Copyright (c) 2021 Permissionless Software Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+12 -8
View File
@@ -1,6 +1,9 @@
# bch-api
This is a node.js JavaScript REST API server using Express.js. The purpose of this code is to create a REST API server that provides a common interface for working with a Bitcoin Cash full node and various indexers. See [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library.
[![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard)
This is a REST API server, written in node.js JavaScript, using [Express.js](https://expressjs.com/) framework. The purpose of this code is to create a REST API server that provides a common interface for working with a Bitcoin Cash full node and various indexers. See [this article](https://psfoundation.cash/blog/cash-stack) to learn about the 'Cash Stack'. Visit [FullStack.cash](https://fullstack.cash), sign up for a free account, and use this REST API right away with the [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) JavaScript library.
This repository is intended to be paired with [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js), an npm JavaScript library for building Bitcoin Cash apps.
@@ -20,7 +23,7 @@ The following features set this repository apart from rest.bitcoin.com:
- Fine grain access is controlled with a JWT token using
[this back end auth server](https://github.com/Permissionless-Software-Foundation/jwt-bch-api) and [this front end](https://github.com/Permissionless-Software-Foundation/jwt-bch-frontend). Can be used to monetize access to the REST API.
- Typescript removed and ES8 JavaScript used instead.
- npm audit run on all dependencies.
- `npm audit fix` frequently run to fix dependencies.
## Live Demo
You can test a live demo of the REST API by running the
@@ -31,17 +34,17 @@ You can bootstrap your own REST API server by downloading and installing the inf
### Configure bch-js
The live servers can be used by [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js) by settings the `restURL` config property to one of these servers:
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v3/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v3/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v3/
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
- Check server status: https://metrics.fullstack.cash
## Installation
There are two installation paths, depending if you want a *development* or
*production* environment. You'll also need to set up the underlying infrastructure
described on [this page](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer).
described on [this page](https://psfoundation.cash/blog/cash-stack).
This code targets the Ubuntu 18.04 LTS Linux OS.
This code targets the Ubuntu 18.04 LTS Linux OS or higher.
### Development
This is a standard node.js project. The installation is as follows:
@@ -86,7 +89,8 @@ Have questions? Need help? Join our community support
## IPFS
Copies of this repository will occasionally be uploaded and hosted on [IPFS](https://ipfs.io):
- v1.15.4: QmSAerFmDJm6KQp2HC8jsRR5MdL5X39hoUsJYvWNDvrD8m
- v1.25.1: [QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX](https://ipfs.io/ipfs/QmXFzFJHenymhReDc9oGAxbpmvK2fJYnisQ2pJn3gGyKKX)
## License
[MIT](./LICENSE.md)
a
+12 -1
View File
@@ -1,11 +1,22 @@
/*
Common configuration settings.
Default settings in this file can be overridden by an environment variable.
*/
const config = {
// This is the same secret used by jwt-bch-api
apiTokenSecret: process.env.TOKENSECRET
? process.env.TOKENSECRET
: 'secret-jwt-token'
: 'secret-jwt-token',
// Rate Limits
anonRateLimit: process.env.ANON_RATE_LIMIT ? Number(process.env.ANON_RATE_LIMIT) : 50,
whitelistRateLimit: process.env.WHITELIST_RATE_LIMIT
? Number(process.env.WHITELIST_RATE_LIMIT)
: 10,
whitelistDomains: process.env.WHITELIST_DOMAINS
? process.env.WHITELIST_DOMAINS.split(',')
: ['fullstack.cash', 'psfoundation.cash']
}
module.exports = config
+43 -12
View File
@@ -1,33 +1,64 @@
#!/bin/bash
# test
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
export NETWORK=mainnet
#export NETWORK=testnet
# Full node
export RPC_BASEURL=http://172.17.0.1:8332/
export RPC_IP=172.17.0.1:8332
export RPC_BASEURL=http://$RPC_IP/
export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password
export NETWORK=mainnet
# SLPDB
export SLPDB_PASS_GP=somelongpassword
export SLPDB_URL=http://172.17.0.1:12300/
export SLPDB_PASS=somelongpassword
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_PASS_WL=somelongpassword
export SLPDB_WHITELIST_URL=http://172.17.0.1:12300/
# slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Blockbook Indexer
export BLOCKBOOK_URL=https://172.17.0.1:9131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1
export FULCRUM_PORT=50002
# Redis DB
# Redis DB - Used for rate limiting
export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1
# JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# slp-api alternative SLP validator.
export SLP_API_URL=http://10.0.0.5:5001/
# Basic Authentication password
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
# Set logging parameters
#1m means no more than 1 megabyte
export LOG_MAX_SIZE=1m
#5d means store no more than 5 days
export LOG_MAX_FILES=5d
npm start
+34 -9
View File
@@ -1,18 +1,32 @@
#!/bin/bash
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
#export NETWORK=mainnet
export NETWORK=testnet
# Full node
export RPC_BASEURL=http://172.17.0.1:18332/
export RPC_IP=172.17.0.1:18332
export RPC_BASEURL=http://$RPC_IP/
export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password
export NETWORK=testnet
# SLPDB
export SLPDB_URL=http://172.17.0.1:13300/
export SLPDB_PASS=somelongpassword
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_WHITELIST_URL=http://172.17.0.1:13300/
# slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Blockbook Indexer
export BLOCKBOOK_URL=https://172.17.0.1:19131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=172.17.0.1
export FULCRUM_PORT=60002
# Redis DB
export REDIS_PORT=6380
@@ -22,9 +36,20 @@ export REDIS_HOST=172.17.0.1
export TOKENSECRET=somelongsecretvalue
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# slp-api alternative SLP validator.
export SLP_API_URL=http://10.0.0.5:5001/
# Basic Authentication password (optional)
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
npm start
+3664 -7654
View File
File diff suppressed because it is too large Load Diff
+16 -23
View File
@@ -14,24 +14,25 @@
"scripts": {
"start": "node ./src/app.js",
"dev": "nodemon ./dist/app.js",
"test": "npm run lint && npm run test-v3",
"test": "npm run lint && npm run test-v4",
"lint": "standard --env mocha --fix",
"test-v3": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v3/",
"test:temp": "export NETWORK=mainnet && export TEST=integration && mocha --timeout 25000 test/v3/encryption.js",
"test:integration": "mocha test/v3/integration",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v3/integration/slp*.js",
"test-v4": "export NETWORK=mainnet && nyc --reporter=text mocha --timeout 60000 test/v4/",
"test:integration": "mocha test/v4/integration",
"test:integration:slpdb": "mocha --timeout 25000 -g '#validate2Single' test/v4/integration/slp*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v3/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v3 -o docs"
"coverage:report": "export NETWORK=mainnet && nyc --reporter=html mocha --timeout 25000 test/v4/",
"docs": "./node_modules/.bin/apidoc -i src/routes/v4 -o docs",
"test:temp1": "export NETWORK=mainnet && export TEST=integration && mocha --exit --timeout 25000 -g '#hydrateUtxosWL' test/v4/integration/",
"test:temp2": "mocha test/v4/rate-limits.js"
},
"engines": {
"node": ">=10.15.1"
},
"dependencies": {
"@psf/bch-js": "^3.7.1",
"apidoc": "^0.23.0",
"axios": "^0.19.0",
"bitcore-lib-cash": "^8.20.3",
"@psf/bch-js": "^4.15.21",
"apidoc": "^0.26.0",
"axios": "^0.21.1",
"bitcore-lib-cash": "^8.23.1",
"body-parser": "^1.18.3",
"cookie-parser": "~1.4.3",
"cors": "^2.8.3",
@@ -44,7 +45,6 @@
"helmet": "^3.21.2",
"ioredis": "^4.14.1",
"jsonwebtoken": "^8.5.1",
"level": "^6.0.0",
"mkdirp": "^1.0.0",
"mocha": "^7.1.1",
"morgan": "^1.9.1",
@@ -52,8 +52,6 @@
"passport": "^0.4.0",
"passport-anonymous": "^1.0.1",
"passport-http": "^0.3.0",
"pg": "^8.0.0",
"pg-hstore": "^2.3.2",
"rate-limiter-flexible": "^2.0.0",
"winston": "^3.2.1",
"winston-daily-rotate-file": "^4.0.0"
@@ -68,17 +66,12 @@
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-standard": "^4.0.0",
"fs-extra": "^9.0.0",
"nock": "^12.0.0",
"node-mocks-http": "^1.7.0",
"nock": "^13.0.5",
"nyc": "^15.0.0",
"prettier": "^2.0.0",
"proxyquire": "^2.1.0",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"semantic-release": "^17.0.0",
"semantic-release": "^17.3.9",
"sinon": "^9.0.0",
"standard": "^14.3.1",
"typescript": "^3.1.4"
"standard": "^14.3.1"
},
"release": {
"publish": [
@@ -90,6 +83,6 @@
},
"apidoc": {
"title": "bch-api",
"url": "https://api.fullstack.cash/v3"
"url": "https://api.fullstack.cash/v4"
}
}
+46 -49
View File
@@ -25,35 +25,34 @@ const jwtAuth = require('./middleware/jwt-auth')
// Logging for API requests.
const logReqInfo = require('./middleware/req-logging')
// v3
const healthCheckV3 = require('./routes/v3/health-check')
const BlockchainV3 = require('./routes/v3/full-node/blockchain')
const ControlV3 = require('./routes/v3/full-node/control')
const MiningV3 = require('./routes/v3/full-node/mining')
const networkV3 = require('./routes/v3/full-node/network')
const RawtransactionsV3 = require('./routes/v3/full-node/rawtransactions')
const utilV3 = require('./routes/v3/util')
const SlpV3 = require('./routes/v3/slp')
const xpubV3 = require('./routes/v3/xpub')
const BlockbookV3 = require('./routes/v3/blockbook')
const Ninsight = require('./routes/v3/ninsight')
const ElectrumXV3 = require('./routes/v3/electrumx')
const EncryptionV3 = require('./routes/v3/encryption')
const PriceV3 = require('./routes/v3/price')
// v4
const healthCheckV4 = require('./routes/v4/health-check')
const BlockchainV4 = require('./routes/v4/full-node/blockchain')
const ControlV4 = require('./routes/v4/full-node/control')
const MiningV4 = require('./routes/v4/full-node/mining')
const networkV4 = require('./routes/v4/full-node/network')
const RawtransactionsV4 = require('./routes/v4/full-node/rawtransactions')
const UtilV4 = require('./routes/v4/util')
const SlpV4 = require('./routes/v4/slp')
const xpubV4 = require('./routes/v4/xpub')
const ElectrumXV4 = require('./routes/v4/electrumx')
const EncryptionV4 = require('./routes/v4/encryption')
const PriceV4 = require('./routes/v4/price')
const Ninsight = require('./routes/v4/ninsight')
require('dotenv').config()
// Instantiate route libraries.
const blockchainV3 = new BlockchainV3()
const controlV3 = new ControlV3()
const miningV3 = new MiningV3()
const rawtransactionsV3 = new RawtransactionsV3()
const slpV3 = new SlpV3()
const blockbookV3 = new BlockbookV3()
const electrumxv3 = new ElectrumXV3()
electrumxv3.connect()
const encryptionv3 = new EncryptionV3()
const pricev3 = new PriceV3()
// Instantiate v4 route libraries.
const blockchainV4 = new BlockchainV4()
const controlV4 = new ControlV4()
const miningV4 = new MiningV4()
const rawtransactionsV4 = new RawtransactionsV4()
const slpV4 = new SlpV4()
const electrumxv4 = new ElectrumXV4()
electrumxv4.connect()
const encryptionv4 = new EncryptionV4()
const pricev4 = new PriceV4()
const utilV4 = new UtilV4({ electrumx: electrumxv4 })
const app = express()
@@ -95,38 +94,36 @@ app.use(express.static(path.join(__dirname, 'public')))
// Log requests for later analysis.
app.use('/', logReqInfo)
// const v2prefix = "v2"
const v3prefix = 'v3'
const v4prefix = 'v4'
// Inspect the header for a JWT token.
app.use(`/${v3prefix}/`, jwtAuth.getTokenFromHeaders)
app.use(`/${v4prefix}/`, jwtAuth.getTokenFromHeaders)
// Instantiate the authorization middleware, used to implement pro-tier rate limiting.
// Handles Anonymous and Basic Authorization schemes used by passport.js
const auth = new AuthMW()
app.use(`/${v3prefix}/`, auth.mw())
app.use(`/${v4prefix}/`, auth.mw())
// Rate limit on all v3 routes
// Rate limit on all v4 routes
// Establish and enforce rate limits.
// app.use(`/${v3prefix}/`, rateLimits.routeRateLimit)
app.use(`/${v3prefix}/`, rateLimits.rateLimitByResource)
app.use(`/${v4prefix}/`, rateLimits.rateLimitByResource)
app.use(`/${v3prefix}/` + 'health-check', healthCheckV3)
app.use(`/${v3prefix}/` + 'blockchain', blockchainV3.router)
app.use(`/${v3prefix}/` + 'control', controlV3.router)
app.use(`/${v3prefix}/` + 'mining', miningV3.router)
app.use(`/${v3prefix}/` + 'network', networkV3)
app.use(`/${v3prefix}/` + 'rawtransactions', rawtransactionsV3.router)
app.use(`/${v3prefix}/` + 'util', utilV3.router)
app.use(`/${v3prefix}/` + 'slp', slpV3.router)
app.use(`/${v3prefix}/` + 'xpub', xpubV3.router)
app.use(`/${v3prefix}/` + 'blockbook', blockbookV3.router)
app.use(`/${v3prefix}/` + 'electrumx', electrumxv3.router)
app.use(`/${v3prefix}/` + 'encryption', encryptionv3.router)
app.use(`/${v3prefix}/` + 'price', pricev3.router)
// Connect v4 routes
app.use(`/${v4prefix}/` + 'health-check', healthCheckV4)
app.use(`/${v4prefix}/` + 'blockchain', blockchainV4.router)
app.use(`/${v4prefix}/` + 'control', controlV4.router)
app.use(`/${v4prefix}/` + 'mining', miningV4.router)
app.use(`/${v4prefix}/` + 'network', networkV4)
app.use(`/${v4prefix}/` + 'rawtransactions', rawtransactionsV4.router)
app.use(`/${v4prefix}/` + 'slp', slpV4.router)
app.use(`/${v4prefix}/` + 'xpub', xpubV4.router)
app.use(`/${v4prefix}/` + 'electrumx', electrumxv4.router)
app.use(`/${v4prefix}/` + 'encryption', encryptionv4.router)
app.use(`/${v4prefix}/` + 'price', pricev4.router)
app.use(`/${v4prefix}/` + 'util', utilV4.router)
const ninsight = new Ninsight()
app.use(`/${v3prefix}/` + 'ninsight', ninsight.router)
app.use(`/${v4prefix}/` + 'ninsight', ninsight.router)
// catch 404 and forward to error handler
app.use((req, res, next) => {
@@ -211,11 +208,11 @@ function onError (error) {
case 'EACCES':
console.error(`${bind} requires elevated privileges`)
process.exit(1)
break
// break
case 'EADDRINUSE':
console.error(`${bind} is already in use`)
process.exit(1)
break
// break
default:
throw error
}
+62 -11
View File
@@ -12,12 +12,17 @@
'use strict'
// Public npm libraries.
const jwt = require('jsonwebtoken')
// local libraries.
const wlogger = require('../util/winston-logging')
const config = require('../../config')
const ANON_LIMITS = 50
const ANON_LIMITS = config.anonRateLimit
const WHITELIST_RATE_LIMIT = config.whitelistRateLimit
const WHITELIST_DOMAINS = config.whitelistDomains
const INTERNAL_RATE_LIMIT = 1
// Redis
const redisOptions = {
@@ -108,6 +113,27 @@ class RateLimits {
err
)
}
//
} else if (req.body && req.body.usrObj) {
// Same as above, but this code path is activated from internal calls to
// bch-js, like hydrateUtxo(), which passes the user object from the
// original API call.
try {
decoded = _this.jwt.verify(
req.body.usrObj.jwtToken,
_this.config.apiTokenSecret
)
// console.log(`decoded: ${JSON.stringify(decoded, null, 2)}`)
userId = decoded.id
} catch (err) {
// This handler will be triggered if the JWT token does not match the
// token secret.
wlogger.error(
'Error in route-ratelimit.js trying to decode JWT token in usrObj'
)
}
} else {
wlogger.debug('No JWT token found!')
}
@@ -124,8 +150,10 @@ class RateLimits {
const resource = _this.getResource(req.url)
wlogger.debug(`resource: ${resource}`)
// Key will be the JWT ID if it exists, otherwise the IP address of the caller.
let key = userId || req.ip
res.locals.key = key // Feedback for tests.
// console.log(`key: ${key}`)
// const pointsToConsume = userId ? 1 : 30
decoded.resource = resource
@@ -144,14 +172,11 @@ class RateLimits {
// If the request originates from one of the approved wallet apps, then
// apply paid-access rate limits.
if (
origin &&
(origin.toString().indexOf('wallet.fullstack.cash') > -1 ||
origin.toString().indexOf('sandbox.fullstack.cash') > -1 ||
origin.toString().indexOf('splitbch.com') > -1 ||
origin === 'slp-api')
) {
pointsToConsume = 10
// console.log(`origin: ${JSON.stringify(origin, null, 2)}`)
// console.log(`whitelist: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
const isInWhitelist = _this.isInWhitelist(origin)
if (isInWhitelist) {
pointsToConsume = WHITELIST_RATE_LIMIT
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
@@ -159,11 +184,10 @@ class RateLimits {
if (
// Comment out the line below when running bch-js e2e rate limit tests.
key.toString().indexOf('::ffff:127.0.0.1') > -1 ||
// Do not comment out this line.
key.toString().indexOf('172.17.') > -1
) {
pointsToConsume = 1
pointsToConsume = INTERNAL_RATE_LIMIT
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
@@ -265,6 +289,33 @@ class RateLimits {
throw err
}
}
// Returns a boolean if the origin of the request matches a domain in the
// whitelist.
isInWhitelist (origin) {
try {
const retVal = false // Default value.
if (!origin) return false
// console.log(`WHITELIST_DOMAINS: ${JSON.stringify(WHITELIST_DOMAINS, null, 2)}`)
for (let i = 0; i < WHITELIST_DOMAINS.length; i++) {
const thisDomain = WHITELIST_DOMAINS[i]
if (origin.toString().indexOf(thisDomain) > -1) {
return true
}
}
return retVal
} catch (err) {
wlogger.error(
'Error in route-ratelimit.js/isInWhitelist(). Returning false by default.'
)
return false
}
}
}
module.exports = RateLimits
@@ -79,6 +79,21 @@ class Electrum {
// Set the connection flag.
_this.isReady = true
// Periodically check the connection. If it's not connected, attempt to
// reconnect.
_this.reconnectIntervalHandle = setInterval(async function () {
const status = _this.electrumx.connection.status
// console.log(`Electrumx status: ${status}`)
// 1 = connected. If we're not connected, attemp to reconnect.
if (status !== 1) {
wlogger.info(`Electrumx not connectes. Status: ${status}`)
wlogger.info('Attempting to reconnect...')
await _this.electrumx.connect()
wlogger.info('...reconnected.')
}
}, 30000)
console.log('...Successfully connected to ElectrumX server.')
// console.log(`_this.isReady: ${_this.isReady}`)
@@ -96,6 +111,9 @@ class Electrum {
// Return immediately if the isReady flag is false.
if (!_this.isReady) return true
// Disable the reconnect timer.
clearInterval(_this.reconnectIntervalHandle)
// Disconnect from the server.
await _this.electrumx.disconnect()
@@ -117,7 +135,7 @@ class Electrum {
const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
return res.json({ success: false, error: msg })
}
// Handle error patterns specific to this route.
@@ -177,7 +195,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/utxos/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance
@@ -246,7 +264,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/utxos" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
@@ -371,7 +389,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/tx/data/a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d" -H "accept: application/json"
*
*/
// GET handler for single transaction
@@ -431,7 +449,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/tx/data" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d","a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d"], "verbose":false}'
*
*
*/
@@ -532,7 +550,7 @@ class Electrum {
* @apiDescription Broadcast a raw transaction and return the transaction ID on success or error on failure.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/tx/broadcast" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txHex":"020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000"}'
*
*/
// POST handler for broadcasting a single transaction
@@ -623,7 +641,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/block/header/42?count=2" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/block/header/42?count=2" -H "accept: application/json"
*
*/
// GET handler for single block headers
@@ -689,7 +707,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}'
*
*/
// POST handler for bulk queries on block headers
@@ -788,7 +806,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/balance/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance
@@ -858,7 +876,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/balance" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
@@ -981,7 +999,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/transactions/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance
@@ -1051,7 +1069,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/transactions" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
@@ -1174,7 +1192,7 @@ class Electrum {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/electrumx/unconfirmed/bitcoincash:qr69kyzha07dcecrsvjwsj4s6slnlq4r8c30lxnur3" -H "accept: application/json"
*
*/
// GET handler for single balance
@@ -1244,7 +1262,7 @@ class Electrum {
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
* curl -X POST "https://api.fullstack.cash/v4/electrumx/unconfirmed" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf","bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf"]}'
*
*
*/
@@ -18,7 +18,7 @@ const routeUtils = new RouteUtils()
const BCHJS = require('@psf/bch-js')
const restURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v3/'
: 'https://api.fullstack.cash/v4/'
const bchjs = new BCHJS({ restURL })
let _this
@@ -45,7 +45,7 @@ class Encryption {
const { msg, status } = _this.routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
return res.json({ success: false, error: msg })
}
// Handle error patterns specific to this route.
@@ -76,7 +76,7 @@ class Encryption {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/encryption/publickey/bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf" -H "accept: application/json"
*
*/
async getPublicKey (req, res, next) {
@@ -92,6 +92,15 @@ class Encryption {
})
}
// Generate a user object that can be passed along with internal calls
// from bch-js.
const usrObj = {
ip: req._remoteAddress,
jwtToken: req.locals.jwtToken,
proLimit: req.locals.proLimit,
apiLevel: req.locals.apiLevel
}
const cashAddr = _this.bchjs.Address.toCashAddress(address)
// Prevent a common user error. Ensure they are using the correct network address.
@@ -110,7 +119,7 @@ class Encryption {
cashAddr
)
const rawTxData = await _this.bchjs.Electrumx.transactions(cashAddr)
const rawTxData = await _this.bchjs.Electrumx.transactions(cashAddr, usrObj)
// console.log(`rawTxData: ${JSON.stringify(rawTxData, null, 2)}`)
// Extract just the TXIDs
@@ -126,9 +135,12 @@ class Encryption {
for (let i = 0; i < txids.length; i++) {
const thisTx = txids[i]
// CT 2/24/21: I might want to convert this to the POST call, to take
// advantage of the usrObj. It does not get passed in a GET call.
const txDetails = await _this.bchjs.RawTransactions.getRawTransaction(
thisTx,
true
true,
usrObj
)
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
@@ -80,7 +80,7 @@ class Blockchain {
* block chain.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBestBlockHash" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBestBlockHash" -H "accept: application/json"
*
* @apiSuccess {String} bestBlockHash 000000000000000002bc884334336d99c9a9c616670a9244c6a8c1fc35aa91a1
*/
@@ -111,7 +111,7 @@ class Blockchain {
* @apiDescription Returns an object containing various state info regarding blockchain processing.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockchainInfo" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockchainInfo" -H "accept: application/json"
*
* @apiSuccess {Object} object Object containing data
* @apiSuccess {String} object.chain "main"
@@ -155,7 +155,7 @@ class Blockchain {
* @apiDescription Returns the number of blocks in the longest blockchain.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockCount" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockCount" -H "accept: application/json"
*
* @apiSuccess {Number} bestBlockCount 587665
*/
@@ -188,7 +188,7 @@ class Blockchain {
* returns an Object with information about blockheader hash.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getBlockHeader/000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201?verbose=true" -H "accept: application/json"
*
* @apiParam {String} hash block hash
* @apiParam {Boolean} verbose Return verbose data
@@ -249,7 +249,7 @@ class Blockchain {
* returns an Object with information about blockheader hash.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
* curl -X POST "https://api.fullstack.cash/v4/blockchain/getBlockHeader" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"hashes\":[\"000000000000000005e14d3f9fdfb70745308706615cfa9edca4f4558332b201\",\"00000000000000000568f0a96bf4348847bc84e455cbfec389f27311037a20f3\"],\"verbose\":true}"
*
* @apiParam {String} hash block hash
* @apiParam {Boolean} verbose Return verbose data
@@ -341,7 +341,7 @@ class Blockchain {
* including the main chain as well as orphaned branches.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getChainTips" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getChainTips" -H "accept: application/json"
*
*/
async getChainTips (req, res, next) {
@@ -372,7 +372,7 @@ class Blockchain {
* power on the network.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getDifficulty" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getDifficulty" -H "accept: application/json"
*
*/
async getDifficulty (req, res, next) {
@@ -404,7 +404,7 @@ class Blockchain {
* mempool (unconfirmed)
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolEntry/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
*
*/
async getMempoolEntrySingle (req, res, next) {
@@ -442,7 +442,7 @@ class Blockchain {
* @apiDescription Returns mempool data for multiple transactions
*
* @apiExample Example usage:
* curl -X POST https://api.fullstack.cash/v3/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}"
* curl -X POST https://api.fullstack.cash/v4/blockchain/getMempoolEntry -H "Content-Type: application/json" -d "{\"txids\":[\"a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1\",\"5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e\"]}"
*/
async getMempoolEntryBulk (req, res, next) {
try {
@@ -515,7 +515,7 @@ class Blockchain {
* against the 25 ancestor chain-limit.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getMempoolAncestors/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
*
*/
async getMempoolAncestorsSingle (req, res, next) {
@@ -557,7 +557,7 @@ class Blockchain {
* @apiDescription Returns details on the active state of the TX memory pool.
*
* @apiExample Example usage:
* curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json"
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json"
*
*/
async getMempoolInfo (req, res, next) {
@@ -587,7 +587,7 @@ class Blockchain {
* @apiDescription Returns details on the active state of the TX memory pool.
*
* @apiExample Example usage:
* curl -X GET https://api.fullstack.cash/v3/getMempoolInfo -H "accept: application/json"
* curl -X GET https://api.fullstack.cash/v4/getMempoolInfo -H "accept: application/json"
*
*/
@@ -599,7 +599,7 @@ class Blockchain {
* of string transaction ids.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/getRawMempool/?verbose=true" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/getRawMempool/?verbose=true" -H "accept: application/json"
*
* @apiParam {Boolean} verbose Return verbose data
*
@@ -635,7 +635,7 @@ class Blockchain {
* @apiDescription Returns details about an unspent transaction output.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOut/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33/0?mempool=false" -H "accept: application/json"
*
* @apiParam {String} txid Transaction id (required)
* @apiParam {Number} n Output number (required)
@@ -692,7 +692,7 @@ class Blockchain {
* @apiDescription Returns details about an unspent transaction output (UTXO).
*
* @apiExample Example usage:
* curl "https://api.fullstack.cash/v3/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}'
* curl "https://api.fullstack.cash/v4/blockchain/getTxOut/" -X POST -H "Content-Type: application/json" --data-binary '{"txid":"d5228d2cdc77fbe5a9aa79f19b0933b6802f9f0067f42847fc4fe343664723e5","vout":0,"mempool":true}'
*
* @apiParam {String} txid Transaction id (required)
* @apiParam {Number} vout of transaction (required)
@@ -746,7 +746,7 @@ class Blockchain {
* @apiDescription Returns a hex-encoded proof that 'txid' was included in a block.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/blockchain/getTxOutProofSingle/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33" -H "accept: application/json"
*
* @apiParam {String} txid Transaction id (required)
*
@@ -53,7 +53,7 @@ class Control {
* @apiDescription RPC call which gets basic full node information.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/control/getnetworkinfo" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/control/getnetworkinfo" -H "accept: application/json"
*
*/
async getNetworkInfo (req, res, next) {
@@ -77,7 +77,7 @@ class Mining {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/mining/getMiningInfo" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/mining/getMiningInfo" -H "accept: application/json"
*
*
*/
@@ -107,7 +107,7 @@ class Mining {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/mining/getNetworkHashps?nblocks=120&height=-1" -H "accept: application/json"
*
*
*/
@@ -65,7 +65,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction/02000000010e991f7ccec410f27d333f737f149b5d3be6728687da81072e638aed0063a176010000006b483045022100cd20443b0af090053450bc4ab00d563d4ac5955bb36e0135b00b8a96a19f233302205047f2c70a08c6ef4b76f2d198b33a31d17edfaa7e1e9e865894da0d396009354121024d4e7f522f67105b7bf5f9dbe557e7b2244613fdfcd6fe09304f93877328f6beffffffff02a0860100000000001976a9140ee020c07f39526ac5505c54fa1ab98490979b8388acb5f0f70b000000001976a9143a9b2b0c12fe722fcf653b6ef5dcc38732d6ff5188ac00000000" -H "accept: application/json"
*/
async decodeRawTransactionSingle (req, res, next) {
try {
@@ -103,7 +103,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
*
*
*/
@@ -177,7 +177,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/decodeScript/4830450221009a51e00ec3524a7389592bc27bea4af5104a59510f5f0cfafa64bbd5c164ca2e02206c2a8bbb47eabdeed52f17d7df668d521600286406930426e3a9415fe10ed592012102e6e1423f7abde8b70bca3e78a7d030e5efabd3eb35c19302542b5fe7879c1a16" -H "accept: application/json"
*
*
*/
@@ -218,7 +218,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
*curl -X POST "https://api.fullstack.cash/v3/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
*curl -X POST "https://api.fullstack.cash/v4/rawtransactions/decodeScript" -H "accept:" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
*
*
*/
@@ -308,7 +308,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}'
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction" -H "accept: application/json" -H "Content-Type: application/json" -d '{"txids":["a5f972572ee1753e2fd2457dd61ce5f40fa2f8a30173d417e49feef7542c96a1","5165dc531aad05d1149bb0f0d9b7bda99c73e2f05e314bcfb5b4bb9ca5e1af5e"],"verbose":true}'
*
*/
async getRawTransactionBulk (req, res, next) {
@@ -383,7 +383,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/getRawTransaction/fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33?verbose=true" -H "accept: application/json"
*
*
*/
@@ -429,7 +429,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
* curl -X POST "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction" -H "accept:application/json" -H "Content-Type: application/json" -d '{"hexes":["01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000","01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000"]}'
*
*
*/
@@ -522,7 +522,7 @@ class RawTransactions {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: "
* curl -X GET "https://api.fullstack.cash/v4/rawtransactions/sendRawTransaction/01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000" -H "accept: "
*
*
*/
@@ -24,11 +24,14 @@ class Price {
this.routeUtils = routeUtils
this.priceUrl = 'https://api.coinbase.com/v2/exchange-rates?currency=BCH'
this.coinexPriceUrl =
'https://api.coinex.com/v1/market/ticker?market=bchausdt'
this.router = express.Router()
this.router.get('/', _this.root)
this.router.get('/usd', _this.getUSD)
this.router.get('/rates', _this.getBCHRate)
this.router.get('/bchausd', _this.getBCHAUSD)
}
// DRY error handler.
@@ -53,11 +56,11 @@ class Price {
* @api {get} /price/usd Get the USD price of BCH
* @apiName Get the USD price of BCH
* @apiGroup Price
* @apiDescription Get the USD price of BCH
* @apiDescription Get the USD price of BCH from Coinbase.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/price/usd" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/price/usd" -H "accept: application/json"
*
*/
async getUSD (req, res, next) {
@@ -85,11 +88,11 @@ class Price {
* @api {get} /price/usd Get rates for several different currencies
* @apiName Get rates for several different currencies
* @apiGroup Price
* @apiDescription Get rates for several different currencies
* @apiDescription Get rates for several different currencies from Coinbase.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/price/rates" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/price/rates" -H "accept: application/json"
*
*/
// Get rates for several different currencies
@@ -113,6 +116,40 @@ class Price {
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /price/bchausd Get the USD price of BCHA
* @apiName Get the USD price of BCHA
* @apiGroup Price
* @apiDescription Get the USD price of BCHA from Coinex.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/price/bchausd" -H "accept: application/json"
*
*/
async getBCHAUSD (req, res, next) {
try {
// Request options
const opt = {
method: 'get',
baseURL: _this.coinexPriceUrl,
timeout: 15000
}
const response = await axios.request(opt)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
const price = Number(response.data.data.ticker.last)
return res.json({ usd: price })
} catch (err) {
// Write out error to error log.
wlogger.error('Error in price.js/getBCHAUSD().', err)
return _this.errorHandler(err, res)
}
}
}
module.exports = Price
@@ -175,8 +175,17 @@ function decodeError (err) {
}
}
// Handle general Error objects.
if (err.message) {
return {
message: err.message,
status: 422
}
}
return { msg: false, status: 500 }
} catch (err) {
console.error('unhandled error in route-utils.js/decodeError(): ', err)
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
return { msg: false, status: 500 }
}
+564 -41
View File
@@ -16,8 +16,10 @@ const wlogger = require('../../util/winston-logging')
// Instantiate a local copy of bch-js using the local REST API server.
const LOCAL_RESTURL = process.env.LOCAL_RESTURL
? process.env.LOCAL_RESTURL
: 'https://api.fullstack.cash/v3/'
: 'https://api.fullstack.cash/v4/'
const BCHJS = require('@psf/bch-js')
// const BCHJS = require('../../../../bch-js')
const bchjs = new BCHJS({ restURL: LOCAL_RESTURL })
// Used to convert error messages to strings, to safely pass to users.
@@ -33,7 +35,10 @@ util.inspect.defaultOptions = { depth: 5 }
// Determine the Access password for a private instance of SLPDB.
// https://gist.github.com/christroutner/fc717ca704dec3dded8b52fae387eab2
const SLPDB_PASS = process.env.SLPDB_PASS ? process.env.SLPDB_PASS : 'BITBOX'
// Password for General Purpose (GP) SLPDB.
const SLPDB_PASS_GP = process.env.SLPDB_PASS_GP ? process.env.SLPDB_PASS_GP : 'BITBOX'
// Password for Whitelist (WL) SLPDB.
const SLPDB_PASS_WL = process.env.SLPDB_PASS_WL ? process.env.SLPDB_PASS_WL : 'BITBOX'
// const rawtransactions = require('./full-node/rawtransactions')
const RawTransactions = require('./full-node/rawtransactions')
@@ -41,9 +46,9 @@ const rawTransactions = new RawTransactions()
// Setup REST and TREST URLs used by slpjs
// Dev note: this allows for unit tests to mock the URL.
if (!process.env.REST_URL) process.env.REST_URL = 'https://rest.bitcoin.com/v2/'
if (!process.env.REST_URL) process.env.REST_URL = 'https://bchn.fullstack.cash/v4/'
if (!process.env.TREST_URL) {
process.env.TREST_URL = 'https://trest.bitcoin.com/v2/'
process.env.TREST_URL = 'https://testnet.fullstack.cash/v4/'
}
let _this
@@ -74,6 +79,9 @@ class Slp {
_this.router.post('/validateTxid', _this.validateBulk)
_this.router.get('/validateTxid/:txid', _this.validateSingle)
_this.router.get('/validateTxid2/:txid', _this.validate2Single)
_this.router.get('/validateTxid3/:txid', _this.validate3Single)
_this.router.post('/validateTxid3', _this.validate3Bulk)
_this.router.get('/whitelist', _this.getSlpWhitelist)
_this.router.get('/txDetails/:txid', _this.txDetails)
_this.router.get('/tokenStats/:tokenId', _this.tokenStats)
_this.router.get(
@@ -86,6 +94,8 @@ class Slp {
)
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos)
_this.router.post('/hydrateUtxosWL', _this.hydrateUtxosWL)
_this.router.get('/status', _this.getStatus)
}
// DRY error handler.
@@ -154,7 +164,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/list/259908ae44f46ef585edef4bcc1e50dc06e4c391ac4be929fae27235b8158cf1" -H "accept:application/json"
*
*
*/
@@ -187,7 +197,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}'
* curl -X POST "https://api.fullstack.cash/v4/slp/list" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenIds":["7380843cd1089a1a01783f86af37734dc99667a1cdc577391b5f6ea42fc1bfb4","9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0"]}'
*
*
*/
@@ -335,7 +345,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/balancesForAddress/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
*
*
*/
@@ -427,7 +437,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
// Request options
const opt = {
method: 'get',
@@ -519,7 +529,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json"
* curl -X POST "https://api.fullstack.cash/v4/slp/balancesForAddress" -d "{\"addresses\":[\"simpleledger:qqss4zp80hn6szsa4jg2s9fupe7g5tcg5ucdyl3r57\"]}" -H "accept:application/json"
*
*
*/
@@ -578,7 +588,7 @@ class Slp {
}
}
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
// Collect an array of promises, one for each request to slpserve.
// This is a nested array of promises.
@@ -734,7 +744,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/balancesForToken/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
*
*
*/
@@ -800,7 +810,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
const opt = {
method: 'get',
baseURL: url,
@@ -841,7 +851,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/convert/simpleledger:qz9tzs6d5097ejpg279rg0rnlhz546q4fsnck9wh5m" -H "accept:application/json"
*
*
*/
@@ -887,7 +897,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}'
* curl -X POST "https://api.fullstack.cash/v4/slp/convert" -H "accept:application/json" -H "Content-Type: application/json" -d '{"addresses":["simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l"]}'
*
*
*/
@@ -949,7 +959,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}'
* curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}'
*
*
*/
@@ -987,8 +997,9 @@ class Slp {
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
// console.log('url: ', url)
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
// Get data from SLPDB.
const opt = {
@@ -1002,7 +1013,7 @@ class Slp {
let formattedTokens = []
// Combine the arrays. Why? Generally there is nothing in the u array.
// Combine the confirmed and unconfirmed collections.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
const tokenIds = []
@@ -1024,15 +1035,27 @@ class Slp {
})
// If a user-provided txid doesn't exist in the data, add it with
// valid:false property.
// valid:null property.
// 'null' indicates that SLPDB does not know about the transaction. It
// either has not seen it or has not processed it yet. A determination
// can not be made.
txids.forEach((txid) => {
if (!tokenIds.includes(txid)) {
formattedTokens.push({
txid: txid,
valid: false
valid: null
})
}
})
} else {
// Corner case: No results were returned from SLPDB. Mark each entry
// as 'null'
for (let i = 0; i < txids.length; i++) {
formattedTokens.push({
txid: txids[i],
valid: null
})
}
}
// Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs,
@@ -1052,8 +1075,21 @@ class Slp {
formattedTokens = newOutput
}
// Put the output array in the same order as the input array.
const outAry = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Need to use Array.find() because the returned output array is out
// of order with respect to the txid input array.
const output = formattedTokens.find((elem) => elem.txid === thisTxid)
// console.log(`output: ${JSON.stringify(output, null, 2)}`)
outAry.push(output)
}
res.status(200)
return res.json(formattedTokens)
return res.json(outAry)
} catch (err) {
wlogger.error('Error in slp.ts/validateBulk().', err)
@@ -1069,7 +1105,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
*
*
*/
@@ -1097,7 +1133,7 @@ class Slp {
}
}
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
@@ -1110,11 +1146,12 @@ class Slp {
}
// Get data from SLPDB.
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
// Default return value.
let result = {
txid: txid,
valid: false
valid: null
}
// Build result.
@@ -1132,15 +1169,15 @@ class Slp {
res.status(200)
return res.json(result)
} catch (err) {
wlogger.error('Error in slp.ts/validateSingle().', err)
wlogger.error('Error in slp.js/validateSingle().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /slp/validateTxid2/{txid} Validate single SLP transaction by txid.
* @apiName Validate single SLP transaction by txid.
* @api {get} /slp/validateTxid2/{txid} Validate 2 Single
* @apiName Validate a single SLP transaction by txid using slp-validate.
* @apiGroup SLP
* @apiDescription Validate single SLP transaction by txid, using slp-validate.
* Slower, less efficient method of validating an SLP TXID using the slp-validate
@@ -1149,7 +1186,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid2/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
*
*
*/
@@ -1199,6 +1236,299 @@ class Slp {
}
}
/**
* @api {get} /slp/whitelist SLP token whitelist.
* @apiName SLP token whitelist
* @apiGroup SLP
* @apiDescription Get tokens that are on the whitelist.
* SLPDB is typically used to validate SLP transactions. It can become unstable
* during periods of high network usage. A second SLPDB has been implemented
* that is much more stable, because it only tracks a whitelist of SLP tokens.
* This endpoint will return information on the SLP tokens that are included
* in that whitelist.
*
* For tokens on the whitelist, the /slp/validateTxid3 endpoints can be used.
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/whitelist" -H "accept:application/json"
*
*/
async getSlpWhitelist (req, res, next) {
try {
const list = [
{
name: 'USDH',
tokenId:
'c4b0d62156b3fa5c8f3436079b5394f7edc1bef5dc1cd2f9d0c4d46f82cca479'
},
{
name: 'SPICE',
tokenId:
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
},
{
name: 'PSF',
tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
},
{
name: 'TROUT',
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
},
{
name: 'PSFTEST',
tokenId:
'd0ef4de95b78222bfee2326ab11382f4439aa0855936e2fe6ac129a8d778baa0'
}
]
res.status(200)
return res.json(list)
} catch (err) {
wlogger.error('Error in slp.ts/whitelist().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /slp/validateTxid3/{txid} Validate 3 Single
* @apiName Validate a single txid against a whitelist SLPDB
* @apiGroup SLP
* @apiDescription Alternative validation for tokens on the whitelist
* This endpoint is exactly the same as /slp/validateTxid/{txid} but it uses
* a different SLPDB. This server only indexes the SLP tokens that are on the
* whitelist. You can see which tokens are on the whitelist by calling the
* /slp/whitelist endpoint.
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/validateTxid3/f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a" -H "accept:application/json"
*
*
*/
async validate3Single (req, res, next) {
try {
const txid = req.params.txid
// console.log('validate3Single txid: ', txid)
// Validate input
if (!txid || txid === '') {
res.status(400)
return res.json({ error: 'txid can not be empty' })
}
wlogger.debug('Executing slp/validate/:txid with this txid: ', txid)
const query = {
v: 3,
q: {
db: ['c', 'u'],
find: {
'tx.h': txid
},
limit: 300,
project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 }
}
}
const options = _this.generateCredentialsWL()
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}`
const opt = {
method: 'get',
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
// Get data from SLPDB.
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
// Default return value.
let result = {
txid: txid,
valid: null
}
// Build result.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
if (concatArray.length > 0) {
result = {
txid: concatArray[0].tx.h,
valid: concatArray[0].slp.valid
}
if (!result.valid) {
result.invalidReason = concatArray[0].slp.invalidReason
}
}
res.status(200)
return res.json(result)
} catch (err) {
wlogger.error('Error in slp.js/validate3Single().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /slp/validateTxid3/ Validate 3 Bulk
* @apiName Validate an array of TXIDs against a whitelist SLPDB
* @apiGroup SLP
* @apiDescription Alternative validation for tokens on the whitelist
* This endpoint is exactly the same as /slp/validateTxid but it uses
* a different SLPDB. This server only indexes the SLP tokens that are on the
* whitelist. You can see which tokens are on the whitelist by calling the
* /slp/whitelist endpoint.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/validateTxid3" -H "accept:application/json" -H "Content-Type: application/json" -d '{"txids":["f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a","fb0eeaa501a6e1acb721669c62a3f70741f48ae0fd7f4b8e1d72088785c51952"]}'
*
*
*/
async validate3Bulk (req, res, next) {
try {
const txids = req.body.txids
// console.log(`validate3Bulk txids: `, txids)
// Reject if txids is not an array.
if (!Array.isArray(txids)) {
res.status(400)
return res.json({ error: 'txids needs to be an array' })
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, txids)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
error: 'Array too large.'
})
}
wlogger.debug('Executing slp/validate with these txids: ', txids)
const query = {
v: 3,
q: {
db: ['c', 'u'],
find: {
'tx.h': { $in: txids }
},
limit: 300,
project: { 'slp.valid': 1, 'tx.h': 1, 'slp.invalidReason': 1 }
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_WHITELIST_URL}q/${b64}`
// console.log('url: ', url)
const options = _this.generateCredentialsWL()
// Get data from SLPDB.
const opt = {
method: 'get',
baseURL: url,
headers: options.headers,
timeout: options.timeout
}
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes.data: ${JSON.stringify(tokenRes.data, null, 2)}`)
let formattedTokens = []
// Combine the confirmed and unconfirmed collections.
const concatArray = tokenRes.data.c.concat(tokenRes.data.u)
const tokenIds = []
if (concatArray.length > 0) {
concatArray.forEach((token) => {
tokenIds.push(token.tx.h) // txid
const validationResult = {
txid: token.tx.h,
valid: token.slp.valid
}
// If the txid is invalid, add the reason it's invalid.
if (!validationResult.valid) {
validationResult.invalidReason = token.slp.invalidReason
}
formattedTokens.push(validationResult)
})
// If a user-provided txid doesn't exist in the data, add it with
// valid:null property.
// 'null' indicates that SLPDB does not know about the transaction. It
// either has not seen it or has not processed it yet. A determination
// can not be made.
txids.forEach((txid) => {
if (!tokenIds.includes(txid)) {
formattedTokens.push({
txid: txid,
valid: null
})
}
})
} else {
// Corner case: No results were returned from SLPDB. Mark each entry
// as 'null'
for (let i = 0; i < txids.length; i++) {
formattedTokens.push({
txid: txids[i],
valid: null
})
}
}
// Catch a corner case of repeated txids. SLPDB will remove redundent TXIDs,
// which will cause the output array to be smaller than the input array.
if (txids.length > formattedTokens.length) {
const newOutput = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Find the element that matches the current txid.
const elem = formattedTokens.filter((x) => x.txid === thisTxid)
newOutput.push(elem[0])
}
// Replace the original output object with the new output object.
formattedTokens = newOutput
}
// console.log(
// `formattedTokens: ${JSON.stringify(formattedTokens, null, 2)}`
// )
// Put the output array in the same order as the input array.
const outAry = []
for (let i = 0; i < txids.length; i++) {
const thisTxid = txids[i]
// Need to use Array.find() because the returned output array is out
// of order with respect to the txid input array.
const output = formattedTokens.find((elem) => elem.txid === thisTxid)
// console.log(`output: ${JSON.stringify(output, null, 2)}`)
outAry.push(output)
}
res.status(200)
return res.json(outAry)
} catch (err) {
wlogger.error('Error in slp.js/validate3Bulk().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {get} /slp/txDetails/{txid} SLP transaction details.
* @apiName SLP transaction details.
@@ -1207,7 +1537,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/txDetails/8ab4ac5dea3f9024e3954ee5b61452955d659a34561f79ef62ac44e133d0980e" -H "accept:application/json"
*
*
*/
@@ -1240,7 +1570,7 @@ class Slp {
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
const options = _this.generateCredentials()
const options = _this.generateCredentialsGP()
const opt = {
method: 'get',
baseURL: url,
@@ -1251,7 +1581,9 @@ class Slp {
const tokenRes = await _this.axios.request(opt)
// console.log(`tokenRes: ${util.inspect(tokenRes)}`)
if (tokenRes.data.c.length === 0) {
// Return 'not found' error if both the confirmed and unconfirmed
// collections are empty.
if (tokenRes.data.c.length === 0 && tokenRes.data.u.length === 0) {
res.status(404)
return res.json({ error: 'TXID not found' })
}
@@ -1297,7 +1629,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/tokenStats/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0" -H "accept:application/json"
*
*
*/
@@ -1328,7 +1660,7 @@ class Slp {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json"
* curl -X GET "https://api.fullstack.cash/v4/slp/transactions/9ba379fe8171176d4e7e6771d9a24cd0e044c7b788d5f86a3fdf80904832b2c0/simpleledger:qrxa0unrn67rtn85v7asfddhhth43ecnxua0antk2l" -H "accept:application/json"
*
*
*/
@@ -1398,11 +1730,32 @@ class Slp {
}
// Generates a Basic Authorization header for slpserve.
generateCredentials () {
generateCredentialsGP () {
// Generate the Basic Authentication header for a private instance of SLPDB.
const username = 'BITBOX'
const password = SLPDB_PASS
const password = SLPDB_PASS_GP
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
const options = {
headers: {
authorization: readyCredential
},
timeout: 15000
}
return options
}
// Generates a Basic Authorization header for slpserve.
generateCredentialsWL () {
// Generate the Basic Authentication header for a private instance of SLPDB.
const username = 'BITBOX'
const password = SLPDB_PASS_WL
const combined = `${username}:${password}`
// console.log(`combined: ${combined}`)
var base64Credential = Buffer.from(combined).toString('base64')
var readyCredential = `Basic ${base64Credential}`
@@ -1437,12 +1790,29 @@ class Slp {
sendOutputs.push(string.toString())
})
// Because you are not using Insight API indexer, you do not get the
// sending addresses from an indexer or from the node.
// However, they are available from the SLPDB output.
const tokenInputs = transaction.in
// Collect the input addresses
const sendInputs = []
for (let i = 0; i < tokenInputs.length; i += 1) {
const tokenInput = tokenInputs[i]
const sendInput = {}
sendInput.address = tokenInput.e.a
sendInputs.push(sendInput)
}
const obj = {
tokenInfo: {
versionType: transaction.slp.detail.versionType,
tokenName: transaction.slp.detail.name,
tokenTicker: transaction.slp.detail.symbol,
transactionType: transaction.slp.detail.transactionType,
tokenIdHex: transaction.slp.detail.tokenIdHex,
sendOutputs: sendOutputs
sendOutputs: sendOutputs,
sendInputsFull: sendInputs,
sendOutputsFull: transaction.slp.detail.outputs
},
tokenIsValid: transaction.slp.valid
}
@@ -1516,7 +1886,7 @@ class Slp {
* (1 or 2) will also be returned.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}'
* curl -X POST "https://api.fullstack.cash/v4/slp/generateSendOpReturn" -H "accept:application/json" -H "Content-Type: application/json" -d '{"tokenUtxos":[{"tokenId": "0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1","decimals": 0, "tokenQty": 2}], "sendQty": 1.5}'
*
*
*/
@@ -1555,6 +1925,8 @@ class Slp {
})
}
// console.log('sendQty: ', sendQty)
// console.log(`tokenUtxos: `, tokenUtxos)
const opReturn = await _this.bchjs.SLP.TokenType1.generateSendOpReturn(
tokenUtxos,
sendQty
@@ -1566,6 +1938,7 @@ class Slp {
res.status(200)
return res.json({ script, outputs: opReturn.outputs })
} catch (err) {
console.log('err: ', err)
wlogger.error('Error in slp.js/generateSendOpReturn().', err)
// Decode the error message.
@@ -1598,7 +1971,7 @@ class Slp {
* not been confirmed.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
* curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
*
*
*/
@@ -1606,6 +1979,24 @@ class Slp {
try {
const utxos = req.body.utxos
// Extract a delay value if the user passed it in.
const usrObjIn = req.body.usrObj
let utxoDelay = 0
if (usrObjIn && usrObjIn.utxoDelay) { utxoDelay = usrObjIn.utxoDelay }
// console.log('req: ', req)
// console.log(`req._remoteAddress: ${req._remoteAddress}`)
// Generate a user object that can be passed along with internal calls
// from bch-js.
const usrObj = {
ip: req._remoteAddress,
jwtToken: req.locals.jwtToken,
proLimit: req.locals.proLimit,
apiLevel: req.locals.apiLevel,
utxoDelay
}
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
@@ -1640,8 +2031,8 @@ class Slp {
const theseUtxos = utxos[i].utxos
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details : ', details)
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos, usrObj)
// console.log('details: ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
@@ -1655,17 +2046,149 @@ class Slp {
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
console.log('msg: ', msg)
console.log('status: ', status)
if (msg) {
res.status(status)
return res.json({ error: msg })
return res.json({ error: msg, message: msg, success: false })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxos()'
error: 'Undetermined error in hydrateUtxos()',
message: err.message
})
}
}
/**
* @api {post} /slp/hydrateUtxosWL/ hydrateUtxosWL
* @apiName SLP hydrateUtxosWL
* @apiGroup SLP
* @apiDescription Hydrate UTXO data with SLP information, using only the whitelist SLPDB.
*
* This call is identical to `hydrateUtxos`, except it will only use the
* filtered SLPDB with a whitelist. This results in faster performance, more
* reliable uptime, but more frequent `isValid: null` values. Some use-cases
* prioritize the speed and reliability over acceptance of a wide range of
* SLP tokens.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v4/slp/hydrateUtxosWL" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}]}'
*
*
*/
async hydrateUtxosWL (req, res, next) {
try {
const utxos = req.body.utxos
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
return res.json({
error: 'Input must be an array.'
})
}
if (!utxos.length) {
res.status(422)
return res.json({
error: 'Array should not be empty'
})
}
if (utxos.length > 20) {
res.status(422)
return res.json({
error: 'Array too long, max length is 20'
})
}
if (!utxos[0].utxos) {
res.status(422)
return res.json({
error: 'Each element in array should have a utxos property'
})
}
// Loop through each address and query the UTXOs for that element.
for (let i = 0; i < utxos.length; i++) {
const theseUtxos = utxos[i].utxos
// console.log(`theseUtxos: ${JSON.stringify(theseUtxos, null, 2)}`)
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetailsWL(theseUtxos)
// console.log('details : ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
}
res.status(200)
return res.json({ slpUtxos: utxos })
} catch (err) {
wlogger.error('Error in slp.js/hydrateUtxosWL().', err)
console.error('Error in slp.js/hydrateUtxosWL().', err)
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
console.log('msg: ', msg)
console.log('status: ', status)
if (msg) {
res.status(status)
return res.json({ error: msg, message: msg, success: false })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxosWL()',
message: 'Error in hydrateUtxosWL()'
})
}
}
/**
* @api {get} /slp/status Get the health status of SLPDB
* @apiName Get the health status of SLPDB
* @apiGroup SLP
* @apiDescription Get the health status of SLPDB
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v4/slp/status" -H "accept:application/json" -H "Content-Type: application/json"
*
*/
async getStatus (req, res, next) {
try {
const query = {
v: 3,
q: {
db: ['s'],
find: { context: 'SLPDB' },
limit: 10
}
}
const s = JSON.stringify(query)
const b64 = Buffer.from(s).toString('base64')
const url = `${process.env.SLPDB_URL}q/${b64}`
// Request options
const opt = {
method: 'get',
baseURL: url
}
const tokenRes = await _this.axios.request(opt)
const status = tokenRes.data.s[0]
res.status(200)
return res.json(status)
} catch (err) {
// console.log(err)
wlogger.error('Error in slp.js/getStatus().', err)
return _this.errorHandler(err, res)
}
}
}
module.exports = Slp
+41 -41
View File
@@ -6,15 +6,13 @@ const axios = require('axios')
const routeUtils = require('./route-utils')
const wlogger = require('../../util/winston-logging')
const Blockbook = require('./blockbook')
const blockbook = new Blockbook()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/'
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v4/'
// const bchjsHTTP = axios.create({
// baseURL: process.env.RPC_BASEURL
@@ -37,9 +35,28 @@ const bchjs = new BCHJS()
let _this
class UtilRoute {
constructor () {
constructor (utilConfig) {
this.bchjs = bchjs
this.blockbook = blockbook
// this.blockbook = blockbook
if (!utilConfig) {
throw new Error(
'Must pass a config object when instantiating the Util library.'
)
}
if (!utilConfig.electrumx) {
throw new Error(
'Must pass an instance of Electrumx when instantiating the Util library.'
)
}
this.electrumx = utilConfig.electrumx
this.router = router
this.router.get('/', this.root)
this.router.get('/validateAddress/:address', this.validateAddressSingle)
this.router.post('/validateAddress', this.validateAddressBulk)
this.router.post('/sweep', this.sweepWif)
_this = this
}
@@ -56,7 +73,7 @@ class UtilRoute {
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
* curl -X GET "https://api.fullstack.cash/v4/util/validateAddress/bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c" -H "accept: application/json"
*
*
*/
@@ -105,8 +122,8 @@ class UtilRoute {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}'
* curl -X POST "https://api.fullstack.cash/v3/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}'
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"]}'
* curl -X POST "https://api.fullstack.cash/v4/util/validateAddress" -H "accept: application/json" -H "Content-Type: application/json" -d '{"addresses":["bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c","bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0"],"from": 1, "to": 5}'
*
*
*/
@@ -165,7 +182,7 @@ class UtilRoute {
} = routeUtils.setEnvVars()
// Loop through each address and creates an array of requests to call in parallel
const promises = addresses.map(async address => {
const promises = addresses.map(async (address) => {
requestConfig.data.id = 'validateaddress'
requestConfig.data.method = 'validateaddress'
requestConfig.data.params = [address]
@@ -177,7 +194,7 @@ class UtilRoute {
const axiosResult = await axios.all(promises)
// Retrieve the data part of the result.
const result = axiosResult.map(x => x.data.result)
const result = axiosResult.map((x) => x.data.result)
res.status(200)
return res.json(result)
@@ -208,8 +225,8 @@ class UtilRoute {
*
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}'
* curl -X POST "https://api.fullstack.cash/v3/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}'
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "balanceOnly": true}'
* curl -X POST "https://api.fullstack.cash/v4/util/sweep" -H "accept: application/json" -H "Content-Type: application/json" -d '{"wif":"Kz52sdXLAiKtH82iAFRi6aTZanWtW5Eyv37KWdpY6tTv7pjoHste", "toAddr": "bitcoincash:qpt8m4kqu963geedyrur6pdggqmv5kxwnq0rn322qu"}'
*
*
*/
@@ -243,12 +260,11 @@ class UtilRoute {
const fromAddr = bchjs.ECPair.toCashAddress(ecPair)
// Get a balance on the public address
const balances = await _this.blockbook.balanceFromBlockbook(fromAddr)
const balances = await _this.electrumx._balanceFromElectrumx(fromAddr)
// console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
// Total balance is the sum of the confirmed and unconfirmed balance.
const totalBalance =
Number(balances.balance) + Number(balances.unconfirmedBalance)
const totalBalance = balances.confirmed + balances.unconfirmed
// Exit if balance is zero.
if (isNaN(totalBalance) || totalBalance === 0) {
@@ -263,7 +279,7 @@ class UtilRoute {
}
// Get all UTXOs help by the address.
const utxos = await _this.blockbook.utxosFromBlockbook(fromAddr)
const utxos = await _this.electrumx._utxosFromElectrumx(fromAddr)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
const tokenUtxos = []
@@ -368,7 +384,7 @@ class UtilRoute {
let utxos = options.utxos
// Ensure all utxos have the satoshis property.
utxos = utxos.map(x => {
utxos = utxos.map((x) => {
x.satoshis = Number(x.value)
return x
})
@@ -386,9 +402,9 @@ class UtilRoute {
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
originalAmount = originalAmount + utxo.satoshis
originalAmount = originalAmount + utxo.value
transactionBuilder.addInput(utxo.txid, utxo.vout)
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
}
if (originalAmount < 546) {
@@ -423,7 +439,7 @@ class UtilRoute {
ecPair,
redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL,
utxo.satoshis
utxo.value
)
}
@@ -461,7 +477,7 @@ class UtilRoute {
// Ensure there is only one class of token in the wallet. Throw an error if
// there is more than one.
const tokenId = tokenUtxos[0].tokenId
const otherTokens = tokenUtxos.filter(x => x.tokenId !== tokenId)
const otherTokens = tokenUtxos.filter((x) => x.tokenId !== tokenId)
if (otherTokens.length > 0) {
throw new Error(
'Multiple token classes detected. This function only supports a single class of token.'
@@ -483,9 +499,9 @@ class UtilRoute {
for (let i = 0; i < allUtxos.length; i++) {
const utxo = allUtxos[i]
originalAmount = originalAmount + utxo.satoshis
originalAmount = originalAmount + utxo.value
transactionBuilder.addInput(utxo.txid, utxo.vout)
transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
}
if (originalAmount < 300) {
@@ -565,7 +581,7 @@ class UtilRoute {
ecPair,
redeemScript,
transactionBuilder.hashTypes.SIGHASH_ALL,
thisUtxo.satoshis
thisUtxo.value
)
}
@@ -585,20 +601,4 @@ class UtilRoute {
}
}
const utilRoute = new UtilRoute()
router.get('/', utilRoute.root)
router.get('/validateAddress/:address', utilRoute.validateAddressSingle)
router.post('/validateAddress', utilRoute.validateAddressBulk)
router.post('/sweep', utilRoute.sweepWif)
module.exports = {
router,
// testableComponents: {
// root,
// validateAddressSingle,
// validateAddressBulk,
// sweepWif
// }
UtilRoute
}
module.exports = UtilRoute
+9
View File
@@ -154,8 +154,17 @@ class RouteUtils {
}
}
// Handle general Error objects.
if (err.message) {
return {
msg: err.message,
status: 422
}
}
return { msg: false, status: 500 }
} catch (err) {
console.error('unhandled error in route-utils.js/decodeError(): ', err)
wlogger.error('unhandled error in route-utils.js/decodeError(): ', err)
return { msg: false, status: 500 }
}
+8 -2
View File
@@ -11,13 +11,19 @@ require('winston-daily-rotate-file')
var NETWORK = process.env.NETWORK
// Default 1 Megabyte
var LOG_MAX_SIZE = process.env.LOG_MAX_SIZE ? process.env.LOG_MAX_SIZE : '1m'
// Default 5 days.
var LOG_MAX_FILES = process.env.LOG_MAX_FILES ? process.env.LOG_MAX_FILES : '5d'
// Configure daily-rotation transport.
var transport = new winston.transports.DailyRotateFile({
filename: `${__dirname}/../../logs/rest-${NETWORK}-%DATE%.log`,
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '1m',
maxFiles: '5d',
maxSize: LOG_MAX_SIZE,
maxFiles: LOG_MAX_FILES,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
+45 -13
View File
@@ -1,32 +1,64 @@
#!/bin/bash
# Full node
export RPC_BASEURL=http://<full node ip>:8332/
export RPC_USERNAME=<RPC username>
export RPC_PASSWORD=<RPC password>
# Docker containers for the below infrastructure is described here:
# https://psfoundation.cash/blog/cash-stack
# Pre-synced databases can be downloaded on the CashStrap page:
# https://fullstack.cash/cashstrap
# Which network are you using?
export NETWORK=mainnet
#export NETWORK=testnet
# Full node
export RPC_IP=<ip>:8332
export RPC_BASEURL=http://$RPC_IP/
export RPC_USERNAME=bitcoin
export RPC_PASSWORD=password
# SLPDB
export SLPDB_PASS_GP=somelongpassword
export SLPDB_URL=http://<SLPDB IP>:12300/
# Blockbook
export BLOCKBOOK_URL=https://<Blockbook IP>:9131/
# Allow node.js to make network calls to https using self-signed certificate.
export NODE_TLS_REJECT_UNAUTHORIZED=0
# Use the same address as SLPDB_URL if you don't have a separate whitelist server.
export SLPDB_PASS_WL=somelongpassword
export SLPDB_WHITELIST_URL=http://<SLPDB IP>:12300/
# slp-api alternative SLP validator using slp-validate:
# https://github.com/Permissionless-Software-Foundation/slp-api
export SLP_API_URL=http://10.0.0.5:5001/
# Mainnet Fulcrum / ElectrumX
export FULCRUM_URL=192.168.0.6
export FULCRUM_PORT=50002
# Redis DB - Used for rate limiting - customize to your own Redis installation.
export REDIS_PORT=6379
export REDIS_HOST=172.17.0.1
# JWT Token Secret
# This is used to verify JWT tokens generated with jwt-bch-api:
# https://github.com/Permissionless-Software-Foundation/jwt-bch-api
export TOKENSECRET=somelongpassword
# So that bch-api can call bch-js locally.
export LOCAL_RESTURL=http://127.0.0.1:3000/v3/
# slp-api alternative SLP validator.
export SLP_API_URL=http://10.0.0.5:5001/
export LOCAL_RESTURL=http://127.0.0.1:3000/v4/
# Basic Authentication password
export PRO_PASS=somerandomepassword:someotherrandompassword:aThirdPassword
# Whitelisted domains. Automatically give pro-tier rate limit access to apps
# that originate froma domain on the whitelist.
export WHITELIST_DOMAINS=fullstack.cash,psfoundation.cash,torlist.cash
# Rate Limits. Numbers are divided into 1000. e.g. 1000 / 50 = 20 RPM for ANON.
# Requests use the ANON rate limit if they fail to pass in a JWT token.
# ANON = 20 requests per minute (RPM)
export ANON_RATE_LIMIT=50
# 10 = 100 RPM
export WHITELIST_RATE_LIMIT=10
# Set logging parameters
#1m means no more than 1 megabyte
export LOG_MAX_SIZE=1m
#5d means store no more than 5 days
export LOG_MAX_FILES=5d
npm start
-1122
View File
File diff suppressed because it is too large Load Diff
@@ -20,7 +20,7 @@ const assert = chai.assert
const sinon = require('sinon')
const ElecrumxRoute = require('../../src/routes/v3/electrumx')
const ElecrumxRoute = require('../../src/routes/v4/electrumx')
// Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks')
@@ -42,7 +42,7 @@ function expectRouteError (res, result, expectedError, code = 400) {
assert.equal(result.success, false)
}
describe('#ElectrumX Router', () => {
describe('#Electrumx', () => {
let req, res
let sandbox
const electrumxRoute = new ElecrumxRoute()
@@ -199,11 +199,11 @@ describe('#ElectrumX Router', () => {
})
describe('#getUtxos', () => {
it('should throw 400 if address is empty', async () => {
it('should throw 422 if address is empty', async () => {
const result = await electrumxRoute.getUtxos(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -233,7 +233,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getUtxos(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1063,7 +1063,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getBalance(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1093,7 +1093,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getBalance(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1350,7 +1350,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getTransactions(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1380,7 +1380,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getTransactions(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1645,7 +1645,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -1675,7 +1675,7 @@ describe('#ElectrumX Router', () => {
const result = await electrumxRoute.getMempool(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.equal(res.statusCode, 400, 'Expect 400 status code')
assert.equal(res.statusCode, 422, 'Expect 422 status code')
assert.property(result, 'error')
assert.include(result.error, 'Unsupported address format')
@@ -12,7 +12,7 @@ const chai = require('chai')
const assert = chai.assert
const sinon = require('sinon')
const Blockchain = require('../../src/routes/v3/full-node/blockchain')
const Blockchain = require('../../src/routes/v4/full-node/blockchain')
const uut = new Blockchain()
const util = require('util')
@@ -821,6 +821,7 @@ describe('#BlockchainRouter', () => {
])
})
})
describe('getRawMempool()', () => {
it('should throw 503 when network issues', async () => {
// Save the existing RPC URL.
@@ -842,6 +843,7 @@ describe('#BlockchainRouter', () => {
'Error message expected'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
@@ -856,6 +858,7 @@ describe('#BlockchainRouter', () => {
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
@@ -871,7 +874,7 @@ describe('#BlockchainRouter', () => {
)
})
it('should GET /getMempoolInfo', async () => {
it('should GET /getRawMempool', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
@@ -886,6 +889,7 @@ describe('#BlockchainRouter', () => {
// Not sure what other assertions should be made here.
})
})
describe('getMempoolEntrySingle()', () => {
it('should throw 400 if txid is empty', async () => {
const result = await uut.getMempoolEntrySingle(req, res)
+1 -1
View File
@@ -11,7 +11,7 @@
const chai = require('chai')
const assert = chai.assert
const ControlRoute = require('../../src/routes/v3/full-node/control')
const ControlRoute = require('../../src/routes/v4/full-node/control')
const uut = new ControlRoute()
const sinon = require('sinon')
@@ -19,7 +19,7 @@ const sinon = require('sinon')
if (!process.env.TEST) process.env.TEST = 'unit'
// Only load blockbook library after setting BLOCKBOOK_URL env var.
const EncryptionRoute = require('../../src/routes/v3/encryption')
const EncryptionRoute = require('../../src/routes/v4/encryption')
const encryptionRoute = new EncryptionRoute()
// Mocking data.
@@ -4,14 +4,14 @@
'use strict'
// const assert = require('chai').assert
const assert = require('chai').assert
// const axios = require('axios')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const Price = require('../../../src/routes/v3/price')
const Price = require('../../../src/routes/v4/price')
const price = new Price()
const { mockReq, mockRes } = require('../mocks/express-mocks')
@@ -29,6 +29,17 @@ describe('#price', () => {
it('should get the USD price', async () => {
const result = await price.getUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
describe('#getBCHAUSD', () => {
it('should get the USD price of BCHA', async () => {
const result = await price.getBCHAUSD(req, res)
console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
})
@@ -13,9 +13,9 @@ const axios = require('axios')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
// const SERVER = `http://192.168.0.36:12400/v3/`
const SERVER = 'http://localhost:3000/v3/'
// const SERVER = 'https://api.fullstack.cash/v3/'
// const SERVER = `http://192.168.0.36:12400/v4/`
const SERVER = 'http://localhost:3000/v4/'
// const SERVER = 'https://api.fullstack.cash/v4/'
//
// const TEST_JWT =
// 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVlODhhY2JmMDIyMWMxMDAxMmFkOTNmZiIsImVtYWlsIjoiY2hyaXMudHJvdXRuZXJAZ21haWwuY29tIiwiYXBpTGV2ZWwiOjQwLCJyYXRlTGltaXQiOjMsImlhdCI6MTYwMDYyODk1MSwiZXhwIjoxNjAzMjIwOTUxfQ.JPXDJQsxJFtCGZjHOd-hRfJuY41Ef_FQ4ET06CtYdNk'
@@ -7,21 +7,22 @@
TODO:
-/rawtransactions/sendRawTransaction is more appropropriate for an e2e test,
so it is omitted here.
- Replace request-promise with axios.
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
// const chai = require('chai')
// const assert = chai.assert
// const rawtransactions = require('../../../dist/routes/v2/rawtransactions')
const rp = require('request-promise')
// const rp = require('request-promise')
// Used for debugging.
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
// const mockData = require('../mocks/raw-transactions-mocks')
/*
describe('#Raw-Transactions', () => {
describe('#root', () => {
it('should return root', async () => {
@@ -189,3 +190,4 @@ describe('#Raw-Transactions', () => {
})
})
})
*/
@@ -19,7 +19,7 @@ if (!process.env.SLPDB_URL) {
)
}
const SLP = require('../../../src/routes/v3/slp')
const SLP = require('../../../src/routes/v4/slp')
const slp = new SLP()
const BCHJS = require('@psf/bch-js')
@@ -110,8 +110,8 @@ describe('#slp', () => {
req.body.tokenUtxos = [
{
tokenId:
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
decimals: 0,
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
decimals: 8,
tokenQty: 2
}
]
@@ -214,6 +214,39 @@ describe('#slp', () => {
})
})
describe('#hydrateUtxosWL', () => {
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
tx_hash:
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
tx_pos: 1,
value: 546
}
]
}
]
req.body.utxos = utxos
const result = await slp.hydrateUtxosWL(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 1)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'tx_hash')
assert.property(result.slpUtxos[0].utxos[0], 'tx_pos')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
})
})
describe('#validate2Single', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
@@ -258,4 +291,171 @@ describe('#slp', () => {
// )
// })
})
describe('#validateBulk', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await slp.validateBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
})
describe('#validate3Bulk', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await slp.validate3Bulk(req, res)
console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
})
describe('#getStatus', () => {
it('should get the SLPDB status', async () => {
const result = await slp.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'bchBlockHeight')
})
})
})
@@ -18,7 +18,7 @@ if (!process.env.SLPDB_URL) {
throw new Error('SLPDB_URL and SLPDB_PASS must be defined in order to run these tests.')
}
const SLPDB = require('../../../src/routes/v3/services/slpdb')
const SLPDB = require('../../../src/routes/v4/services/slpdb')
const slpdb = new SLPDB()
describe('#slpdb', () => {
+1 -1
View File
@@ -10,7 +10,7 @@
const chai = require('chai')
const assert = chai.assert
const MiningRoute = require('../../src/routes/v3/full-node/mining')
const MiningRoute = require('../../src/routes/v4/full-node/mining')
const uut = new MiningRoute()
// const nock = require('nock') // HTTP mocking
@@ -216,6 +216,26 @@ const mockCoinbaseFeed = {
}
}
module.exports = {
mockCoinbaseFeed
const mockCoinexFeed = {
code: 0,
data: {
date: 1605649499848,
ticker: {
vol: '26717.39062407',
low: '10.4000',
open: '11.8000',
high: '19.0029',
last: '18.5000',
buy: '18.0100',
buy_amount: '200.00000000',
sell: '18.5000',
sell_amount: '47.89580474'
}
},
message: 'OK'
}
module.exports = {
mockCoinbaseFeed,
mockCoinexFeed
}
@@ -341,6 +341,22 @@ const mockTwoRedundentTxid = {
u: []
}
const mockPsfToken = {
c: [
{
_id: '5fcaf6152898f9887902986c',
tx: {
h: 'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
},
slp: {
valid: true,
invalidReason: null
}
}
],
u: []
}
const mockTxHistory = [
{
tx: {
@@ -526,6 +542,483 @@ const mockTxHistory = [
}
]
const mockValidateBulk = {
c: [
{
_id: '5fcc1ae2aff6379ca5bbb213',
tx: {
h: '336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fc9b4db4d54eece25b52762',
tx: {
h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fc989a14d54eece25af88a6',
tx: {
h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
},
slp: {
valid: false,
invalidReason: 'Token outputs are greater than valid token inputs.'
}
},
{
_id: '5fc974d14d54eece25ad5df2',
tx: {
h: '3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
},
slp: {
valid: true,
invalidReason: null
}
}
],
u: []
}
const mockValidate3Bulk = {
c: [
{
_id: '5fcaf60b2898f98879029754',
tx: {
h: 'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
},
slp: {
valid: true,
invalidReason: null
}
},
{
_id: '5fcaf5382898f988790275d0',
tx: {
h: '2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
},
slp: {
valid: false,
invalidReason: 'Token outputs are greater than valid token inputs.'
}
}
],
u: []
}
const mockStatus = {
_id: '5fe408b7cd2bd2000f7fdd0a',
version: '1.0.0-beta-rc13',
versionHash: 'bb4a805610b9e4d67c1595b716df36190c75e8b1',
deplVersionHash: null,
startCmd: 'node index run',
context: 'SLPDB',
lastStatusUpdate: {
utc: 'Thu, 24 Dec 2020 03:19:19 GMT',
unix: 1608779959
},
lastIncomingTxnZmq: {
utc: 'Thu, 24 Dec 2020 03:19:16 GMT',
unix: 1608779956
},
lastIncomingBlockZmq: {
utc: 'Thu, 24 Dec 2020 03:00:27 GMT',
unix: 1608778827
},
lastOutgoingTxnZmq: null,
lastOutgoingBlockZmq: null,
state: 'RUNNING',
stateHistory: [
{
utc: 'Mon, 07 Dec 2020 15:26:23 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Mon, 07 Dec 2020 15:52:20 GMT',
state: 'RUNNING'
},
{
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
state: 'PRE_STARTUP'
},
{
utc: 'Tue, 08 Dec 2020 14:42:29 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Tue, 08 Dec 2020 14:59:53 GMT',
state: 'RUNNING'
},
{
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
state: 'EXITED_ON_ERROR'
},
{
utc: 'Tue, 08 Dec 2020 19:35:36 GMT',
state: 'EXITED_ON_ERROR'
},
{
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
state: 'PRE_STARTUP'
},
{
utc: 'Tue, 08 Dec 2020 19:35:49 GMT',
state: 'STARTUP_BLOCK_SYNC'
},
{
utc: 'Tue, 08 Dec 2020 19:53:04 GMT',
state: 'RUNNING'
}
],
network: 'mainnet',
bchBlockHeight: 667203,
bchBlockHash:
'0000000000000000035f9c26b9ef3db37eba8c9351218c67c92d80444b867858',
slpProcessedBlockHeight: 667203,
mempoolInfoBch: {
loaded: true,
size: 441,
bytes: 192651,
usage: 663056,
maxmempool: 300000000,
mempoolminfee: 0.00001,
minrelaytxfee: 0.00001
},
mempoolSizeSlp: 68,
tokensCount: 77879,
pastStackTraces: [
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)',
'[Tue, 08 Dec 2020 19:35:36 GMT] MongoServerSelectionError: connection <monitor> to 172.17.0.1:12301 timed out\n at Timeout._onTimeout (/home/safeuser/SLPDB/node_modules/mongodb/lib/core/sdam/topology.js:438:30)\n at listOnTimeout (internal/timers.js:554:17)\n at processTimers (internal/timers.js:497:7)'
],
doubleSpends: [
{
txo:
'3e377f67e9d065f02e47633d847cdb04b0679d51baccdc0feea5297529e330b5:820',
details: {
originalTxid:
'dc148149d25a546fffe0b207c62b192811c288481df5f43f1f76f67b6004f98c',
current:
'de12db60e253c378eeebb1df85153168a998db8267ec54b38589cc31348b2b8d',
time: {
utc: 'Sat, 19 Dec 2020 02:25:37 GMT',
unix: 1608344737
}
}
},
{
txo: '4b7e94fc59a685bce31ee9444d20ebb0ae0478111a8bfa7ec9b5c381e2657191:2',
details: {
originalTxid:
'a4e00c025c07a770874ecc6b2d1004fe3c561f2aceb0a8b95996e26709df1a57',
current:
'33ccab4a971331897bb3ff3978366e3575c54d83ae1a35984e50d93cffe66b95',
time: {
utc: 'Sat, 19 Dec 2020 02:33:28 GMT',
unix: 1608345208
}
}
},
{
txo: 'bd5ac3d02652038cc888922e50c9b57db0dfdbb5b26bb28a2e85fd28493e85e1:2',
details: {
originalTxid:
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
current:
'0b3ab983c5abb1ceb6f60035ff869e159e27ab5d2a4173d0d6ce027348d4dbdd',
time: {
utc: 'Sat, 19 Dec 2020 04:21:27 GMT',
unix: 1608351687
}
}
},
{
txo:
'8d91432598943cc22b93d10df6808abccba4baebdf2273b375ce3616a2001c07:573',
details: {
originalTxid:
'53032355ab142ac050f1e37445ecb19ad22c5331185df7b3173e88563194ba18',
current:
'7ddc241b62625ddc1d134ae17e74bf2b6f63a4c28e3c3a4f11d833270bdebcdb',
time: {
utc: 'Sat, 19 Dec 2020 04:26:12 GMT',
unix: 1608351972
}
}
},
{
txo: 'ff3a9268952408217f60f1689aa1137e929e6572413e429554996ec3e9b223b5:2',
details: {
originalTxid:
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
current:
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
time: {
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
unix: 1608382716
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:314',
details: {
originalTxid:
'342adc882eff630aa7b50098adaace3626c1e60acbdf937e41832c4508bbdb4b',
current:
'01538a27e7e164bd52a9fd3df36cd724846f44a4c26aea8764ee982486b580ff',
time: {
utc: 'Sat, 19 Dec 2020 12:58:36 GMT',
unix: 1608382716
}
}
},
{
txo: 'ff28ad4063faa250797fa39c823b919d157ecae82b8bf68199eb0b9babbb0c12:2',
details: {
originalTxid:
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
current:
'512073e40e033106c45af47acfd05092b0206ac276bda459f76dfa019a0e257c',
time: {
utc: 'Sat, 19 Dec 2020 12:58:38 GMT',
unix: 1608382718
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:316',
details: {
originalTxid:
'53cffad1ffd9de20355f89ee0310cd39876c21e2d2a9d5b09548286de1e78565',
current:
'a988c9f46f97de27eccd4f50e1bba751cdc9c49a6cac31de2f4dbf3763fb4ab3',
time: {
utc: 'Sat, 19 Dec 2020 12:58:45 GMT',
unix: 1608382725
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:10',
details: {
originalTxid:
'eced12b6e07e4d830edf6d3244055ba22e953065070985755c9e7f9ea7626d1d',
current:
'8ef975bd11d82c3dfb6645cc15eeaecebde23423b6506daa5145d3b20c02fab5',
time: {
utc: 'Sat, 19 Dec 2020 13:01:30 GMT',
unix: 1608382890
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:317',
details: {
originalTxid:
'5bf0b00b41a9cc1068d8573f0fb65e875743ce3ea5a3e521fff960da9223d508',
current:
'4f3bf7452f5409964cdb9f68a493b2c1f89dd16fc6eb1cc6d2b1dc753c7bfb59',
time: {
utc: 'Sat, 19 Dec 2020 13:04:46 GMT',
unix: 1608383086
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:11',
details: {
originalTxid:
'f1973c96dabf923f7f1e65630366921da9a806316f3e4f2478cbd754d52f6c70',
current:
'76493aaa8bdcb21bb45452250f9518674ea94b3e70fb890252278c9f8bfe3cef',
time: {
utc: 'Sat, 19 Dec 2020 13:06:31 GMT',
unix: 1608383191
}
}
},
{
txo:
'1a57786c472034b2d3d944a3cad69447c30ed5de46bd708ddead33f946ea2578:12',
details: {
originalTxid:
'15b22f6c97a3390c13ae79a61eb9beb34ab87d2245bf8e63d91d7b2477ccdf00',
current:
'6e529eeecd268b1763467152ff76a427c80a26750229de3ad0edbfb2b40ee43a',
time: {
utc: 'Sat, 19 Dec 2020 13:07:37 GMT',
unix: 1608383257
}
}
},
{
txo: 'c75126b129584314d7657ce7140f4aef64ff410053a3d9017a13307b778d4e16:2',
details: {
originalTxid:
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
current:
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
time: {
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
unix: 1608389548
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:343',
details: {
originalTxid:
'cf2234e2a71a5ae7cf525c2ced332c16bde86734d841649913b4ce698a43506d',
current:
'f98a430d9772962353cb15a58d447d3de056abde41730b92d9301d7510d1d04c',
time: {
utc: 'Sat, 19 Dec 2020 14:52:28 GMT',
unix: 1608389548
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:25',
details: {
originalTxid:
'5ed601fb5b2c6fee6eb1298671471108cf656f5d19186db484c2a6711f0867c8',
current:
'a30535841e874fe7ddefe6285bc44e5f932d1866c343b3298448985c428049a1',
time: {
utc: 'Sat, 19 Dec 2020 18:41:13 GMT',
unix: 1608403273
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:26',
details: {
originalTxid:
'a7015340be3ff27b1773208bf7c4501811e9adfb11363c11a7ac080946fb1cd2',
current:
'b9959428becca1c0850409a960153a55fa8cacac3ad5c7b0ba67c57e46814baf',
time: {
utc: 'Sat, 19 Dec 2020 18:42:13 GMT',
unix: 1608403333
}
}
},
{
txo:
'22fa91e7961388ec071254c65cccf824d7923a0291af0967394fc7bffb6185c1:27',
details: {
originalTxid:
'72435b1d8234d5ca00c6653beec318c3d40f4e902f0fec892f769125bdfc0987',
current:
'43277c2fffa2a7ff632fd2bdd0a2be950523816c42012fa00efa860ade428127',
time: {
utc: 'Sat, 19 Dec 2020 18:53:26 GMT',
unix: 1608404006
}
}
},
{
txo: 'b46d46d2d8b081cf4dd3fbf4bb1d36c8f22954e92f999a07c730be52006303ee:2',
details: {
originalTxid:
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
current:
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
time: {
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
unix: 1608409994
}
}
},
{
txo:
'9f7de7ea735d46fbb9214353fec0b250f48a24340e0909eecd83f53817b6aaa3:409',
details: {
originalTxid:
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
current:
'377c89f649de123c52ef23a1f0ec844aa6ac032c4ec1242b59ca15e1b64a883d',
time: {
utc: 'Sat, 19 Dec 2020 20:33:14 GMT',
unix: 1608409994
}
}
},
{
txo: '4bd177144edbf495f614b839c5828ec6c2d21b4a5a478b6324d681b7ecdce976:2',
details: {
originalTxid:
'4f355dfeed4a71ec0fd10688ddb3c03ea10db7629e844adfb033a859f12dc87f',
current:
'9c480d23e99fc3a31558ffb8a402bc819bf0906ca21351749db4d5fceca263b6',
time: {
utc: 'Sat, 19 Dec 2020 20:33:16 GMT',
unix: 1608409996
}
}
},
{
txo:
'190c84d8cb06fe750fcbf0bd11c62e23fa60cd4f7c970a1079ac746e270982be:155',
details: {
originalTxid:
'93aca660517a8ea847ca5bb39dfa1b26ae72f2eabfedc7cb3ef6e804c293c697',
current:
'75e3759d5167395aa1dca327b9b042eb1b86e893bdffc66c2480e8194c2cdc61',
time: {
utc: 'Sat, 19 Dec 2020 20:45:04 GMT',
unix: 1608410704
}
}
}
],
reorgs: [],
mongoDbStats: {
db: 'slpdb',
collections: 5,
views: 0,
objects: 2495075,
avgObjSize: 3188.4621913168944,
dataSize: 7586.910535812378,
storageSize: 3353.39453125,
indexes: 71,
indexSize: 1490.08203125,
totalSize: 4843.4765625,
scaleFactor: 1048576,
fsUsedSize: 74352.5859375,
fsTotalSize: 153676.98046875,
ok: 1
},
publicUrl: 'fullstack--bchn-02',
telemetryHash: null,
system: {
loadAvg1: 0.04,
loadAvg5: 0.03,
loadAvg15: 0,
platform: 'linux',
cpuCount: 8,
freeMem: 5832.546875,
totalMem: 31360.8828125,
uptime: 1603692,
processUptime: 1323812.053445836
}
}
module.exports = {
mockList,
mockSingleToken,
@@ -541,5 +1034,9 @@ module.exports = {
mockSingleValidTxid,
mockTwoValidTxid,
mockTwoRedundentTxid,
mockTxHistory
mockTxHistory,
mockPsfToken,
mockValidateBulk,
mockValidate3Bulk,
mockStatus
}
@@ -13,75 +13,70 @@ const mockAddress = {
isscript: false
}
// const mockBalance = {
// page: 1,
// totalPages: 1,
// itemsOnPage: 1000,
// address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv43u3mdxyc77j3u6k',
// balance: '2546',
// totalReceived: '2546',
// totalSent: '0',
// unconfirmedBalance: '0',
// unconfirmedTxs: 0,
// txs: 2,
// txids: [
// 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
// '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
// ]
// }
const mockBalance = {
page: 1,
totalPages: 1,
itemsOnPage: 1000,
address: 'bitcoincash:qzp7gdl52edm24xlpkyqnza9rv33u3mdxyc77j3u6k',
balance: '2546',
totalReceived: '2546',
totalSent: '0',
unconfirmedBalance: '0',
unconfirmedTxs: 0,
txs: 2,
txids: [
'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
'44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6'
]
confirmed: 2546,
unconfirmed: 0
}
const mockUtxos = [
{
txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
vout: 0,
value: '2000',
height: 605873,
confirmations: 298,
satoshis: 2000
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0,
value: 2000,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
}
]
const mockThreeUtxos = [
{
txid: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
vout: 0,
value: '2000',
height: 605873,
confirmations: 298,
satoshis: 2000
tx_hash: 'e190d13b88578132608ab912a4d2be3e55aa2792d6042d481ae21d700639de56',
tx_pos: 0,
value: 2000,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
},
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
height: 605873,
confirmations: 298,
satoshis: 546
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_pos: 1,
value: 546,
height: 605873
}
]
const mockIsTokenUtxos = [
false,
{
txid: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
vout: 1,
value: '546',
tx_hash: '44e1f48c4093fc61db1a8fa206aa402fc34e482b3f788cb38c123ca0e1a35db6',
tx_out: 1,
value: 546,
height: 605873,
confirmations: 298,
satoshis: 546,
+67 -1
View File
@@ -13,7 +13,7 @@ const chai = require('chai')
const assert = chai.assert
const sinon = require('sinon')
const Price = require('../../src/routes/v3/price')
const Price = require('../../src/routes/v4/price')
let uut
// Mocking data.
@@ -211,4 +211,70 @@ describe('#PriceRouter', () => {
assert.property(result, 'error')
})
})
describe('#getBCHAUSD', () => {
// const getNetworkInfo = controlRoute.testableComponents.getNetworkInfo
it('should throw 500 when network issues', async () => {
uut.coinexPriceUrl = 'http://fakeurl/api/'
await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isAbove(
res.statusCode,
499,
'HTTP status code 500 or greater expected.'
)
// console.log(res)
assert.include(
res.output.error,
'Network error: Could not communicate with full node or other external service'
)
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNABORTED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(uut.axios, 'request').throws({ code: 'ECONNREFUSED' })
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should get the USD price of BCH', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox
.stub(uut.axios, 'request')
.resolves({ data: mockData.mockCoinexFeed })
}
const result = await uut.getBCHAUSD(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isNumber(result.usd)
})
})
})
@@ -2,7 +2,6 @@
const chai = require('chai')
const assert = chai.assert
// const nock = require('nock') // HTTP mocking
const sinon = require('sinon')
// Used for debugging.
@@ -16,7 +15,7 @@ const { mockReq, mockRes, mockNext } = require('./mocks/express-mocks')
const RateLimits = require('../../src/middleware/route-ratelimit')
let rateLimits = new RateLimits()
// const controlRoute = require('../../src/routes/v3/full-node/control')
// const controlRoute = require('../../src/routes/v4/full-node/control')
const jwtAuth = require('../../src/middleware/jwt-auth')
let req, res, next
@@ -249,7 +248,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// NOTE: this test will fail if you run multiple integration tests in a
// short period. Because it talks to the Redis DB.
it('should pass through rate-limit middleware', async () => {
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -263,7 +262,7 @@ describe('#route-ratelimits & jwt-auth', () => {
})
it('should trigger rate-limit handler if rate limits exceeds 5 request per minute', async () => {
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -287,7 +286,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -323,7 +322,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -357,7 +356,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -393,7 +392,7 @@ describe('#route-ratelimits & jwt-auth', () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
req.baseUrl = '/v3'
req.baseUrl = '/v4'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
@@ -425,24 +424,52 @@ describe('#route-ratelimits & jwt-auth', () => {
)
})
it('should handle misconfigured token secret', async () => {
// Create a new instance of the rate limit so we start with zeroed tracking.
rateLimits = new RateLimits()
// CT 2/24/21 This test may have been invalidated by the interal IP address
// passing that I implemented to get hydrateUtxos() working properly.
// I'm commenting this out until I can study the side effects of this change,
// and why exactly this test is breaking.
// it('should handle misconfigured token secret', async () => {
// // Create a new instance of the rate limit so we start with zeroed tracking.
// rateLimits = new RateLimits()
//
// req.baseUrl = '/v4'
// req.path = '/control/getNetworkInfo'
// req.url = req.path
// req.method = 'GET'
//
// req.locals.jwtToken = 'some-token'
//
// next.reset() // reset the stubbed next() function.
//
// await rateLimits.rateLimitByResource(req, res, next)
//
// // Issues with token secret should treat incoming requests as anonymous
// // calls with 50 points, or 20 RPM.
// assert.equal(res.locals.pointsToConsume, 50)
// })
})
req.baseUrl = '/v3'
req.path = '/control/getNetworkInfo'
req.url = req.path
req.method = 'GET'
describe('#isInWhitelist', () => {
it('should return false when no argument is passed in', () => {
const result = rateLimits.isInWhitelist()
req.locals.jwtToken = 'some-token'
assert.equal(result, false)
})
next.reset() // reset the stubbed next() function.
it('should return false when origin is not in the whitelist', () => {
const origin = 'blah.com'
await rateLimits.rateLimitByResource(req, res, next)
const result = rateLimits.isInWhitelist(origin)
// Issues with token secret should treat incoming requests as anonymous
// calls with 30 points or 3 RPM.
assert.equal(res.locals.pointsToConsume, 50)
assert.equal(result, false)
})
it('should return true when origin is in the whitelist', () => {
const origin = 'message.fullstack.cash'
const result = rateLimits.isInWhitelist(origin)
assert.equal(result, true)
})
})
})
@@ -14,7 +14,7 @@
const chai = require('chai')
const assert = chai.assert
const Rawtransactions = require('../../src/routes/v3/full-node/rawtransactions')
const Rawtransactions = require('../../src/routes/v4/full-node/rawtransactions')
const uut = new Rawtransactions()
// const nock = require('nock') // HTTP mocking
+674 -52
View File
@@ -40,11 +40,11 @@ if (process.env.TEST === 'unit') {
}
// Prepare the slpRoute for stubbing dependcies on slpjs.
const SlpRoute = require('../../src/routes/v3/slp')
const SlpRoute = require('../../src/routes/v4/slp')
const slpRoute = new SlpRoute()
// const pathStub = {} // Used to stub methods within slpjs.
// const slpRouteStub = proxyquire('../../src/routes/v3/slp', { slpjs: pathStub })
// const slpRouteStub = proxyquire('../../src/routes/v4/slp', { slpjs: pathStub })
// Mocking data.
const { mockReq, mockRes } = require('./mocks/express-mocks')
@@ -192,6 +192,7 @@ describe('#SLP', () => {
'Error message expected'
)
})
it('should get token balance for an address', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
@@ -338,7 +339,231 @@ describe('#SLP', () => {
}
})
describe('validateBulk()', () => {
describe('#validate2Single', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: false } })
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, false)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: true } })
}
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, true)
})
// This test can only be run as a mocked unit test. It's too inconsistent
// to run as an integration test, due to the caching built into slp-validate.
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validateSingle', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: {
c: [],
u: []
}
})
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, null)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: mockData.mockSingleValidTxid })
}
const txid =
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, true)
})
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validateSingle(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validate3Single', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: {
c: [],
u: []
}
})
}
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.valid, null)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: mockData.mockSingleValidTxid })
}
const txid =
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// assert.equal(result.txid, txid)
assert.equal(result.valid, true)
})
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
req.params.txid = txid
const result = await slpRoute.validate3Single(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
}
})
describe('#validateBulk()', () => {
const validateBulk = slpRoute.validateBulk
it('should throw 400 if txid array is empty', async () => {
@@ -461,77 +686,272 @@ describe('#SLP', () => {
assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockValidateBulk
})
}
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.body.txids = txids
const result = await validateBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// BCHN expected results
if (process.env.ISBCHN) {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
} else {
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, true)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
}
})
})
describe('#validate2Single', () => {
it('should throw 400 if txid is empty', async () => {
req.params.txid = ''
const result = await slpRoute.validate2Single(req, res)
describe('#validate3Bulk', () => {
const validate3Bulk = slpRoute.validate3Bulk
it('should throw 400 if txid array is empty', async () => {
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'txid can not be empty')
assert.include(result.error, 'txids needs to be an array')
assert.equal(res.statusCode, 400)
})
it('should invalidate a known invalid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: false } })
}
it('should throw 400 error if array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
req.body.txids = testArray
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too large')
})
it('returns proper error when downstream service stalls', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNABORTED' })
req.body.txids = [
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, false)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should validate a known valid TXID', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { isValid: true } })
}
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' })
const txid =
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
req.body.txids = [
'77872738b6bddee6c0cbdb9509603de20b15d4f6b26602f629417aec2f5d5e8d'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.txid, txid)
assert.equal(result.isValid, true)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
)
})
it('should validate array with single element', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
})
it('should validate array with two elements', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc',
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2)
})
// Captures a regression bug that went out to production, captured in this
// GitHub Issue: https://github.com/Bitcoin-com/rest.bitcoin.com/issues/518
it('should return two elements if given two elements', async () => {
// Mock the RPC call for unit tests.
if (process.env.TEST === 'unit') {
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockPsfToken
})
}
req.body.txids = [
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc',
'cdfed769ef7b69e09be06d2821b88598d9a5d711b8f9bd369763c78b7a578fbc'
]
const result = await validate3Bulk(req, res)
// console.log(`result: ${util.inspect(result)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], ['txid', 'valid'])
assert.equal(result.length, 2)
})
// This test can only be run as a mocked unit test. It's too inconsistent
// to run as an integration test, due to the caching built into slp-validate.
if (process.env.TEST === 'unit') {
it('should cancel if validation takes too long', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({
code: 'ECONNABORTED'
// This is a unit-test only test, as the results change depending on if
// its tested against the BCHN or ABC networks. There are integration tests
// for this test case in the ../integration/slp.js file.
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
// Mock the RPC call for unit tests.
sandbox.stub(slpRoute.axios, 'request').resolves({
data: mockData.mockValidate3Bulk
})
const txid =
'eacb1085dfa296fef6d4ae2c0f4529a1bef096dd2325bdcc6dcb5241b3bdb579'
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
// Known invalid SLP token send of PSF tokens.
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74'
]
req.params.txid = txid
const result = await slpRoute.validate2Single(req, res)
req.body.txids = txids
const result = await validate3Bulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
assert.equal(result[6].txid, txids[6])
assert.equal(result[6].valid, false)
assert.include(
result.error,
'Could not communicate with full node',
'Error message expected'
result[6].invalidReason,
'Token outputs are greater than valid token inputs'
)
})
}
@@ -867,7 +1287,7 @@ describe('#SLP', () => {
})
})
describe('generateSendOpReturn()', () => {
describe('#generateSendOpReturn()', () => {
const generateSendOpReturn = slpRoute.generateSendOpReturn
// Validate tokenUtxos input
it('should throw 400 if tokenUtxos is missing', async () => {
@@ -943,8 +1363,8 @@ describe('#SLP', () => {
req.body.tokenUtxos = [
{
tokenId:
'0a321bff9761f28e06a268b14711274bb77617410a16807bd0437ef234a072b1',
decimals: 0,
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
decimals: 8,
tokenQty: 2
}
]
@@ -1138,6 +1558,208 @@ describe('#SLP', () => {
)
})
})
describe('#hydrateUtxosWL', () => {
it('should throw error if input is not an array.', async () => {
req.body.utxos = 'test'
const result = await slpRoute.hydrateUtxosWL(req, res)
// console.log(`result: `, result)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Input must be an array')
})
it('should throw error if Array is empty', async () => {
req.body.utxos = []
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array should not be empty')
})
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
}
const utxos = []
// Populate array with 21 utxos
for (let i = 0; i < 21; i++) {
utxos.push(utxo)
}
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too long, max length is 20')
})
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetailsWL').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816,
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546,
utxoType: 'token',
transactionType: 'send',
tokenId:
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
tokenTicker: 'TAP',
tokenName: 'Thoughts and Prayers',
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 0,
tokenType: 1,
tokenQty: 5,
isValid: true
}
])
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should throw error for missing properties', async () => {
const utxos = [
{
height: 639443,
tx_hash:
'30707fffb9b295a06a68d217f49c198e9e1dbe1edc3874a0928ca1905f1709df',
tx_pos: 0,
value: 6000
},
{
height: 639443,
tx_hash:
'8962566e413501224d178a02effc89be5ac0d8e4195f617415d443dc4c38fe50',
tx_pos: 1,
value: 546
}
]
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxosWL(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(
result.error,
'Each element in array should have a utxos property'
)
})
})
describe('#getStatus', () => {
it('should get the SLPDB status', async () => {
if (process.env.TEST === 'unit') {
// Mock to prevent live network connection.
sandbox
.stub(slpRoute.axios, 'request')
.resolves({ data: { s: [mockData.mockStatus] } })
}
const result = await slpRoute.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'bchBlockHeight')
})
it('returns proper error when downstream service is down', async () => {
// Mock the timeout error.
sandbox.stub(slpRoute.axios, 'request').throws({ code: 'ECONNREFUSED' })
slpRoute.getStatus(req, res)
// const result = slpRoute.getStatus(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(res.statusCode, 499, 'HTTP status code 503 expected.')
})
})
})
/*
+46 -33
View File
@@ -4,16 +4,24 @@
This test file uses the environment variable TEST to switch between unit
and integration tests. By default, TEST is set to 'unit'. Set this variable
to 'integration' to run the tests against BCH mainnet.
These tests use this private key:
L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq
Which corresponds to this address:
bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r
*/
'use strict'
const chai = require('chai')
const assert = chai.assert
const utilRoute = require('../../src/routes/v3/util')
// Public npm libraries
const assert = require('chai').assert
const nock = require('nock') // HTTP mocking
const sinon = require('sinon')
// Local libraries
const Electrumx = require('../../src/routes/v4/electrumx')
const UtilRoute = require('../../src/routes/v4/util')
let originalEnvVars // Used during transition from integration to unit tests.
// Mocking data.
@@ -24,13 +32,16 @@ const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
// const UtilRoute = utilRoute.UtilRoute
const utilRouteInst = new utilRoute.UtilRoute()
// const electrumx = new Electrumx()
const utilRouteInst = new UtilRoute({ electrumx: {} })
describe('#Util', () => {
let req, res
let sandbox
let electrumx
let utilRoute
before(() => {
before(async () => {
// Save existing environment variables.
originalEnvVars = {
BITCOINCOM_BASEURL: process.env.BITCOINCOM_BASEURL,
@@ -47,6 +58,9 @@ describe('#Util', () => {
process.env.RPC_USERNAME = 'fakeusername'
process.env.RPC_PASSWORD = 'fakepassword'
}
electrumx = new Electrumx()
// await electrumx.connect()
})
// Setup the mocks before each test.
@@ -64,6 +78,8 @@ describe('#Util', () => {
if (!nock.isActive()) nock.activate()
sandbox = sinon.createSandbox()
utilRoute = new UtilRoute({ electrumx })
})
afterEach(() => {
@@ -357,29 +373,29 @@ describe('#Util', () => {
// Mock the RPC call for unit tests.
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.mockIsTokenUtxos)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
wif: 'L5GEFg1tETLWBugmhSo9Zc4ms968qVmfmTroDxsJ982AiudAQGyt',
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
wif: 'L1wAGEN721LHDoiN8pLwwBb87bYrU6Gs21UPcCRR7LjKypQyVaCq',
toAddr: 'bitcoincash:qp2g4cnekxsjspccmtvh5k73mczz6273js4mjr353r'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid')
@@ -389,13 +405,13 @@ describe('#Util', () => {
// Mock the RPC call for unit tests.
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -403,33 +419,30 @@ describe('#Util', () => {
balanceOnly: true
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isNumber(result)
})
}
// Unit tests only
if (process.env.TEST === 'unit') {
it('should generate transaction for valid BCH-only sweep', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves([false, false])
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -437,7 +450,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result, 'test-txid')
@@ -445,22 +458,22 @@ describe('#Util', () => {
it('should throw 422 error if no non-token UTXOs', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.tokensOnly)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -468,7 +481,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422)
@@ -481,22 +494,22 @@ describe('#Util', () => {
it('should detect and throw error for multiple token classes', async () => {
sandbox
.stub(utilRouteInst.blockbook, 'balanceFromBlockbook')
.stub(utilRoute.electrumx, '_balanceFromElectrumx')
.resolves(mockData.mockBalance)
sandbox
.stub(utilRouteInst.blockbook, 'utxosFromBlockbook')
.stub(utilRoute.electrumx, '_utxosFromElectrumx')
.resolves(mockData.mockThreeUtxos)
// Force token utxo to appear as regular BCH utxo.
sandbox
.stub(utilRouteInst.bchjs.SLP.Utils, 'tokenUtxoDetails')
.stub(utilRoute.bchjs.SLP.Utils, 'tokenUtxoDetails')
.resolves(mockData.multipleTokens)
// Mock sendRawTransaction() so that the hex does not actually get broadcast
// to the network.
sandbox
.stub(utilRouteInst.bchjs.RawTransactions, 'sendRawTransaction')
.stub(utilRoute.bchjs.RawTransactions, 'sendRawTransaction')
.resolves('test-txid')
req.body = {
@@ -504,7 +517,7 @@ describe('#Util', () => {
toAddr: 'bitcoincash:qz2qn6zt4qmacf4r6c0e2pdcqsgnkxaa3ql2xpee6p'
}
const result = await utilRouteInst.sweepWif(req, res)
const result = await utilRoute.sweepWif(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(res.statusCode, 422)
+1 -1
View File
@@ -10,7 +10,7 @@
const chai = require('chai')
const assert = chai.assert
const xpubRoute = require('../../src/routes/v3/xpub')
const xpubRoute = require('../../src/routes/v4/xpub')
const nock = require('nock') // HTTP mocking
// let originalUrl // Used during transition from integration to unit tests.