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 | |
|---|---|---|---|
|
|
7274475898 | ||
|
|
7bb347a833 | ||
|
|
fadceedd00 | ||
|
|
7f4ef60f8e | ||
|
|
81e54e0590 | ||
|
|
1d5e3da976 | ||
|
|
2cff305f46 | ||
|
|
9495381cf4 | ||
|
|
32f89346c3 | ||
|
|
e8582fd0ab | ||
|
|
7049b96df1 | ||
|
|
9cc6824591 | ||
|
|
bb82d520ed | ||
|
|
fef05124f1 | ||
|
|
978db07526 | ||
|
|
68bab5dd26 | ||
|
|
20e685206d | ||
|
|
e90afac701 | ||
|
|
96f96f837e | ||
|
|
03be1d5838 | ||
|
|
6b6bfa2ae2 | ||
|
|
da3af3656a | ||
|
|
26fdb5c5d5 | ||
|
|
86d71d0f49 |
Generated
+2086
-2255
File diff suppressed because it is too large
Load Diff
+10
-10
@@ -13,11 +13,11 @@
|
||||
"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 '#tokenUtxoDetailsWL' test/integration/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#tokenUtxoDetailsWL' 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:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#UTXO' test/integration/chains/bchn/",
|
||||
"test:temp2": "mocha --timeout=30000 -g '#Util' test/unit/",
|
||||
"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,6 +30,11 @@
|
||||
"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",
|
||||
"assert": "^2.0.0",
|
||||
@@ -39,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",
|
||||
|
||||
@@ -31,6 +31,7 @@ const Schnorr = require('./schnorr')
|
||||
const SLP = require('./slp/slp')
|
||||
const IPFS = require('./ipfs')
|
||||
const Encryption = require('./encryption')
|
||||
const Utxo = require('./utxo')
|
||||
|
||||
// Indexers
|
||||
const Ninsight = require('./ninsight')
|
||||
@@ -113,6 +114,7 @@ class BCHJS {
|
||||
this.SLP.HDNode = this.HDNode
|
||||
|
||||
this.IPFS = new IPFS()
|
||||
this.Utxo = new Utxo(libConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
+42
-2
@@ -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()
|
||||
@@ -652,6 +653,7 @@ class Utils {
|
||||
const path = `${this.restURL}slp/validateTxid3`
|
||||
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
// console.log(`path: ${JSON.stringify(path, null, 2)}`)
|
||||
|
||||
// Handle a single TXID or an array of TXIDs.
|
||||
let txids
|
||||
@@ -1109,7 +1111,7 @@ class Utils {
|
||||
|
||||
// Validate using a 'waterfall' of validators.
|
||||
utxo.isValid = await this.waterfallValidateTxid(utxo.txid)
|
||||
// console.log(`isValid: ${JSON.stringify(isValid, null, 2)}`)
|
||||
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,6 +1450,8 @@ class Utils {
|
||||
|
||||
async waterfallValidateTxid (txid) {
|
||||
try {
|
||||
// console.log('txid: ', txid)
|
||||
|
||||
const cachedTxValidation = {}
|
||||
|
||||
// If the value has been cached, use the cached version first.
|
||||
@@ -1728,6 +1732,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
|
||||
|
||||
+123
@@ -27,6 +27,129 @@ 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.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,34 @@
|
||||
/*
|
||||
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.bchUtxos)
|
||||
assert.isArray(result.slpUtxos)
|
||||
assert.isArray(result.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))
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -766,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 () => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1716,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 {
|
||||
|
||||
@@ -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