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 | |
|---|---|---|---|
|
|
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 | ||
|
|
81e54e0590 | ||
|
|
1d5e3da976 | ||
|
|
2cff305f46 | ||
|
|
9495381cf4 | ||
|
|
32f89346c3 | ||
|
|
e8582fd0ab | ||
|
|
7049b96df1 | ||
|
|
9cc6824591 | ||
|
|
bb82d520ed | ||
|
|
fef05124f1 | ||
|
|
978db07526 | ||
|
|
68bab5dd26 | ||
|
|
20e685206d | ||
|
|
e90afac701 | ||
|
|
96f96f837e | ||
|
|
03be1d5838 | ||
|
|
6b6bfa2ae2 | ||
|
|
da3af3656a | ||
|
|
26fdb5c5d5 | ||
|
|
86d71d0f49 | ||
|
|
86351aaf4f | ||
|
|
36432af7d5 | ||
|
|
95c88b5f29 | ||
|
|
28f563b58c | ||
|
|
79ea4b77b9 | ||
|
|
d37a2f8139 | ||
|
|
3b873b6064 | ||
|
|
687b5bca02 | ||
|
|
b80697500c | ||
|
|
626d27033e | ||
|
|
ce2ee3fc1b | ||
|
|
4768dcadc6 | ||
|
|
5fef09edbf | ||
|
|
37109cd7ef | ||
|
|
83d523260d | ||
|
|
8768a810c7 | ||
|
|
8832bfc8df | ||
|
|
a5208110b9 | ||
|
|
99ad7cd87f | ||
|
|
68ae084fb0 | ||
|
|
b0b0e17abc | ||
|
|
a54306e382 | ||
|
|
1860232fa6 | ||
|
|
d8437cf3fb | ||
|
|
338788c46d | ||
|
|
cad2c6378e | ||
|
|
2e6938e21f | ||
|
|
1e13c71715 | ||
|
|
d4b0807d16 | ||
|
|
611c7977ef | ||
|
|
39ac634675 | ||
|
|
278a54bd9b | ||
|
|
30b1f71d81 | ||
|
|
e7d7fc1019 | ||
|
|
e2ba265878 |
@@ -3,6 +3,7 @@ node_modules/*
|
||||
wallet-info.txt
|
||||
wallet.json
|
||||
integration-test-sweet.sh
|
||||
integration-test-ss.sh
|
||||
|
||||
docs/
|
||||
coverage/
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2020 Chris Troutner
|
||||
Copyright 2021 Chris Troutner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# bch-js
|
||||
|
||||
[](https://travis-ci.org/Permissionless-Software-Foundation/bch-js)
|
||||
[](https://www.npmjs.com/package/@psf/bch-js)
|
||||
[](https://npmjs.org/package/@psf/bch-js)
|
||||
[](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
|
||||
[](https://github.com/feross/standard)
|
||||
|
||||
[bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) blockchains. It can be used for free, but requires an account on [FullStack.cash](https://fullstack.cash) for increased rate limits. Learn more from [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) about Full Stack Bitcoin Cash development.
|
||||
|
||||
@@ -117,7 +117,7 @@ Have questions? Need help? Join our community support
|
||||
|
||||
Copies of this repository are also published on [IPFS](https://ipfs.io).
|
||||
|
||||
- v3.8.10: QmauhggzKDo9A9je5y2K6WRAp32JmGRFCw2ZFTiSnW8yot
|
||||
- v4.5.4: QmWv3pxJy3MH8vU5nLUVyzqFxfNKXLQQEnz1rgStNuQijd
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Generated
+20212
-3447
File diff suppressed because it is too large
Load Diff
+16
-29
@@ -13,11 +13,14 @@
|
||||
"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:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
|
||||
"test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration",
|
||||
"test:integration:local:bchn": "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/testnet",
|
||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#tokenUtxoDetails' test/integration/",
|
||||
"test:temp2": "mocha --timeout 30000 -g '#generateSendOpReturn' test/unit/",
|
||||
"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:integration:decatur:bchn": "export RESTURL=http://192.168.0.36:3000/v4/ && mocha --timeout 30000 test/integration/",
|
||||
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#decodeOpReturn' test/unit/",
|
||||
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
|
||||
"test:temp4": "export RESTURL=http://localhost:3000/v4/ && 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",
|
||||
@@ -30,52 +33,36 @@
|
||||
"url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@psf/bip21": "^2.0.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",
|
||||
"apidoc": "^0.25.0",
|
||||
"assert": "^2.0.0",
|
||||
"axios": "^0.21.1",
|
||||
"bc-bip68": "^1.0.5",
|
||||
"bchaddrjs-slp": "^0.2.5",
|
||||
"bigi": "^1.4.2",
|
||||
"bignumber.js": "^9.0.0",
|
||||
"bip-schnorr": "^0.3.0",
|
||||
"@psf/bip21": "^2.0.1",
|
||||
"@psf/bip32-utils": "^0.13.1",
|
||||
"bip38": "^2.0.2",
|
||||
"bip39": "^3.0.2",
|
||||
"bip66": "^1.1.5",
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "^4.0.2",
|
||||
"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",
|
||||
"@psf/coininfo": "^4.0.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 +77,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"
|
||||
},
|
||||
|
||||
+17
-9
@@ -26,29 +26,40 @@ 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')
|
||||
|
||||
// Indexers
|
||||
const OpenBazaar = require('./openbazaar')
|
||||
const Ninsight = require('./ninsight')
|
||||
const Electrumx = require('./electrumx')
|
||||
|
||||
class BCHJS {
|
||||
constructor (config) {
|
||||
// Try to retrieve the REST API URL from different sources.
|
||||
if (config && config.restURL && config.restURL !== '') { this.restURL = config.restURL } else if (process.env.RESTURL && process.env.RESTURL !== '') { this.restURL = process.env.RESTURL } else this.restURL = DEFAULT_REST_API
|
||||
if (config && config.restURL && config.restURL !== '') {
|
||||
this.restURL = config.restURL
|
||||
} else if (process.env.RESTURL && process.env.RESTURL !== '') {
|
||||
this.restURL = process.env.RESTURL
|
||||
} else this.restURL = DEFAULT_REST_API
|
||||
|
||||
// Retrieve the apiToken
|
||||
this.apiToken = '' // default value.
|
||||
if (config && config.apiToken && config.apiToken !== '') { this.apiToken = config.apiToken } else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') { this.apiToken = process.env.BCHJSTOKEN }
|
||||
if (config && config.apiToken && config.apiToken !== '') {
|
||||
this.apiToken = config.apiToken
|
||||
} else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') {
|
||||
this.apiToken = process.env.BCHJSTOKEN
|
||||
}
|
||||
|
||||
// Retrieve the Basic Authentication password.
|
||||
this.authPass = '' // default value.
|
||||
if (config && config.authPass && config.authPass !== '') { this.authPass = config.authPass } else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') { this.authPass = process.env.BCHJSAUTHPASS }
|
||||
if (config && config.authPass && config.authPass !== '') {
|
||||
this.authPass = config.authPass
|
||||
} else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') {
|
||||
this.authPass = process.env.BCHJSAUTHPASS
|
||||
}
|
||||
|
||||
// Generate a Basic Authentication token from an auth password
|
||||
this.authToken = ''
|
||||
@@ -68,9 +79,6 @@ class BCHJS {
|
||||
|
||||
// console.log(`apiToken: ${this.apiToken}`)
|
||||
|
||||
// Populate OpenBazaar endpoints
|
||||
this.OpenBazaar = new OpenBazaar(libConfig)
|
||||
|
||||
// Bitcoin.com Ninsight indexer
|
||||
this.Ninsight = new Ninsight(config)
|
||||
|
||||
@@ -98,13 +106,13 @@ 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)
|
||||
this.SLP.HDNode = this.HDNode
|
||||
|
||||
this.IPFS = new IPFS()
|
||||
this.Utxo = new Utxo(libConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+111
-4
@@ -2,9 +2,12 @@
|
||||
This library interacts with the ElectrumX bch-api REST API endpoints operated
|
||||
by FullStack.cash
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const axios = require('axios')
|
||||
|
||||
// Local libraries.
|
||||
const Blockchain = require('./blockchain')
|
||||
|
||||
let _this
|
||||
|
||||
class ElectrumX {
|
||||
@@ -29,6 +32,8 @@ class ElectrumX {
|
||||
}
|
||||
}
|
||||
|
||||
this.blockchain = new Blockchain(config)
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -279,7 +284,7 @@ class ElectrumX {
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async transactions (address) {
|
||||
async transactions (address, usrObj = null) {
|
||||
try {
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
@@ -294,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
|
||||
)
|
||||
@@ -452,7 +458,9 @@ class ElectrumX {
|
||||
} catch (error) {
|
||||
// console.log("error: ", error)
|
||||
if (error.response && error.response.data) {
|
||||
if (error.response && error.response.data) { throw new Error(error.response.data.error) } else throw error.response.data
|
||||
if (error.response && error.response.data) {
|
||||
throw new Error(error.response.data.error)
|
||||
} else throw error.response.data
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
@@ -596,6 +604,105 @@ class ElectrumX {
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Electrumx.sortConfTxs() sortConfTxs()
|
||||
* @apiName ElectrumX sortConfTxs
|
||||
* @apiGroup ElectrumX
|
||||
* @apiDescription Sort the output of Electrum.transactions() by block height.
|
||||
*
|
||||
* A simple sort function for the output of Electrum.transactions(). Ignores
|
||||
* unconfirmed transactions.
|
||||
*
|
||||
* Sorts in 'DESCENDING' order by default, or 'ASCENDING' can be specified.
|
||||
* Descending makes the first element the newest (largest block height).
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
|
||||
* const sortedTxs = bchjs.Electrumx.sortConfTxs(txs.transactions, 'ASCENDING')
|
||||
* console.log(sortedTxs)
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "height": 560430,
|
||||
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
|
||||
* // },
|
||||
* // {
|
||||
* // "height": 560534,
|
||||
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
|
||||
* // }
|
||||
* // ]
|
||||
*/
|
||||
// Sort confirmed Transactions by the block height
|
||||
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
|
||||
try {
|
||||
// Filter out unconfirmed transactions, with a height of 0 or less.
|
||||
txs = txs.filter(elem => elem.height > 0)
|
||||
|
||||
if (sortingOrder === 'DESCENDING') {
|
||||
return txs.sort((a, b) => b.height - a.height)
|
||||
}
|
||||
return txs.sort((a, b) => a.height - b.height)
|
||||
} catch (err) {
|
||||
console.log('Error in util.js/sortConfTxs()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Electrumx.sortAllTxs() sortAllTxs()
|
||||
* @apiName ElectrumX sortAllTxs
|
||||
* @apiGroup ElectrumX
|
||||
* @apiDescription Sort the output of Electrum.transactions() by block height.
|
||||
*
|
||||
* A simple sort function for the output of Electrum.transactions().
|
||||
* Assumes that unconfirmed transactions will make it into the next block. Any
|
||||
* unconfirmed transactions have their block height with the height of the next
|
||||
* block. Returns a Promise.
|
||||
*
|
||||
* Sorts in 'ASCENDING' order by default, or 'DESCENDING' can be specified.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
|
||||
* const sortedTxs = await bchjs.Electrumx.sortAllTxs(txs.transactions, 'ASCENDING')
|
||||
* console.log(sortedTxs)
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "height": 560430,
|
||||
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
|
||||
* // },
|
||||
* // {
|
||||
* // "height": 560534,
|
||||
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
|
||||
* // }
|
||||
* // ]
|
||||
*/
|
||||
// Substitute zero-conf txs with the current block-height + 1
|
||||
async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
|
||||
try {
|
||||
// Calculate the height of the next block
|
||||
const nextBlock = (await this.blockchain.getBlockCount()) + 1
|
||||
|
||||
// Replace the height of any zero-conf transactions with the height of
|
||||
// the next block.
|
||||
const modifiedTxs = txs.map(elem => {
|
||||
if (elem.height <= 0) elem.height = nextBlock
|
||||
return elem
|
||||
})
|
||||
// console.log(`modifiedTxs: ${JSON.stringify(modifiedTxs, null, 2)}`)
|
||||
|
||||
// Sort the modified array of transactions.
|
||||
return this.sortConfTxs(modifiedTxs, sortingOrder)
|
||||
} catch (err) {
|
||||
console.log('Error in util.js/sort0ConfTxs')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ElectrumX
|
||||
|
||||
+22
-22
@@ -19,7 +19,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* bchjs.HDNode.fromSeed(seedBuffer);
|
||||
*
|
||||
@@ -28,7 +28,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* bchjs.HDNode.fromSeed(seedBuffer);
|
||||
*/
|
||||
@@ -54,7 +54,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to legacy address
|
||||
@@ -66,7 +66,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to cash address
|
||||
@@ -88,7 +88,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to cash address
|
||||
@@ -100,7 +100,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to cash address
|
||||
@@ -121,7 +121,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer);
|
||||
* // to cash address
|
||||
@@ -133,7 +133,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.SLP.HDNode.fromSeed(seedBuffer);
|
||||
* // to cash address
|
||||
@@ -156,7 +156,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to WIF
|
||||
@@ -168,7 +168,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to WIF
|
||||
@@ -190,7 +190,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to extended public key
|
||||
@@ -202,7 +202,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to extended public key
|
||||
@@ -224,7 +224,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to extended private key
|
||||
@@ -236,7 +236,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // to extended private key
|
||||
@@ -258,7 +258,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create root seed buffer from mnemonic
|
||||
* let rootSeed= bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let rootSeed= await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from root seed
|
||||
* let hdNode = bchjs.HDNode.fromSeed(rootSeed);
|
||||
* // create public key buffer from HDNode
|
||||
@@ -269,7 +269,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // create public key buffer from HDNode
|
||||
@@ -290,7 +290,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create root seed buffer from mnemonic
|
||||
* let rootSeed= bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let rootSeed= await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from root seed
|
||||
* let hdNode = bchjs.HDNode.fromSeed(rootSeed);
|
||||
* // create public key buffer from HDNode
|
||||
@@ -302,7 +302,7 @@ class HDNode {
|
||||
* // create mnemonic from entropy
|
||||
* let mnemonic = bchjs.Mnemonic.fromEntropy(entropy);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // create public key buffer from HDNode
|
||||
@@ -370,7 +370,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // derive hardened child HDNode
|
||||
@@ -391,7 +391,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // derive unhardened child HDNode
|
||||
@@ -412,7 +412,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create seed buffer from mnemonic
|
||||
* let seedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let seedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create HDNode from seed buffer
|
||||
* let hdNode = bchjs.HDNode.fromSeed(seedBuffer);
|
||||
* // derive hardened child HDNode
|
||||
@@ -615,7 +615,7 @@ class HDNode {
|
||||
* // create mnemonic
|
||||
* let mnemonic = bchjs.Mnemonic.generate(128);
|
||||
* // create root seed buffer
|
||||
* let rootSeedBuffer = bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* let rootSeedBuffer = await bchjs.Mnemonic.toSeed(mnemonic);
|
||||
* // create master hd node
|
||||
* let masterHDNode = bchjs.HDNode.fromSeed(rootSeedBuffer);
|
||||
* // derive child node
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ class IPFS {
|
||||
constructor (config) {
|
||||
this.IPFS_API = process.env.IPFS_API
|
||||
? process.env.IPFS_API
|
||||
: 'https://ipfs-api.fullstack.cash'
|
||||
: 'https://ipfs-file-upload.fullstackcash.nl'
|
||||
// : `http://localhost:5001`
|
||||
|
||||
// Default options when calling axios.
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
This library interacts with the public REST API endpoints that OpenBazaar
|
||||
provides for their application.
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
let _this
|
||||
|
||||
class OpenBazaar {
|
||||
constructor (config) {
|
||||
this.restURL = config.restURL
|
||||
this.apiToken = config.apiToken
|
||||
|
||||
// Add JWT token to the authorization header.
|
||||
this.axiosOptions = {
|
||||
headers: {
|
||||
authorization: `Token ${this.apiToken}`
|
||||
}
|
||||
}
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api OpenBazaar.balance() balance()
|
||||
* @apiName OpenBazaar Balance
|
||||
* @apiGroup OpenBazaar
|
||||
* @apiDescription Return Balance about an address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let balance = await bchjs.OpenBazaar.balance('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
|
||||
* console.log(balance)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* {
|
||||
* page: 1,
|
||||
* totalPages: 1,
|
||||
* itemsOnPage: 1000,
|
||||
* addrStr: "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9",
|
||||
* balance: "0.00001",
|
||||
* totalReceived: "0.00001",
|
||||
* totalSent: "0",
|
||||
* unconfirmedBalance: "0",
|
||||
* unconfirmedTxApperances: 0,
|
||||
* txApperances: 1,
|
||||
* transactions: [
|
||||
* "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7"
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async balance (address) {
|
||||
try {
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
let response
|
||||
if (process.env.NETWORK === 'testnet') {
|
||||
response = await axios.get(
|
||||
`https://tbch.blockbook.api.openbazaar.org/api/address/${address}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
} else {
|
||||
response = await axios.get(
|
||||
`https://bch.blockbook.api.openbazaar.org/api/address/${address}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
}
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input address must be a string.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api OpenBazaar.utxo() utxo()
|
||||
* @apiName OpenBazaar Utxo
|
||||
* @apiGroup OpenBazaar
|
||||
* @apiDescription Return list of uxto for address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxo = await bchjs.OpenBazaar.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
|
||||
* console.log(utxo);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* [
|
||||
* {
|
||||
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
|
||||
* vout: 0,
|
||||
* amount: "0.00001",
|
||||
* satoshis: 1000,
|
||||
* height: 602405,
|
||||
* confirmations: 11
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
async utxo (address) {
|
||||
try {
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
let response
|
||||
if (process.env.NETWORK === 'testnet') {
|
||||
response = await axios.get(
|
||||
`https://tbch.blockbook.api.openbazaar.org/api/utxo/${address}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
} else {
|
||||
response = await axios.get(
|
||||
`https://bch.blockbook.api.openbazaar.org/api/utxo/${address}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
throw new Error('Input address must be a string.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api OpenBazaar.tx() tx()
|
||||
* @apiName OpenBazaar Tx
|
||||
* @apiGroup OpenBazaar
|
||||
* @apiDescription Get details on a transaction.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let txDetails = await bchjs.OpenBazaar.tx(`2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7`);
|
||||
* console.log(txDetails);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
*{
|
||||
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
|
||||
* version: 2,
|
||||
* vin: [
|
||||
* {
|
||||
* txid: "5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac",
|
||||
* vout: 1,
|
||||
* sequence: 4294967295,
|
||||
* n: 0,
|
||||
* scriptSig: {
|
||||
* hex:
|
||||
* "473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9"
|
||||
* },
|
||||
* addresses: ["bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe"],
|
||||
* value: "0.00047504"
|
||||
* }
|
||||
* ],
|
||||
* vout: [
|
||||
* {
|
||||
* value: "0.00001",
|
||||
* n: 0,
|
||||
* scriptPubKey: {
|
||||
* hex: "76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac",
|
||||
* addresses: ["bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9"]
|
||||
* },
|
||||
* spent: false
|
||||
* },
|
||||
* {
|
||||
* value: "0.00046256",
|
||||
* n: 1,
|
||||
* scriptPubKey: {
|
||||
* hex: "76a9142dbf5e1804c39a497b908c876097d63210c8490288ac",
|
||||
* addresses: ["bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e"]
|
||||
* },
|
||||
* spent: false
|
||||
* }
|
||||
* ],
|
||||
* blockhash: "0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5",
|
||||
* blockheight: 602405,
|
||||
* confirmations: 11,
|
||||
* blocktime: 1569792892,
|
||||
* valueOut: "0.00047256",
|
||||
* valueIn: "0.00047504",
|
||||
* fees: "0.00000248",
|
||||
* hex:
|
||||
* "0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000"
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async tx (txid) {
|
||||
try {
|
||||
// Handle single txid.
|
||||
if (typeof txid === 'string') {
|
||||
let response
|
||||
if (process.env.NETWORK === 'testnet') {
|
||||
response = await axios.get(
|
||||
`https://tbch.blockbook.api.openbazaar.org/api/tx/${txid}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
} else {
|
||||
response = await axios.get(
|
||||
`https://bch.blockbook.api.openbazaar.org/api/tx/${txid}`,
|
||||
_this.axiosOptions
|
||||
)
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input txid must be a string.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OpenBazaar
|
||||
+182
-2
@@ -1,5 +1,7 @@
|
||||
const axios = require('axios')
|
||||
|
||||
const SlpUtils = require('./slp/utils')
|
||||
|
||||
let _this
|
||||
|
||||
class RawTransactions {
|
||||
@@ -27,6 +29,9 @@ class RawTransactions {
|
||||
// Encapsulate dependencies
|
||||
this.axios = axios
|
||||
|
||||
// Dependencies
|
||||
this.slpUtils = new SlpUtils(config)
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -258,7 +263,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(
|
||||
@@ -273,7 +278,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,6 +295,180 @@ class RawTransactions {
|
||||
}
|
||||
}
|
||||
|
||||
// Given verbose transaction details, this function retrieves the transaction
|
||||
// data for the inputs (the parent transactions). It returns an array of
|
||||
// objects. Each object corresponds to a transaction input, and contains
|
||||
// the address that generated that input UTXO.
|
||||
//
|
||||
// Assumes a single TX. Does not yet work with an array of TXs.
|
||||
// This function returns an array of objects, each object if formated as follows:
|
||||
// {
|
||||
// vin: 0, // The position of the input for the given txid
|
||||
// address: bitcoincash:qzhrpmu7nruyfcemeanqh5leuqcnf6zkjq4qm9nqh0
|
||||
// }
|
||||
async _getInputAddrs (txDetails) {
|
||||
try {
|
||||
const retArray = [] // Return array
|
||||
|
||||
for (let i = 0; i < txDetails.vin.length; i++) {
|
||||
// The first input represents the sender of the BCH or tokens.
|
||||
const vin = txDetails.vin[i]
|
||||
const inputTxid = vin.txid
|
||||
const inputVout = vin.vout
|
||||
|
||||
// Get the TX details for the input, in order to retrieve the address of
|
||||
// the sender.
|
||||
const txDetailsParent = await this.getRawTransaction(inputTxid, true)
|
||||
// console.log(
|
||||
// `txDetailsParent: ${JSON.stringify(txDetailsParent, null, 2)}`
|
||||
// )
|
||||
|
||||
// The vout from the previous tx that represents the sender.
|
||||
const voutSender = txDetailsParent.vout[inputVout]
|
||||
|
||||
retArray.push({
|
||||
vin: i,
|
||||
address: voutSender.scriptPubKey.addresses[0]
|
||||
})
|
||||
}
|
||||
|
||||
return retArray
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api RawTransactions.getTxData() getTxData()
|
||||
* @apiName getTxData
|
||||
* @apiGroup RawTransactions
|
||||
* @apiDescription
|
||||
* Returns an object of transaction data, including addresses for input UTXOs.
|
||||
*
|
||||
* This function is equivalent to running `getRawTransaction (txid, true)`,
|
||||
* execept the `vin` array will be populated with an `address` property that
|
||||
* contains the `bitcoincash:` address of the sender for each input.
|
||||
*
|
||||
* 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.RawTransactions.getTxData("0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098");
|
||||
* console.log(txData);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*/
|
||||
// Equivalent to running: async getRawTransaction (txid, verbose = true)
|
||||
// Only handles a single TXID (not arrays).
|
||||
// Appends the BCH address to the inputs of the transaction.
|
||||
async getTxData (txid) {
|
||||
try {
|
||||
if (typeof txid !== 'string') {
|
||||
throw new Error('Input must be a string or array of strings.')
|
||||
}
|
||||
|
||||
// Get the TX details for the transaction under consideration.
|
||||
const txDetails = await this.getRawTransaction(txid, true)
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
const inAddrs = await this._getInputAddrs(txDetails)
|
||||
// console.log(`inAddrs: ${JSON.stringify(inAddrs, null, 2)}`)
|
||||
|
||||
// Add the input address to the transaction data.
|
||||
for (let i = 0; i < inAddrs.length; i++) {
|
||||
txDetails.vin[i].address = inAddrs[i].address
|
||||
}
|
||||
|
||||
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
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api RawTransactions.sendRawTransaction() sendRawTransaction()
|
||||
* @apiName sendRawTransaction
|
||||
|
||||
@@ -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(
|
||||
|
||||
+372
-174
@@ -436,10 +436,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 +451,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
|
||||
)
|
||||
@@ -602,7 +604,8 @@ class Utils {
|
||||
* @api SLP.Utils.validateTxid3() validateTxid3()
|
||||
* @apiName validateTxid3
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription Validate that txid is an SLP transaction using the SLPDB whitelist server.
|
||||
* @apiDescription
|
||||
* Validate that txid is an SLP transaction using the SLPDB whitelist server.
|
||||
* Same exact functionality as the validateTxid() function, but this function
|
||||
* calls the whitelist SLPDB. It will only validate SLP tokens that are in the
|
||||
* whitelist. You can retrieve the whitelist with the SLP.Utils.whitelist()
|
||||
@@ -648,10 +651,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
|
||||
@@ -662,7 +667,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
|
||||
)
|
||||
@@ -883,18 +889,14 @@ class Utils {
|
||||
* @api SLP.Utils.decodeOpReturn() decodeOpReturn()
|
||||
* @apiName decodeOpReturn
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
|
||||
* @apiDescription
|
||||
* Retrieves transactions data from a txid and decodes the SLP OP_RETURN data.
|
||||
*
|
||||
* Similar to decodeOpReturn(), except decodeOpReturn2() uses the slp-parser
|
||||
* library maintained by JT Freeman. Outputs have slightly different format.
|
||||
* Throws an error if given a non-SLP txid.
|
||||
*
|
||||
* If optional associative array parameter cache is used, will cache and
|
||||
* reuse responses for the same input.
|
||||
*
|
||||
* Throws an error if given a non-SLP txid.
|
||||
*
|
||||
* In a future version of bch-js, this method will replace the origonal
|
||||
* decodeOpReturn() method.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
@@ -921,9 +923,10 @@ class Utils {
|
||||
* "99883300000000"
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
*/
|
||||
// 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
|
||||
@@ -940,16 +943,32 @@ 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/${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.
|
||||
@@ -996,8 +1015,15 @@ class Utils {
|
||||
|
||||
return tokenData
|
||||
} catch (error) {
|
||||
// console.log('decodeOpReturn error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
console.log('decodeOpReturn error: ', error)
|
||||
console.log(`decodeOpReturn error.message: ${error.message}`)
|
||||
|
||||
if (error.response && error.response.data) {
|
||||
// throw error.response.data
|
||||
|
||||
error.bchApiMessage = error.message
|
||||
error.message = error.response.data
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1020,6 +1046,9 @@ class Utils {
|
||||
* 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 is an API-heavy call. If you get a lot of null values, then slow down
|
||||
* the calls or request info on fewer UTXOs at a time.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* (async () => {
|
||||
@@ -1059,33 +1088,19 @@ class Utils {
|
||||
// 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 {
|
||||
// utxo list may have duplicate tx_hash, varying tx_pos
|
||||
// only need to call decodeOpReturn once for those
|
||||
const decodeOpReturnCache = {}
|
||||
const cachedTxValidation = {}
|
||||
// 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++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// CT 1/9/21: This code can be removed after 2/1/21
|
||||
// if (!utxo.satoshis) {
|
||||
// // If Electrumx, convert the value to satoshis.
|
||||
// if (utxo.value) {
|
||||
// utxo.satoshis = utxo.value
|
||||
// } else {
|
||||
// // If there is neither a satoshis or value property, throw an error.
|
||||
// throw new Error(
|
||||
// `utxo ${i} does not have a satoshis or value property.`
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
// Ensure the UTXO has a txid or tx_hash property.
|
||||
if (!utxo.txid) {
|
||||
// If Electrumx, convert the tx_hash property to txid.
|
||||
if (utxo.tx_hash) {
|
||||
@@ -1110,6 +1125,134 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos, usrObj)
|
||||
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
||||
|
||||
// *After* each UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
for (let i = 0; i < outAry.length; i++) {
|
||||
const utxo = outAry[i]
|
||||
|
||||
// *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'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// Validate using a 'waterfall' of validators.
|
||||
utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
|
||||
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.tokenUtxoDetailsWL() tokenUtxoDetailsWL()
|
||||
* @apiName tokenUtxoDetailsWL
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription
|
||||
*
|
||||
* Same as tokenUtxoDetails(), but it only uses the whitelist SLPDB to
|
||||
* validate UTXOs. This will result in a lot of `isValid: null` values,
|
||||
* but much more performant handling of SLP tokens. Some wallet apps prefer
|
||||
* the scaling performance over the breadth of supported tokens.
|
||||
*
|
||||
*/
|
||||
async tokenUtxoDetailsWL (utxos) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
|
||||
// Loop through each element in the array and validate the input before
|
||||
// further processing.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const utxo = utxos[i]
|
||||
|
||||
// Ensure the UTXO has a txid or tx_hash property.
|
||||
if (!utxo.txid) {
|
||||
// If Electrumx, convert the tx_hash property to txid.
|
||||
if (utxo.tx_hash) {
|
||||
utxo.txid = utxo.tx_hash
|
||||
} else {
|
||||
// If there is neither a txid or tx_hash property, throw an error.
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a txid or tx_hash property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the UTXO has a vout or tx_pos property.
|
||||
if (!Number.isInteger(utxo.vout)) {
|
||||
if (Number.isInteger(utxo.tx_pos)) {
|
||||
utxo.vout = utxo.tx_pos
|
||||
} else {
|
||||
throw new Error(
|
||||
`utxo ${i} does not have a vout or tx_pos property.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate each UTXO with data from SLP OP_REUTRNs.
|
||||
const outAry = await this._hydrateUtxo(utxos)
|
||||
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
|
||||
|
||||
// *After* each UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
for (let i = 0; i < outAry.length; i++) {
|
||||
const utxo = outAry[i]
|
||||
|
||||
// *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'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// Validate against the whitelist SLPDB.
|
||||
const whitelistResult = await this.validateTxid3(utxo.txid)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
let isValid = null
|
||||
|
||||
// Safely retrieve the returned value.
|
||||
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
||||
|
||||
utxo.isValid = isValid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// If this function hits a rate limit... TODO: describe specific behavior.
|
||||
async _hydrateUtxo (utxos, usrObj = null) {
|
||||
try {
|
||||
const decodeOpReturnCache = {}
|
||||
|
||||
// console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
|
||||
|
||||
// Output Array
|
||||
const outAry = []
|
||||
|
||||
@@ -1122,10 +1265,14 @@ class Utils {
|
||||
// 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'
|
||||
@@ -1136,10 +1283,10 @@ class Utils {
|
||||
err.message.indexOf('lokad id') === -1 &&
|
||||
err.message.indexOf('trailing data') === -1)
|
||||
) {
|
||||
// console.log(
|
||||
// 'unknown error from decodeOpReturn(). Marking as \'null\'',
|
||||
// err
|
||||
// )
|
||||
console.log(
|
||||
'unknown error from decodeOpReturn(). Marking as \'null\'',
|
||||
err
|
||||
)
|
||||
|
||||
utxo.isValid = null
|
||||
outAry.push(utxo)
|
||||
@@ -1148,7 +1295,7 @@ class Utils {
|
||||
// an SLP UTXO.
|
||||
// Mark as false and continue the loop.
|
||||
} else {
|
||||
// console.log('marking as invalid')
|
||||
console.log('marking as invalid')
|
||||
utxo.isValid = false
|
||||
outAry.push(utxo)
|
||||
}
|
||||
@@ -1169,7 +1316,6 @@ class Utils {
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
@@ -1194,7 +1340,6 @@ class Utils {
|
||||
utxo.decimals = slpData.decimals
|
||||
utxo.tokenType = slpData.tokenType
|
||||
|
||||
// something
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
@@ -1206,15 +1351,16 @@ class Utils {
|
||||
utxo.vout !== 1 // UTXO is not the reciever of the genesis or mint tokens.
|
||||
) {
|
||||
// Can safely be marked as false.
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
|
||||
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)}`)
|
||||
|
||||
@@ -1258,15 +1404,16 @@ class Utils {
|
||||
// console.log('tokenQty: ', tokenQty)
|
||||
|
||||
if (!tokenQty) {
|
||||
// outAry[i] = false
|
||||
utxo.isValid = false
|
||||
|
||||
outAry[i] = utxo
|
||||
} else {
|
||||
// If UTXO passes validation, then return formatted token data.
|
||||
|
||||
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)}`)
|
||||
|
||||
@@ -1296,133 +1443,145 @@ class Utils {
|
||||
outAry[i] = utxo
|
||||
}
|
||||
}
|
||||
|
||||
// *After* the UTXO has been hydrated with SLP data,
|
||||
// validate the TXID with SLPDB.
|
||||
if (outAry[i].tokenType) {
|
||||
// Only execute this block if the current UTXO has a 'tokenType'
|
||||
// property. i.e. it has been successfully hydrated with SLP
|
||||
// information.
|
||||
|
||||
// CT 12/13/2020: Disabling the cache until I get processing of tokens
|
||||
// to be more stable.
|
||||
// If the value has been cached, use the cached version first.
|
||||
let isValid = cachedTxValidation[utxo.txid]
|
||||
if (!isValid) isValid = null
|
||||
// let isValid = null
|
||||
|
||||
// There are two possible responses from SLPDB. If SLPDB is functioning
|
||||
// correctly, then validateTxid() will return this:
|
||||
// isValid: [
|
||||
// {
|
||||
// "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
|
||||
// "valid": true
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// If SLPDB has fallen behind real-time processing, it will return this:
|
||||
// isValid: [
|
||||
// null
|
||||
// ]
|
||||
//
|
||||
// Note: validateTxid3() has the same output as validateTxid().
|
||||
// validateTxid2() uses slp-validate, which has a different output format.
|
||||
|
||||
// If not in the cache, try the general SLPDB.
|
||||
if (isValid === null) {
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
isValid = await this.validateTxid(utxo.txid)
|
||||
// console.log(
|
||||
// `validateTxid() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// Handle corner case where SLPDB returns an array with a null element.
|
||||
if (isValid[0] === null) {
|
||||
isValid = [{ txid: utxo.txid, valid: null }]
|
||||
}
|
||||
|
||||
isValid = isValid[0].valid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
|
||||
// console.log(
|
||||
// `pre-validateTxid3() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// If still null, check the whitelist SLPDB
|
||||
if (isValid === null) {
|
||||
// console.log(
|
||||
// `checking against whitelist SLPDB. outAry[${i}]: ${JSON.stringify(
|
||||
// outAry[i],
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
// Figure out if the token UTXO is in the whitelist.
|
||||
let utxoInWhitelist = false
|
||||
for (let j = 0; j < this.whitelist.length; j++) {
|
||||
if (outAry[i].tokenId === this.whitelist[j].tokenId) {
|
||||
utxoInWhitelist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If the utxo.tokenId is in the whitelist, check the validity with
|
||||
// the whitelist SLPDB. This should still be functioning properly
|
||||
// if the general SLPDB is not.
|
||||
if (utxoInWhitelist) {
|
||||
isValid = await this.validateTxid3(utxo.txid)
|
||||
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${utxo.txid}: ${JSON.stringify(
|
||||
// isValid,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
if (isValid[0] !== null) isValid = isValid[0].valid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `pre-validateTxid2() isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
// If still null, as a last resort, check it against slp-validate
|
||||
if (isValid === null) {
|
||||
try {
|
||||
isValid = await this.validateTxid2(utxo.txid)
|
||||
} catch (err) {
|
||||
// Mark as invalid if validateTxid2() throws an error.
|
||||
isValid = null
|
||||
}
|
||||
// console.log(
|
||||
// `slp-validate isValid: ${JSON.stringify(isValid, null, 2)}`
|
||||
// )
|
||||
|
||||
if (isValid !== null) isValid = isValid.isValid
|
||||
|
||||
// Save the result to the local cache.
|
||||
// cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
outAry[i].isValid = isValid
|
||||
|
||||
// Save the txid to the local cache to reduce API calls.
|
||||
cachedTxValidation[utxo.txid] = isValid
|
||||
}
|
||||
}
|
||||
|
||||
return outAry
|
||||
} catch (error) {
|
||||
console.log('_hydrateUtxo error: ', error)
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.waterfallValidateTxid() waterfallValidateTxid()
|
||||
* @apiName waterfallValidateTxid
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription Use multiple validators to validate an SLP TXID.
|
||||
*
|
||||
* This function aggregates all the available SLP token validation sources.
|
||||
* It starts with the fastest, most-efficient source first, and continues
|
||||
* to other validation sources until the txid is validated (true or false).
|
||||
* If the txid goes through all sources and can't be validated, it will
|
||||
* return null.
|
||||
*
|
||||
* Validation sources from most efficient to least efficient:
|
||||
* - SLPDB with whitelist filter
|
||||
* - SLPDB general purpose
|
||||
* - slp-api
|
||||
*
|
||||
* Currently only supports a single txid at a time.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* // validate single SLP txid
|
||||
* (async () => {
|
||||
* try {
|
||||
* let validated = await bchjs.SLP.Utils.waterfallValidateTxid(
|
||||
* "df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb"
|
||||
* );
|
||||
* console.log(validated);
|
||||
* } catch (error) {
|
||||
* console.error(error);
|
||||
* }
|
||||
* })();
|
||||
*
|
||||
* // returns
|
||||
* true
|
||||
*/
|
||||
async waterfallValidateTxid (txid, usrObj = null) {
|
||||
try {
|
||||
// console.log('txid: ', txid)
|
||||
|
||||
const cachedTxValidation = {}
|
||||
|
||||
// If the value has been cached, use the cached version first.
|
||||
let isValid = cachedTxValidation[txid]
|
||||
if (!isValid && isValid !== false) {
|
||||
isValid = null
|
||||
} else {
|
||||
return isValid
|
||||
}
|
||||
|
||||
// There are two possible responses from SLPDB. If SLPDB is functioning
|
||||
// correctly, then validateTxid() will return this:
|
||||
// isValid: [
|
||||
// {
|
||||
// "txid": "ff0c0354f8d3ddb34fa36f73494eb58ea24f8b8da6904aa8ed43b7a74886c583",
|
||||
// "valid": true
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// If SLPDB has fallen behind real-time processing, it will return this:
|
||||
// isValid: [
|
||||
// null
|
||||
// ]
|
||||
//
|
||||
// Note: validateTxid3() has the same output as validateTxid().
|
||||
// validateTxid2() uses slp-validate, which has a different output format.
|
||||
|
||||
// Validate against the whitelist SLPDB first.
|
||||
const whitelistResult = await this.validateTxid3(txid, usrObj)
|
||||
// console.log(
|
||||
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
|
||||
// whitelistResult,
|
||||
// null,
|
||||
// 2
|
||||
// )}`
|
||||
// )
|
||||
|
||||
// Safely retrieve the returned value.
|
||||
if (whitelistResult[0] !== null) isValid = whitelistResult[0].valid
|
||||
|
||||
// Exit if isValid is not null.
|
||||
if (isValid !== null) {
|
||||
// Save to the cache.
|
||||
cachedTxValidation[txid] = isValid
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// Try the general SLPDB, if the whitelist returned null.
|
||||
const generalResult = await this.validateTxid(txid, usrObj)
|
||||
// console.log(
|
||||
// `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
|
||||
// )
|
||||
|
||||
// Safely retrieve the returned value.
|
||||
if (generalResult[0] !== null) isValid = generalResult[0].valid
|
||||
|
||||
// Exit if isValid is not null.
|
||||
if (isValid !== null) {
|
||||
// Save to the cache.
|
||||
cachedTxValidation[txid] = isValid
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// If still null, as a last resort, check it against slp-validate
|
||||
let slpValidateResult = null
|
||||
try {
|
||||
slpValidateResult = await this.validateTxid2(txid)
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
}
|
||||
// console.log(
|
||||
// `slpValidateResult: ${JSON.stringify(slpValidateResult, null, 2)}`
|
||||
// )
|
||||
|
||||
// Exit if isValid is not null.
|
||||
if (slpValidateResult !== null) {
|
||||
isValid = slpValidateResult.isValid
|
||||
|
||||
// Save to the cache.
|
||||
cachedTxValidation[txid] = isValid
|
||||
|
||||
return isValid
|
||||
}
|
||||
|
||||
// If isValid is still null, return that value, signaling that the txid
|
||||
// could not be validated.
|
||||
return isValid
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
throw error
|
||||
@@ -1445,11 +1604,14 @@ class Utils {
|
||||
* 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 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 is an API-heavy call. If you get a lot of null values, then slow down
|
||||
* the calls or request info on fewer UTXOs at a time.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*
|
||||
* (async () => {
|
||||
@@ -1614,6 +1776,42 @@ class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.hydrateUtxosWL() hydrateUtxosWL()
|
||||
* @apiName hydrateUtxosWL
|
||||
* @apiGroup SLP Utils
|
||||
* @apiDescription
|
||||
* This call is exactly the same as `hydrateUtxos()`. This version hydrate a
|
||||
* UTXO with SLP token metadata, but only uses the whitelist SLPDB for
|
||||
* validation.
|
||||
*
|
||||
* Whitelist SLPDBs will return `isValid: null` for any token not in the
|
||||
* 'whitelist' filter. Filtered SLPDBs are much smaller and more reliable
|
||||
* to operate.
|
||||
*
|
||||
*/
|
||||
// Same as tokenUtxoDetailsWL(), but reduces API calls by having bch-api server
|
||||
// do the heavy lifting.
|
||||
async hydrateUtxosWL (utxos) {
|
||||
try {
|
||||
// Throw error if input is not an array.
|
||||
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
|
||||
|
||||
const response = await _this.axios.post(
|
||||
`${this.restURL}slp/hydrateUtxosWL`,
|
||||
{
|
||||
utxos: utxos
|
||||
},
|
||||
_this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api SLP.Utils.getStatus() getStatus()
|
||||
* @apiName getStatus
|
||||
|
||||
@@ -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
|
||||
+147
@@ -27,6 +27,153 @@ class Util {
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.floor8() floor8()
|
||||
* @apiName floor8
|
||||
* @apiGroup Util
|
||||
* @apiDescription Round a number down to 8 decimal places.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const num = 1.234567891111
|
||||
* const result = bchjs.Util.floor8(num)
|
||||
* console.log(result)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* 1.23456789
|
||||
*/
|
||||
// floor8 - round to 8 decimal places
|
||||
// Takes a number and returns it, rounded to the nearest 8 decimal place.
|
||||
floor8 (num) {
|
||||
const thisNum = Number(num)
|
||||
|
||||
if (isNaN(thisNum)) throw new Error('input must be a number')
|
||||
|
||||
let tempNum = thisNum * 100000000
|
||||
tempNum = Math.floor(tempNum)
|
||||
tempNum = tempNum / 100000000
|
||||
|
||||
return tempNum
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.floor2() floor2()
|
||||
* @apiName floor2
|
||||
* @apiGroup Util
|
||||
* @apiDescription Round a number down to 2 decimal places.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const num = 1.234567891111
|
||||
* const result = bchjs.Util.floor2(num)
|
||||
* console.log(result)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* 1.23
|
||||
*/
|
||||
// floor2 - round down to 2 decimal places
|
||||
// Takes a number and returns it, rounded to the nearest 2 decimal place.
|
||||
floor2 (num) {
|
||||
const thisNum = Number(num)
|
||||
|
||||
if (isNaN(thisNum)) throw new Error('input must be a number')
|
||||
|
||||
let tempNum = thisNum * 100
|
||||
tempNum = Math.floor(tempNum)
|
||||
tempNum = tempNum / 100
|
||||
|
||||
return tempNum
|
||||
}
|
||||
|
||||
/**
|
||||
* @api util.chunk20() chunk20()
|
||||
* @apiName chunk20
|
||||
* @apiGroup Util
|
||||
* @apiDescription chunk up an array into multiple arrays of 20 elements each.
|
||||
* Input: arrayToSlice - a one-dimensional array of elements.
|
||||
* Returns a two-dimensional array. An array of 20-element arrays.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]
|
||||
*
|
||||
* const chunked = bchjs.Util.chunk20(bigArray)
|
||||
* console.log(chunked)
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* [
|
||||
* [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],
|
||||
* [20,21,22,23,24,25,26]
|
||||
* ]
|
||||
*/
|
||||
// chunk20 - chunk up an array into multiple arrays of 20 elements each.
|
||||
// Input: arrayToSlice - a one-dimensional array of elements.
|
||||
// Returns a two-dimensional array. An array of 20-element arrays.
|
||||
chunk20 (arrayToSlice) {
|
||||
try {
|
||||
// Validate inputs
|
||||
if (!Array.isArray(arrayToSlice)) {
|
||||
throw new Error('input must be an array')
|
||||
}
|
||||
|
||||
let offset = 0
|
||||
const result = []
|
||||
|
||||
// Loop over the array and slice off chunks of 20 elements.
|
||||
while (offset < arrayToSlice.length) {
|
||||
const chunk = arrayToSlice.slice(offset, offset + 20)
|
||||
result.push(chunk)
|
||||
offset = offset + 20
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Error in chunk20()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
High-level functions for working with UTXOs
|
||||
|
||||
TODO:
|
||||
- Make a getWL() clone of get(), but uses hydrateUtxosWL()
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const Electrumx = require('./electrumx')
|
||||
const Slp = require('./slp/slp')
|
||||
|
||||
class UTXO {
|
||||
constructor (config) {
|
||||
// Encapsulate dependencies for easier mocking.
|
||||
this.electrumx = new Electrumx(config)
|
||||
this.slp = new Slp(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.get() get()
|
||||
* @apiName get
|
||||
* @apiGroup UTXO
|
||||
* @apiDescription Get UTXOs for an address
|
||||
*
|
||||
* Given an address, this function will return an object with thre following
|
||||
* properties:
|
||||
* - address: "" - the address these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
* Note: You can pass in an optional second Boolean argument. The default
|
||||
* `false` will use the normal waterfall validation method. Set to `true`,
|
||||
* SLP UTXOs will be validated with the whitelist filtered SLPDB. This will
|
||||
* result is many more UTXOs in the `nullUtxos` array.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
|
||||
* console.log(utxos);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* [
|
||||
* {
|
||||
* "address": "bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv",
|
||||
* "bchUtxos": [
|
||||
* {
|
||||
* "height": 674513,
|
||||
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "tx_pos": 3,
|
||||
* "value": 38134,
|
||||
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "vout": 3,
|
||||
* "isValid": false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos": [],
|
||||
* "slpUtxos": {
|
||||
* "type1": {
|
||||
* "mintBatons": [
|
||||
* {
|
||||
* "height": 674512,
|
||||
* "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "tx_pos": 2,
|
||||
* "value": 546,
|
||||
* "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "vout": 2,
|
||||
* "utxoType": "minting-baton",
|
||||
* "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "tokenTicker": "SLPTEST",
|
||||
* "tokenName": "SLP Test Token",
|
||||
* "tokenDocumentUrl": "https://FullStack.cash",
|
||||
* "tokenDocumentHash": "",
|
||||
* "decimals": 8,
|
||||
* "tokenType": 1,
|
||||
* "isValid": true
|
||||
* }
|
||||
* ],
|
||||
* "tokens": [
|
||||
* {
|
||||
* "height": 674512,
|
||||
* "tx_hash": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "tx_pos": 1,
|
||||
* "value": 546,
|
||||
* "txid": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "vout": 1,
|
||||
* "utxoType": "token",
|
||||
* "tokenQty": "100",
|
||||
* "tokenId": "acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230",
|
||||
* "tokenTicker": "SLPTEST",
|
||||
* "tokenName": "SLP Test Token",
|
||||
* "tokenDocumentUrl": "https://FullStack.cash",
|
||||
* "tokenDocumentHash": "",
|
||||
* "decimals": 8,
|
||||
* "tokenType": 1,
|
||||
* "isValid": true
|
||||
* }
|
||||
* ]
|
||||
* },
|
||||
* "nft": {
|
||||
* "groupMintBatons": [
|
||||
* {
|
||||
* "height": 674513,
|
||||
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "tx_pos": 2,
|
||||
* "value": 546,
|
||||
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "vout": 2,
|
||||
* "utxoType": "minting-baton",
|
||||
* "transactionType": "mint",
|
||||
* "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395",
|
||||
* "tokenType": 129,
|
||||
* "tokenTicker": "NFTGT",
|
||||
* "tokenName": "NFT Test Group Token",
|
||||
* "tokenDocumentUrl": "https://FullStack.cash",
|
||||
* "tokenDocumentHash": "",
|
||||
* "decimals": 0,
|
||||
* "mintBatonVout": 2,
|
||||
* "isValid": true
|
||||
* }
|
||||
* ],
|
||||
* "groupTokens": [
|
||||
* {
|
||||
* "height": 674513,
|
||||
* "tx_hash": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "tx_pos": 1,
|
||||
* "value": 546,
|
||||
* "txid": "705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04",
|
||||
* "vout": 1,
|
||||
* "utxoType": "token",
|
||||
* "tokenQty": "10",
|
||||
* "transactionType": "mint",
|
||||
* "tokenId": "a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395",
|
||||
* "tokenType": 129,
|
||||
* "tokenTicker": "NFTGT",
|
||||
* "tokenName": "NFT Test Group Token",
|
||||
* "tokenDocumentUrl": "https://FullStack.cash",
|
||||
* "tokenDocumentHash": "",
|
||||
* "decimals": 0,
|
||||
* "mintBatonVout": 2,
|
||||
* "isValid": true
|
||||
* }
|
||||
* ],
|
||||
* "tokens": [
|
||||
* {
|
||||
* "height": 674512,
|
||||
* "tx_hash": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
|
||||
* "tx_pos": 1,
|
||||
* "value": 546,
|
||||
* "txid": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
|
||||
* "vout": 1,
|
||||
* "utxoType": "token",
|
||||
* "tokenQty": "1",
|
||||
* "tokenId": "eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b",
|
||||
* "tokenTicker": "NFT004",
|
||||
* "tokenName": "NFT Child",
|
||||
* "tokenDocumentUrl": "https://FullStack.cash",
|
||||
* "tokenDocumentHash": "",
|
||||
* "decimals": 0,
|
||||
* "tokenType": 65,
|
||||
* "isValid": true
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
*
|
||||
*/
|
||||
async get (address, useWhitelist = false) {
|
||||
try {
|
||||
// Convert address to an array if it is a string.
|
||||
if (typeof address === 'string') address = [address]
|
||||
|
||||
// Throw an error if there are more than 20 addresses passed in at a time.
|
||||
if (address.length > 20) throw new Error('Too many elements, 20 max.')
|
||||
|
||||
// Covert each address to a BCH address.
|
||||
const addr = address.map(elem => this.slp.Address.toCashAddress(elem))
|
||||
|
||||
// Get the UTXOs associated with the address.
|
||||
const utxoData = await this.electrumx.utxo(addr)
|
||||
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
|
||||
|
||||
// Hydate the utxos with token information.
|
||||
let hydratedUtxos
|
||||
if (useWhitelist) {
|
||||
hydratedUtxos = await this.slp.Utils.hydrateUtxosWL(utxoData.utxos)
|
||||
} else {
|
||||
hydratedUtxos = await this.slp.Utils.hydrateUtxos(utxoData.utxos)
|
||||
}
|
||||
// console.log(`hydratedUtxos: ${JSON.stringify(hydratedUtxos, null, 2)}`)
|
||||
|
||||
const retAry = [] // Return array
|
||||
|
||||
// Loop through each address.
|
||||
for (let i = 0; i < hydratedUtxos.slpUtxos.length; i++) {
|
||||
const thisAddr = hydratedUtxos.slpUtxos[i]
|
||||
|
||||
const addrObj = {
|
||||
address: thisAddr.address
|
||||
}
|
||||
|
||||
// Filter out the different types of UTXOs.
|
||||
addrObj.bchUtxos = thisAddr.utxos.filter(elem => elem.isValid === false)
|
||||
addrObj.nullUtxos = thisAddr.utxos.filter(elem => elem.isValid === null)
|
||||
|
||||
// Break down the SLP UTXOs.
|
||||
addrObj.slpUtxos = {
|
||||
type1: {},
|
||||
nft: {}
|
||||
}
|
||||
|
||||
// Token Type 1 Minting Batons.
|
||||
addrObj.slpUtxos.type1.mintBatons = thisAddr.utxos.filter(elem => {
|
||||
const isValid = elem.isValid === true
|
||||
const tokenTypeIs1 = elem.tokenType === 1
|
||||
const isMintingBaton = elem.utxoType === 'minting-baton'
|
||||
|
||||
return isValid && tokenTypeIs1 && isMintingBaton
|
||||
})
|
||||
|
||||
// Token Type 1 tokens.
|
||||
addrObj.slpUtxos.type1.tokens = thisAddr.utxos.filter(elem => {
|
||||
const isValid = elem.isValid === true
|
||||
const tokenTypeIs1 = elem.tokenType === 1
|
||||
const isToken = elem.utxoType === 'token'
|
||||
|
||||
return isValid && tokenTypeIs1 && isToken
|
||||
})
|
||||
|
||||
// NFT Group Minting Batons
|
||||
addrObj.slpUtxos.nft.groupMintBatons = thisAddr.utxos.filter(elem => {
|
||||
const isValid = elem.isValid === true
|
||||
const tokenTypeIs129 = elem.tokenType === 129
|
||||
const isMintingBaton = elem.utxoType === 'minting-baton'
|
||||
|
||||
return isValid && tokenTypeIs129 && isMintingBaton
|
||||
})
|
||||
|
||||
// NFT Group tokens
|
||||
addrObj.slpUtxos.nft.groupTokens = thisAddr.utxos.filter(elem => {
|
||||
const isValid = elem.isValid === true
|
||||
const tokenTypeIs129 = elem.tokenType === 129
|
||||
const isToken = elem.utxoType === 'token'
|
||||
|
||||
return isValid && tokenTypeIs129 && isToken
|
||||
})
|
||||
|
||||
// NFT (Child) tokens
|
||||
addrObj.slpUtxos.nft.tokens = thisAddr.utxos.filter(elem => {
|
||||
const isValid = elem.isValid === true
|
||||
const tokenTypeIs65 = elem.tokenType === 65
|
||||
const isToken = elem.utxoType === 'token'
|
||||
|
||||
return isValid && tokenTypeIs65 && isToken
|
||||
})
|
||||
|
||||
retAry.push(addrObj)
|
||||
}
|
||||
|
||||
return retAry
|
||||
} catch (err) {
|
||||
console.error('Error in bchjs.utxo.get()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
|
||||
* @apiName findBiggestUtxo
|
||||
* @apiGroup UTXO
|
||||
* @apiDescription Get the biggest UTXO in an array.
|
||||
*
|
||||
* Given an array of BCH UTXOs, this method will return the biggest UTXO.
|
||||
* This is often the simplest way to pick a UTXO for generating a transaction.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const utxos = await bchjs.Utxo.get('bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z');
|
||||
* const utxo = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||
* console.log(utxo);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // returns
|
||||
* {
|
||||
* "height": 655431,
|
||||
* "tx_hash": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
|
||||
* "tx_pos": 0,
|
||||
* "value": 800,
|
||||
* "txid": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
|
||||
* "vout": 0,
|
||||
* "isValid": false,
|
||||
* "satoshis": 800
|
||||
* }
|
||||
*/
|
||||
// Returns the utxo with the biggest balance from an array of utxos.
|
||||
findBiggestUtxo (utxos) {
|
||||
let largestAmount = 0
|
||||
let largestIndex = 0
|
||||
|
||||
if (!Array.isArray(utxos)) {
|
||||
throw new Error('utxos input to findBiggestUtxo() must be an array')
|
||||
}
|
||||
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const thisUtxo = utxos[i]
|
||||
|
||||
// Give Elecrumx utxos a satoshis property.
|
||||
if (thisUtxo.value) {
|
||||
if (!thisUtxo.satoshis) thisUtxo.satoshis = Number(thisUtxo.value)
|
||||
}
|
||||
|
||||
if (!thisUtxo.satoshis) {
|
||||
throw new Error(
|
||||
'UTXOs require a satoshis or value property for findBiggestUtxo()'
|
||||
)
|
||||
}
|
||||
|
||||
if (thisUtxo.satoshis > largestAmount) {
|
||||
largestAmount = thisUtxo.satoshis
|
||||
largestIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
return utxos[largestIndex]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UTXO
|
||||
@@ -8,7 +8,7 @@
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
const bchjs = new BCHJS({ restURL: process.env.RESTURL })
|
||||
|
||||
// Inspect utility used for debugging.
|
||||
const util = require('util')
|
||||
@@ -44,6 +44,7 @@ describe('#rawtransaction', () => {
|
||||
|
||||
assert.hasAllKeys(err, ['error'])
|
||||
assert.include(err.error, 'bad-txns-inputs-missingorspent')
|
||||
// assert.include(err.error, 'Missing inputs')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -61,6 +62,7 @@ describe('#rawtransaction', () => {
|
||||
|
||||
assert.hasAllKeys(err, ['error'])
|
||||
assert.include(err.error, 'bad-txns-inputs-missingorspent')
|
||||
// assert.include(err.error, 'Missing inputs')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('#SLP', () => {
|
||||
// Introduce a delay so that the BVT doesn't trip the rate limits.
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
bchjs = new BCHJS({ restURL: process.env.RESTURL })
|
||||
})
|
||||
|
||||
describe('#util', () => {
|
||||
@@ -113,6 +113,7 @@ describe('#SLP', () => {
|
||||
|
||||
// Token send on BCHN network
|
||||
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
|
||||
// assert.equal(data[4].isValid, true)
|
||||
assert.equal(data[4].isValid, null)
|
||||
|
||||
// Token send on ABC network
|
||||
@@ -180,6 +181,7 @@ describe('#SLP', () => {
|
||||
assert.equal(result[0].valid, null)
|
||||
assert.equal(result[1].valid, null)
|
||||
assert.equal(result[2].valid, true)
|
||||
// assert.equal(result[2].valid, null)
|
||||
assert.equal(result[3].valid, true)
|
||||
assert.equal(result[4].valid, null)
|
||||
})
|
||||
@@ -209,6 +211,7 @@ describe('#SLP', () => {
|
||||
assert.equal(result[0].valid, null)
|
||||
assert.equal(result[1].valid, null)
|
||||
assert.equal(result[2].valid, true)
|
||||
// assert.equal(result[2].valid, null)
|
||||
assert.equal(result[3].valid, true)
|
||||
assert.equal(result[4].valid, null)
|
||||
assert.equal(result[5].valid, true)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
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(1000)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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(1000)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
it('should use the whitelist when flag is set', async () => {
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
const useWhitelist = true
|
||||
|
||||
const result = await bchjs.Utxo.get(addr, useWhitelist)
|
||||
// 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)
|
||||
|
||||
// Most token UTXOs should end up in the nullUtxos array.
|
||||
assert.isAbove(result[0].bchUtxos.length, 0)
|
||||
assert.isAbove(result[0].nullUtxos.length, 1)
|
||||
})
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -63,7 +63,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('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
|
||||
}
|
||||
|
||||
// const result = await bchjs.Electrumx.utxo(addr)
|
||||
await bchjs.Electrumx.utxo(addr)
|
||||
@@ -116,7 +118,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('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
|
||||
}
|
||||
|
||||
// const result = await bchjs.Electrumx.balance(addr)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
@@ -172,7 +176,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('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0')
|
||||
}
|
||||
|
||||
// const result = await bchjs.Electrumx.transactions(addr)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
@@ -235,7 +241,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('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
|
||||
}
|
||||
|
||||
await bchjs.Electrumx.unconfirmed(addr)
|
||||
|
||||
@@ -360,6 +368,22 @@ describe('#ElectrumX', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sortAllTxs', () => {
|
||||
it('should GET transaction history for a single address', async () => {
|
||||
const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
|
||||
const txs = await bchjs.Electrumx.transactions(addr)
|
||||
const sortedTransactions = await bchjs.Electrumx.sortAllTxs(
|
||||
txs.transactions
|
||||
)
|
||||
// console.log(
|
||||
// `sortedTransactions: ${JSON.stringify(sortedTransactions, null, 2)}`
|
||||
// )
|
||||
|
||||
assert.isAbove(sortedTransactions[0].height, sortedTransactions[1].height)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
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(1000)
|
||||
})
|
||||
|
||||
/*
|
||||
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))
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
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(1000)
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
|
||||
describe('#util', () => {
|
||||
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()
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
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))
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
tests for OpenBazaar library.
|
||||
*/
|
||||
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#OpenBazaar', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
|
||||
})
|
||||
|
||||
describe('#Balance', () => {
|
||||
it('should GET balance for a single address', async () => {
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.OpenBazaar.balance(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'page',
|
||||
'totalPages',
|
||||
'itemsOnPage',
|
||||
'addrStr',
|
||||
'balance',
|
||||
'totalReceived',
|
||||
'totalSent',
|
||||
'unconfirmedBalance',
|
||||
'unconfirmedTxApperances',
|
||||
'txApperances',
|
||||
'transactions'
|
||||
])
|
||||
assert.isArray(result.transactions)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxo', () => {
|
||||
it('should GET utxos for a single address', async () => {
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.OpenBazaar.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.hasAllKeys(result[0], [
|
||||
'txid',
|
||||
'vout',
|
||||
'amount',
|
||||
'height',
|
||||
'confirmations',
|
||||
'satoshis'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tx', () => {
|
||||
it('should GET transactions for a single address', async () => {
|
||||
const addr =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
const result = await bchjs.OpenBazaar.tx(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'txid',
|
||||
'version',
|
||||
'vin',
|
||||
'vout',
|
||||
'blockhash',
|
||||
'blockheight',
|
||||
'confirmations',
|
||||
'blocktime',
|
||||
'valueOut',
|
||||
'valueIn',
|
||||
'fees',
|
||||
'hex',
|
||||
'time'
|
||||
])
|
||||
assert.isArray(result.vin)
|
||||
assert.isArray(result.vout)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
@@ -230,7 +230,7 @@ describe('#rawtransaction', () => {
|
||||
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', () => {
|
||||
describe('#sendRawTransaction', () => {
|
||||
it('should throw error on array size rate limit', async () => {
|
||||
try {
|
||||
const dataMock =
|
||||
@@ -238,9 +238,9 @@ describe('#rawtransaction', () => {
|
||||
const data = []
|
||||
for (let i = 0; i < 25; i++) data.push(dataMock)
|
||||
|
||||
const result = await bchjs.RawTransactions.sendRawTransaction(data)
|
||||
await bchjs.RawTransactions.sendRawTransaction(data)
|
||||
|
||||
console.log(`result: ${util.inspect(result)}`)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
assert.hasAnyKeys(err, ['error'])
|
||||
@@ -248,6 +248,58 @@ describe('#rawtransaction', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_getInputAddrs', () => {
|
||||
it('should return an array of input addresses', async () => {
|
||||
// const txid = '32233db13f2ae6d82b6262f335643dccf09fc0bcfcef4bc3fbe023355f02e112'
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const txDetails = await bchjs.RawTransactions.getRawTransaction(txid, true)
|
||||
|
||||
const result = await bchjs.RawTransactions._getInputAddrs(txDetails)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.equal(result.length, 1)
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'address')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxData', () => {
|
||||
it('should return tx data with input addresses', async () => {
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxData(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result.vin[0], 'address')
|
||||
})
|
||||
})
|
||||
|
||||
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) {
|
||||
|
||||
+227
-1
@@ -32,7 +32,8 @@ describe('#SLP', () => {
|
||||
describe('#util', () => {
|
||||
describe('#list', () => {
|
||||
it('should get information on the Spice token', async () => {
|
||||
const tokenId = '4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
const tokenId =
|
||||
'4de69e374a8ed21cbddd47f2338cc0f479dc58daa2bbe11cd604ca488eca0ddf'
|
||||
|
||||
const result = await bchjs.SLP.Utils.list(tokenId)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -490,6 +491,116 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 660554,
|
||||
tx_hash:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'utxoType')
|
||||
assert.property(data[0], 'tokenId')
|
||||
assert.property(data[0], 'tokenTicker')
|
||||
assert.property(data[0], 'tokenName')
|
||||
assert.property(data[0], 'tokenDocumentUrl')
|
||||
assert.property(data[0], 'tokenDocumentHash')
|
||||
assert.property(data[0], 'decimals')
|
||||
assert.property(data[0], 'tokenQty')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it('should return false for BCH-only UTXOs', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'a937f792c7c9eb23b4f344ce5c233d1ac0909217d0a504d71e6b1e4efb864a3b',
|
||||
vout: 0,
|
||||
amount: 0.00001,
|
||||
satoshis: 1000,
|
||||
confirmations: 0,
|
||||
ts: 1578424704
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'53fd141c2e999e080a5860887441a2c45e9cbe262027e2bd2ac998fc76e43c44',
|
||||
vout: 0,
|
||||
amount: 0.00001,
|
||||
satoshis: 1000,
|
||||
confirmations: 0,
|
||||
ts: 1578424634
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
assert.equal(data[0].isValid, false)
|
||||
assert.equal(data[1].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle a dust attack', async () => {
|
||||
// it("#dustattack", async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 655965,
|
||||
tx_hash:
|
||||
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
|
||||
tx_pos: 151,
|
||||
value: 547,
|
||||
satoshis: 547,
|
||||
txid:
|
||||
'a675af87dcd8d39be782737aa52e0076b52eb2f5ce355ffcb5567a64dd96b77e',
|
||||
vout: 151,
|
||||
address: 'bitcoincash:qq4dw3sm8qvglspy6w2qg0u2ugsy9zcfcqrpeflwww',
|
||||
hdIndex: 11
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.equal(data[0].isValid, false)
|
||||
})
|
||||
|
||||
it('should handle null SLPDB validations', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data)
|
||||
// assert.equal(data[0].isValid, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balancesForAddress', () => {
|
||||
it('should fetch all balances for address: simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', async () => {
|
||||
const balances = await bchjs.SLP.Utils.balancesForAddress(
|
||||
@@ -655,6 +766,99 @@ describe('#SLP', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should hydrate UTXOs', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
txid:
|
||||
'89b3f0c84efe8b01b24e2d7ac08636de5781f31dbb84478e3de868ca0a7ed93a',
|
||||
vout: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 1)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 1)
|
||||
|
||||
// Test the non-slp UTXO.
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'txid')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'vout')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'value')
|
||||
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, true)
|
||||
})
|
||||
|
||||
it('should process data directly from Electrumx', async () => {
|
||||
const addrs = [
|
||||
'bitcoincash:qqnt53cfw990u6y38xgezm0lfa8hknw0wueezcpagp',
|
||||
'bitcoincash:qrh3qgtax6adegzc7zxm6m4sgrv9wq28yqnfn33ce5',
|
||||
'bitcoincash:qqcaee5w4ws77n8s2gzy6fwtma6uxd7ctq8553e495'
|
||||
]
|
||||
|
||||
const utxos = await bchjs.Electrumx.utxo(addrs)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const result = await bchjs.SLP.Utils.hydrateUtxosWL(utxos.utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Test the general structure of the output.
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.equal(result.slpUtxos.length, 3)
|
||||
assert.equal(result.slpUtxos[0].utxos.length, 1)
|
||||
assert.equal(result.slpUtxos[1].utxos.length, 1)
|
||||
assert.equal(result.slpUtxos[2].utxos.length, 2)
|
||||
|
||||
try {
|
||||
// Test the expected values.
|
||||
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
|
||||
assert.equal(result.slpUtxos[1].utxos[0].isValid, true)
|
||||
assert.equal(result.slpUtxos[1].utxos[0].tokenTicker, 'TROUT')
|
||||
assert.equal(result.slpUtxos[2].utxos[0].isValid, false)
|
||||
// assert.equal(result.slpUtxos[2].utxos[1].isValid, null)
|
||||
assert.equal(result.slpUtxos[2].utxos[1].tokenTicker, 'VALENTINE')
|
||||
} catch (err) {
|
||||
console.error(
|
||||
'The hydrateUtxos call may hitting rate limits, or SLPDB may be having issues if "isValid" results are "null"'
|
||||
)
|
||||
console.log('Error: ', err)
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle null SLPDB validations', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'4b89405c54d1c0bde8aa476a47561a42a6e7a5e927daa2ec69d428810eae3419',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 665577,
|
||||
tx_hash:
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
}
|
||||
]
|
||||
|
||||
const data = await bchjs.SLP.Utils.hydrateUtxosWL([{ utxos: utxos }])
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
assert.isArray(data.slpUtxos)
|
||||
assert.equal(data.slpUtxos[0].isValid, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid', () => {
|
||||
// This test is not necessary.
|
||||
it('should handle a null response from SLPDB', async () => {
|
||||
@@ -735,6 +939,28 @@ describe('#SLP', () => {
|
||||
assert.property(result, 'slpProcessedBlockHeight')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#waterfallValidateTxid', () => {
|
||||
it('should validate known good txid not in whitelist', async () => {
|
||||
const txid =
|
||||
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488'
|
||||
|
||||
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should invalidate a known invalid TXID', async () => {
|
||||
const txid =
|
||||
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
|
||||
|
||||
const result = await bchjs.SLP.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tokentype1', () => {
|
||||
|
||||
@@ -5,11 +5,11 @@ const bchjs = new BCHJS()
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('#Control', () => {
|
||||
describe('#getNetworkInfo', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#getNetworkInfo', () => {
|
||||
it('should get info', done => {
|
||||
const data = {
|
||||
version: 170000,
|
||||
@@ -36,10 +36,6 @@ describe('#Control', () => {
|
||||
})
|
||||
|
||||
describe('#getMemoryInfo', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
it('should get memory info', done => {
|
||||
const data = {
|
||||
locked: {
|
||||
|
||||
@@ -390,6 +390,7 @@ describe('#ElectrumX', () => {
|
||||
assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
@@ -402,6 +403,7 @@ describe('#ElectrumX', () => {
|
||||
assert.include(err.message, 'Input txHex must be a string.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should broadcast a single transaction', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.broadcast })
|
||||
@@ -418,4 +420,75 @@ describe('#ElectrumX', () => {
|
||||
assert.equal(result.txid, txid)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sortConfTxs', () => {
|
||||
it('should sort in ascending order', () => {
|
||||
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions, 'ASCENDING')
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isBelow(result[0].height, result[1].height)
|
||||
})
|
||||
|
||||
it('should sort in descending order by default', () => {
|
||||
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(result[0].height, result[1].height)
|
||||
})
|
||||
|
||||
it('should ignore unconfirmed txs', () => {
|
||||
const result = bchjs.Electrumx.sortConfTxs(mockData.txHistoryWithUnconfirmed.transactions)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(result[0].height, 0)
|
||||
})
|
||||
|
||||
it('should handle errors', () => {
|
||||
try {
|
||||
bchjs.Electrumx.sortConfTxs('abc')
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err.message)
|
||||
assert.equal(err.message, 'txs.filter is not a function')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// These tests use mocked data that contains unconfirmed transactions.
|
||||
describe('#sortAllTxs', () => {
|
||||
it('should sort in ascending', async () => {
|
||||
// Stub network calls
|
||||
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
|
||||
|
||||
const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions, 'ASCENDING')
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isBelow(result[0].height, result[1].height)
|
||||
})
|
||||
|
||||
it('should sort in descending order by default', async () => {
|
||||
// Stub network calls
|
||||
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
|
||||
|
||||
const result = await bchjs.Electrumx.sortAllTxs(mockData.txHistoryWithUnconfirmed.transactions)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isAbove(result[1].height, result[2].height)
|
||||
})
|
||||
|
||||
it('should handle errors', async () => {
|
||||
try {
|
||||
// Stub network calls
|
||||
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
|
||||
|
||||
await bchjs.Electrumx.sortAllTxs('abc')
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err.message)
|
||||
assert.equal(err.message, 'txs.map is not a function')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -168,7 +168,8 @@ const txDetails = {
|
||||
blocktime: 1578327094,
|
||||
confirmations: 31861,
|
||||
hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
|
||||
hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
|
||||
hex:
|
||||
'020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
|
||||
locktime: 0,
|
||||
size: 392,
|
||||
time: 1578327094,
|
||||
@@ -177,8 +178,10 @@ const txDetails = {
|
||||
vin: [
|
||||
{
|
||||
scriptSig: {
|
||||
asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
|
||||
hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
|
||||
asm:
|
||||
'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
|
||||
hex:
|
||||
'41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
|
||||
@@ -186,8 +189,10 @@ const txDetails = {
|
||||
},
|
||||
{
|
||||
scriptSig: {
|
||||
asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
|
||||
hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
|
||||
asm:
|
||||
'347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
|
||||
hex:
|
||||
'41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
|
||||
@@ -210,7 +215,8 @@ const txDetails = {
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'],
|
||||
asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash'
|
||||
@@ -221,7 +227,8 @@ const txDetails = {
|
||||
n: 2,
|
||||
scriptPubKey: {
|
||||
addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'],
|
||||
asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash'
|
||||
@@ -255,6 +262,34 @@ const broadcast = {
|
||||
txid: txDetails.txid
|
||||
}
|
||||
|
||||
const txHistoryWithUnconfirmed = {
|
||||
success: true,
|
||||
transactions: [
|
||||
{
|
||||
height: 668539,
|
||||
tx_hash:
|
||||
'fd9bffca3dd7a2d801c87cb36e8b43949d2e171bb8e25e3d92cf39ac5e2ba179'
|
||||
},
|
||||
{
|
||||
height: 668544,
|
||||
tx_hash:
|
||||
'9d1b438aa55d75da8ebe9dcdfa4fa96714042ce0a19c8f3c10d86e1e3dcbccf5'
|
||||
},
|
||||
{
|
||||
fee: 440,
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'30d3fa9bf78eab2162d4f1894223e417e7275fdf0d590fe5085381dd475ff077'
|
||||
},
|
||||
{
|
||||
fee: 468,
|
||||
height: -1,
|
||||
tx_hash:
|
||||
'4b93c26a4612b6819fde2bf5d1f6387bf92f49a3375c53a472001e7d76a80e59'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
utxo,
|
||||
utxos,
|
||||
@@ -267,5 +302,6 @@ module.exports = {
|
||||
blockHeaders,
|
||||
details,
|
||||
detailsArray,
|
||||
broadcast
|
||||
broadcast,
|
||||
txHistoryWithUnconfirmed
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Mocking data for the RawTransactions unit tests.
|
||||
*/
|
||||
|
||||
const mockTx = {
|
||||
txid: '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3',
|
||||
vin: [
|
||||
{
|
||||
txid: '8a6b3b70569270f0bdbf68fd12a410aef8f7bf044bc88ab02386a1572024b2bd',
|
||||
vout: 0,
|
||||
scriptSig: {
|
||||
asm:
|
||||
'3044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba0[ALL|FORKID] 030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9',
|
||||
hex:
|
||||
'473044022035c42f5b10d412445c5ecc5feea42c7f885c433669306c699da0f687216c61d5022018c81cd0ea68101cf3cbe0af67165fca1ce3d667be69d0c9329f0679bbee6ba04121030152eb20beaa692daaa1a27596dcc98cc06ccbc6eec23d6182a08c7bdaa29ea9'
|
||||
},
|
||||
sequence: 4294967295
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockParentTx1 = {
|
||||
vout: [
|
||||
{
|
||||
value: 0.04199959,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 d5258a73b5c8f94c939d7fe96f78ce97906083be OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a914d5258a73b5c8f94c939d7fe96f78ce97906083be88ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt']
|
||||
}
|
||||
},
|
||||
{
|
||||
value: 1.66296161,
|
||||
n: 1,
|
||||
scriptPubKey: {
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 5e95c308c25c74c64c5ffe44a60a4d9b35743e90 OP_EQUALVERIFY OP_CHECKSIG',
|
||||
hex: '76a9145e95c308c25c74c64c5ffe44a60a4d9b35743e9088ac',
|
||||
reqSigs: 1,
|
||||
type: 'pubkeyhash',
|
||||
addresses: ['bitcoincash:qp0ftscgcfw8f3jvtllyffs2fkdn2ap7jqreakn4ye']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockGetInputAddrsOutput = [
|
||||
{
|
||||
vin: 0,
|
||||
address: 'bitcoincash:qr2jtznnkhy0jnynn4l7jmmce6teqcyrhc8herhlgt'
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
mockTx,
|
||||
mockParentTx1,
|
||||
mockGetInputAddrsOutput
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
const mockUtxoData = {
|
||||
success: true,
|
||||
utxos: [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
height: 569108,
|
||||
tx_hash:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674331,
|
||||
tx_hash:
|
||||
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
|
||||
tx_pos: 0,
|
||||
value: 1000
|
||||
}
|
||||
],
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockHydratedUtxos = {
|
||||
slpUtxos: [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
height: 569108,
|
||||
tx_hash:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
transactionType: 'send',
|
||||
tokenId:
|
||||
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||
tokenTicker: 'sleven',
|
||||
tokenName: 'sleven',
|
||||
tokenDocumentUrl: 'sleven',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 7,
|
||||
tokenType: 1,
|
||||
tokenQty: '1',
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674331,
|
||||
tx_hash:
|
||||
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
|
||||
tx_pos: 0,
|
||||
value: 1000,
|
||||
txid:
|
||||
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
|
||||
vout: 0,
|
||||
isValid: false
|
||||
}
|
||||
],
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockTwoHydratedAddrs = {
|
||||
slpUtxos: [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
height: 569108,
|
||||
tx_hash:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
transactionType: 'send',
|
||||
tokenId:
|
||||
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||
tokenTicker: 'sleven',
|
||||
tokenName: 'sleven',
|
||||
tokenDocumentUrl: 'sleven',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 7,
|
||||
tokenType: 1,
|
||||
tokenQty: '1',
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674331,
|
||||
tx_hash:
|
||||
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
|
||||
tx_pos: 0,
|
||||
value: 1000,
|
||||
txid:
|
||||
'5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31',
|
||||
vout: 0,
|
||||
isValid: false
|
||||
}
|
||||
],
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
height: 602405,
|
||||
tx_hash:
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
|
||||
tx_pos: 0,
|
||||
value: 1000,
|
||||
txid:
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
|
||||
vout: 0,
|
||||
isValid: false
|
||||
}
|
||||
],
|
||||
address: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const mockEveryUtxoType = {
|
||||
slpUtxos: [
|
||||
{
|
||||
utxos: [
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
tokenQty: '100',
|
||||
tokenId:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tokenTicker: 'SLPTEST',
|
||||
tokenName: 'SLP Test Token',
|
||||
tokenDocumentUrl: 'https://FullStack.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 8,
|
||||
tokenType: 1,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tx_pos: 2,
|
||||
value: 546,
|
||||
txid:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
vout: 2,
|
||||
utxoType: 'minting-baton',
|
||||
tokenId:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tokenTicker: 'SLPTEST',
|
||||
tokenName: 'SLP Test Token',
|
||||
tokenDocumentUrl: 'https://FullStack.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 8,
|
||||
tokenType: 1,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid:
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
tokenQty: '1',
|
||||
tokenId:
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
tokenTicker: 'NFT004',
|
||||
tokenName: 'NFT Child',
|
||||
tokenDocumentUrl: 'https://FullStack.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 0,
|
||||
tokenType: 65,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
tokenQty: '10',
|
||||
transactionType: 'mint',
|
||||
tokenId:
|
||||
'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
tokenType: 129,
|
||||
tokenTicker: 'NFTGT',
|
||||
tokenName: 'NFT Test Group Token',
|
||||
tokenDocumentUrl: 'https://FullStack.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 2,
|
||||
value: 546,
|
||||
txid:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
vout: 2,
|
||||
utxoType: 'minting-baton',
|
||||
transactionType: 'mint',
|
||||
tokenId:
|
||||
'a9a2458a0f9f0761d5b8725c256f2e7fa35b9de4dec6f47b46e9f20d92d0e395',
|
||||
tokenType: 129,
|
||||
tokenTicker: 'NFTGT',
|
||||
tokenName: 'NFT Test Group Token',
|
||||
tokenDocumentUrl: 'https://FullStack.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 0,
|
||||
mintBatonVout: 2,
|
||||
isValid: true
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 3,
|
||||
value: 38134,
|
||||
txid:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
vout: 3,
|
||||
isValid: false
|
||||
}
|
||||
],
|
||||
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const electrumxUtxos = {
|
||||
success: true,
|
||||
utxos: [
|
||||
{
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
|
||||
tx_pos: 0,
|
||||
value: 600
|
||||
},
|
||||
{
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'f913646f7c180f8de020cb1387951272e8a3f764a0f88e68f7fc1145a0bf02e9',
|
||||
tx_pos: 0,
|
||||
value: 700
|
||||
},
|
||||
{
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b',
|
||||
tx_pos: 0,
|
||||
value: 800
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockUtxoData,
|
||||
mockHydratedUtxos,
|
||||
mockTwoHydratedAddrs,
|
||||
mockEveryUtxoType,
|
||||
electrumxUtxos
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
const axios = require('axios')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const mockData = require('./fixtures/openbazaar-mock')
|
||||
|
||||
describe('#OpenBazaar', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#Balance', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
|
||||
await bchjs.OpenBazaar.balance(addr)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(err.message, 'Input address must be a string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should GET balance for a single address', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'get').resolves({ data: mockData.balance })
|
||||
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.OpenBazaar.balance(addr)
|
||||
// console.log(`result: ${util.inspect(result)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'page',
|
||||
'totalPages',
|
||||
'itemsOnPage',
|
||||
'addrStr',
|
||||
'balance',
|
||||
'totalReceived',
|
||||
'totalSent',
|
||||
'unconfirmedBalance',
|
||||
'unconfirmedTxApperances',
|
||||
'txApperances',
|
||||
'transactions'
|
||||
])
|
||||
assert.isArray(result.transactions)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxo', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
|
||||
await bchjs.OpenBazaar.utxo(addr)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(err.message, 'Input address must be a string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should GET utxos for a single address', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'get').resolves({ data: mockData.utxo })
|
||||
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.OpenBazaar.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.hasAllKeys(result[0], [
|
||||
'txid',
|
||||
'vout',
|
||||
'amount',
|
||||
'height',
|
||||
'confirmations',
|
||||
'satoshis'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#tx', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const txid = 12345
|
||||
|
||||
await bchjs.OpenBazaar.tx(txid)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(err.message, 'Input txid must be a string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should GET tx details for a single txid', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'get').resolves({ data: mockData.tx })
|
||||
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
const result = await bchjs.OpenBazaar.tx(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.hasAllKeys(result, [
|
||||
'txid',
|
||||
'version',
|
||||
'vin',
|
||||
'vout',
|
||||
'blockhash',
|
||||
'blockheight',
|
||||
'confirmations',
|
||||
'blocktime',
|
||||
'valueOut',
|
||||
'valueIn',
|
||||
'fees',
|
||||
'hex'
|
||||
])
|
||||
assert.isArray(result.vin)
|
||||
assert.isArray(result.vout)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('assert')
|
||||
const assert2 = require('chai').assert
|
||||
const axios = require('axios')
|
||||
const sinon = require('sinon')
|
||||
// const nock = require("nock") // HTTP mocking
|
||||
@@ -18,16 +19,19 @@ let bchjs
|
||||
// const util = require("util")
|
||||
// util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const mockData = require('./fixtures/rawtransaction-mock')
|
||||
|
||||
describe('#RawTransactions', () => {
|
||||
let sandbox
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
bchjs = new BCHJS()
|
||||
})
|
||||
|
||||
describe('#decodeRawTransaction', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#decodeRawTransaction', () => {
|
||||
it('should decode raw transaction', done => {
|
||||
const data = {
|
||||
txid:
|
||||
@@ -132,4 +136,82 @@ describe('#RawTransactions', () => {
|
||||
// assert.strictEqual(data, result.data)
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#_getInputAddrs', () => {
|
||||
it('should return an array of input addresses', async () => {
|
||||
sandbox
|
||||
.stub(bchjs.RawTransactions, 'getRawTransaction')
|
||||
.resolves(mockData.mockParentTx1)
|
||||
|
||||
const result = await bchjs.RawTransactions._getInputAddrs(mockData.mockTx)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.isArray(result)
|
||||
assert2.equal(result.length, 1)
|
||||
assert2.property(result[0], 'vin')
|
||||
assert2.property(result[0], 'address')
|
||||
})
|
||||
|
||||
it('should catch and throw and error', async () => {
|
||||
try {
|
||||
sandbox
|
||||
.stub(bchjs.RawTransactions, 'getRawTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await bchjs.RawTransactions._getInputAddrs(mockData.mockTx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
assert2.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
const result = await bchjs.RawTransactions.getTxData(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.property(result.vin[0], 'address')
|
||||
})
|
||||
|
||||
it('should throw an error for a non-txid input', async () => {
|
||||
try {
|
||||
await bchjs.RawTransactions.getTxData(1234)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
assert2.include(err.message, 'Input must be a string or array of strings')
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force a network error.
|
||||
sandbox
|
||||
.stub(bchjs.RawTransactions, 'getRawTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
const txid = '05f7d4a4e25f53d63a360434eb54f221abf159112b7fffc91da1072a079cded3'
|
||||
|
||||
await bchjs.RawTransactions.getTxData(txid)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
assert2.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+155
-260
@@ -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'
|
||||
@@ -497,41 +497,6 @@ describe('#SLP Utils', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// CT 1/9/21: This test can be removed after 2/1/21
|
||||
// it('should throw error if utxo does not have satoshis or value property.', async () => {
|
||||
// try {
|
||||
// const utxos = [
|
||||
// {
|
||||
// txid:
|
||||
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
// vout: 3,
|
||||
// amount: 0.00002015,
|
||||
// satoshis: 2015,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// },
|
||||
// {
|
||||
// txid:
|
||||
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
// vout: 2,
|
||||
// amount: 0.00000546,
|
||||
// height: 594892,
|
||||
// confirmations: 5
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// await uut.Utils.tokenUtxoDetails(utxos)
|
||||
//
|
||||
// assert.equal(true, false, 'Unexpected result.')
|
||||
// } catch (err) {
|
||||
// assert.include(
|
||||
// err.message,
|
||||
// 'utxo 1 does not have a satoshis or value property',
|
||||
// 'Expected error message.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
|
||||
it('should throw error if utxo does not have txid or tx_hash property.', async () => {
|
||||
try {
|
||||
const utxos = [
|
||||
@@ -569,14 +534,7 @@ describe('#SLP Utils', () => {
|
||||
// // change UTXO will contain the same SLP txid, but it is not an SLP UTXO.
|
||||
it('should return details on minting baton from genesis transaction', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves({
|
||||
@@ -675,13 +633,7 @@ describe('#SLP Utils', () => {
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'cf4b922d1e1aa56b52d752d4206e1448ea76c3ebe69b3b97d8f8f65413bd5c76',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -746,14 +698,7 @@ describe('#SLP Utils', () => {
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -790,9 +735,10 @@ describe('#SLP Utils', () => {
|
||||
|
||||
it('should handle BCH and SLP utxos in the same TX', async () => {
|
||||
// Mock external dependencies.
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(mockData.mockDualValidation)
|
||||
// sandbox
|
||||
// .stub(uut.Utils, 'validateTxid')
|
||||
// .resolves(mockData.mockDualValidation)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
sandbox
|
||||
.stub(uut.Utils, 'decodeOpReturn')
|
||||
@@ -898,14 +844,7 @@ describe('#SLP Utils', () => {
|
||||
qty: '2000000000000000000'
|
||||
})
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'67fd3c7c3a6eb0fea9ab311b91039545086220f7eeeefa367fa28e6e43009f19',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1017,14 +956,7 @@ describe('#SLP Utils', () => {
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox.stub(uut.Utils, 'decodeOpReturn').resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'd2ec6abff5d1c8ed9ab5db6d140dcaebb813463e42933a4a4db171e7222a0954',
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1095,14 +1027,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '10000000000'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'880587f01e3112e779c0fdf1b9b859c242a28e56ead85483eeedcaa52f051a04',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1118,11 +1042,12 @@ describe('#SLP Utils', () => {
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
// sandbox
|
||||
// .stub(uut.Utils, 'validateTxid')
|
||||
// .resolves(stubValid)
|
||||
// .onCall(1)
|
||||
// .resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1184,14 +1109,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1206,14 +1123,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(4)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
.onCall(2)
|
||||
.resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1286,14 +1196,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'35846676e7514658bbd2fd60b1f0d4d86195908f6b2de5328d54c8e4a2d05919',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1308,14 +1210,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(4)
|
||||
.resolves(genesisData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
.onCall(2)
|
||||
.resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1379,14 +1274,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'9b6db26b64aedcedc0bd9a3037b29b3598573ec5cea99eec03faa838616cd683',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1395,8 +1282,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(1)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1453,14 +1339,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'6d68a7ffbb63ef851c43025f801a1d365cddda50b00741bca022c743d74cd61a',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1471,8 +1349,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(2)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1530,14 +1407,6 @@ describe('#SLP Utils', () => {
|
||||
qty: '1'
|
||||
}
|
||||
|
||||
const stubValid = [
|
||||
{
|
||||
txid:
|
||||
'57cc47c265ce878679e95e2cec510d8a1a9840f5c62feb4743cc5947d57d9766',
|
||||
valid: true
|
||||
}
|
||||
]
|
||||
|
||||
// Mock external dependencies.
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1552,12 +1421,7 @@ describe('#SLP Utils', () => {
|
||||
.onCall(4)
|
||||
.resolves(slpData)
|
||||
|
||||
// Stub the call to validateTxid
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.resolves(stubValid)
|
||||
.onCall(1)
|
||||
.resolves(stubValid)
|
||||
sandbox.stub(uut.Utils, 'waterfallValidateTxid').resolves(true)
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
@@ -1695,8 +1559,10 @@ describe('#SLP Utils', () => {
|
||||
|
||||
assert.equal(data[0].isValid, false)
|
||||
})
|
||||
})
|
||||
|
||||
it('should use backup validators when regular SLPDB returns null', async () => {
|
||||
describe('#tokenUtxoDetailsWL', () => {
|
||||
it('should return details for a simple SEND SLP token utxo', async () => {
|
||||
// Mock the call to REST API
|
||||
// Stub the calls to decodeOpReturn.
|
||||
sandbox
|
||||
@@ -1724,36 +1590,15 @@ describe('#SLP Utils', () => {
|
||||
qty: '2100000000000000'
|
||||
})
|
||||
|
||||
// Force default SLPDB to return 'null', which should trigger usage of the
|
||||
// backup whitelist-SLPDB.
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: null
|
||||
}
|
||||
])
|
||||
|
||||
// Force the token UTXO to be in the whitelist.
|
||||
uut.Utils.whitelist = mockData.whitelist
|
||||
|
||||
// Mock the response from the whitelist-SLPDB.
|
||||
// Stub the call to validateTxid
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves([
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
valid: null
|
||||
valid: true
|
||||
}
|
||||
])
|
||||
|
||||
// Mock the response from slp-api
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
isValid: true,
|
||||
msg: ''
|
||||
})
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
@@ -1766,68 +1611,21 @@ describe('#SLP Utils', () => {
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils.tokenUtxoDetails(utxos)
|
||||
const data = await uut.Utils.tokenUtxoDetailsWL(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// The UTXO should have been validated by the third validation source.
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
|
||||
it('should handle SLPDB returning null corner case', 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'
|
||||
})
|
||||
|
||||
// Force default SLPDB to return 'null' corner case.
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves([null])
|
||||
|
||||
// Mock the response from slp-api
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
isValid: true,
|
||||
msg: ''
|
||||
})
|
||||
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'fde117b1f176b231e2fa9a6cb022e0f7c31c288221df6bcb05f8b7d040ca87cb',
|
||||
vout: 1,
|
||||
amount: 0.00000546,
|
||||
satoshis: 546,
|
||||
height: 596089,
|
||||
confirmations: 748
|
||||
}
|
||||
]
|
||||
|
||||
const data = await uut.Utils.tokenUtxoDetails(utxos)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
// The UTXO should have been validated by the third validation source.
|
||||
assert.property(data[0], 'txid')
|
||||
assert.property(data[0], 'vout')
|
||||
assert.property(data[0], 'height')
|
||||
assert.property(data[0], 'utxoType')
|
||||
assert.property(data[0], 'tokenId')
|
||||
assert.property(data[0], 'tokenTicker')
|
||||
assert.property(data[0], 'tokenName')
|
||||
assert.property(data[0], 'tokenDocumentUrl')
|
||||
assert.property(data[0], 'tokenDocumentHash')
|
||||
assert.property(data[0], 'decimals')
|
||||
assert.property(data[0], 'tokenQty')
|
||||
assert.property(data[0], 'isValid')
|
||||
assert.equal(data[0].isValid, true)
|
||||
})
|
||||
})
|
||||
@@ -1855,7 +1653,8 @@ describe('#SLP Utils', () => {
|
||||
.throws({ error: 'TXID not found' })
|
||||
}
|
||||
|
||||
const txid = 'd284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e'
|
||||
const txid =
|
||||
'd284e71227ec89f714b964d8eda595be6392bebd2fac46082bc5a9ce6fb7b33e'
|
||||
|
||||
await uut.Utils.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -1875,7 +1674,8 @@ describe('#SLP Utils', () => {
|
||||
.resolves({ data: mockData.mockTxDetails })
|
||||
}
|
||||
|
||||
const txid = '9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0'
|
||||
const txid =
|
||||
'9dbaaafc48c49a21beabada8de632009288a2cd52eecefd0c00edcffca9955d0'
|
||||
|
||||
const result = await uut.Utils.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -1916,6 +1716,21 @@ describe('#SLP Utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateUtxosWL', () => {
|
||||
it('should throw an error if input is not an array', async () => {
|
||||
try {
|
||||
const utxos = 1234
|
||||
|
||||
await uut.Utils.hydrateUtxosWL(utxos)
|
||||
|
||||
assert.equal(true, false, 'Uh oh. Code path should not end here.')
|
||||
} catch (err) {
|
||||
// console.log(`Error: `, err)
|
||||
assert.include(err.message, 'Input must be an array.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#validateTxid2', () => {
|
||||
it('should throw an error if the input is an array', async () => {
|
||||
try {
|
||||
@@ -2055,6 +1870,86 @@ describe('#SLP Utils', () => {
|
||||
*/
|
||||
})
|
||||
|
||||
describe('#waterfallValidateTxid', () => {
|
||||
// This test ensures that the whitelist SLPDB is called before the
|
||||
// general purpose SLPDB.
|
||||
it('should call validateTxid3() first', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal = [{ txid, valid: true }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal)
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid2')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// This test ensures that the general purpose SLPDB is called after the
|
||||
// whitelist SLPDB, but before slp-api.
|
||||
it('should call validateTxid() second', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal1 = [{ txid, valid: null }]
|
||||
const retVal2 = [{ txid, valid: true }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal2)
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid2')
|
||||
.rejects(new Error('Unexpected code path'))
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// This test ensures that slp-api is called if both SLPDBs return null.
|
||||
it('should call slp-api last', async () => {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
const retVal1 = [{ txid, valid: null }]
|
||||
|
||||
// Use stubs to force the desired code path.
|
||||
sandbox.stub(uut.Utils, 'validateTxid3').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid').resolves(retVal1)
|
||||
sandbox.stub(uut.Utils, 'validateTxid2').resolves({ isValid: true })
|
||||
|
||||
const result = await uut.Utils.waterfallValidateTxid(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('catches and throws an error', async () => {
|
||||
try {
|
||||
const txid = 'fakeTxid'
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.Utils, 'validateTxid3')
|
||||
.rejects(new Error('fake error'))
|
||||
|
||||
await await uut.Utils.waterfallValidateTxid(txid)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
assert.include(err.message, 'fake error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getWhitelist', () => {
|
||||
it('should return the list', async () => {
|
||||
sandbox
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const assert = require('assert')
|
||||
const assert2 = require('chai').assert
|
||||
const axios = require('axios')
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
@@ -32,4 +33,143 @@ describe('#Util', () => {
|
||||
.then(done, done)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#floor8', () => {
|
||||
it('should floor a number to 8 decimals', () => {
|
||||
const num = 1.234567891111
|
||||
|
||||
const result = bchjs.Util.floor8(num)
|
||||
// console.log(result)
|
||||
|
||||
assert2.equal(result, 1.23456789)
|
||||
})
|
||||
|
||||
it('should throw an error for non-number input', () => {
|
||||
try {
|
||||
const num = 'string'
|
||||
|
||||
bchjs.Util.floor8(num)
|
||||
|
||||
assert2.equal(true, false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert2.include(err.message, 'input must be a number')
|
||||
}
|
||||
})
|
||||
|
||||
it('should not effect a number with less than 8 decimals', () => {
|
||||
const num = 1.23
|
||||
|
||||
const result = bchjs.Util.floor8(num)
|
||||
// console.log(result)
|
||||
|
||||
assert2.equal(result, 1.23)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#floor2', () => {
|
||||
it('should round a number to 2 decimals', () => {
|
||||
const num = 1.234567891111
|
||||
|
||||
const result = bchjs.Util.floor2(num)
|
||||
// console.log(result)
|
||||
|
||||
assert2.equal(result, 1.23)
|
||||
})
|
||||
|
||||
it('should throw an error for non-number input', () => {
|
||||
try {
|
||||
const num = 'string'
|
||||
|
||||
bchjs.Util.floor2(num)
|
||||
|
||||
assert2.equal(true, false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert2.include(err.message, 'input must be a number')
|
||||
}
|
||||
})
|
||||
|
||||
it('should not effect a number with less than 8 decimals', () => {
|
||||
const num = 1.2
|
||||
|
||||
const result = bchjs.Util.floor2(num)
|
||||
// console.log(result)
|
||||
|
||||
assert2.equal(result, 1.2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#chunk20', () => {
|
||||
const thirtyFiveElements = [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34
|
||||
]
|
||||
|
||||
it('should split 35 elements into 2 arrays', () => {
|
||||
const result = bchjs.Util.chunk20(thirtyFiveElements)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.isArray(result)
|
||||
assert2.isArray(result[0])
|
||||
assert2.equal(result.length, 2)
|
||||
assert2.equal(result[0].length, 20)
|
||||
})
|
||||
|
||||
it('should return accurately with an array of less than 20 elements', () => {
|
||||
const elems = [0, 1, 2, 3, 4, 5]
|
||||
|
||||
const result = bchjs.Util.chunk20(elems)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert2.isArray(result)
|
||||
assert2.isArray(result[0])
|
||||
assert2.equal(result.length, 1)
|
||||
assert2.equal(result[0].length, 6)
|
||||
})
|
||||
|
||||
it('should throw an error for non-array input', () => {
|
||||
try {
|
||||
bchjs.Util.chunk20('string')
|
||||
|
||||
assert2.equal(true, false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert2.include(err.message, 'input must be an array')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
Unit tests for the utxo.js library.
|
||||
*/
|
||||
|
||||
const sinon = require('sinon')
|
||||
const assert = require('chai').assert
|
||||
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
const mockData = require('./fixtures/utxo-mocks')
|
||||
|
||||
describe('#utxo', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#get', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
|
||||
.resolves(mockData.mockHydratedUtxos)
|
||||
|
||||
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 catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.electrumx, 'utxo')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
await bchjs.Utxo.get(addr)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle an array of addresses', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} })
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
|
||||
.resolves(mockData.mockTwoHydratedAddrs)
|
||||
|
||||
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.equal(result.length, 2)
|
||||
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 throw an error for array of 21 elements', async () => {
|
||||
try {
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
const addrs = []
|
||||
for (let i = 0; i < 21; i++) {
|
||||
addrs.push(addr)
|
||||
}
|
||||
|
||||
await bchjs.Utxo.get(addrs)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Too many elements, 20 max.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle NFTs and minting batons', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves({ utxos: {} })
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxos')
|
||||
.resolves(mockData.mockEveryUtxoType)
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
it('should use the whitelist when flag is set', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.slp.Utils, 'hydrateUtxosWL')
|
||||
.resolves(mockData.mockHydratedUtxos)
|
||||
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
const useWhitelist = true
|
||||
|
||||
const result = await bchjs.Utxo.get(addr, useWhitelist)
|
||||
// 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)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#findBiggestUtxo', () => {
|
||||
it('should throw error for non-array input', async () => {
|
||||
try {
|
||||
await bchjs.Utxo.findBiggestUtxo({})
|
||||
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'utxos input to findBiggestUtxo() must be an array'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if input does not have a value or satoshis property', async () => {
|
||||
try {
|
||||
const badUtxos = [
|
||||
{
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
|
||||
tx_pos: 0
|
||||
}
|
||||
]
|
||||
|
||||
await bchjs.Utxo.findBiggestUtxo(badUtxos)
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'UTXOs require a satoshis or value property for findBiggestUtxo()'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should sort UTXOs from Electrumx', async () => {
|
||||
const result = bchjs.Utxo.findBiggestUtxo(mockData.electrumxUtxos.utxos)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'satoshis')
|
||||
assert.equal(result.satoshis, 800)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user