mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 09:02:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dc88c0af3 | ||
|
|
85caa9556b | ||
|
|
bbdc417eb2 | ||
|
|
cc3bc002e7 | ||
|
|
a75e1ab633 | ||
|
|
8c5f1e2274 | ||
|
|
c30e76c942 | ||
|
|
a575621c50 | ||
|
|
d3a0c5948a | ||
|
|
61bb509195 | ||
|
|
a18e549694 | ||
|
|
7d53f47d8a | ||
|
|
e8e615795d | ||
|
|
5759b096d5 | ||
|
|
5b300f68b2 | ||
|
|
3cce6f9754 | ||
|
|
3da04d9327 | ||
|
|
79091815a2 | ||
|
|
258144d26d | ||
|
|
4c4a45841b | ||
|
|
010afd10a3 | ||
|
|
82b4d2ab9e | ||
|
|
8eb6a21dc2 | ||
|
|
5f2a762d4f | ||
|
|
24e2c78d03 | ||
|
|
eab02fdbc9 | ||
|
|
f7bd9c75db | ||
|
|
2a96f3a4f5 | ||
|
|
3dc4a8ba6f | ||
|
|
65916f2c5c | ||
|
|
18188bfdce | ||
|
|
45acc2b0b7 | ||
|
|
31913c3fb7 | ||
|
|
2311643398 | ||
|
|
7501aa0238 | ||
|
|
92a89a543d | ||
|
|
cdf8171994 | ||
|
|
385baf0afd | ||
|
|
9a9aababd2 | ||
|
|
c228acd28a | ||
|
|
c1c140bdce | ||
|
|
54d9b80f26 | ||
|
|
961a190309 | ||
|
|
2d2a91dfde | ||
|
|
fbac18d456 | ||
|
|
62ae67ceab | ||
|
|
8e64d854cf | ||
|
|
5fe8e515ff | ||
|
|
f11297339b | ||
|
|
61d52c8ebe | ||
|
|
d89bbe0bf6 | ||
|
|
2a1c2500e6 | ||
|
|
8879a5170a | ||
|
|
b29cb7c12b | ||
|
|
69fa10d3a6 | ||
|
|
d16431d165 | ||
|
|
faa2972460 | ||
|
|
5d3ea2b559 | ||
|
|
40fa6440f4 | ||
|
|
ce951afddb | ||
|
|
d2e847d3fb | ||
|
|
fe697455c0 | ||
|
|
25832d52fd | ||
|
|
9d0840c2fe | ||
|
|
74dc3e7e1d | ||
|
|
171786a470 | ||
|
|
5cbd685060 | ||
|
|
ef05aff7a3 | ||
|
|
3f01fdba7c | ||
|
|
232747280e | ||
|
|
e96da1615b | ||
|
|
9538501f01 | ||
|
|
0a4fee5716 | ||
|
|
cec79be4fa | ||
|
|
c839f0b891 | ||
|
|
24d629ebe1 | ||
|
|
f729918385 | ||
|
|
b7ab02c0ed | ||
|
|
1c291880e0 | ||
|
|
09b79c4d3e | ||
|
|
828f6a1b1c | ||
|
|
1b8727da55 | ||
|
|
f84862d875 | ||
|
|
179d924899 | ||
|
|
676fb85abf | ||
|
|
d59c1fe445 | ||
|
|
0a34611557 | ||
|
|
c13b029e99 | ||
|
|
b038e9a7fd | ||
|
|
7b728fb45d | ||
|
|
d4dbce8df0 | ||
|
|
2419aeceaa | ||
|
|
158b009069 | ||
|
|
f3c3250a69 | ||
|
|
5ae4db88c3 | ||
|
|
33e161df26 | ||
|
|
81c910361c | ||
|
|
9751a39538 | ||
|
|
7058f77ee9 | ||
|
|
ad580d67a4 | ||
|
|
acc0300a44 | ||
|
|
4e1b806230 | ||
|
|
357e350176 | ||
|
|
f6140acc76 | ||
|
|
df65b12aab | ||
|
|
754b4cd807 | ||
|
|
3c6079e54c | ||
|
|
18621cd76c | ||
|
|
456159a4cc | ||
|
|
33e723befe | ||
|
|
30fdee1920 | ||
|
|
feb336c331 | ||
|
|
8bacc0a991 | ||
|
|
d22f1ec8a6 | ||
|
|
7eb3a796b8 | ||
|
|
9919f811b0 | ||
|
|
e7854481a0 | ||
|
|
0b406c467e | ||
|
|
e41217b710 | ||
|
|
c22ce8d57d | ||
|
|
bf3612583e | ||
|
|
ef56452ec9 | ||
|
|
b3be470c0e | ||
|
|
7274475898 | ||
|
|
7bb347a833 | ||
|
|
fadceedd00 | ||
|
|
7f4ef60f8e |
@@ -57,7 +57,7 @@ rate limits when interacting with the back end server. See [this article](https:
|
||||
```
|
||||
const BCHJS = require("@psf/bch-js")
|
||||
let bchjs = new BCHJS({
|
||||
restURL: 'https://bchn.fullstack.cash/v3/',
|
||||
restURL: 'https://bchn.fullstack.cash/v5/',
|
||||
apiToken: 'eyJhbGciO...' // Your JWT token here.
|
||||
})
|
||||
```
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
Examples have been moved to this repository:
|
||||
Examples have been moved to this repository
|
||||
|
||||
https://github.com/Permissionless-Software-Foundation/bch-js-examples
|
||||
|
||||
Generated
+2089
-21529
File diff suppressed because it is too large
Load Diff
+17
-27
@@ -4,20 +4,26 @@
|
||||
"description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"contributors": [
|
||||
"Gabriel Cardona <gabriel@bitcoin.com>"
|
||||
"Gabriel Cardona <gabriel@bitcoin.com>",
|
||||
"Stoyan Zhekov <zh@zhware.net>"
|
||||
],
|
||||
"main": "src/bch-js",
|
||||
"scripts": {
|
||||
"test": "nyc mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
|
||||
"test:integration": "npm run test:integration:bchn",
|
||||
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v4/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v4/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
|
||||
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
|
||||
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#UTXO' test/integration/chains/bchn/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#Util' test/unit/",
|
||||
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v5/ && mocha --timeout 30000 test/integration/",
|
||||
"test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
|
||||
"test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
|
||||
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v5/ && mocha --timeout 30000 -g '#DSProof' test/integration/chains/",
|
||||
"test:temp4": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
@@ -31,13 +37,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@psf/bip21": "^2.0.1",
|
||||
"@psf/bip32-utils": "^0.13.1",
|
||||
"@psf/bip32-utils": "^1.0.0",
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "^4.0.2",
|
||||
"@psf/coininfo": "^4.0.0",
|
||||
"@uppy/core": "^1.10.4",
|
||||
"@uppy/tus": "^1.5.12",
|
||||
"assert": "^2.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"bc-bip68": "^1.0.5",
|
||||
"bchaddrjs-slp": "^0.2.5",
|
||||
@@ -49,33 +54,18 @@
|
||||
"bip66": "^1.1.5",
|
||||
"bitcoinjs-message": "^2.0.0",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer": "^5.1.0",
|
||||
"cashaddrjs": "^0.3.3",
|
||||
"chalk": "^2.3.0",
|
||||
"clear": "0.1.0",
|
||||
"commander": "^3.0.0",
|
||||
"cp-file": "^7.0.0",
|
||||
"ecurve": "^1.0.6",
|
||||
"figlet": "^1.2.0",
|
||||
"git-clone": "^0.1.0",
|
||||
"ini": "^1.3.8",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-cmd": "^3.0.0",
|
||||
"node-emoji": "^1.8.1",
|
||||
"qrcode": "^1.4.1",
|
||||
"randombytes": "^2.0.6",
|
||||
"repl.history": "^0.1.4",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"satoshi-bitcoin": "^1.0.4",
|
||||
"slp-mdm": "0.0.6",
|
||||
"slp-parser": "0.0.4",
|
||||
"socket.io": "^2.1.1",
|
||||
"socket.io-client": "^2.1.1",
|
||||
"touch": "^3.1.0",
|
||||
"wif": "^2.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "^0.17.7",
|
||||
"assert": "^2.0.0",
|
||||
"chai": "^4.1.2",
|
||||
"coveralls": "^3.0.2",
|
||||
"eslint": "5.16.0",
|
||||
@@ -90,7 +80,7 @@
|
||||
"node-mocks-http": "^1.7.0",
|
||||
"nyc": "^14.1.0",
|
||||
"prettier": "^1.18.2",
|
||||
"semantic-release": "^17.3.1",
|
||||
"semantic-release": "^17.3.9",
|
||||
"sinon": "^7.3.2",
|
||||
"standard": "^16.0.3"
|
||||
},
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ const coininfo = require('@psf/coininfo')
|
||||
class Address {
|
||||
constructor (config) {
|
||||
const tmp = {}
|
||||
if (!config || !config.restURL) tmp.restURL = 'https://api.bchjs.cash/v4/'
|
||||
if (!config || !config.restURL) tmp.restURL = 'https://api.bchjs.cash/v5/'
|
||||
else tmp.restURL = config.restURL
|
||||
|
||||
this.restURL = tmp.restURL
|
||||
|
||||
+7
-4
@@ -7,8 +7,8 @@
|
||||
*/
|
||||
|
||||
// bch-api mainnet.
|
||||
const DEFAULT_REST_API = 'https://api.fullstack.cash/v4/'
|
||||
// const DEFAULT_REST_API = "http://localhost:3000/v4/"
|
||||
const DEFAULT_REST_API = 'https://api.fullstack.cash/v5/'
|
||||
// const DEFAULT_REST_API = "http://localhost:3000/v5/"
|
||||
|
||||
// local deps
|
||||
const BitcoinCash = require('./bitcoincash')
|
||||
@@ -26,12 +26,13 @@ const TransactionBuilder = require('./transaction-builder')
|
||||
const ECPair = require('./ecpair')
|
||||
const Script = require('./script')
|
||||
const Price = require('./price')
|
||||
const Socket = require('./socket')
|
||||
const Schnorr = require('./schnorr')
|
||||
const SLP = require('./slp/slp')
|
||||
const IPFS = require('./ipfs')
|
||||
const Encryption = require('./encryption')
|
||||
const Utxo = require('./utxo')
|
||||
const Transaction = require('./transaction')
|
||||
const DSProof = require('./dsproof')
|
||||
|
||||
// Indexers
|
||||
const Ninsight = require('./ninsight')
|
||||
@@ -107,7 +108,6 @@ class BCHJS {
|
||||
this.TransactionBuilder = TransactionBuilder
|
||||
this.TransactionBuilder.setAddress(this.Address)
|
||||
this.Util = new Util(libConfig)
|
||||
this.Socket = Socket
|
||||
this.Schnorr = new Schnorr(libConfig)
|
||||
|
||||
this.SLP = new SLP(libConfig)
|
||||
@@ -115,6 +115,9 @@ class BCHJS {
|
||||
|
||||
this.IPFS = new IPFS()
|
||||
this.Utxo = new Utxo(libConfig)
|
||||
this.Transaction = new Transaction(libConfig)
|
||||
|
||||
this.DSProof = new DSProof(libConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+32
-22
@@ -68,8 +68,8 @@ class Blockchain {
|
||||
* @apiName getBlock
|
||||
* @apiGroup Blockchain
|
||||
* @apiDescription
|
||||
* If verbose is false, returns a string that is serialized, hex-encoded data for block 'hash'. If verbose is true, returns an Object with information about block hash.
|
||||
*
|
||||
* If verbose is 0, returns a string that is serialized, hex-encoded data for block 'hash'. If verbose is 1, returns an Object with information about block hash.
|
||||
* If verbose is 2, returns an Object with information about block hash and information about tx.
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
@@ -80,28 +80,38 @@ class Blockchain {
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // { hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
|
||||
* // confirmations: 528236,
|
||||
* // size: 216,
|
||||
* // height: 1000,
|
||||
* // version: 1,
|
||||
* // versionHex: '00000001',
|
||||
* // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
|
||||
* // tx:
|
||||
* // [ 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33' ],
|
||||
* // time: 1232346882,
|
||||
* // mediantime: 1232344831,
|
||||
* // nonce: 2595206198,
|
||||
* // bits: '1d00ffff',
|
||||
* // difficulty: 1,
|
||||
* // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9',
|
||||
* // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d',
|
||||
* // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6' }
|
||||
* // {
|
||||
* // hash: '00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
|
||||
* // confirmations: 528236,
|
||||
* // size: 216,
|
||||
* // height: 1000,
|
||||
* // version: 1,
|
||||
* // versionHex: '00000001',
|
||||
* // merkleroot: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
|
||||
* // tx:
|
||||
* // [ 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33' ],
|
||||
* // time: 1232346882,
|
||||
* // mediantime: 1232344831,
|
||||
* // nonce: 2595206198,
|
||||
* // bits: '1d00ffff',
|
||||
* // difficulty: 1,
|
||||
* // chainwork: '000000000000000000000000000000000000000000000000000003e903e903e9',
|
||||
* // previousblockhash: '0000000008e647742775a230787d66fdf92c46a48c896bfbc85cdc8acc67e87d',
|
||||
* // nextblockhash: '00000000a2887344f8db859e372e7e4bc26b23b9de340f725afbf2edb265b4c6'
|
||||
* // }
|
||||
*/
|
||||
async getBlock (blockhash, verbose = true) {
|
||||
async getBlock (blockhash, verbosity = 1) {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${this.restURL}blockchain/getBlock/${blockhash}?verbose=${verbose}`,
|
||||
// Input validation
|
||||
if (!blockhash || typeof blockhash !== 'string') {
|
||||
throw new Error('blockhash must be a string')
|
||||
}
|
||||
const response = await axios.post(
|
||||
`${this.restURL}blockchain/getblock`,
|
||||
{
|
||||
blockhash,
|
||||
verbosity
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
const axios = require('axios')
|
||||
|
||||
let _this
|
||||
|
||||
class DSProof {
|
||||
constructor (config) {
|
||||
this.restURL = config.restURL
|
||||
this.apiToken = config.apiToken
|
||||
this.authToken = config.authToken
|
||||
this.axios = axios
|
||||
|
||||
if (this.authToken) {
|
||||
// Add Basic Authentication token to the authorization header.
|
||||
this.axiosOptions = {
|
||||
headers: {
|
||||
authorization: this.authToken
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Add JWT token to the authorization header.
|
||||
this.axiosOptions = {
|
||||
headers: {
|
||||
authorization: `Token ${this.apiToken}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api DSProof.getDSProof() getDSProof()
|
||||
* @apiName getDSProof
|
||||
* @apiGroup DSProof
|
||||
* @apiDescription Checks if a transaction generated a double-spend proof.
|
||||
*
|
||||
* If a double-spend is attempted, one of the transactions will generate a
|
||||
* 'double spend proof'. This call can be used to check if a transaction
|
||||
* generated such a proof.
|
||||
*
|
||||
* Merchants should wait 3-5 seconds after receiving notification of a
|
||||
* transaction before calling this endpoint, to see if the TXID generated a
|
||||
* proof. If this method returns no data, then the TX can be considered 'safe'
|
||||
* and not a double spend. If proof data is returned by this method, then
|
||||
* the transaction generated a proof and can be considered a 'double spend'.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
|
||||
* const result = await bchjs.DSProof.getDSProof(txid)
|
||||
* console.log(result);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* null
|
||||
*/
|
||||
async getDSProof (txid) {
|
||||
try {
|
||||
if (!txid) {
|
||||
throw new Error('txid is required')
|
||||
}
|
||||
if (txid.length !== 64) {
|
||||
throw new Error(`txid must be of length 64 (not ${txid.length})`)
|
||||
}
|
||||
const response = await _this.axios.get(
|
||||
`${this.restURL}dsproof/getdsproof/${txid}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DSProof
|
||||
+3
-2
@@ -284,7 +284,7 @@ class ElectrumX {
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async transactions (address) {
|
||||
async transactions (address, usrObj = null) {
|
||||
try {
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
@@ -299,7 +299,8 @@ class ElectrumX {
|
||||
const response = await axios.post(
|
||||
`${this.restURL}electrumx/transactions`,
|
||||
{
|
||||
addresses: address
|
||||
addresses: address,
|
||||
usrObj // pass user data when making an internal call.
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
|
||||
@@ -156,6 +156,41 @@ class Price {
|
||||
else throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api price.getBchUsd() getBchUsd()
|
||||
* @apiName Price getBchUsd()
|
||||
* @apiGroup Price
|
||||
* @apiDescription Return current price of BCH in USD.
|
||||
* This endpoint gets the USD price of BCH from the Coinex API. The price
|
||||
* comes from bch-api, so it has a better chance of working in Tor.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*(async () => {
|
||||
* try {
|
||||
* let current = await bchjs.Price.getBchUsd();
|
||||
* console.log(current);
|
||||
* } catch(err) {
|
||||
* console.err(err)
|
||||
* }
|
||||
*})()
|
||||
*
|
||||
* // 512.81
|
||||
*/
|
||||
async getBchUsd () {
|
||||
try {
|
||||
const response = await this.axios.get(
|
||||
`${this.restURL}price/bchusd`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
return response.data.usd
|
||||
} catch (err) {
|
||||
if (err.response && err.response.data) throw err.response.data
|
||||
else throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Price
|
||||
|
||||
+23
-99
@@ -1,7 +1,5 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const SlpUtils = require('./slp/utils')
|
||||
|
||||
let _this
|
||||
|
||||
class RawTransactions {
|
||||
@@ -29,9 +27,6 @@ class RawTransactions {
|
||||
// Encapsulate dependencies
|
||||
this.axios = axios
|
||||
|
||||
// Dependencies
|
||||
this.slpUtils = new SlpUtils(config)
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -116,9 +111,7 @@ class RawTransactions {
|
||||
data: {
|
||||
hexes: hex
|
||||
},
|
||||
headers: {
|
||||
authorization: `Token ${_this.apiToken}`
|
||||
}
|
||||
headers: _this.axiosOptions.headers
|
||||
}
|
||||
const response = await axios(options)
|
||||
|
||||
@@ -127,7 +120,8 @@ class RawTransactions {
|
||||
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
@@ -183,9 +177,7 @@ class RawTransactions {
|
||||
data: {
|
||||
hexes: script
|
||||
},
|
||||
headers: {
|
||||
authorization: `Token ${this.apiToken}`
|
||||
}
|
||||
headers: _this.axiosOptions.headers
|
||||
}
|
||||
const response = await axios(options)
|
||||
|
||||
@@ -194,7 +186,8 @@ class RawTransactions {
|
||||
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
@@ -263,7 +256,7 @@ class RawTransactions {
|
||||
* // time: 1547752564,
|
||||
* // blocktime: 1547752564 } ]
|
||||
*/
|
||||
async getRawTransaction (txid, verbose = false) {
|
||||
async getRawTransaction (txid, verbose = false, usrObj = null) {
|
||||
try {
|
||||
if (typeof txid === 'string') {
|
||||
const response = await axios.get(
|
||||
@@ -278,7 +271,8 @@ class RawTransactions {
|
||||
url: `${this.restURL}rawtransactions/getRawTransaction`,
|
||||
data: {
|
||||
txids: txid,
|
||||
verbose: verbose
|
||||
verbose: verbose,
|
||||
usrObj // pass user data when making an internal call.
|
||||
},
|
||||
headers: _this.axiosOptions.headers
|
||||
}
|
||||
@@ -289,7 +283,8 @@ class RawTransactions {
|
||||
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
@@ -327,13 +322,15 @@ class RawTransactions {
|
||||
|
||||
retArray.push({
|
||||
vin: i,
|
||||
address: voutSender.scriptPubKey.addresses[0]
|
||||
address: voutSender.scriptPubKey.addresses[0],
|
||||
value: voutSender.value
|
||||
})
|
||||
}
|
||||
|
||||
return retArray
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
@@ -368,7 +365,9 @@ class RawTransactions {
|
||||
async getTxData (txid) {
|
||||
try {
|
||||
if (typeof txid !== 'string') {
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
throw new Error(
|
||||
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
|
||||
)
|
||||
}
|
||||
|
||||
// Get the TX details for the transaction under consideration.
|
||||
@@ -381,89 +380,13 @@ class RawTransactions {
|
||||
// Add the input address to the transaction data.
|
||||
for (let i = 0; i < inAddrs.length; i++) {
|
||||
txDetails.vin[i].address = inAddrs[i].address
|
||||
txDetails.vin[i].value = inAddrs[i].value
|
||||
}
|
||||
|
||||
return txDetails
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Wraps getTxData(), but also appends SLP token information to each input
|
||||
// and output of the transaction. This is a very API-heavy call.
|
||||
// Returns false if the txid is not an SLP transaction.
|
||||
//
|
||||
// Warning! This is a prototype function and the output can change at any time
|
||||
// without reflecting a change in the semantic version. DO NOT USE IN PRODUCTION.
|
||||
async getTxDataSlp (txid) {
|
||||
try {
|
||||
if (typeof txid !== 'string') {
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
}
|
||||
|
||||
const txDetails = await this.getTxData(txid)
|
||||
|
||||
// First get the token information for the output. If that fails, then
|
||||
// this is not an SLP transaction, and this method can return false.
|
||||
let outTokenData
|
||||
try {
|
||||
outTokenData = await this.slpUtils.decodeOpReturn(txid)
|
||||
// console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`)
|
||||
|
||||
// Add token information to the tx details object.
|
||||
txDetails.tokenTxType = outTokenData.txType
|
||||
txDetails.tokenId = outTokenData.tokenId
|
||||
|
||||
// Add the token quantity to each output.
|
||||
for (let i = 0; i < outTokenData.amounts.length; i++) {
|
||||
txDetails.vout[i + 1].tokenQty = outTokenData.amounts[i]
|
||||
}
|
||||
|
||||
// Loop through each input and retrieve the token data.
|
||||
for (let i = 0; i < txDetails.vin.length; i++) {
|
||||
const thisVin = txDetails.vin[i]
|
||||
|
||||
try {
|
||||
// If decodeOpReturn() throws an error, then this input is not
|
||||
// from an SLP transaction and can be ignored.
|
||||
const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid)
|
||||
// console.log(
|
||||
// `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}`
|
||||
// )
|
||||
|
||||
// Get the appropriate vout token amount. This may throw an error,
|
||||
// which means this Vin is not actually a token UTXO, it was just
|
||||
// associated with a previous token TX.
|
||||
const tokenQty = inTokenData.amounts[thisVin.vout - 1]
|
||||
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
||||
|
||||
if (tokenQty) {
|
||||
thisVin.tokenQty = tokenQty
|
||||
// txDetails.vin[i].tokenQty = tokenQty
|
||||
} else {
|
||||
thisVin.tokenQty = null
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log('catch 2: ', err)
|
||||
// If decodeOpReturn() throws an error, then this input is not
|
||||
// from an SLP transaction and can be ignored.
|
||||
// thisVin.tokenQty = null
|
||||
thisVin.tokenQty = null
|
||||
continue
|
||||
}
|
||||
// console.log(
|
||||
// `2: txDetails.vin[i]: ${JSON.stringify(txDetails.vin[i], null, 2)}`
|
||||
// )
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log('catch 1: ', err)
|
||||
return false
|
||||
}
|
||||
|
||||
return txDetails
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
@@ -536,7 +459,8 @@ class RawTransactions {
|
||||
|
||||
throw new Error('Input hex must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
if (error.error) throw new Error(error.error)
|
||||
else if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
+100
-2
@@ -8,16 +8,21 @@
|
||||
(Parent) token.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const axios = require('axios')
|
||||
|
||||
// Local libraries.
|
||||
const Address = require('./address')
|
||||
|
||||
// const BigNumber = require('bignumber.js')
|
||||
const slpMdm = require('slp-mdm')
|
||||
|
||||
let _this
|
||||
// const addy = new Address()
|
||||
let addy
|
||||
const TransactionBuilder = require('../transaction-builder')
|
||||
|
||||
class TokenType1 {
|
||||
class Nft1 {
|
||||
constructor (config) {
|
||||
this.restURL = config.restURL
|
||||
|
||||
@@ -25,6 +30,8 @@ class TokenType1 {
|
||||
|
||||
// Instantiate the transaction builder.
|
||||
TransactionBuilder.setAddress(addy)
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,6 +419,97 @@ class TokenType1 {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.NFT1.listNFTGroupChildren() listNFTGroupChildren()
|
||||
* @apiName listNFTGroupChildren
|
||||
* @apiGroup SLP NFT1
|
||||
* @apiDescription Return list of NFT children tokens in a NFT Group.
|
||||
* It's assumed provided groupId parameter is for an NFT Group token (type=129)
|
||||
*
|
||||
* Returns an Array with GENESIS transaction IDs of the children tokens.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
|
||||
* const children = await bchjs.SLP.Nft1.listNFTGroupChildren(groupId)
|
||||
*
|
||||
* children = {
|
||||
* "nftChildren": [
|
||||
* "45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9",
|
||||
* "928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55"
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
async listNFTGroupChildren (groupId) {
|
||||
try {
|
||||
if (typeof groupId === 'string') {
|
||||
const response = await axios.get(
|
||||
`${this.restURL}slp/nftChildren/${groupId}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('groupId must be a string.')
|
||||
} catch (error) {
|
||||
// console.log('Error in listNFTGroupChildren()')
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.NFT1.parentNFTGroup() parentNFTGroup()
|
||||
* @apiName parentNFTGroup
|
||||
* @apiGroup SLP NFT1
|
||||
* @apiDescription Return parent NFT Group information for a given NFT child token.
|
||||
* It's assumed provided groupId parameter is for an NFT Child token (type=65)
|
||||
*
|
||||
* Returns a JSON with NFT group information.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
|
||||
* const group = await bchjs.SLP.Nft1.parentNFTGroup(tokenId)
|
||||
*
|
||||
* group = {
|
||||
* "nftGroup": {
|
||||
* "decimals": 0,
|
||||
* "timestamp": "2021-05-03 10:36:01",
|
||||
* "timestamp_unix": 1620038161,
|
||||
* "versionType": 129,
|
||||
* "documentUri": "psfoundation.cash",
|
||||
* "symbol": "PSF.TEST.GROUP",
|
||||
* "name": "PSF Test NFT Group",
|
||||
* "containsBaton": true,
|
||||
* "id": "68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a",
|
||||
* "documentHash": null,
|
||||
* "initialTokenQty": 1000000,
|
||||
* "blockCreated": 686117,
|
||||
* "totalMinted": null,
|
||||
* "totalBurned": null,
|
||||
* "circulatingSupply": null
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async parentNFTGroup (tokenId) {
|
||||
try {
|
||||
if (typeof tokenId === 'string') {
|
||||
const response = await axios.get(
|
||||
`${this.restURL}slp/nftGroup/${tokenId}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('tokenId must be a string.')
|
||||
} catch (error) {
|
||||
// console.log('Error in parentNFTGroup()')
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TokenType1
|
||||
module.exports = Nft1
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ class SLP {
|
||||
this.Address = new Address(config)
|
||||
this.ECPair = ECPair
|
||||
this.TokenType1 = new TokenType1(config)
|
||||
this.NFT1 = new NFT1(this.restURL)
|
||||
this.NFT1 = new NFT1(config)
|
||||
this.Utils = new Utils(config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,11 +118,11 @@ class TokenType1 {
|
||||
|
||||
// Convert the send quantity to the format expected by slp-mdm.
|
||||
const baseQty = sendQtyBig.toString()
|
||||
console.log('baseQty: ', baseQty)
|
||||
// console.log('baseQty: ', baseQty)
|
||||
|
||||
// Convert the change quantity to the format expected by slp-mdm.
|
||||
const baseChange = change.toString()
|
||||
console.log('baseChange: ', baseChange)
|
||||
// console.log('baseChange: ', baseChange)
|
||||
|
||||
// Check for potential burns
|
||||
const outputQty = new BigNumber(baseChange).plus(
|
||||
|
||||
+179
-63
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable no-useless-catch */
|
||||
|
||||
// Public npm libraries
|
||||
const axios = require('axios')
|
||||
const slpParser = require('slp-parser')
|
||||
const BigNumber = require('bignumber.js')
|
||||
|
||||
// const Script = require("../script")
|
||||
// const scriptLib = new Script()
|
||||
|
||||
// const BigNumber = require("bignumber.js")
|
||||
// Local libraries
|
||||
const Util = require('../util')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -37,6 +37,8 @@ class Utils {
|
||||
_this = this
|
||||
|
||||
this.whitelist = []
|
||||
|
||||
this.util = new Util(config)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -436,10 +438,11 @@ class Utils {
|
||||
// will be like the examples above. If SLPDB has fallen behind real-time
|
||||
// processing, it will return this output:
|
||||
// [ null ]
|
||||
async validateTxid (txid) {
|
||||
async validateTxid (txid, usrObj = null) {
|
||||
const path = `${this.restURL}slp/validateTxid`
|
||||
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
// console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
||||
|
||||
// Handle a single TXID or an array of TXIDs.
|
||||
let txids
|
||||
@@ -450,7 +453,8 @@ class Utils {
|
||||
const response = await _this.axios.post(
|
||||
path,
|
||||
{
|
||||
txids: txids
|
||||
txids: txids,
|
||||
usrObj // pass user data when making an internal call.
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
@@ -649,11 +653,12 @@ class Utils {
|
||||
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
|
||||
* valid: true } ]
|
||||
*/
|
||||
async validateTxid3 (txid) {
|
||||
async validateTxid3 (txid, usrObj = null) {
|
||||
const path = `${this.restURL}slp/validateTxid3`
|
||||
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
// console.log(`path: ${JSON.stringify(path, null, 2)}`)
|
||||
// console.log('validateTxid3 usrObj: ', usrObj)
|
||||
|
||||
// Handle a single TXID or an array of TXIDs.
|
||||
let txids
|
||||
@@ -664,7 +669,8 @@ class Utils {
|
||||
const response = await _this.axios.post(
|
||||
path,
|
||||
{
|
||||
txids: txids
|
||||
txids: txids,
|
||||
usrObj // pass user data when making an internal call.
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
@@ -893,6 +899,9 @@ class Utils {
|
||||
* If optional associative array parameter cache is used, will cache and
|
||||
* reuse responses for the same input.
|
||||
*
|
||||
* A third optional input, `usrObj`, is used by bch-api for managing rate limits.
|
||||
* It can be safely ignored when writing apps using this call.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
@@ -922,7 +931,7 @@ class Utils {
|
||||
*
|
||||
*/
|
||||
// Reimplementation of decodeOpReturn() using slp-parser.
|
||||
async decodeOpReturn (txid, cache = null) {
|
||||
async decodeOpReturn (txid, cache = null, usrObj = null) {
|
||||
// The cache object is an in-memory cache (JS Object) that can be passed
|
||||
// into this function. It helps if multiple vouts from the same TXID are
|
||||
// being evaluated. In that case, it can significantly reduce the number
|
||||
@@ -939,16 +948,27 @@ class Utils {
|
||||
if (cachedVal) return cachedVal
|
||||
}
|
||||
|
||||
// console.log(`decodeOpReturn usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
||||
|
||||
try {
|
||||
// Validate the txid input.
|
||||
if (!txid || txid === '' || typeof txid !== 'string') {
|
||||
throw new Error('txid string must be included.')
|
||||
}
|
||||
|
||||
// CT: 2/24/21 Deprected GET in favor of POST, to pass IP address.
|
||||
// Retrieve the transaction object from the full node.
|
||||
const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
|
||||
const response = await _this.axios.get(path, _this.axiosOptions)
|
||||
const txDetails = response.data
|
||||
const path = `${this.restURL}rawtransactions/getRawTransaction`
|
||||
const response = await _this.axios.post(
|
||||
path,
|
||||
{
|
||||
verbose: true,
|
||||
txids: [txid],
|
||||
usrObj // pass user data when making an internal call.
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
const txDetails = response.data[0]
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
// SLP spec expects OP_RETURN to be the first output of the transaction.
|
||||
@@ -995,8 +1015,16 @@ class Utils {
|
||||
|
||||
return tokenData
|
||||
} catch (error) {
|
||||
// Used for debugging
|
||||
// console.log('decodeOpReturn error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
// console.log(`decodeOpReturn error.message: ${error.message}`)
|
||||
// if (error.response && error.response.data) {
|
||||
// console.log(
|
||||
// `decodeOpReturn error.response.data: ${JSON.stringify(
|
||||
// error.response.data
|
||||
// )}`
|
||||
// )
|
||||
// }
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1009,15 +1037,30 @@ class Utils {
|
||||
*
|
||||
* Expects an array of UTXO objects as input. Returns an array of equal size.
|
||||
* Returns UTXO data hydrated with token information.
|
||||
* If the
|
||||
* UTXO does not belong to a SLP transaction, it will return an `isValid` property
|
||||
* set to false.
|
||||
* If the UTXO is part of an SLP transaction, it will return the UTXO object
|
||||
* with additional SLP information attached. An `isValid` property will be included.
|
||||
* If its value is true, the UTXO is a valid SLP UTXO.
|
||||
* If the isValid value is null,
|
||||
* then SLPDB has not yet processed that txid and validity has not been confirmed,
|
||||
* or a 429 rate-limit error was enountered during the processing of the request.
|
||||
*
|
||||
* - If the UTXO does not belong to a SLP transaction, it will return an
|
||||
* `isValid` property set to `false`.
|
||||
*
|
||||
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
|
||||
* with additional SLP information attached. An `isValid` property will be
|
||||
* included.
|
||||
* - If the `isValid` property is `true`, the UTXO is a valid SLP UTXO.
|
||||
* - If the `isValid` property is `null`, then SLPDB has not yet processed
|
||||
* that txid and validity has not been confirmed, or a 429 rate-limit error
|
||||
* was enountered during the processing of the request.
|
||||
*
|
||||
* An optional second input object, `usrObj`, allows the user to inject an
|
||||
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
||||
* a number, the call will delay by that number of milliseconds between
|
||||
* processing UTXOs.
|
||||
*
|
||||
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
||||
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
||||
* UTXOs at a
|
||||
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
||||
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
||||
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
@@ -1025,7 +1068,8 @@ class Utils {
|
||||
* try {
|
||||
* const utxos = await bchjs.Electrumx.utxo(`bitcoincash:qpcqs0n5xap26un2828n55gan2ylj7wavvzeuwdx05`)
|
||||
*
|
||||
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
* // Delay 100mS between processing UTXOs, to prevent rate-limit errors.
|
||||
* const utxoInfo = await bchjs.SLP.Utils.tokenUtxoDetails(utxos, { utxoDelay: 100 })
|
||||
*
|
||||
* console.log(`utxoInfo: ${JSON.stringify(utxoInfo, null, 2)}`)
|
||||
* } catch (error) {
|
||||
@@ -1053,16 +1097,13 @@ class Utils {
|
||||
* "tokenType": 1
|
||||
* }
|
||||
*/
|
||||
|
||||
// CT 1/11/20: Refactored to comply with this GitHub Issue:
|
||||
// https://github.com/Bitcoin-com/slp-sdk/issues/84
|
||||
|
||||
// CT 5/31/20: Refactored to use slp-parse library.
|
||||
async tokenUtxoDetails (utxos) {
|
||||
async tokenUtxoDetails (utxos, usrObj = null) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
|
||||
// console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
||||
|
||||
// Loop through each element in the array and validate the input before
|
||||
// further processing.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
@@ -1094,7 +1135,7 @@ class Utils {
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos)
|
||||
const outAry = await this._hydrateUtxo(utxos, usrObj)
|
||||
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
||||
|
||||
// *After* each UTXO has been hydrated with SLP data,
|
||||
@@ -1105,18 +1146,19 @@ class Utils {
|
||||
// *After* the UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
if (utxo.tokenType) {
|
||||
// Only execute this block if the current UTXO has a 'tokenType'
|
||||
// Only execute this code-path if the current UTXO has a 'tokenType'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// Validate using a 'waterfall' of validators.
|
||||
utxo.isValid = await this.waterfallValidateTxid(utxo.txid)
|
||||
utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
|
||||
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
// console.log('Error in tokenUtxoDetails()')
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
@@ -1133,8 +1175,20 @@ class Utils {
|
||||
* but much more performant handling of SLP tokens. Some wallet apps prefer
|
||||
* the scaling performance over the breadth of supported tokens.
|
||||
*
|
||||
* An optional second input object, `usrObj`, allows the user to inject an
|
||||
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
||||
* a number, the call will delay by that number of milliseconds between
|
||||
* processing UTXOs.
|
||||
*
|
||||
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
||||
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
||||
* UTXOs at a
|
||||
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
||||
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
||||
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
||||
*
|
||||
*/
|
||||
async tokenUtxoDetailsWL (utxos) {
|
||||
async tokenUtxoDetailsWL (utxos, usrObj = null) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
@@ -1170,7 +1224,7 @@ class Utils {
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos)
|
||||
const outAry = await this._hydrateUtxo(utxos, usrObj)
|
||||
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
||||
|
||||
// *After* each UTXO has been hydrated with SLP data,
|
||||
@@ -1186,7 +1240,7 @@ class Utils {
|
||||
// information.
|
||||
|
||||
// Validate against the whitelist SLPDB.
|
||||
const whitelistResult = await this.validateTxid3(utxo.txid)
|
||||
const whitelistResult = await this.validateTxid3(utxo.txid, usrObj)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
@@ -1214,10 +1268,26 @@ class Utils {
|
||||
// This is a private function that is called by tokenUtxoDetails().
|
||||
// It loops through an array of UTXOs and tries to hydrate them with SLP
|
||||
// token information from the OP_RETURN data.
|
||||
async _hydrateUtxo (utxos) {
|
||||
//
|
||||
// This function makes several calls to decodeOpReturn() to retrieve SLP
|
||||
// token data. If that call throws an error due to hitting rate limits, this
|
||||
// function will not throw an error. Instead, it will mark the `isValid`
|
||||
// property as `null`
|
||||
//
|
||||
// Exception to the above: It *will* throw an error if decodeOpReturn() throws
|
||||
// an error while trying to get the Genesis transaction for a Send or Mint
|
||||
// transaction. However, that is a rare occurence since the cache of
|
||||
// decodeOpReturn() will minimize API calls for this case. This behavior
|
||||
// could be changed, but right now it's a corner case of a corner case.
|
||||
//
|
||||
// If the usrObj has a utxoDelay property, then it will delay the loop for
|
||||
// each UTXO by that many milliseconds.
|
||||
async _hydrateUtxo (utxos, usrObj = null) {
|
||||
try {
|
||||
const decodeOpReturnCache = {}
|
||||
|
||||
// console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
||||
|
||||
// Output Array
|
||||
const outAry = []
|
||||
|
||||
@@ -1225,15 +1295,28 @@ class Utils {
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// If the user passes in a delay, then wait.
|
||||
if (usrObj && usrObj.utxoDelay && !isNaN(Number(usrObj.utxoDelay))) {
|
||||
const delayMs = Number(usrObj.utxoDelay)
|
||||
await this.util.sleep(delayMs)
|
||||
}
|
||||
|
||||
// Get raw transaction data from the full node and attempt to decode
|
||||
// the OP_RETURN data.
|
||||
// If there is no OP_RETURN, mark the UTXO as false.
|
||||
let slpData = false
|
||||
try {
|
||||
slpData = await this.decodeOpReturn(utxo.txid, decodeOpReturnCache)
|
||||
slpData = await this.decodeOpReturn(
|
||||
utxo.txid,
|
||||
decodeOpReturnCache,
|
||||
usrObj // pass user data when making an internal call.
|
||||
)
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`error from decodeOpReturn(${utxo.txid}): `, err)
|
||||
// console.log(
|
||||
// `error in _hydrateUtxo() from decodeOpReturn(${utxo.txid}): `,
|
||||
// err
|
||||
// )
|
||||
|
||||
// An error will be thrown if the txid is not SLP.
|
||||
// If error is for some other reason, like a 429 error, mark utxo as 'null'
|
||||
@@ -1245,7 +1328,7 @@ class Utils {
|
||||
err.message.indexOf('trailing data') === -1)
|
||||
) {
|
||||
// console.log(
|
||||
// 'unknown error from decodeOpReturn(). Marking as \'null\'',
|
||||
// "unknown error from decodeOpReturn(). Marking as 'null'",
|
||||
// err
|
||||
// )
|
||||
|
||||
@@ -1264,6 +1347,7 @@ class Utils {
|
||||
// Halt the execution of the loop and increase to the next index.
|
||||
continue
|
||||
}
|
||||
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
|
||||
|
||||
const txType = slpData.txType.toLowerCase()
|
||||
|
||||
@@ -1301,6 +1385,10 @@ class Utils {
|
||||
utxo.decimals = slpData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1320,7 +1408,8 @@ class Utils {
|
||||
|
||||
const genesisData = await this.decodeOpReturn(
|
||||
slpData.tokenId,
|
||||
decodeOpReturnCache
|
||||
decodeOpReturnCache,
|
||||
usrObj // pass user data when making an internal call.
|
||||
)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
@@ -1349,6 +1438,10 @@ class Utils {
|
||||
|
||||
utxo.mintBatonVout = slpData.mintBatonVout
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1372,7 +1465,8 @@ class Utils {
|
||||
|
||||
const genesisData = await this.decodeOpReturn(
|
||||
slpData.tokenId,
|
||||
decodeOpReturnCache
|
||||
decodeOpReturnCache,
|
||||
usrObj // pass user data when making an internal call.
|
||||
)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
@@ -1389,6 +1483,10 @@ class Utils {
|
||||
utxo.decimals = genesisData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// Initial value is null until UTXO can be validated and confirmed
|
||||
// to be valid (true) or not (false).
|
||||
utxo.isValid = null
|
||||
|
||||
// Calculate the real token quantity.
|
||||
|
||||
const tokenQtyBig = new BigNumber(tokenQty).div(
|
||||
@@ -1406,7 +1504,7 @@ class Utils {
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
// console.log('_hydrateUtxo error: ', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1447,8 +1545,7 @@ class Utils {
|
||||
* // returns
|
||||
* true
|
||||
*/
|
||||
|
||||
async waterfallValidateTxid (txid) {
|
||||
async waterfallValidateTxid (txid, usrObj = null) {
|
||||
try {
|
||||
// console.log('txid: ', txid)
|
||||
|
||||
@@ -1480,7 +1577,7 @@ class Utils {
|
||||
// validateTxid2() uses slp-validate, which has a different output format.
|
||||
|
||||
// Validate against the whitelist SLPDB first.
|
||||
const whitelistResult = await this.validateTxid3(txid)
|
||||
const whitelistResult = await this.validateTxid3(txid, usrObj)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
@@ -1501,7 +1598,7 @@ class Utils {
|
||||
}
|
||||
|
||||
// Try the general SLPDB, if the whitelist returned null.
|
||||
const generalResult = await this.validateTxid(txid)
|
||||
const generalResult = await this.validateTxid(txid, usrObj)
|
||||
// console.log(
|
||||
// `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
|
||||
// )
|
||||
@@ -1542,6 +1639,7 @@ class Utils {
|
||||
// could not be validated.
|
||||
return isValid
|
||||
} catch (error) {
|
||||
// console.log('Error in waterfallValidateTxid()')
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
@@ -1554,19 +1652,33 @@ class Utils {
|
||||
* @apiDescription Hydrate a UTXO with SLP token metadata.
|
||||
*
|
||||
* The same as tokenUtxoDetails(), but uses bch-api to do the heavy lifting,
|
||||
* which greatly reduces the number of API calls.
|
||||
* which greatly increases the speed, since fewer API calls need to be made.
|
||||
* However, internal API calls are still counted against your rate limits.
|
||||
*
|
||||
* Expects an array of UTXO objects as input. Returns an array of equal size.
|
||||
* Returns UTXO data hydrated with token information.
|
||||
* If the
|
||||
* UTXO does not belong to a SLP transaction, it will return an `isValid` property
|
||||
* set to false.
|
||||
* If the UTXO is part of an SLP transaction, it will return the UTXO object
|
||||
* with additional SLP information attached. An `isValid` property will be included.
|
||||
* If its value is true, the UTXO is a valid SLP UTXO. \
|
||||
* If the isValid value is null,
|
||||
* then SLPDB has not yet processed that txid and validity has not been confirmed,
|
||||
* or a 429 rate-limit error was enountered during the processing of the request.
|
||||
* This function expects an array of UTXO objects as input. It returns an
|
||||
* array of equal size. The UTXO data hydrated with token information.
|
||||
* - If the UTXO does not belong to a SLP transaction, it will return an
|
||||
* `isValid` property set to `false`.
|
||||
* - If the UTXO is part of an SLP transaction, it will return the UTXO object
|
||||
* with additional SLP information attached. An `isValid` property will be
|
||||
* included.
|
||||
* - If `isValid` is `true`, the UTXO is a valid SLP UTXO.
|
||||
* - If `isValid` is `null`, then SLPDB has not yet processed that txid
|
||||
* and validity has not been confirmed,
|
||||
* or a 429 rate-limit error was enountered during the processing of the
|
||||
* request.
|
||||
*
|
||||
* An optional second input object, `usrObj`, allows the user to inject an
|
||||
* artifical delay while processing UTXOs. If `usrObj.utxoDelay` is set to
|
||||
* a number, the call will delay by that number of milliseconds between
|
||||
* processing UTXOs.
|
||||
*
|
||||
* This is an API-heavy call. If you get a lot of `null` values, then slow down
|
||||
* the calls by using the usrObj.utxoDelay property, or request info on fewer
|
||||
* UTXOs at a
|
||||
* time. `null` indicates that the UTXO can *not* be safely spent, because
|
||||
* a judgement as to weather it is a token UTXO has not been made. Spending it
|
||||
* could burn tokens. It's safest to ignore UTXOs with a value of `null`.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
@@ -1578,7 +1690,8 @@ class Utils {
|
||||
* "bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk"
|
||||
* ])
|
||||
*
|
||||
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos)
|
||||
* // Wait 100mS between processing UTXOs, to prevent rate limit errors.
|
||||
* const utxoInfo = await bchjs.SLP.Utils.hydrateUtxos(utxos.utxos, { utxoDelay: 100 })
|
||||
*
|
||||
* console.log(`${JSON.stringify(utxoInfo, null, 2)}`)
|
||||
* } catch (error) {
|
||||
@@ -1712,7 +1825,7 @@ class Utils {
|
||||
*/
|
||||
// Same as tokenUtxoDetails(), but reduces API calls by having bch-api server
|
||||
// do the heavy lifting.
|
||||
async hydrateUtxos (utxos) {
|
||||
async hydrateUtxos (utxos, usrObj) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
@@ -1720,15 +1833,18 @@ class Utils {
|
||||
const response = await _this.axios.post(
|
||||
`${this.restURL}slp/hydrateUtxos`,
|
||||
{
|
||||
utxos: utxos
|
||||
utxos: utxos,
|
||||
usrObj
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
if (error.response && error.response.data) {
|
||||
throw new Error(JSON.stringify(error.response.data, null, 2))
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
const io = require('socket.io-client')
|
||||
|
||||
class Socket {
|
||||
constructor (config = {}) {
|
||||
if (typeof config === 'string') {
|
||||
// TODO remove this check in v2.0
|
||||
this.socket = io(`${config}`)
|
||||
} else {
|
||||
if (config.restURL) {
|
||||
this.socket = io(`${config.restURL}`)
|
||||
} else {
|
||||
const restURL = 'https://rest.bitcoin.com'
|
||||
this.socket = io(`${restURL}`)
|
||||
}
|
||||
|
||||
if (config.callback) config.callback()
|
||||
}
|
||||
}
|
||||
|
||||
listen (endpoint, cb) {
|
||||
this.socket.emit(endpoint)
|
||||
|
||||
if (endpoint === 'blocks') this.socket.on('blocks', msg => cb(msg))
|
||||
else if (endpoint === 'transactions') { this.socket.on('transactions', msg => cb(msg)) }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Socket
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
High-level functions for working with Transactions
|
||||
*/
|
||||
|
||||
const RawTransaction = require('./raw-transactions')
|
||||
const SlpUtils = require('./slp/utils')
|
||||
|
||||
class Transaction {
|
||||
constructor (config) {
|
||||
// Encapsulate dependencies
|
||||
this.slpUtils = new SlpUtils(config)
|
||||
this.rawTransaction = new RawTransaction(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Transaction.get() get()
|
||||
* @apiName get
|
||||
* @apiGroup Transaction
|
||||
* @apiDescription
|
||||
* Returns an object of transaction data, including addresses for input UTXOs.
|
||||
* If it is a SLP token transaction, the token information for inputs and
|
||||
* outputs will also be included.
|
||||
*
|
||||
* This is an API heavy call. This function will only work with a single txid.
|
||||
* It does not yet support an array of TXIDs.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let txData = await bchjs.Transaction.get("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
|
||||
* console.log(txData);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*/
|
||||
async get (txid) {
|
||||
try {
|
||||
if (typeof txid !== 'string') {
|
||||
throw new Error(
|
||||
'Input to Transaction.get() must be a string containing a TXID.'
|
||||
)
|
||||
}
|
||||
|
||||
const txDetails = await this.rawTransaction.getTxData(txid)
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
// Setup default SLP properties.
|
||||
txDetails.isValidSLPTx = false
|
||||
|
||||
// First get the token information for the output. If that fails, then
|
||||
// this is not an SLP transaction, and this method can return false.
|
||||
let outTokenData
|
||||
try {
|
||||
outTokenData = await this.slpUtils.decodeOpReturn(txid)
|
||||
// console.log(`outTokenData: ${JSON.stringify(outTokenData, null, 2)}`)
|
||||
|
||||
// Get Genesis data for this token.
|
||||
const genesisData = await this.slpUtils.decodeOpReturn(
|
||||
outTokenData.tokenId
|
||||
// decodeOpReturnCache
|
||||
// usrObj // pass user data when making an internal call.
|
||||
)
|
||||
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
|
||||
|
||||
// Add token information to the tx details object.
|
||||
txDetails.tokenTxType = outTokenData.txType
|
||||
txDetails.tokenId = outTokenData.tokenId
|
||||
txDetails.tokenTicker = genesisData.ticker
|
||||
txDetails.tokenName = genesisData.name
|
||||
txDetails.tokenDecimals = genesisData.decimals
|
||||
txDetails.tokenUri = genesisData.documentUri
|
||||
txDetails.tokenDocHash = genesisData.documentHash
|
||||
|
||||
// Add the token quantity to each output.
|
||||
for (let i = 0; i < outTokenData.amounts.length; i++) {
|
||||
const rawQty = outTokenData.amounts[i]
|
||||
const realQty = Number(rawQty) / Math.pow(10, txDetails.tokenDecimals)
|
||||
|
||||
txDetails.vout[i + 1].tokenQty = realQty
|
||||
}
|
||||
|
||||
// Add tokenQty = null to any outputs that don't have a value.
|
||||
for (let i = 0; i < txDetails.vout.length; i++) {
|
||||
const thisVout = txDetails.vout[i]
|
||||
|
||||
if (!thisVout.tokenQty && thisVout.tokenQty !== 0) {
|
||||
thisVout.tokenQty = null
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through each input and retrieve the token data.
|
||||
for (let i = 0; i < txDetails.vin.length; i++) {
|
||||
const thisVin = txDetails.vin[i]
|
||||
|
||||
try {
|
||||
// If decodeOpReturn() throws an error, then this input is not
|
||||
// from an SLP transaction and can be ignored.
|
||||
const inTokenData = await this.slpUtils.decodeOpReturn(thisVin.txid)
|
||||
// console.log(
|
||||
// `vin[${i}] tokenData: ${JSON.stringify(inTokenData, null, 2)}`
|
||||
// )
|
||||
|
||||
// Get the appropriate vout token amount. This may throw an error,
|
||||
// which means this Vin is not actually a token UTXO, it was just
|
||||
// associated with a previous token TX.
|
||||
const tokenQty = inTokenData.amounts[thisVin.vout - 1]
|
||||
// console.log(`tokenQty: ${JSON.stringify(tokenQty, null, 2)}`)
|
||||
|
||||
if (tokenQty) {
|
||||
const realQty =
|
||||
Number(tokenQty) / Math.pow(10, txDetails.tokenDecimals)
|
||||
|
||||
thisVin.tokenQty = realQty
|
||||
// txDetails.vin[i].tokenQty = tokenQty
|
||||
} else {
|
||||
thisVin.tokenQty = null
|
||||
}
|
||||
} catch (err) {
|
||||
// If decodeOpReturn() throws an error, then this input is not
|
||||
// from an SLP transaction and can be ignored.
|
||||
// thisVin.tokenQty = null
|
||||
thisVin.tokenQty = null
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, validate the SLP TX.
|
||||
// this.slpUtils.waterfallValidateTxid(txid, usrObj)
|
||||
txDetails.isValidSLPTx = await this.slpUtils.waterfallValidateTxid(txid)
|
||||
} catch (err) {
|
||||
// console.log('Error: ', err)
|
||||
|
||||
// If decoding the op_return fails, then it's not an SLP transaction,
|
||||
// and the non-hyrated TX details can be returned.
|
||||
return txDetails
|
||||
}
|
||||
|
||||
return txDetails
|
||||
} catch (err) {
|
||||
// console.error('Error in transactions.js/get(): ', err)
|
||||
|
||||
if (err.error) throw new Error(err.error)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Transaction
|
||||
+28
-4
@@ -28,7 +28,7 @@ class Util {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.floor8() floor8()
|
||||
* @api Util.floor8() floor8()
|
||||
* @apiName floor8
|
||||
* @apiGroup Util
|
||||
* @apiDescription Round a number down to 8 decimal places.
|
||||
@@ -63,7 +63,7 @@ class Util {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.floor2() floor2()
|
||||
* @api Util.floor2() floor2()
|
||||
* @apiName floor2
|
||||
* @apiGroup Util
|
||||
* @apiDescription Round a number down to 2 decimal places.
|
||||
@@ -98,7 +98,7 @@ class Util {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.chunk20() chunk20()
|
||||
* @api Util.chunk20() chunk20()
|
||||
* @apiName chunk20
|
||||
* @apiGroup Util
|
||||
* @apiDescription chunk up an array into multiple arrays of 20 elements each.
|
||||
@@ -151,7 +151,31 @@ class Util {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.validateAddress() validateAddress()
|
||||
* @api Util.sleep() sleep()
|
||||
* @apiName sleep
|
||||
* @apiGroup Util
|
||||
* @apiDescription Promise-based delay.
|
||||
* Expects an integer as input, which represents milliseconds. This function
|
||||
* will return a Promise that resolves that many milliseconds later.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const tenSeconds = 10000
|
||||
* await bchjs.Util.sleep(tenSeconds)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
*/
|
||||
sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Util.validateAddress() validateAddress()
|
||||
* @apiName Validate Address.
|
||||
* @apiGroup Util
|
||||
* @apiDescription Return information about the given bitcoin address.
|
||||
|
||||
@@ -276,6 +276,8 @@ class UTXO {
|
||||
return retAry
|
||||
} catch (err) {
|
||||
console.error('Error in bchjs.utxo.get()')
|
||||
|
||||
if (err.error) throw new Error(err.error)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
const assert = require('chai').assert
|
||||
|
||||
// const RESTURL = `https://abc.fullstack.cash/v4/`
|
||||
const RESTURL = 'https://bchn.fullstack.cash/v4/'
|
||||
// const RESTURL = `http://localhost:3000/v4/`
|
||||
// const RESTURL = `https://abc.fullstack.cash/v5/`
|
||||
const RESTURL = 'https://bchn.fullstack.cash/v5/'
|
||||
// const RESTURL = `http://localhost:3000/v5/`
|
||||
|
||||
const BCHJS = require('../../../src/bch-js')
|
||||
const bchjs = new BCHJS({ restURL: RESTURL })
|
||||
|
||||
@@ -13,9 +13,9 @@ const PRO_PASS = 'testpassword'
|
||||
|
||||
const BCHJS = require('../../../src/bch-js')
|
||||
const bchjs = new BCHJS({
|
||||
// restURL: `https://bchn.fullstack.cash/v4/`,
|
||||
restURL: 'https://abc.fullstack.cash/v4/',
|
||||
// restURL: `http://localhost:3000/v4/`,
|
||||
// restURL: `https://bchn.fullstack.cash/v5/`,
|
||||
restURL: 'https://abc.fullstack.cash/v5/',
|
||||
// restURL: `http://localhost:3000/v5/`,
|
||||
authPass: PRO_PASS
|
||||
})
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ const JWT_TOKEN =
|
||||
|
||||
const BCHJS = require('../../../src/bch-js')
|
||||
const bchjs = new BCHJS({
|
||||
restURL: 'https://api.fullstack.cash/v4/',
|
||||
// restURL: `http://localhost:3000/v4/`,
|
||||
restURL: 'https://api.fullstack.cash/v5/',
|
||||
// restURL: `http://localhost:3000/v5/`,
|
||||
apiToken: JWT_TOKEN
|
||||
})
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ const JWT_TOKEN =
|
||||
|
||||
const BCHJS = require('../../../src/bch-js')
|
||||
const bchjs = new BCHJS({
|
||||
// restURL: `https://bchn.fullstack.cash/v4/`,
|
||||
restURL: 'https:/abc.fullstack.cash/v4/',
|
||||
// restURL: `http://localhost:3000/v4/`,
|
||||
// restURL: `https://bchn.fullstack.cash/v5/`,
|
||||
restURL: 'https:/abc.fullstack.cash/v5/',
|
||||
// restURL: `http://localhost:3000/v5/`,
|
||||
apiToken: JWT_TOKEN
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('#full node rate limits', () => {
|
||||
|
||||
it('should throw error for more than 100 RPM to fullnode', async () => {
|
||||
try {
|
||||
console.log('This test usually doesn\'t pass, because of latency.')
|
||||
console.log("This test usually doesn't pass, because of latency.")
|
||||
for (let i = 0; i < 100; i++) await bchjs.Control.getNetworkInfo()
|
||||
} catch (err) {
|
||||
// console.log(`validating after 10th call`)
|
||||
|
||||
@@ -14,9 +14,9 @@ const JWT_TOKEN =
|
||||
|
||||
const BCHJS = require('../../../src/bch-js')
|
||||
const bchjs = new BCHJS({
|
||||
// restURL: `https://bchn.fullstack.cash/v4/`,
|
||||
restURL: 'https://abc.fullstack.cash/v4/',
|
||||
// restURL: `http://localhost:3000/v4/`,
|
||||
// restURL: `https://bchn.fullstack.cash/v5/`,
|
||||
restURL: 'https://abc.fullstack.cash/v5/',
|
||||
// restURL: `http://localhost:3000/v5/`,
|
||||
apiToken: JWT_TOKEN
|
||||
})
|
||||
|
||||
|
||||
@@ -276,6 +276,43 @@ describe('#blockchain', () => {
|
||||
assert.equal(result, null)
|
||||
})
|
||||
})
|
||||
describe('#getBlock', () => {
|
||||
it('should get block information with default verbosity', async () => {
|
||||
const blockhash =
|
||||
'0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536'
|
||||
const result = await bchjs.Blockchain.getBlock(blockhash)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'hash',
|
||||
'confirmations',
|
||||
'size',
|
||||
'height',
|
||||
'version',
|
||||
'versionHex',
|
||||
'merkleroot',
|
||||
'tx',
|
||||
'time',
|
||||
'mediantime',
|
||||
'nonce',
|
||||
'bits',
|
||||
'difficulty',
|
||||
'chainwork',
|
||||
'nTx',
|
||||
'previousblockhash',
|
||||
'nextblockhash'
|
||||
])
|
||||
assert.isArray(result.tx)
|
||||
})
|
||||
|
||||
it('should get block information with verbosity 0', async () => {
|
||||
const blockhash =
|
||||
'0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536'
|
||||
const verbosity = 0
|
||||
const result = await bchjs.Blockchain.getBlock(blockhash, verbosity)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -22,9 +22,13 @@ describe('#UTXO', () => {
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result.bchUtxos)
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.isArray(result.nullUtxos)
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'bchUtxos')
|
||||
assert.property(result[0], 'nullUtxos')
|
||||
assert.property(result[0], 'slpUtxos')
|
||||
assert.isArray(result[0].bchUtxos)
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
Integration tests for the bchjs. Only covers calls made to
|
||||
rest.bitcoin.com.
|
||||
|
||||
TODO
|
||||
*/
|
||||
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = {
|
||||
showHidden: true,
|
||||
colors: true,
|
||||
depth: 3
|
||||
}
|
||||
|
||||
describe('#rawtransaction', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
})
|
||||
|
||||
/*
|
||||
Testing sentRawTransaction isn't really possible with an integration test,
|
||||
as the endpoint really needs an e2e test to be properly tested. The tests
|
||||
below expect error messages returned from the server, but at least test
|
||||
that the server is responding on those endpoints, and responds consistently.
|
||||
*/
|
||||
describe('sendRawTransaction', () => {
|
||||
it('should send a single transaction hex', async () => {
|
||||
try {
|
||||
const hex =
|
||||
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
|
||||
|
||||
await bchjs.RawTransactions.sendRawTransaction(hex)
|
||||
// console.log(`result ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: ${util.inspect(err)}`)
|
||||
|
||||
assert.hasAllKeys(err, ['error'])
|
||||
assert.include(err.error, 'Missing inputs')
|
||||
}
|
||||
})
|
||||
|
||||
it('should send an array of tx hexes', async () => {
|
||||
try {
|
||||
const hexes = [
|
||||
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000',
|
||||
'01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
|
||||
]
|
||||
|
||||
const result = await bchjs.RawTransactions.sendRawTransaction(hexes)
|
||||
console.log(`result ${JSON.stringify(result, null, 2)}`)
|
||||
} catch (err) {
|
||||
// console.log(`err: ${util.inspect(err)}`)
|
||||
|
||||
assert.hasAllKeys(err, ['error'])
|
||||
assert.include(err.error, 'Missing inputs')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
Integration tests for the bchjs covering SLP tokens.
|
||||
These tests are specific to the ABC chain.
|
||||
*/
|
||||
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
let bchjs
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = {
|
||||
showHidden: true,
|
||||
colors: true,
|
||||
depth: 1
|
||||
}
|
||||
|
||||
describe('#SLP', () => {
|
||||
// before(() => {
|
||||
// console.log(`bchjs.SLP.restURL: ${bchjs.SLP.restURL}`)
|
||||
// console.log(`bchjs.SLP.apiToken: ${bchjs.SLP.apiToken}`)
|
||||
// })
|
||||
|
||||
beforeEach(async () => {
|
||||
// Introduce a delay so that the BVT doesn't trip the rate limits.
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
|
||||
describe('#util', () => {
|
||||
describe('#decodeOpReturn', () => {
|
||||
it('should decode a NFT Child transaction', async () => {
|
||||
const txid =
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
|
||||
|
||||
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data, 'tokenType')
|
||||
assert.property(data, 'txType')
|
||||
assert.property(data, 'ticker')
|
||||
assert.property(data, 'name')
|
||||
assert.property(data, 'tokenId')
|
||||
assert.property(data, 'documentUri')
|
||||
assert.property(data, 'documentHash')
|
||||
assert.property(data, 'decimals')
|
||||
assert.property(data, 'mintBatonVout')
|
||||
assert.property(data, 'qty')
|
||||
|
||||
assert.equal(data.tokenType, 65)
|
||||
assert.equal(data.mintBatonVout, 0)
|
||||
assert.equal(data.qty, '1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetails', () => {
|
||||
it('should handle a range of UTXO types', async () => {
|
||||
const utxos = [
|
||||
// Malformed SLP tx
|
||||
{
|
||||
note: 'Malformed SLP tx',
|
||||
tx_hash:
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
// Normal TX (non-SLP)
|
||||
{
|
||||
note: 'Normal TX (non-SLP)',
|
||||
tx_hash:
|
||||
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
|
||||
tx_pos: 0,
|
||||
value: 400000
|
||||
},
|
||||
// Valid PSF SLP tx
|
||||
{
|
||||
note: 'Valid PSF SLP tx',
|
||||
tx_hash:
|
||||
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
// Valid SLP token not in whitelist
|
||||
{
|
||||
note: 'Valid SLP token not in whitelist',
|
||||
tx_hash:
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
// Token send on BCHN network.
|
||||
{
|
||||
note: 'Token send on BCHN network',
|
||||
tx_hash:
|
||||
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
// Token send on ABC network.
|
||||
{
|
||||
note: 'Token send on ABC network',
|
||||
tx_hash:
|
||||
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
// Known invalid SLP token send of PSF tokens.
|
||||
{
|
||||
note: 'Known invalid SLP token send of PSF tokens',
|
||||
tx_hash:
|
||||
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// Malformed SLP tx
|
||||
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
|
||||
assert.equal(data[0].isValid, false)
|
||||
|
||||
// Normal TX (non-SLP)
|
||||
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
|
||||
assert.equal(data[1].isValid, false)
|
||||
|
||||
// Valid PSF SLP tx
|
||||
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
|
||||
assert.equal(data[2].isValid, true)
|
||||
|
||||
// Valid SLP token not in whitelist
|
||||
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
|
||||
assert.equal(data[3].isValid, true)
|
||||
|
||||
// Token send on BCHN network
|
||||
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
|
||||
assert.equal(data[4].isValid, true)
|
||||
|
||||
// Token send on ABC network
|
||||
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
|
||||
assert.equal(data[5].isValid, null)
|
||||
|
||||
// Known invalid SLP token send of PSF tokens
|
||||
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
|
||||
assert.equal(data[6].isValid, false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid3', () => {
|
||||
it('should invalidate a known invalid TXID', async () => {
|
||||
const txid =
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
|
||||
|
||||
const result = await bchjs.SLP.Utils.validateTxid3(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
|
||||
assert.property(result[0], 'txid')
|
||||
assert.equal(result[0].txid, txid)
|
||||
|
||||
assert.property(result[0], 'valid')
|
||||
assert.equal(result[0].valid, null)
|
||||
})
|
||||
|
||||
it('should validate a known valid TXID', async () => {
|
||||
const txid =
|
||||
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
|
||||
|
||||
const result = await bchjs.SLP.Utils.validateTxid3(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
|
||||
assert.property(result[0], 'txid')
|
||||
assert.equal(result[0].txid, txid)
|
||||
|
||||
assert.property(result[0], 'valid')
|
||||
assert.equal(result[0].valid, true)
|
||||
})
|
||||
|
||||
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
|
||||
const txids = [
|
||||
// Malformed SLP tx
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
|
||||
// Normal TX (non-SLP)
|
||||
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
|
||||
// Valid PSF SLP tx
|
||||
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
|
||||
// Valid SLP token not in whitelist
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
// Unprocessed SLP TX
|
||||
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.validateTxid3(txids)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
|
||||
assert.equal(result[0].txid, txids[0])
|
||||
assert.equal(result[0].valid, null)
|
||||
|
||||
assert.equal(result[1].txid, txids[1])
|
||||
assert.equal(result[1].valid, null)
|
||||
|
||||
assert.equal(result[2].txid, txids[2])
|
||||
assert.equal(result[2].valid, true)
|
||||
|
||||
// True in validateTxid() (no whitelist SLPDB) but null in validateTxid3()
|
||||
assert.equal(result[3].txid, txids[3])
|
||||
assert.equal(result[3].valid, true)
|
||||
|
||||
// Note: This should change from null to true once SLPDB finishes indexing.
|
||||
assert.equal(result[4].txid, txids[4])
|
||||
// True when no whitelist SLPDB is used.
|
||||
assert.equal(result[4].valid, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid', () => {
|
||||
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
|
||||
const txids = [
|
||||
// Malformed SLP tx
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
|
||||
// Normal TX (non-SLP)
|
||||
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
|
||||
// Valid PSF SLP tx
|
||||
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
|
||||
// Valid SLP token not in whitelist
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
// Token send on BCHN network.
|
||||
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
|
||||
// Token send on ABC network.
|
||||
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.validateTxid(txids)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
|
||||
assert.equal(result[0].txid, txids[0])
|
||||
assert.equal(result[0].valid, null)
|
||||
|
||||
assert.equal(result[1].txid, txids[1])
|
||||
assert.equal(result[1].valid, null)
|
||||
|
||||
assert.equal(result[2].txid, txids[2])
|
||||
assert.equal(result[2].valid, true)
|
||||
|
||||
// True in validateTxid() but null in validateTxid3()
|
||||
assert.equal(result[3].txid, txids[3])
|
||||
assert.equal(result[3].valid, true)
|
||||
|
||||
assert.equal(result[4].txid, txids[4])
|
||||
assert.equal(result[4].valid, true)
|
||||
|
||||
assert.equal(result[5].txid, txids[5])
|
||||
assert.equal(result[5].valid, null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Promise-based sleep function
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Integration tests for the utxo.js library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#UTXO', () => {
|
||||
beforeEach(async () => {
|
||||
// sandbox = sinon.createSandbox()
|
||||
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'bchUtxos')
|
||||
assert.property(result[0], 'nullUtxos')
|
||||
assert.property(result[0], 'slpUtxos')
|
||||
assert.isArray(result[0].bchUtxos)
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
})
|
||||
|
||||
it('should handle an array of addresses', async () => {
|
||||
const addr = [
|
||||
'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9',
|
||||
'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
]
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'bchUtxos')
|
||||
assert.property(result[0], 'nullUtxos')
|
||||
assert.property(result[0], 'slpUtxos')
|
||||
assert.isArray(result[0].bchUtxos)
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
})
|
||||
|
||||
it('should handle NFTs and minting batons', async () => {
|
||||
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'bchUtxos')
|
||||
assert.property(result[0], 'nullUtxos')
|
||||
assert.property(result[0], 'slpUtxos')
|
||||
assert.isArray(result[0].bchUtxos)
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
|
||||
assert.isArray(result[0].slpUtxos.type1.mintBatons)
|
||||
assert.isArray(result[0].slpUtxos.type1.tokens)
|
||||
assert.isArray(result[0].slpUtxos.nft.groupMintBatons)
|
||||
assert.isArray(result[0].slpUtxos.nft.groupTokens)
|
||||
assert.isArray(result[0].slpUtxos.nft.tokens)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#findBiggestUtxo', () => {
|
||||
it('should sort UTXOs from Electrumx', async () => {
|
||||
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
|
||||
|
||||
const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
|
||||
// console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
|
||||
|
||||
const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'satoshis')
|
||||
assert.equal(result.satoshis, 800)
|
||||
})
|
||||
|
||||
it('should sort UTXOs from Utxos.get()', async () => {
|
||||
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
|
||||
|
||||
const utxos = await bchjs.Utxo.get(addr)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'satoshis')
|
||||
assert.equal(result.satoshis, 800)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Integration tests for bchjs dsproof library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#DSProof', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
|
||||
})
|
||||
|
||||
describe('#getDSProof', () => {
|
||||
it('should get TX info from the full node', async () => {
|
||||
const txid =
|
||||
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
|
||||
const result = await bchjs.DSProof.getDSProof(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, null)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -20,7 +20,7 @@ util.inspect.defaultOptions = {
|
||||
|
||||
describe('#rawtransaction', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
})
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,12 +25,37 @@ describe('#SLP', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
// Introduce a delay so that the BVT doesn't trip the rate limits.
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
|
||||
describe('#util', () => {
|
||||
describe('#decodeOpReturn', () => {
|
||||
it('should decode a NFT Child transaction', async () => {
|
||||
const txid =
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
|
||||
|
||||
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data, 'tokenType')
|
||||
assert.property(data, 'txType')
|
||||
assert.property(data, 'ticker')
|
||||
assert.property(data, 'name')
|
||||
assert.property(data, 'tokenId')
|
||||
assert.property(data, 'documentUri')
|
||||
assert.property(data, 'documentHash')
|
||||
assert.property(data, 'decimals')
|
||||
assert.property(data, 'mintBatonVout')
|
||||
assert.property(data, 'qty')
|
||||
|
||||
assert.equal(data.tokenType, 65)
|
||||
assert.equal(data.mintBatonVout, 0)
|
||||
assert.equal(data.qty, '1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetails', () => {
|
||||
it('should handle a range of UTXO types', async () => {
|
||||
const utxos = [
|
||||
@@ -239,6 +264,31 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('#nft1', () => {
|
||||
describe('#listNFTGroupChildren', () => {
|
||||
it('should return array of children GENESIS transactions IDs', async () => {
|
||||
const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
|
||||
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
assert.property(result, 'nftChildren')
|
||||
assert.isArray(result.nftChildren)
|
||||
})
|
||||
})
|
||||
describe('#parentNFTGroup', () => {
|
||||
it('should return parent NFT group information', async () => {
|
||||
const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
|
||||
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
assert.property(result, 'nftGroup')
|
||||
assert.property(result.nftGroup, 'id')
|
||||
assert.property(result.nftGroup, 'name')
|
||||
assert.property(result.nftGroup, 'symbol')
|
||||
assert.property(result.nftGroup, 'documentUri')
|
||||
assert.property(result.nftGroup, 'versionType')
|
||||
assert.property(result.nftGroup, 'initialTokenQty')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Promise-based sleep function
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('#UTXO', () => {
|
||||
beforeEach(async () => {
|
||||
// sandbox = sinon.createSandbox()
|
||||
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
@@ -112,7 +112,7 @@ describe('#UTXO', () => {
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||
console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'satoshis')
|
||||
assert.equal(result.satoshis, 800)
|
||||
|
||||
@@ -12,11 +12,11 @@ const assert = chai.assert
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
|
||||
@@ -7,7 +7,7 @@ const assert = chai.assert
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
|
||||
@@ -5,7 +5,7 @@ const util = require('util')
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
@@ -68,7 +68,9 @@ describe('#ElectrumX', () => {
|
||||
it('should throw error on array size rate limit', async () => {
|
||||
try {
|
||||
const addr = []
|
||||
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
|
||||
}
|
||||
|
||||
// const result = await bchjs.Electrumx.utxo(addr)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
@@ -121,7 +123,9 @@ describe('#ElectrumX', () => {
|
||||
it('should throw error on array size rate limit', async () => {
|
||||
try {
|
||||
const addr = []
|
||||
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
|
||||
}
|
||||
|
||||
const result = await bchjs.Electrumx.balance(addr)
|
||||
|
||||
@@ -175,7 +179,9 @@ describe('#ElectrumX', () => {
|
||||
it('should throw error on array size rate limit', async () => {
|
||||
try {
|
||||
const addr = []
|
||||
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
|
||||
}
|
||||
|
||||
const result = await bchjs.Electrumx.transactions(addr)
|
||||
|
||||
@@ -236,7 +242,9 @@ describe('#ElectrumX', () => {
|
||||
it('should throw error on array size rate limit', async () => {
|
||||
try {
|
||||
const addr = []
|
||||
for (let i = 0; i < 25; i++) { addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bchtest:qrvn2n228aa39xupcw9jw0d3fj8axxky656e4j62z2')
|
||||
}
|
||||
|
||||
await bchjs.Electrumx.unconfirmed(addr)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
@@ -355,7 +363,7 @@ describe('#ElectrumX', () => {
|
||||
assert.property(err, 'success')
|
||||
assert.equal(err.success, false)
|
||||
assert.include(
|
||||
err.error,
|
||||
err.error.error,
|
||||
'the transaction was rejected by network rules'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
// const chai = require('chai')
|
||||
// const assert = chai.assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS({ ninsightURL: 'https://trest.bitcoin.com/v2' })
|
||||
// const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS({ ninsightURL: 'https://trest.bitcoin.com/v2' })
|
||||
|
||||
describe('#Ninsight', () => {
|
||||
let sandbox
|
||||
@@ -16,130 +16,132 @@ describe('#Ninsight', () => {
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#utxo', () => {
|
||||
it('should GET utxos for a single address', async () => {
|
||||
const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
|
||||
|
||||
const result = await bchjs.Ninsight.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result[0], 'utxos')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'slpAddress')
|
||||
assert.property(result[0], 'scriptPubKey')
|
||||
assert.property(result[0], 'asm')
|
||||
|
||||
assert.isArray(result[0].utxos)
|
||||
|
||||
assert.property(result[0].utxos[0], 'txid')
|
||||
assert.property(result[0].utxos[0], 'vout')
|
||||
assert.property(result[0].utxos[0], 'amount')
|
||||
assert.property(result[0].utxos[0], 'satoshis')
|
||||
assert.property(result[0].utxos[0], 'height')
|
||||
assert.property(result[0].utxos[0], 'confirmations')
|
||||
})
|
||||
|
||||
it('should POST utxo details for an array of addresses', async () => {
|
||||
const addr = [
|
||||
'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
|
||||
'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.isArray(result[0].utxos)
|
||||
|
||||
assert.property(result[0], 'utxos')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'slpAddress')
|
||||
assert.property(result[0], 'scriptPubKey')
|
||||
assert.property(result[0], 'asm')
|
||||
|
||||
// assert.hasAnyKeys(result[0][0], [
|
||||
// "txid",
|
||||
// "vout",
|
||||
// "value",
|
||||
// "height",
|
||||
// "confirmations"
|
||||
// ])
|
||||
})
|
||||
})
|
||||
describe('#transactions', () => {
|
||||
it('should POST transactions history for a single address', async () => {
|
||||
const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
|
||||
|
||||
const result = await bchjs.Ninsight.transactions(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'txs')
|
||||
assert.isArray(result[0].txs)
|
||||
assert.property(result[0].txs[0], 'txid')
|
||||
assert.property(result[0].txs[0], 'vin')
|
||||
assert.property(result[0].txs[0], 'vout')
|
||||
})
|
||||
it('should POST transactions history for an array of addresses', async () => {
|
||||
const addr = [
|
||||
'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
|
||||
'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.transactions(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'txs')
|
||||
assert.isArray(result[0].txs)
|
||||
assert.property(result[0].txs[0], 'txid')
|
||||
assert.property(result[0].txs[0], 'vin')
|
||||
assert.property(result[0].txs[0], 'vout')
|
||||
})
|
||||
})
|
||||
describe('#txDetails', () => {
|
||||
it('should POST transactions details for a single address', async () => {
|
||||
const txid =
|
||||
'76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
|
||||
|
||||
const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'txid')
|
||||
assert.property(result[0], 'version')
|
||||
assert.property(result[0], 'locktime')
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'vout')
|
||||
assert.property(result[0], 'blockhash')
|
||||
assert.property(result[0], 'blockheight')
|
||||
assert.property(result[0], 'confirmations')
|
||||
assert.property(result[0], 'time')
|
||||
assert.property(result[0], 'blocktime')
|
||||
assert.property(result[0], 'valueOut')
|
||||
assert.property(result[0], 'size')
|
||||
})
|
||||
it('should POST transactions details for an array of addresses', async () => {
|
||||
const txid = [
|
||||
'f191d1062789d7071da6f2168ba306869a829294f6b1c09d03492db4ca3a8e77',
|
||||
'76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'txid')
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'vout')
|
||||
})
|
||||
})
|
||||
// describe('#utxo', () => {
|
||||
// it('should GET utxos for a single address', async () => {
|
||||
// const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
|
||||
//
|
||||
// const result = await bchjs.Ninsight.utxo(addr)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.property(result[0], 'utxos')
|
||||
// assert.property(result[0], 'legacyAddress')
|
||||
// assert.property(result[0], 'cashAddress')
|
||||
// assert.property(result[0], 'slpAddress')
|
||||
// assert.property(result[0], 'scriptPubKey')
|
||||
// assert.property(result[0], 'asm')
|
||||
//
|
||||
// assert.isArray(result[0].utxos)
|
||||
//
|
||||
// assert.property(result[0].utxos[0], 'txid')
|
||||
// assert.property(result[0].utxos[0], 'vout')
|
||||
// assert.property(result[0].utxos[0], 'amount')
|
||||
// assert.property(result[0].utxos[0], 'satoshis')
|
||||
// assert.property(result[0].utxos[0], 'height')
|
||||
// assert.property(result[0].utxos[0], 'confirmations')
|
||||
// })
|
||||
//
|
||||
// it('should POST utxo details for an array of addresses', async () => {
|
||||
// const addr = [
|
||||
// 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
|
||||
// 'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.Ninsight.utxo(addr)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.isArray(result[0].utxos)
|
||||
//
|
||||
// assert.property(result[0], 'utxos')
|
||||
// assert.property(result[0], 'legacyAddress')
|
||||
// assert.property(result[0], 'cashAddress')
|
||||
// assert.property(result[0], 'slpAddress')
|
||||
// assert.property(result[0], 'scriptPubKey')
|
||||
// assert.property(result[0], 'asm')
|
||||
//
|
||||
// // assert.hasAnyKeys(result[0][0], [
|
||||
// // "txid",
|
||||
// // "vout",
|
||||
// // "value",
|
||||
// // "height",
|
||||
// // "confirmations"
|
||||
// // ])
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#transactions', () => {
|
||||
// it('should POST transactions history for a single address', async () => {
|
||||
// const addr = 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr'
|
||||
//
|
||||
// const result = await bchjs.Ninsight.transactions(addr)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.property(result[0], 'cashAddress')
|
||||
// assert.property(result[0], 'legacyAddress')
|
||||
// assert.property(result[0], 'txs')
|
||||
// assert.isArray(result[0].txs)
|
||||
// assert.property(result[0].txs[0], 'txid')
|
||||
// assert.property(result[0].txs[0], 'vin')
|
||||
// assert.property(result[0].txs[0], 'vout')
|
||||
// })
|
||||
// it('should POST transactions history for an array of addresses', async () => {
|
||||
// const addr = [
|
||||
// 'bchtest:qzjtnzcvzxx7s0na88yrg3zl28wwvfp97538sgrrmr',
|
||||
// 'bchtest:qp6hgvevf4gzz6l7pgcte3gaaud9km0l459fa23dul'
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.Ninsight.transactions(addr)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.property(result[0], 'cashAddress')
|
||||
// assert.property(result[0], 'legacyAddress')
|
||||
// assert.property(result[0], 'txs')
|
||||
// assert.isArray(result[0].txs)
|
||||
// assert.property(result[0].txs[0], 'txid')
|
||||
// assert.property(result[0].txs[0], 'vin')
|
||||
// assert.property(result[0].txs[0], 'vout')
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#txDetails', () => {
|
||||
// it('should POST transactions details for a single address', async () => {
|
||||
// const txid =
|
||||
// '76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
|
||||
//
|
||||
// const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.property(result[0], 'txid')
|
||||
// assert.property(result[0], 'version')
|
||||
// assert.property(result[0], 'locktime')
|
||||
// assert.property(result[0], 'vin')
|
||||
// assert.property(result[0], 'vout')
|
||||
// assert.property(result[0], 'blockhash')
|
||||
// assert.property(result[0], 'blockheight')
|
||||
// assert.property(result[0], 'confirmations')
|
||||
// assert.property(result[0], 'time')
|
||||
// assert.property(result[0], 'blocktime')
|
||||
// assert.property(result[0], 'valueOut')
|
||||
// assert.property(result[0], 'size')
|
||||
// })
|
||||
// it('should POST transactions details for an array of addresses', async () => {
|
||||
// const txid = [
|
||||
// 'f191d1062789d7071da6f2168ba306869a829294f6b1c09d03492db4ca3a8e77',
|
||||
// '76856d82e00b2696acd8d989e1fa6c46b431005046a285ce905814cac0ff8fea'
|
||||
// ]
|
||||
//
|
||||
// const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.isArray(result)
|
||||
// assert.property(result[0], 'txid')
|
||||
// assert.property(result[0], 'vin')
|
||||
// assert.property(result[0], 'vout')
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -10,11 +10,11 @@ const assert = chai.assert
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
|
||||
@@ -7,11 +7,11 @@ const assert = chai.assert
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
export RESTURL=https://free-test.fullstack.cash/v4/
|
||||
export RESTURL=https://free-test.fullstack.cash/v5/
|
||||
export NETWORK=testnet
|
||||
export IS_USING_FREE_TIER=true
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ const assert = chai.assert
|
||||
|
||||
const RESTURL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://testnet3.fullstack.cash/v4/'
|
||||
: 'https://testnet3.fullstack.cash/v5/'
|
||||
// if (process.env.RESTURL) RESTURL = process.env.RESTURL
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v4/` })
|
||||
// const bchjs = new BCHJS({ restURL: `https://testnet.bchjs.cash/v5/` })
|
||||
const bchjs = new BCHJS({ restURL: RESTURL, apiToken: process.env.BCHJSTOKEN })
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
|
||||
@@ -359,10 +359,12 @@ describe('#ElectrumX', () => {
|
||||
try {
|
||||
await bchjs.Electrumx.broadcast(txHex)
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
assert.property(err, 'success')
|
||||
assert.equal(err.success, false)
|
||||
assert.include(
|
||||
err.error,
|
||||
err.error.error,
|
||||
'the transaction was rejected by network rules'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,14 @@ describe('#price', () => {
|
||||
assert.isNumber(result)
|
||||
})
|
||||
})
|
||||
describe('#getBchUsd', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
const result = await bchjs.Price.getBchUsd()
|
||||
console.log(result)
|
||||
|
||||
assert.isNumber(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rates', () => {
|
||||
it('should get the price of BCH in several currencies', async () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Integration tests for the bchjs. Only covers calls made to
|
||||
rest.bitcoin.com.
|
||||
Integration tests for the bchjs.
|
||||
|
||||
TODO
|
||||
*/
|
||||
@@ -252,9 +251,13 @@ describe('#rawtransaction', () => {
|
||||
describe('#_getInputAddrs', () => {
|
||||
it('should return an array of input addresses', async () => {
|
||||
// const txid = '32233db13f2ae6d82b6262f335643dccf09fc0bcfcef4bc3fbe023355f02e112'
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
const txid =
|
||||
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const txDetails = await bchjs.RawTransactions.getRawTransaction(txid, true)
|
||||
const txDetails = await bchjs.RawTransactions.getRawTransaction(
|
||||
txid,
|
||||
true
|
||||
)
|
||||
|
||||
const result = await bchjs.RawTransactions._getInputAddrs(txDetails)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -263,43 +266,46 @@ describe('#rawtransaction', () => {
|
||||
assert.equal(result.length, 1)
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'address')
|
||||
assert.property(result[0], 'value')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxData', () => {
|
||||
it('should return tx data with input addresses', async () => {
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
const txid =
|
||||
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxData(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxDataSlp', () => {
|
||||
it('should return tx data with SLP information', async () => {
|
||||
const txid = 'b438855cfcab64516b44097d7212df9cdb99226c8d7c7ab504d35fcfd834cb5b'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxDataSlp(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'tokenQty')
|
||||
|
||||
assert.equal(result.vin[0].tokenQty, null)
|
||||
assert.equal(result.vin[1].tokenQty, '100000000000')
|
||||
})
|
||||
|
||||
it('should handle non-slp tx', async () => {
|
||||
const txid = '04a6aca328af2445327015895b8da9766093b1989b52e477559759eb8072fc0a'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxDataSlp(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
// describe('#getTxDataSlp', () => {
|
||||
// it('should return tx data with SLP information', async () => {
|
||||
// const txid = 'b438855cfcab64516b44097d7212df9cdb99226c8d7c7ab504d35fcfd834cb5b'
|
||||
//
|
||||
// const result = await bchjs.RawTransactions.getTxDataSlp(txid)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.property(result.vin[0], 'address')
|
||||
// assert.property(result.vin[0], 'tokenQty')
|
||||
//
|
||||
// assert.equal(result.vin[0].tokenQty, null)
|
||||
// assert.equal(result.vin[1].tokenQty, '100000000000')
|
||||
// })
|
||||
//
|
||||
// it('should handle non-slp tx', async () => {
|
||||
// const txid = '04a6aca328af2445327015895b8da9766093b1989b52e477559759eb8072fc0a'
|
||||
//
|
||||
// const result = await bchjs.RawTransactions.getTxDataSlp(txid)
|
||||
// // console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
//
|
||||
// assert.equal(result, false)
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('#SLP', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
// Introduce a delay so that the BVT doesn't trip the rate limits.
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
@@ -639,7 +639,7 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxos', () => {
|
||||
describe('#hydrateUtxos-', () => {
|
||||
it('should hydrate UTXOs', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Integration tests for the transaction.js library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#Transaction', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
it('should get details about a non-SLP transaction', async () => {
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[0].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, false)
|
||||
})
|
||||
|
||||
it('should get details about a SLP transaction', async () => {
|
||||
const txid =
|
||||
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
|
||||
|
||||
const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[1].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vout[0], 'tokenQty')
|
||||
assert.equal(result.vout[0].tokenQty, null)
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result.vin[0], 'tokenQty')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
+28
-7
@@ -61,16 +61,40 @@ describe('#Blockchain', () => {
|
||||
|
||||
it('should get block by hash', done => {
|
||||
const resolved = new Promise(resolve => resolve({ data: data }))
|
||||
sandbox.stub(axios, 'get').returns(resolved)
|
||||
sandbox.stub(axios, 'post').returns(resolved)
|
||||
|
||||
bchjs.Blockchain.getBlock(
|
||||
const blockhash =
|
||||
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09'
|
||||
)
|
||||
bchjs.Blockchain.getBlock(blockhash)
|
||||
.then(result => {
|
||||
assert.deepStrictEqual(data, result)
|
||||
})
|
||||
.then(done, done)
|
||||
})
|
||||
it('should throw error if blockhash is not provided', async () => {
|
||||
try {
|
||||
await bchjs.Blockchain.getBlock()
|
||||
assert2.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert2.include(err.message, 'blockhash must be a string')
|
||||
}
|
||||
})
|
||||
it('should handle response error', async () => {
|
||||
try {
|
||||
const error = new Error()
|
||||
error.response = {
|
||||
data: 'Test Error'
|
||||
}
|
||||
sandbox.stub(axios, 'post').throws(error)
|
||||
|
||||
const blockhash =
|
||||
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09'
|
||||
await bchjs.Blockchain.getBlock(blockhash)
|
||||
assert2.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert2.include(err, 'Test Error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBlockchainInfo', () => {
|
||||
@@ -355,10 +379,7 @@ describe('#Blockchain', () => {
|
||||
try {
|
||||
await bchjs.Blockchain.getTxOut('badtxid')
|
||||
} catch (err) {
|
||||
assert2.include(
|
||||
err.message,
|
||||
'txid needs to be a proper transaction ID'
|
||||
)
|
||||
assert2.include(err.message, 'txid needs to be a proper transaction ID')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Unit under test (uut)
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const mockData = require('./fixtures/dsproof-mock')
|
||||
|
||||
let bchjs
|
||||
const txid = 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
|
||||
describe('#DSProof', () => {
|
||||
let sandbox
|
||||
beforeEach(() => {
|
||||
bchjs = new BCHJS()
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#getDSProof', () => {
|
||||
it('should throw error if input is not provided', async () => {
|
||||
try {
|
||||
await bchjs.DSProof.getDSProof()
|
||||
assert.equal(false, true, 'unexpected error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'txid is required')
|
||||
}
|
||||
})
|
||||
it('should throw error txid is invalid', async () => {
|
||||
try {
|
||||
await bchjs.DSProof.getDSProof('txid')
|
||||
assert.equal(false, true, 'unexpected error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'txid must be of length 64')
|
||||
}
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(bchjs.DSProof.axios, 'get').throws(new Error('test error'))
|
||||
await bchjs.DSProof.getDSProof(txid)
|
||||
|
||||
assert.equal(false, true, 'unexpected error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should get double spend proof', async () => {
|
||||
try {
|
||||
sandbox
|
||||
.stub(bchjs.DSProof.axios, 'get')
|
||||
.resolves({ data: mockData.dsproof })
|
||||
const result = await bchjs.DSProof.getDSProof(txid)
|
||||
|
||||
assert.property(result, 'dspid')
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'outpoint')
|
||||
assert.property(result, 'path')
|
||||
assert.property(result, 'descendants')
|
||||
|
||||
assert.property(result.outpoint, 'txid')
|
||||
assert.property(result.outpoint, 'vout')
|
||||
} catch (error) {
|
||||
assert.equal(false, true, 'unexpected error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Mock data used for unit testing.
|
||||
*/
|
||||
const dsproof = {
|
||||
dspid: '6234fff2a9dbcaa50e2c50d74e1d725a2bed1757d7a05a2db7a82df659554397',
|
||||
txid: 'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e',
|
||||
outpoint: {
|
||||
txid: '17ebef34f7e9a0692317dd6fbb43ffccff45b7d688a2a754dbccfc5e6d93ce3e',
|
||||
vout: 1
|
||||
},
|
||||
path: ['ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'],
|
||||
descendants: [
|
||||
'ee0df780b58f6f24467605b2589c44c3a50fc849fb8f91b89669a4ae0d86bc7e'
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dsproof
|
||||
}
|
||||
@@ -119,6 +119,33 @@ const mockTokens = [
|
||||
}
|
||||
]
|
||||
|
||||
const mockNftChildrenList = {
|
||||
nftChildren: [
|
||||
'45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9',
|
||||
'928ce61fe1006b1325a0ba0dce700bf83986a6f0691ba26e121c9ac035d12a55'
|
||||
]
|
||||
}
|
||||
|
||||
const mockNftGroup = {
|
||||
nftGroup: {
|
||||
decimals: 0,
|
||||
timestamp: '2021-05-03 10:36:01',
|
||||
timestamp_unix: 1620038161,
|
||||
versionType: 129,
|
||||
documentUri: 'psfoundation.cash',
|
||||
symbol: 'PSF.TEST.GROUP',
|
||||
name: 'PSF Test NFT Group',
|
||||
containsBaton: true,
|
||||
id: '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a',
|
||||
documentHash: null,
|
||||
initialTokenQty: 1000000,
|
||||
blockCreated: 686117,
|
||||
totalMinted: null,
|
||||
totalBurned: null,
|
||||
circulatingSupply: null
|
||||
}
|
||||
}
|
||||
|
||||
const balancesForAddress = [
|
||||
{
|
||||
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||
@@ -1290,10 +1317,106 @@ const slpdbStatus = {
|
||||
}
|
||||
}
|
||||
|
||||
const txDetailsSLPNftChild = {
|
||||
txid: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
hash: 'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
version: 2,
|
||||
size: 453,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
vout: 1,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'3045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
|
||||
hex:
|
||||
'483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
|
||||
},
|
||||
sequence: 4294967295
|
||||
},
|
||||
{
|
||||
txid: 'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
vout: 3,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026[ALL|FORKID] 03136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b',
|
||||
hex:
|
||||
'47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623b'
|
||||
},
|
||||
sequence: 4294967295
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_RETURN 5262419 65 47454e45534953 4e4654303034 4e4654204368696c64 68747470733a2f2f46756c6c537461636b2e63617368 0 0 0 0000000000000001',
|
||||
hex:
|
||||
'6a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c00080000000000000001',
|
||||
type: 'nulldata'
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.0003923,
|
||||
n: 2,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 f6fc6bce702ef779d2373aa4356a32113d95e674 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv']
|
||||
}
|
||||
}
|
||||
],
|
||||
hex:
|
||||
'020000000295e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9010000006b483045022100890ff2f80f3c184419ba03a575b9d159c0a5edfa817e4548cfdb6c74d16ce8d202205a8237d2139196716c273fe80506896d369a0022bb54401e2eadb364286a9f07412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff95e3d0920df2e9467bf4c6dee49d5ba37f2e6f255c72b8d561079f0f8a45a2a9030000006a47304402203905a61ec4e3567f7cad7eb514132e5b32d8a1d77c8838baac1b34b735282a5f022044da6130b5cc41b65615b17df6610087fe1baedd1a3a9f37552b56ea8d36d026412103136d9ec06adef1c9154105950e82d470d290aa019ed34a5e3bbd0b679805623bffffffff030000000000000000476a04534c500001410747454e45534953064e4654303034094e4654204368696c641668747470733a2f2f46756c6c537461636b2e636173684c0001004c0008000000000000000122020000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac3e990000000000001976a914f6fc6bce702ef779d2373aa4356a32113d95e67488ac00000000',
|
||||
blockhash: '000000000000000001360fe42629f4136e9adec53546b278de0e7ef788eed328',
|
||||
confirmations: 2113,
|
||||
time: 1613181364,
|
||||
blocktime: 1613181364
|
||||
}
|
||||
|
||||
const mock429Error = new Error('Request failed with status code 429')
|
||||
mock429Error.response = {
|
||||
status: 429,
|
||||
statusText: 'Too Many Requests',
|
||||
data: {
|
||||
error:
|
||||
'Too many requests. Your limits are currently 100 requests per minute. Increase rate limits at https://fullstack.cash'
|
||||
}
|
||||
}
|
||||
|
||||
const mock503Error = new Error('Request failed with status code 503')
|
||||
mock503Error.response = {
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
data: {
|
||||
error: 'Some error message'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockList,
|
||||
mockToken,
|
||||
mockTokens,
|
||||
mockNftChildrenList,
|
||||
mockNftGroup,
|
||||
balancesForAddress,
|
||||
balancesForAddresses,
|
||||
mockRawTx,
|
||||
@@ -1311,6 +1434,7 @@ module.exports = {
|
||||
txDetailsSLPGenesisNoBaton,
|
||||
txDetailsSLPSendAlt,
|
||||
txDetailsSLPNftGenesis,
|
||||
txDetailsSLPNftChild,
|
||||
mockTxDetails,
|
||||
mockDualValidation,
|
||||
mockDualOpData,
|
||||
@@ -1320,5 +1444,7 @@ module.exports = {
|
||||
mockValidateTxid3Invalid,
|
||||
whitelist,
|
||||
slpdbStatus,
|
||||
mockValidateTxidArray
|
||||
mockValidateTxidArray,
|
||||
mock429Error,
|
||||
mock503Error
|
||||
}
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Mocking data used in the transaction-unit.js tests.
|
||||
*/
|
||||
|
||||
const nonSlpTxDetails = {
|
||||
txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
|
||||
hash: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
|
||||
version: 2,
|
||||
size: 225,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: '5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac',
|
||||
vout: 1,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'3044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c[ALL|FORKID] 038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9',
|
||||
hex:
|
||||
'473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
address: 'bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe',
|
||||
value: 0.00047504
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: 0.00001,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00046256,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 2dbf5e1804c39a497b908c876097d63210c84902 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a9142dbf5e1804c39a497b908c876097d63210c8490288ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e']
|
||||
}
|
||||
}
|
||||
],
|
||||
hex:
|
||||
'0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000',
|
||||
blockhash: '0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5',
|
||||
confirmations: 77741,
|
||||
time: 1569792892,
|
||||
blocktime: 1569792892
|
||||
}
|
||||
|
||||
const slpTxDetails = {
|
||||
txid: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1',
|
||||
hash: '266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1',
|
||||
version: 2,
|
||||
size: 479,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: 'abc685f1f2a95f51e5e05a350f3fb9c74676e9f78c835b2a019c888ac0a2a736',
|
||||
vout: 2,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'3045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f7[ALL|FORKID] 0245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac',
|
||||
hex:
|
||||
'483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07ac'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
address: 'bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn',
|
||||
value: 0.00000546
|
||||
},
|
||||
{
|
||||
txid: '58c8576404c01c23a224053307399483d3a070599b3e9eb6d45be9714b8d6856',
|
||||
vout: 1,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'30430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db12753[ALL|FORKID] 0209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505',
|
||||
hex:
|
||||
'4630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
address: 'bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7',
|
||||
value: 0.68369626
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: 0,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_RETURN 5262419 1 1145980243 497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7 0000000005f5e100 00005ad7e49d9100',
|
||||
hex:
|
||||
'6a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d9100',
|
||||
type: 'nulldata'
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 36be3b7d185a85b6cf6fc61d63c16f2f10e54260 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qqmtuwmarpdgtdk0dlrp6c7pduh3pe2zvqrkys2ex8']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.00000546,
|
||||
n: 2,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 99e5a8229a9af0dbf3cadb25ea981df49c9d93bf OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qzv7t2pzn2d0pklnetdjt65crh6fe8vnhuwvhsk2nn']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 0.68368564,
|
||||
n: 3,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 422e1b3b400864bfd3146dd9c628de0f1da62289 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qppzuxemgqyxf07nz3kan33gmc83mf3z3yz295c4s7']
|
||||
}
|
||||
}
|
||||
],
|
||||
hex:
|
||||
'020000000236a7a2c08a889c012a5b838cf7e97646c7b93f0f355ae0e5515fa9f2f185c6ab020000006b483045022100e5f0f6f1212fcbb10eedb7fdc38fca6e86629b4e7e8356a3ad7371a109fc37a602204bfff37d1a34d2e2908b81c23677706fb59ff4ab639fa3299da6c303de74e1f741210245b9b3586fab3cfd46db6d116c4588004fe7fe9798216ccb8e55a89bcebc07acffffffff56688d4b71e95bd4b69e3e9b5970a0d383943907330524a2231cc0046457c85801000000694630430220784f6d81fa8f54db8a4948259e8c15972a0285f8b1640c433d4e9f606dc38f0c021f14eecc2e8af2efede0867ce459c400dde54186a0e64babdbe89f795db1275341210209ebe6d9da5043945ed1d81bec0fcace299eba05e5f46b72d6838c790d31c505ffffffff040000000000000000406a04534c500001010453454e4420497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7080000000005f5e1000800005ad7e49d910022020000000000001976a91436be3b7d185a85b6cf6fc61d63c16f2f10e5426088ac22020000000000001976a91499e5a8229a9af0dbf3cadb25ea981df49c9d93bf88acb4381304000000001976a914422e1b3b400864bfd3146dd9c628de0f1da6228988ac00000000',
|
||||
blockhash: '0000000000000000015284202422a688554b7fc80c54f18122847a99c4f79607',
|
||||
confirmations: 76722,
|
||||
time: 1570392893,
|
||||
blocktime: 1570392893
|
||||
}
|
||||
|
||||
const mockOpReturnData01 = {
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['100000000', '99883300000000']
|
||||
}
|
||||
|
||||
const mockOpReturnData02 = {
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'TOK-CH',
|
||||
name: 'TokyoCash',
|
||||
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
documentUri: '',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 0,
|
||||
qty: '2100000000000000'
|
||||
}
|
||||
|
||||
const mockOpReturnData03 = {
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId: '497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['1000000000', '99883400000000']
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
nonSlpTxDetails,
|
||||
slpTxDetails,
|
||||
mockOpReturnData01,
|
||||
mockOpReturnData02,
|
||||
mockOpReturnData03
|
||||
}
|
||||
@@ -67,4 +67,26 @@ describe('#price', () => {
|
||||
assert.isNumber(result)
|
||||
})
|
||||
})
|
||||
describe('#getBchUsd', () => {
|
||||
it('should get the USD price of BCH', async () => {
|
||||
sandbox.stub(bchjs.Price.axios, 'get').resolves({ data: { usd: 510.39 } })
|
||||
|
||||
const result = await bchjs.Price.getBchUsd()
|
||||
// console.log(result)
|
||||
|
||||
assert.isNumber(result)
|
||||
})
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(bchjs.Price.axios, 'get').throws(new Error('test error'))
|
||||
|
||||
await bchjs.Price.getBchUsd()
|
||||
// console.log(result)
|
||||
|
||||
assert.equal(false, true, 'unexpected error')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
TODO:
|
||||
-Create a mocking library of data to compare unit and integration tests.
|
||||
Unit tests for raw-transactions.js library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
@@ -172,10 +171,15 @@ describe('#RawTransactions', () => {
|
||||
describe('#getTxData', () => {
|
||||
it('should return tx data with input addresses', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(bchjs.RawTransactions, 'getRawTransaction').resolves(mockData.mockTx)
|
||||
sandbox.stub(bchjs.RawTransactions, '_getInputAddrs').resolves(mockData.mockGetInputAddrsOutput)
|
||||
sandbox
|
||||
.stub(bchjs.RawTransactions, 'getRawTransaction')
|
||||
.resolves(mockData.mockTx)
|
||||
sandbox
|
||||
.stub(bchjs.RawTransactions, '_getInputAddrs')
|
||||
.resolves(mockData.mockGetInputAddrsOutput)
|
||||
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
const txid =
|
||||
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxData(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -191,7 +195,10 @@ describe('#RawTransactions', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
assert2.include(err.message, 'Input must be a string or array of strings')
|
||||
assert2.include(
|
||||
err.message,
|
||||
'Input to raw-transaction.js/getTxData() must be a string containg a TXID.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -202,7 +209,8 @@ describe('#RawTransactions', () => {
|
||||
.stub(bchjs.RawTransactions, 'getRawTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
const txid =
|
||||
'05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
await bchjs.RawTransactions.getTxData(txid)
|
||||
|
||||
|
||||
+93
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
// const axios = require("axios")
|
||||
const axios = require('axios')
|
||||
|
||||
// Default to unit tests unless some other value for TEST is passed.
|
||||
if (!process.env.TEST) process.env.TEST = 'unit'
|
||||
@@ -14,7 +14,7 @@ const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
// Mock data used for unit tests
|
||||
// const mockData = require("./fixtures/slp/mock-utils")
|
||||
const mockData = require('./fixtures/slp/mock-utils')
|
||||
|
||||
// Default to unit tests unless some other value for TEST is passed.
|
||||
if (!process.env.TEST) process.env.TEST = 'unit'
|
||||
@@ -253,4 +253,95 @@ describe('#SLP NFT1', () => {
|
||||
assert.isNumber(result.outputs)
|
||||
})
|
||||
})
|
||||
describe('#listNFTGroupChildren', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const groupId = 12345
|
||||
|
||||
await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'groupId must be a string'
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should handle API response error', async () => {
|
||||
try {
|
||||
const error = new Error()
|
||||
error.response = {
|
||||
error: 'NFT group does not exists'
|
||||
}
|
||||
sandbox.stub(axios, 'get').throws(error)
|
||||
|
||||
const groupId = 'non-exists'
|
||||
await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.response,
|
||||
{ error: 'NFT group does not exists' }
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should return array of children GENESIS transactions IDs', async () => {
|
||||
sandbox.stub(axios, 'get').resolves({ data: mockData.mockNftChildrenList })
|
||||
|
||||
const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
|
||||
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'nftChildren')
|
||||
assert.isArray(result.nftChildren)
|
||||
})
|
||||
})
|
||||
describe('#parentNFTGroup', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const tokenId = 12345
|
||||
|
||||
await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'tokenId must be a string'
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should handle API response error', async () => {
|
||||
try {
|
||||
const error = new Error()
|
||||
error.response = {
|
||||
error: 'NFT child does not exists'
|
||||
}
|
||||
sandbox.stub(axios, 'get').throws(error)
|
||||
|
||||
const tokenId = 'non-exists'
|
||||
await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.response,
|
||||
{ error: 'NFT child does not exists' }
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should return parent NFT group information', async () => {
|
||||
sandbox.stub(axios, 'get').resolves({ data: mockData.mockNftGroup })
|
||||
|
||||
const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
|
||||
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'nftGroup')
|
||||
assert.property(result.nftGroup, 'id')
|
||||
assert.property(result.nftGroup, 'name')
|
||||
assert.property(result.nftGroup, 'symbol')
|
||||
assert.property(result.nftGroup, 'documentUri')
|
||||
assert.property(result.nftGroup, 'versionType')
|
||||
assert.property(result.nftGroup, 'initialTokenQty')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+396
-46
@@ -10,7 +10,7 @@ let uut
|
||||
|
||||
const REST_URL = process.env.RESTURL
|
||||
? process.env.RESTURL
|
||||
: 'https://bchn.fullstack.cash/v4/'
|
||||
: 'https://bchn.fullstack.cash/v5/'
|
||||
|
||||
// Mock data used for unit tests
|
||||
const mockDataLib = require('./fixtures/slp/mock-utils')
|
||||
@@ -273,8 +273,8 @@ describe('#SLP Utils', () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.nonSLPTxDetailsWithoutOpReturn] })
|
||||
|
||||
const txid =
|
||||
'3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec'
|
||||
@@ -292,8 +292,8 @@ describe('#SLP Utils', () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.nonSLPTxDetailsWithOpReturn })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.nonSLPTxDetailsWithOpReturn] })
|
||||
|
||||
const txid =
|
||||
'2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f'
|
||||
@@ -310,8 +310,8 @@ describe('#SLP Utils', () => {
|
||||
it('should decode a genesis transaction', async () => {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPGenesis })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPGenesis] })
|
||||
|
||||
const txid =
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90'
|
||||
@@ -336,8 +336,8 @@ describe('#SLP Utils', () => {
|
||||
it('should decode a mint transaction', async () => {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPMint })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPMint] })
|
||||
|
||||
const txid =
|
||||
'65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4'
|
||||
@@ -357,8 +357,8 @@ describe('#SLP Utils', () => {
|
||||
it('should decode a send transaction', async () => {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPSend })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPSend] })
|
||||
|
||||
const txid =
|
||||
'4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa'
|
||||
@@ -372,8 +372,8 @@ describe('#SLP Utils', () => {
|
||||
it('should properly decode a Genesis transaction with no minting baton', async () => {
|
||||
// Mock the call to the REST API.
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPGenesisNoBaton })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPGenesisNoBaton] })
|
||||
|
||||
const txid =
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
|
||||
@@ -387,8 +387,8 @@ describe('#SLP Utils', () => {
|
||||
it('should decode a send transaction with alternate encoding', async () => {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPSendAlt })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPSendAlt] })
|
||||
|
||||
const txid =
|
||||
'd94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae'
|
||||
@@ -411,8 +411,8 @@ describe('#SLP Utils', () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.mockInvalidSlpSend })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.mockInvalidSlpSend] })
|
||||
|
||||
const txid =
|
||||
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
|
||||
@@ -428,8 +428,8 @@ describe('#SLP Utils', () => {
|
||||
it('should decode a NFT Parent transaction', async () => {
|
||||
// Mock the call to the REST API.
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'get')
|
||||
.resolves({ data: mockData.txDetailsSLPNftGenesis })
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPNftGenesis] })
|
||||
|
||||
const txid =
|
||||
'4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4'
|
||||
@@ -453,33 +453,383 @@ describe('#SLP Utils', () => {
|
||||
assert.equal(data.qty, 1)
|
||||
})
|
||||
|
||||
// it("should decode a NFT Child transaction", async () => {
|
||||
// // Mock the call to the REST API.
|
||||
// // sandbox
|
||||
// // .stub(uut.Utils.axios, "get")
|
||||
// // .resolves({ data: mockData.txDetailsSLPNftGenesis })
|
||||
//
|
||||
// const txid =
|
||||
// "3de3766b10506c9156533f1639979e49d1884521543c13e4af73647df1ed3f76"
|
||||
//
|
||||
// const data = await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
//
|
||||
// // assert.property(data, "tokenType")
|
||||
// // assert.property(data, "txType")
|
||||
// // assert.property(data, "ticker")
|
||||
// // assert.property(data, "name")
|
||||
// // assert.property(data, "tokenId")
|
||||
// // assert.property(data, "documentUri")
|
||||
// // assert.property(data, "documentHash")
|
||||
// // assert.property(data, "decimals")
|
||||
// // assert.property(data, "mintBatonVout")
|
||||
// // assert.property(data, "qty")
|
||||
// //
|
||||
// // assert.equal(data.tokenType, 129)
|
||||
// // assert.equal(data.mintBatonVout, 2)
|
||||
// // assert.equal(data.qty, 1)
|
||||
// })
|
||||
it('should decode a NFT Child transaction', async () => {
|
||||
// Mock the call to the REST API.
|
||||
sandbox
|
||||
.stub(uut.Utils.axios, 'post')
|
||||
.resolves({ data: [mockData.txDetailsSLPNftChild] })
|
||||
|
||||
const txid =
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
|
||||
|
||||
const data = await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data, 'tokenType')
|
||||
assert.property(data, 'txType')
|
||||
assert.property(data, 'ticker')
|
||||
assert.property(data, 'name')
|
||||
assert.property(data, 'tokenId')
|
||||
assert.property(data, 'documentUri')
|
||||
assert.property(data, 'documentHash')
|
||||
assert.property(data, 'decimals')
|
||||
assert.property(data, 'mintBatonVout')
|
||||
assert.property(data, 'qty')
|
||||
|
||||
assert.equal(data.tokenType, 65)
|
||||
assert.equal(data.mintBatonVout, 0)
|
||||
assert.equal(data.qty, '1')
|
||||
})
|
||||
|
||||
// 429 means the user is exceeding the rate limits.
|
||||
it('should throw error when axios returns 429 status', async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox.stub(uut.Utils.axios, 'post').rejects(mockData.mock429Error)
|
||||
|
||||
const txid =
|
||||
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
|
||||
|
||||
await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.fail('Unexpect result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 429)
|
||||
assert.equal(err.response.statusText, 'Too Many Requests')
|
||||
assert.include(err.response.data.error, 'Too many requests')
|
||||
}
|
||||
})
|
||||
|
||||
// 503 means the service is down.
|
||||
it('should throw error when axios returns 503 status', async () => {
|
||||
try {
|
||||
// Mock the call to the REST API
|
||||
sandbox.stub(uut.Utils.axios, 'post').rejects(mockData.mock503Error)
|
||||
|
||||
const txid =
|
||||
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
|
||||
|
||||
await uut.Utils.decodeOpReturn(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.fail('Unexpect result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 503)
|
||||
assert.equal(err.response.statusText, 'Service Unavailable')
|
||||
assert.property(err.response, 'data')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_hydrateUtxo', () => {
|
||||
// // This captures an important corner-case. When an SLP token is created, the
|
||||
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
it('should return details on minting baton from genesis transaction', async () => {
|
||||
// Mock the call to REST API
|
||||
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'SLPSDK',
|
||||
name: 'SLP SDK example using BITBOX',
|
||||
tokenId:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
documentUri: 'developer.bitcoin.com',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 2,
|
||||
qty: '50700000000'
|
||||
})
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// assert.equal(data[0], false, "Change UTXO marked as false.")
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'amount')
|
||||
assert.property(data[0], 'satoshis')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'confirmations')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, false)
|
||||
|
||||
assert.property(data[1], 'txid')
|
||||
assert.property(data[1], 'vout')
|
||||
assert.property(data[1], 'amount')
|
||||
assert.property(data[1], 'satoshis')
|
||||
assert.property(data[1], 'height')
|
||||
assert.property(data[1], 'confirmations')
|
||||
assert.property(data[1], 'utxoType')
|
||||
assert.property(data[1], 'tokenId')
|
||||
assert.property(data[1], 'tokenTicker')
|
||||
assert.property(data[1], 'tokenName')
|
||||
assert.property(data[1], 'tokenDocumentUrl')
|
||||
assert.property(data[1], 'tokenDocumentHash')
|
||||
assert.property(data[1], 'decimals')
|
||||
})
|
||||
|
||||
// 429 means the user is exceeding the rate limits.
|
||||
it('should return isValid=null for 429 rate limit error', async () => {
|
||||
// Force decodeOpReturn() to throw a 429 error.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock429Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result[0].isValid, null)
|
||||
assert.equal(result[1].isValid, null)
|
||||
})
|
||||
|
||||
// 503 means the server is down or is not responding.
|
||||
it('should return isValid=null for 503 rate limit error', async () => {
|
||||
// Force decodeOpReturn() to throw a 429 error.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').rejects(mockData.mock503Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 3,
|
||||
amount: 0.00002015,
|
||||
satoshis: 2015,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
vout: 2,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 594892,
|
||||
confirmations: 5
|
||||
}
|
||||
]
|
||||
|
||||
const result = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result[0].isValid, null)
|
||||
assert.equal(result[1].isValid, null)
|
||||
})
|
||||
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['200000000', '99887500000000']
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'TOK-CH',
|
||||
name: 'TokyoCash',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
documentUri: '',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 0,
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils._hydrateUtxo(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'amount')
|
||||
assert.property(data[0], 'satoshis')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'confirmations')
|
||||
assert.property(data[0], 'utxoType')
|
||||
assert.property(data[0], 'tokenId')
|
||||
assert.property(data[0], 'tokenTicker')
|
||||
assert.property(data[0], 'tokenName')
|
||||
assert.property(data[0], 'tokenDocumentUrl')
|
||||
assert.property(data[0], 'tokenDocumentHash')
|
||||
assert.property(data[0], 'decimals')
|
||||
assert.property(data[0], 'tokenQty')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, null)
|
||||
})
|
||||
|
||||
// I don't know if this is the ideal behavior, but this is the behavior
|
||||
// that is in production, so I wanted to capture it in a test case. This
|
||||
// behavior can always be changed in the future.
|
||||
it('should throw error if 429 when querying Genesis transaction', async () => {
|
||||
try {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['200000000', '99887500000000']
|
||||
})
|
||||
.onCall(1)
|
||||
.rejects(mockData.mock429Error)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
await uut.Utils._hydrateUtxo(utxos)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
|
||||
assert.property(err, 'message')
|
||||
assert.property(err, 'response')
|
||||
assert.equal(err.response.status, 429)
|
||||
assert.equal(err.response.statusText, 'Too Many Requests')
|
||||
assert.include(err.response.data.error, 'Too many requests')
|
||||
}
|
||||
})
|
||||
|
||||
it('should add delay if delay is specified', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'SEND',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
amounts: ['200000000', '99887500000000']
|
||||
})
|
||||
.onCall(1)
|
||||
.resolves({
|
||||
tokenType: 1,
|
||||
txType: 'GENESIS',
|
||||
ticker: 'TOK-CH',
|
||||
name: 'TokyoCash',
|
||||
tokenId:
|
||||
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7',
|
||||
documentUri: '',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonVout: 0,
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const usrObj = {
|
||||
utxoDelay: 100
|
||||
}
|
||||
|
||||
await uut.Utils._hydrateUtxo(utxos, usrObj)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// TODO: This test should realy assert that the test took at least 100mS
|
||||
// to complete. However, as-is, it exercises the code path, so not
|
||||
// throwing an error can be considered a pass.
|
||||
assert.equal(true, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetails', () => {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
Unit tests for the transaction.js library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
const mockData = require('./fixtures/transaction-mock.js')
|
||||
|
||||
describe('#TransactionLib', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#get', () => {
|
||||
it('should throw an error if txid is not specified', async () => {
|
||||
try {
|
||||
await bchjs.Transaction.get()
|
||||
|
||||
assert.fail('Unexpected code path!')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input to Transaction.get() must be a string containing a TXID.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should get details about a non-SLP transaction', async () => {
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
|
||||
.resolves(mockData.nonSlpTxDetails)
|
||||
|
||||
const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[0].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, false)
|
||||
})
|
||||
|
||||
it('should get details about a SLP transaction', async () => {
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
|
||||
.resolves(mockData.slpTxDetails)
|
||||
sandbox
|
||||
.stub(bchjs.Transaction.slpUtils, 'decodeOpReturn')
|
||||
.onCall(0)
|
||||
.resolves(mockData.mockOpReturnData01)
|
||||
.onCall(1)
|
||||
.resolves(mockData.mockOpReturnData02)
|
||||
.onCall(2)
|
||||
.resolves(mockData.mockOpReturnData03)
|
||||
.onCall(3)
|
||||
.rejects(new Error('No OP_RETURN'))
|
||||
sandbox
|
||||
.stub(bchjs.Transaction.slpUtils, 'waterfallValidateTxid')
|
||||
.resolves(true)
|
||||
|
||||
const txid =
|
||||
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
|
||||
|
||||
const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[1].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vout[0], 'tokenQty')
|
||||
assert.equal(result.vout[0].tokenQty, null)
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result.vin[0], 'tokenQty')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, true)
|
||||
})
|
||||
|
||||
it('should catch and throw error on network error', async () => {
|
||||
try {
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await bchjs.Transaction.get(txid)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user