Compare commits

..
33 Commits
Author SHA1 Message Date
Chris Troutner 6b6bfa2ae2 Merge pull request #101 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxosWL): hydrateUtxo but uses SLPDB with whitelist filter
2021-02-09 12:37:15 -08:00
Chris Troutner da3af3656a feat(hydrateUtxosWL): hydrateUtxo but uses SLPDB with whitelist filter 2021-02-09 12:34:25 -08:00
Chris Troutner 26fdb5c5d5 Merge pull request #100 from Permissionless-Software-Foundation/ct-unstable
fix(package.json): Slight reconfiguration by npm
2021-02-09 09:12:41 -08:00
Chris Troutner 86d71d0f49 fix(package.json): Slight reconfiguration by npm 2021-02-09 09:10:11 -08:00
Chris Troutner 86351aaf4f Merge pull request #99 from Permissionless-Software-Foundation/ct-unstable
tokenUtxoDetailsWL() and waterfallValidateTxid()
2021-02-09 07:25:13 -08:00
Chris Troutner 36432af7d5 feat(tokenUtxoDetailsWL): tokenUtxoDetails that use whitelist SLPDB 2021-02-09 07:22:01 -08:00
Chris Troutner 95c88b5f29 fix(_hydrateUtxo): Moving utxo hydration into a private function 2021-02-08 20:33:27 -08:00
Chris Troutner 28f563b58c Updating unit tests to reflect use of waterfallValidateTxid() 2021-02-08 19:17:50 -08:00
Chris Troutner 79ea4b77b9 fix(tokenUtxoDetails): Replacing validation with waterfallValidateTxid() 2021-02-08 17:25:08 -08:00
Chris Troutner d37a2f8139 Merge pull request #98 from Permissionless-Software-Foundation/ct-unstable
feat(waterfallValidateTxid): SLP.Utils.waterfallValidateTxid(txid)
2021-02-08 17:16:35 -08:00
Chris Troutner 3b873b6064 feat(waterfallValidateTxid): SLP.Utils.waterfallValidateTxid(txid) 2021-02-08 17:10:32 -08:00
Chris Troutner 687b5bca02 feat(waterfallValidateTxid): Adding a new waterfall SLP validation method 2021-02-08 10:45:24 -08:00
Chris Troutner b80697500c Adding integration test script to gitignore 2021-02-08 09:43:25 -08:00
Chris Troutner 626d27033e Merge pull request #96 from Permissionless-Software-Foundation/ct-unstable
fix(typo): Fixing typo in IPFS class
2021-02-03 17:41:29 -08:00
Chris Troutner ce2ee3fc1b fix(typo): Fixing typo in IPFS class 2021-02-03 17:38:43 -08:00
Chris Troutner 4768dcadc6 Merge pull request #95 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Updating the server that handles bch-ipfs file uploads
2021-02-03 15:42:47 -08:00
Chris Troutner 5fef09edbf fix(ipfs): Updating the server that handles bch-ipfs file uploads 2021-02-03 15:40:38 -08:00
Chris Troutner 37109cd7ef Merge pull request #94 from Permissionless-Software-Foundation/ct-unstable
fix(getTxDataSlp): Adding prototype function
2021-01-30 14:43:13 -08:00
Chris Troutner 83d523260d fix(getTxDataSlp): Adding prototype function 2021-01-30 14:40:46 -08:00
Chris Troutner 8768a810c7 Merge pull request #93 from Permissionless-Software-Foundation/ct-unstable
RawTransactions.getTxData() returns tx info with vin addresses
2021-01-29 12:49:50 -08:00
Chris Troutner 8832bfc8df fix(decodeOpReturn): Fixing documentation 2021-01-29 12:47:22 -08:00
Chris Troutner a5208110b9 feat(getTxData): RawTransactions.getTxData() returns tx info with vin addresses 2021-01-29 12:43:02 -08:00
Chris Troutner 99ad7cd87f fix(_getInputAddrs): Added private function with tests get tx input addrs 2021-01-29 12:11:23 -08:00
Chris Troutner 68ae084fb0 Merge pull request #92 from Permissionless-Software-Foundation/ct-unstable
Merging documentation changes from @uncletammy
2021-01-29 07:20:32 -08:00
Chris Troutner b0b0e17abc Merge pull request #91 from uncletammy/master
Corrects example usages for HDNode.Mnemonic.toSeed method
2021-01-29 07:11:57 -08:00
Nicholas Crumrine a54306e382 corrects example usages for HDNode.toSeed method 2021-01-28 15:24:03 -06:00
Chris Troutner 1860232fa6 Merge pull request #90 from Permissionless-Software-Foundation/ct-unstable
fix(sortAllTxs): Renaming Electrumx.sort0ConfTxs to sortAllTxs
2021-01-27 07:28:53 -08:00
Chris Troutner d8437cf3fb fix(sortAllTxs): Renaming Electrumx.sort0ConfTxs to sortAllTxs 2021-01-27 07:24:28 -08:00
Chris Troutner 338788c46d Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
fix(sort0ConfTxs): Switching to Descending by default
2021-01-27 06:57:15 -08:00
Chris Troutner cad2c6378e fix(sort0ConfTxs): Switching to Descending by default 2021-01-27 06:51:36 -08:00
Chris Troutner 2e6938e21f Merge pull request #88 from Permissionless-Software-Foundation/ct-unstable
feat(tx sorting): added Electrumx.sort0ConfTxs()
2021-01-26 20:28:40 -08:00
Chris Troutner 1e13c71715 Added integration test 2021-01-26 20:25:03 -08:00
Chris Troutner d4b0807d16 feat(tx sorting): added Electrumx.sort0ConfTxs() 2021-01-26 20:20:49 -08:00
16 changed files with 20320 additions and 1242 deletions
+1
View File
@@ -3,6 +3,7 @@ node_modules/*
wallet-info.txt
wallet.json
integration-test-sweet.sh
integration-test-ss.sh
docs/
coverage/
+18985 -794
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -16,8 +16,8 @@
"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:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxosWL' test/integration/",
"test:temp2": "mocha --timeout=30000 -g '#tokenUtxoDetailsWL' test/unit/",
"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,9 +30,13 @@
"url": "git+https://github.com/Permissionless-Software-Foundation/bch-js.git"
},
"dependencies": {
"@psf/bip21": "^2.0.1",
"@psf/bip32-utils": "^0.13.1",
"@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",
@@ -40,20 +44,15 @@
"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",
@@ -76,6 +75,7 @@
"wif": "^2.0.6"
},
"devDependencies": {
"apidoc": "^0.17.7",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
+108 -2
View File
@@ -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
}
@@ -452,7 +457,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 +603,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
View File
@@ -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
View File
@@ -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.
+179
View File
@@ -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
}
@@ -289,6 +294,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
+304 -153
View File
@@ -602,7 +602,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()
@@ -883,18 +884,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,6 +918,7 @@ class Utils {
* "99883300000000"
* ]
* }
*
*/
// Reimplementation of decodeOpReturn() using slp-parser.
async decodeOpReturn (txid, cache = null) {
@@ -1061,10 +1059,6 @@ class Utils {
// CT 5/31/20: Refactored to use slp-parse library.
async tokenUtxoDetails (utxos) {
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.')
@@ -1073,19 +1067,7 @@ class Utils {
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 +1092,131 @@ class Utils {
}
}
// 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 using a 'waterfall' of validators.
utxo.isValid = await this.waterfallValidateTxid(utxo.txid)
// console.log(`isValid: ${JSON.stringify(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.
async _hydrateUtxo (utxos) {
try {
const decodeOpReturnCache = {}
// Output Array
const outAry = []
@@ -1169,7 +1276,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 +1300,6 @@ class Utils {
utxo.decimals = slpData.decimals
utxo.tokenType = slpData.tokenType
// something
outAry[i] = utxo
}
}
@@ -1206,8 +1311,8 @@ 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.
@@ -1258,8 +1363,8 @@ 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.
@@ -1296,130 +1401,6 @@ 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
@@ -1429,6 +1410,140 @@ class Utils {
}
}
/**
* @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) {
try {
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)
// 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)
// 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
}
}
/**
* @api SLP.Utils.hydrateUtxos() hydrateUtxos()
* @apiName hydrateUtxos
@@ -1614,6 +1729,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
+28 -4
View File
@@ -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) {
+55 -3
View File
@@ -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
View File
@@ -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', () => {
+73
View File
@@ -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')
}
})
})
})
+44 -8
View File
@@ -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
}
+62
View File
@@ -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
}
+86 -4
View File
@@ -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')
}
})
})
})
+137 -242
View File
@@ -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