Compare commits

...
35 Commits
Author SHA1 Message Date
Chris Troutner 357e350176 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Adding usrObj to Electrumx.Transactions()
2021-02-24 17:59:35 -08:00
Chris Troutner f6140acc76 fix(electrumx): Adding usrObj to Electrumx.Transactions() 2021-02-24 17:53:53 -08:00
Chris Troutner df65b12aab Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): replacing ip with usrObj
2021-02-24 16:44:58 -08:00
Chris Troutner 754b4cd807 fix(decodeOpReturn): replacing ip with usrObj 2021-02-24 16:42:54 -08:00
Chris Troutner 3c6079e54c Merge pull request #114 from Permissionless-Software-Foundation/ct-unstable
fix(usrObj): Fixing typo of usbObj to usrObj
2021-02-24 16:37:09 -08:00
Chris Troutner 18621cd76c fix(usrObj): Fixing typo of usbObj to usrObj 2021-02-24 16:35:09 -08:00
Chris Troutner 456159a4cc Merge pull request #113 from Permissionless-Software-Foundation/ct-unstable
fix(usbObj): Passing a usrObj when bch-api calls bch-js
2021-02-24 16:29:54 -08:00
Chris Troutner 33e723befe fix(usbObj): Passing a usrObj when bch-api calls bch-js 2021-02-24 16:27:49 -08:00
Chris Troutner 30fdee1920 Merge pull request #112 from Permissionless-Software-Foundation/ct-unstable
fix(decodeOpReturn): Switching to POST from GET to getRawTx call
2021-02-24 15:14:51 -08:00
Chris Troutner feb336c331 fix(decodeOpReturn): Switching to POST from GET to getRawTx call 2021-02-24 15:11:58 -08:00
Chris Troutner 8bacc0a991 Merge pull request #111 from Permissionless-Software-Foundation/ct-unstable
fix(ip): Renaming origin to ip
2021-02-24 14:10:07 -08:00
Chris Troutner d22f1ec8a6 fix(ip): Renaming origin to ip 2021-02-24 14:05:25 -08:00
Chris Troutner 7eb3a796b8 Merge pull request #110 from Permissionless-Software-Foundation/ct-unstable
fix(origin): Testing idea around passing origin when bch-api calls bc…
2021-02-24 13:29:05 -08:00
Chris Troutner 9919f811b0 fix(origin): Testing idea around passing origin when bch-api calls bch-js 2021-02-24 13:26:08 -08:00
Chris Troutner e7854481a0 Merge pull request #109 from Permissionless-Software-Foundation/ct-unstable
fix(dependencies): Removing several unused dependencies
2021-02-23 10:29:22 -08:00
Chris Troutner 0b406c467e fix(dependencies): Removing more unnecessary dependencies 2021-02-23 10:26:12 -08:00
Chris Troutner e41217b710 fix(dependencies): Removing several unused dependencies 2021-02-23 09:04:49 -08:00
Chris Troutner c22ce8d57d fix(dependencies): Ran npm audit with 0 vulns 2021-02-23 08:46:27 -08:00
Chris Troutner bf3612583e Merge pull request #108 from Permissionless-Software-Foundation/ct-unstable
Sleep: Adding a Promise-based wait function to Util lib
2021-02-15 15:28:26 -08:00
Chris Troutner ef56452ec9 feat(sleep): Adding sleep function to Util lib 2021-02-15 15:16:30 -08:00
Chris Troutner b3be470c0e fix(utxo.get()): Fixing ABC integration test 2021-02-15 15:12:39 -08:00
Chris Troutner 7274475898 Merge pull request #107 from Permissionless-Software-Foundation/ct-unstable
fix(package-lock.json): Updating package-lock.json
2021-02-13 17:46:19 -08:00
Chris Troutner 7bb347a833 fix(package-lock.json): Updating package-lock.json 2021-02-13 17:44:14 -08:00
Chris Troutner fadceedd00 Merge pull request #106 from Permissionless-Software-Foundation/ct-unstable
fix(debug): Removing debugging lines
2021-02-13 17:33:43 -08:00
Chris Troutner 7f4ef60f8e fix(debug): Removing debugging lines 2021-02-13 17:30:55 -08:00
Chris Troutner 81e54e0590 Merge pull request #105 from Permissionless-Software-Foundation/ct-unstable
Porting bch-util into bch-js
2021-02-13 15:07:44 -08:00
Chris Troutner 1d5e3da976 feat(chunk20): bchjs.Util.chunk20() - chunk array into 20-element arrays 2021-02-13 15:04:39 -08:00
Chris Troutner 2cff305f46 feat(floor2): bchjs.Util.floor2(num) - round down to 2 decimal places 2021-02-13 14:59:33 -08:00
Chris Troutner 9495381cf4 feat(floor8): bchjs.Util.floor8(num) - Round down to 8 decimal places 2021-02-13 14:55:12 -08:00
Chris Troutner 32f89346c3 linting 2021-02-13 14:38:37 -08:00
Chris Troutner e8582fd0ab feat(findBiggestUtxo): Ported from bch-util. Finds biggest UTXO in an array 2021-02-13 14:38:01 -08:00
Chris Troutner 7049b96df1 Merge pull request #104 from Permissionless-Software-Foundation/ct-unstable
utxo.get(): Refined and finalized.
2021-02-12 19:48:13 -08:00
Chris Troutner 9cc6824591 feat(utxo.get whitelist): Added flag to validate against the whitelist filtered SLPDB 2021-02-12 19:44:24 -08:00
Chris Troutner bb82d520ed feat(utxo.get()): Output changed for different types of utxos 2021-02-12 18:30:25 -08:00
Chris Troutner fef05124f1 feat(utxo.get()): Changes to accept arrays of up to 20 addresses 2021-02-12 15:58:16 -08:00
16 changed files with 5380 additions and 5854 deletions
+4250 -5697
View File
File diff suppressed because it is too large Load Diff
+6 -20
View File
@@ -16,8 +16,10 @@
"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 '#utxo' test/unit/",
"test:temp": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
"test:temp2": "mocha --timeout=30000 -g '#Util' test/unit/",
"test:temp3": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#hydrateUtxos' test/integration/",
"test:temp4": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 -g '#decodeOpReturn' test/integration/",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
@@ -37,7 +39,6 @@
"@psf/coininfo": "^4.0.0",
"@uppy/core": "^1.10.4",
"@uppy/tus": "^1.5.12",
"assert": "^2.0.0",
"axios": "^0.21.1",
"bc-bip68": "^1.0.5",
"bchaddrjs-slp": "^0.2.5",
@@ -49,33 +50,18 @@
"bip66": "^1.1.5",
"bitcoinjs-message": "^2.0.0",
"bs58": "^4.0.1",
"buffer": "^5.1.0",
"cashaddrjs": "^0.3.3",
"chalk": "^2.3.0",
"clear": "0.1.0",
"commander": "^3.0.0",
"cp-file": "^7.0.0",
"ecurve": "^1.0.6",
"figlet": "^1.2.0",
"git-clone": "^0.1.0",
"ini": "^1.3.8",
"mkdirp": "^0.5.1",
"node-cmd": "^3.0.0",
"node-emoji": "^1.8.1",
"qrcode": "^1.4.1",
"randombytes": "^2.0.6",
"repl.history": "^0.1.4",
"safe-buffer": "^5.1.2",
"satoshi-bitcoin": "^1.0.4",
"slp-mdm": "0.0.6",
"slp-parser": "0.0.4",
"socket.io": "^2.1.1",
"socket.io-client": "^2.1.1",
"touch": "^3.1.0",
"wif": "^2.0.6"
},
"devDependencies": {
"apidoc": "^0.17.7",
"assert": "^2.0.0",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
@@ -90,7 +76,7 @@
"node-mocks-http": "^1.7.0",
"nyc": "^14.1.0",
"prettier": "^1.18.2",
"semantic-release": "^17.3.1",
"semantic-release": "^17.3.9",
"sinon": "^7.3.2",
"standard": "^16.0.3"
},
-2
View File
@@ -26,7 +26,6 @@ const TransactionBuilder = require('./transaction-builder')
const ECPair = require('./ecpair')
const Script = require('./script')
const Price = require('./price')
const Socket = require('./socket')
const Schnorr = require('./schnorr')
const SLP = require('./slp/slp')
const IPFS = require('./ipfs')
@@ -107,7 +106,6 @@ class BCHJS {
this.TransactionBuilder = TransactionBuilder
this.TransactionBuilder.setAddress(this.Address)
this.Util = new Util(libConfig)
this.Socket = Socket
this.Schnorr = new Schnorr(libConfig)
this.SLP = new SLP(libConfig)
+3 -2
View File
@@ -284,7 +284,7 @@ class ElectrumX {
* }
*
*/
async transactions (address) {
async transactions (address, usrObj = null) {
try {
// Handle single address.
if (typeof address === 'string') {
@@ -299,7 +299,8 @@ class ElectrumX {
const response = await axios.post(
`${this.restURL}electrumx/transactions`,
{
addresses: address
addresses: address,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
+3 -2
View File
@@ -263,7 +263,7 @@ class RawTransactions {
* // time: 1547752564,
* // blocktime: 1547752564 } ]
*/
async getRawTransaction (txid, verbose = false) {
async getRawTransaction (txid, verbose = false, usrObj = null) {
try {
if (typeof txid === 'string') {
const response = await axios.get(
@@ -278,7 +278,8 @@ class RawTransactions {
url: `${this.restURL}rawtransactions/getRawTransaction`,
data: {
txids: txid,
verbose: verbose
verbose: verbose,
usrObj // pass user data when making an internal call.
},
headers: _this.axiosOptions.headers
}
+2 -2
View File
@@ -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(
+55 -20
View File
@@ -436,10 +436,11 @@ class Utils {
// will be like the examples above. If SLPDB has fallen behind real-time
// processing, it will return this output:
// [ null ]
async validateTxid (txid) {
async validateTxid (txid, usrObj = null) {
const path = `${this.restURL}slp/validateTxid`
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
// console.log(`validateTxid usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Handle a single TXID or an array of TXIDs.
let txids
@@ -450,7 +451,8 @@ class Utils {
const response = await _this.axios.post(
path,
{
txids: txids
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
@@ -649,11 +651,12 @@ class Utils {
* '00ea27261196a411776f81029c0ebe34362936b4a9847deb1f7a40a02b3a1476',
* valid: true } ]
*/
async validateTxid3 (txid) {
async validateTxid3 (txid, usrObj = null) {
const path = `${this.restURL}slp/validateTxid3`
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
// console.log(`path: ${JSON.stringify(path, null, 2)}`)
// console.log('validateTxid3 usrObj: ', usrObj)
// Handle a single TXID or an array of TXIDs.
let txids
@@ -664,7 +667,8 @@ class Utils {
const response = await _this.axios.post(
path,
{
txids: txids
txids: txids,
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
@@ -922,7 +926,7 @@ class Utils {
*
*/
// Reimplementation of decodeOpReturn() using slp-parser.
async decodeOpReturn (txid, cache = null) {
async decodeOpReturn (txid, cache = null, usrObj = null) {
// The cache object is an in-memory cache (JS Object) that can be passed
// into this function. It helps if multiple vouts from the same TXID are
// being evaluated. In that case, it can significantly reduce the number
@@ -939,16 +943,32 @@ class Utils {
if (cachedVal) return cachedVal
}
// console.log(`decodeOpReturn usrObj: ${JSON.stringify(usrObj, null, 2)}`)
try {
// Validate the txid input.
if (!txid || txid === '' || typeof txid !== 'string') {
throw new Error('txid string must be included.')
}
// CT: 2/24/21 Deprected GET in favor of POST, to pass IP address.
// Retrieve the transaction object from the full node.
const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
const response = await _this.axios.get(path, _this.axiosOptions)
const txDetails = response.data
// const path = `${this.restURL}rawtransactions/getRawTransaction/${txid}?verbose=true`
// const response = await _this.axios.get(path, _this.axiosOptions)
// const txDetails = response.data
const path = `${this.restURL}rawtransactions/getRawTransaction`
const response = await _this.axios.post(
path,
{
verbose: true,
txids: [txid],
usrObj // pass user data when making an internal call.
},
_this.axiosOptions
)
const txDetails = response.data[0]
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
// SLP spec expects OP_RETURN to be the first output of the transaction.
@@ -1019,6 +1039,9 @@ class Utils {
* then SLPDB has not yet processed that txid and validity has not been confirmed,
* or a 429 rate-limit error was enountered during the processing of the request.
*
* This is an API-heavy call. If you get a lot of null values, then slow down
* the calls or request info on fewer UTXOs at a time.
*
* @apiExample Example usage:
*
* (async () => {
@@ -1058,11 +1081,13 @@ class Utils {
// https://github.com/Bitcoin-com/slp-sdk/issues/84
// CT 5/31/20: Refactored to use slp-parse library.
async tokenUtxoDetails (utxos) {
async tokenUtxoDetails (utxos, usrObj = null) {
try {
// Throw error if input is not an array.
if (!Array.isArray(utxos)) throw new Error('Input must be an array.')
// console.log(`tokenUtxoDetails usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Loop through each element in the array and validate the input before
// further processing.
for (let i = 0; i < utxos.length; i++) {
@@ -1094,7 +1119,7 @@ class Utils {
}
// Hydrate each UTXO with data from SLP OP_REUTRNs.
const outAry = await this._hydrateUtxo(utxos)
const outAry = await this._hydrateUtxo(utxos, usrObj)
// console.log(`outAry: ${JSON.stringify(outAry, null, 2)}`)
// *After* each UTXO has been hydrated with SLP data,
@@ -1110,7 +1135,7 @@ class Utils {
// information.
// Validate using a 'waterfall' of validators.
utxo.isValid = await this.waterfallValidateTxid(utxo.txid)
utxo.isValid = await this.waterfallValidateTxid(utxo.txid, usrObj)
// console.log(`isValid: ${JSON.stringify(utxo.isValid, null, 2)}`)
}
}
@@ -1214,10 +1239,12 @@ class Utils {
// This is a private function that is called by tokenUtxoDetails().
// It loops through an array of UTXOs and tries to hydrate them with SLP
// token information from the OP_RETURN data.
async _hydrateUtxo (utxos) {
async _hydrateUtxo (utxos, usrObj = null) {
try {
const decodeOpReturnCache = {}
// console.log(`_hydrateUtxo usrObj: ${JSON.stringify(usrObj, null, 2)}`)
// Output Array
const outAry = []
@@ -1230,7 +1257,11 @@ class Utils {
// If there is no OP_RETURN, mark the UTXO as false.
let slpData = false
try {
slpData = await this.decodeOpReturn(utxo.txid, decodeOpReturnCache)
slpData = await this.decodeOpReturn(
utxo.txid,
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`slpData: ${JSON.stringify(slpData, null, 2)}`)
} catch (err) {
// console.log(`error from decodeOpReturn(${utxo.txid}): `, err)
@@ -1320,7 +1351,8 @@ class Utils {
const genesisData = await this.decodeOpReturn(
slpData.tokenId,
decodeOpReturnCache
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
@@ -1372,7 +1404,8 @@ class Utils {
const genesisData = await this.decodeOpReturn(
slpData.tokenId,
decodeOpReturnCache
decodeOpReturnCache,
usrObj // pass user data when making an internal call.
)
// console.log(`genesisData: ${JSON.stringify(genesisData, null, 2)}`)
@@ -1447,8 +1480,7 @@ class Utils {
* // returns
* true
*/
async waterfallValidateTxid (txid) {
async waterfallValidateTxid (txid, usrObj = null) {
try {
// console.log('txid: ', txid)
@@ -1480,7 +1512,7 @@ class Utils {
// validateTxid2() uses slp-validate, which has a different output format.
// Validate against the whitelist SLPDB first.
const whitelistResult = await this.validateTxid3(txid)
const whitelistResult = await this.validateTxid3(txid, usrObj)
// console.log(
// `whitelist-SLPDB for ${txid}: ${JSON.stringify(
// whitelistResult,
@@ -1501,7 +1533,7 @@ class Utils {
}
// Try the general SLPDB, if the whitelist returned null.
const generalResult = await this.validateTxid(txid)
const generalResult = await this.validateTxid(txid, usrObj)
// console.log(
// `validateTxid() isValid: ${JSON.stringify(generalResult, null, 2)}`
// )
@@ -1563,11 +1595,14 @@ class Utils {
* set to false.
* If the UTXO is part of an SLP transaction, it will return the UTXO object
* with additional SLP information attached. An `isValid` property will be included.
* If its value is true, the UTXO is a valid SLP UTXO. \
* If its value is true, the UTXO is a valid SLP UTXO.
* If the isValid value is null,
* then SLPDB has not yet processed that txid and validity has not been confirmed,
* or a 429 rate-limit error was enountered during the processing of the request.
*
* This is an API-heavy call. If you get a lot of null values, then slow down
* the calls or request info on fewer UTXOs at a time.
*
* @apiExample Example usage:
*
* (async () => {
-28
View File
@@ -1,28 +0,0 @@
const io = require('socket.io-client')
class Socket {
constructor (config = {}) {
if (typeof config === 'string') {
// TODO remove this check in v2.0
this.socket = io(`${config}`)
} else {
if (config.restURL) {
this.socket = io(`${config.restURL}`)
} else {
const restURL = 'https://rest.bitcoin.com'
this.socket = io(`${restURL}`)
}
if (config.callback) config.callback()
}
}
listen (endpoint, cb) {
this.socket.emit(endpoint)
if (endpoint === 'blocks') this.socket.on('blocks', msg => cb(msg))
else if (endpoint === 'transactions') { this.socket.on('transactions', msg => cb(msg)) }
}
}
module.exports = Socket
+147
View File
@@ -27,6 +27,153 @@ class Util {
_this = this
}
/**
* @api util.floor8() floor8()
* @apiName floor8
* @apiGroup Util
* @apiDescription Round a number down to 8 decimal places.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const num = 1.234567891111
* const result = bchjs.Util.floor8(num)
* console.log(result)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* 1.23456789
*/
// floor8 - round to 8 decimal places
// Takes a number and returns it, rounded to the nearest 8 decimal place.
floor8 (num) {
const thisNum = Number(num)
if (isNaN(thisNum)) throw new Error('input must be a number')
let tempNum = thisNum * 100000000
tempNum = Math.floor(tempNum)
tempNum = tempNum / 100000000
return tempNum
}
/**
* @api util.floor2() floor2()
* @apiName floor2
* @apiGroup Util
* @apiDescription Round a number down to 2 decimal places.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const num = 1.234567891111
* const result = bchjs.Util.floor2(num)
* console.log(result)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* 1.23
*/
// floor2 - round down to 2 decimal places
// Takes a number and returns it, rounded to the nearest 2 decimal place.
floor2 (num) {
const thisNum = Number(num)
if (isNaN(thisNum)) throw new Error('input must be a number')
let tempNum = thisNum * 100
tempNum = Math.floor(tempNum)
tempNum = tempNum / 100
return tempNum
}
/**
* @api util.chunk20() chunk20()
* @apiName chunk20
* @apiGroup Util
* @apiDescription chunk up an array into multiple arrays of 20 elements each.
* Input: arrayToSlice - a one-dimensional array of elements.
* Returns a two-dimensional array. An array of 20-element arrays.
*
* @apiExample Example usage:
* (async () => {
* try {
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]
*
* const chunked = bchjs.Util.chunk20(bigArray)
* console.log(chunked)
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* [
* [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],
* [20,21,22,23,24,25,26]
* ]
*/
// chunk20 - chunk up an array into multiple arrays of 20 elements each.
// Input: arrayToSlice - a one-dimensional array of elements.
// Returns a two-dimensional array. An array of 20-element arrays.
chunk20 (arrayToSlice) {
try {
// Validate inputs
if (!Array.isArray(arrayToSlice)) {
throw new Error('input must be an array')
}
let offset = 0
const result = []
// Loop over the array and slice off chunks of 20 elements.
while (offset < arrayToSlice.length) {
const chunk = arrayToSlice.slice(offset, offset + 20)
result.push(chunk)
offset = offset + 20
}
return result
} catch (err) {
console.error('Error in chunk20()')
throw err
}
}
/**
* @api util.sleep() sleep()
* @apiName sleep
* @apiGroup Util
* @apiDescription Promise-based delay.
* Expects an integer as input, which represents milliseconds. This function
* will return a Promise that resolves that many milliseconds later.
*
*
* @apiExample Example usage:
* (async () => {
* try {
* const tenSeconds = 10000
* await bchjs.Util.sleep(tenSeconds)
* } catch(error) {
* console.error(error)
* }
* })()
*
*/
sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
/**
* @api util.validateAddress() validateAddress()
* @apiName Validate Address.
+290 -51
View File
@@ -22,16 +22,30 @@ class UTXO {
* @apiGroup UTXO
* @apiDescription Get UTXOs for an address
*
* Given an address, this function will return an object with three arrays:
* - bchUtxos - UTXOs confirmed to be spendable as normal BCH
* - slpUtxos - UTXOs confirmed to be colored as valid SLP tokens
* - nullUtxo - UTXOs that did not pass SLP validation. Should be ignored and
* 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:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9');
* let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
* console.log(utxos);
* } catch(error) {
* console.error(error)
@@ -39,71 +53,296 @@ class UTXO {
* })()
*
* // returns
* {
* "bchUtxos": [
* {
* "height": 674331,
* "tx_hash": "5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31",
* "tx_pos": 0,
* "value": 1000,
* "txid": "5e86cd911110e4f5db0cc3e8f459d5e8850b49adf57059a71daee674b2867b31",
* "vout": 0,
* "isValid": false
* [
* {
* "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
* }
* ]
* }
* }
* ],
* "slpUtxos": [
* {
* "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
* }
* ],
* "nullUtxos": []
* }
* }
* ]
*
*
*/
async get (address) {
async get (address, useWhitelist = false) {
try {
const addr = this.slp.Address.toCashAddress(address)
// 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])
const utxoData = await this.electrumx.utxo(addr)
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
// Hydate the utxos with token information.
const hydratedUtxos = await this.slp.Utils.hydrateUtxos(utxoData.utxos)
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 retObj = {} // return object
const retAry = [] // Return array
// Filter out the different types of UTXOs.
retObj.bchUtxos = hydratedUtxos.slpUtxos[0].utxos.filter(elem => elem.isValid === false)
retObj.slpUtxos = hydratedUtxos.slpUtxos[0].utxos.filter(elem => elem.isValid === true)
retObj.nullUtxos = hydratedUtxos.slpUtxos[0].utxos.filter(elem => elem.isValid === null)
// Note: true, false, and null should be only values. An element with
// isValid set to any other value should be ignored.
// Loop through each address.
for (let i = 0; i < hydratedUtxos.slpUtxos.length; i++) {
const thisAddr = hydratedUtxos.slpUtxos[i]
return retObj
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
@@ -22,9 +22,13 @@ describe('#UTXO', () => {
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result.bchUtxos)
assert.isArray(result.slpUtxos)
assert.isArray(result.nullUtxos)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
})
})
@@ -22,9 +22,100 @@ describe('#UTXO', () => {
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result.bchUtxos)
assert.isArray(result.slpUtxos)
assert.isArray(result.nullUtxos)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
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)
})
})
})
+218 -1
View File
@@ -66,7 +66,224 @@ const mockHydratedUtxos = {
]
}
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
mockHydratedUtxos,
mockTwoHydratedAddrs,
mockEveryUtxoType,
electrumxUtxos
}
+18 -18
View File
@@ -273,8 +273,8 @@ describe('#SLP Utils', () => {
try {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.nonSLPTxDetailsWithoutOpReturn })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.nonSLPTxDetailsWithoutOpReturn] })
const txid =
'3793d4906654f648e659f384c0f40b19c8f10c1e9fb72232a9b8edd61abaa1ec'
@@ -292,8 +292,8 @@ describe('#SLP Utils', () => {
try {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.nonSLPTxDetailsWithOpReturn })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.nonSLPTxDetailsWithOpReturn] })
const txid =
'2ff74c48a5d657cf45f699601990bffbbe7a2a516d5480674cbf6c6a4497908f'
@@ -310,8 +310,8 @@ describe('#SLP Utils', () => {
it('should decode a genesis transaction', async () => {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPGenesis })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPGenesis] })
const txid =
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90'
@@ -336,8 +336,8 @@ describe('#SLP Utils', () => {
it('should decode a mint transaction', async () => {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPMint })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPMint] })
const txid =
'65f21bbfcd545e5eb515e38e861a9dfe2378aaa2c4e458eb9e59e4d40e38f3a4'
@@ -357,8 +357,8 @@ describe('#SLP Utils', () => {
it('should decode a send transaction', async () => {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPSend })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPSend] })
const txid =
'4f922565af664b6fdf0a1ba3924487344be721b3d8815c62cafc8a51e04a8afa'
@@ -372,8 +372,8 @@ describe('#SLP Utils', () => {
it('should properly decode a Genesis transaction with no minting baton', async () => {
// Mock the call to the REST API.
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPGenesisNoBaton })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPGenesisNoBaton] })
const txid =
'497291b8a1dfe69c8daea50677a3d31a5ef0e9484d8bebb610dac64bbc202fb7'
@@ -387,8 +387,8 @@ describe('#SLP Utils', () => {
it('should decode a send transaction with alternate encoding', async () => {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPSendAlt })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPSendAlt] })
const txid =
'd94357179775425ebc59c93173bd6dc9854095f090a2eb9dcfe9797398bc8eae'
@@ -411,8 +411,8 @@ describe('#SLP Utils', () => {
try {
// Mock the call to the REST API
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.mockInvalidSlpSend })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.mockInvalidSlpSend] })
const txid =
'a60a522cc11ad7011b74e57fbabbd99296e4b9346bcb175dcf84efb737030415'
@@ -428,8 +428,8 @@ describe('#SLP Utils', () => {
it('should decode a NFT Parent transaction', async () => {
// Mock the call to the REST API.
sandbox
.stub(uut.Utils.axios, 'get')
.resolves({ data: mockData.txDetailsSLPNftGenesis })
.stub(uut.Utils.axios, 'post')
.resolves({ data: [mockData.txDetailsSLPNftGenesis] })
const txid =
'4ef6eb92950a13a69e97c2c02c7967d806aa874c0e2a6b5546a8880f2cd14bc4'
+140
View File
@@ -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')
}
})
})
})
+147 -5
View File
@@ -19,22 +19,30 @@ describe('#utxo', () => {
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)
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.bchUtxos)
assert.isArray(result.slpUtxos)
assert.isArray(result.nullUtxos)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').rejects(new Error('test error'))
sandbox
.stub(bchjs.Utxo.electrumx, 'utxo')
.rejects(new Error('test error'))
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
@@ -45,5 +53,139 @@ describe('#utxo', () => {
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)
})
})
})