mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 00:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84b44b299a | ||
|
|
b6456df1f4 | ||
|
|
b1826c04cb | ||
|
|
71755964c8 | ||
|
|
bd1c3b622d | ||
|
|
bdced13230 | ||
|
|
c925bfce46 | ||
|
|
056a47f6b2 | ||
|
|
51038681d3 | ||
|
|
1a2f144baf | ||
|
|
6d70568082 | ||
|
|
0b1cf57852 | ||
|
|
48f7f75c9c | ||
|
|
0bc96fded5 | ||
|
|
9869211572 | ||
|
|
1056c3724c | ||
|
|
6b838ef9cd | ||
|
|
f09573554d |
+1
-1
@@ -18,7 +18,7 @@
|
||||
"test:integration:local:abc": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.2.139:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.2.129:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:decatur:abc": "export RESTURL=http://192.168.2.141:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
|
||||
"test:integration:temp:bchn": "export RESTURL=http://157.90.174.219:3000/v5/ && mocha --timeout 30000 test/integration/",
|
||||
"test:temp": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 -g '#Encryption' test/integration/",
|
||||
|
||||
@@ -36,7 +36,6 @@ const DSProof = require('./dsproof')
|
||||
const Ecash = require('./ecash')
|
||||
|
||||
// Indexers
|
||||
const Ninsight = require('./ninsight')
|
||||
const Electrumx = require('./electrumx')
|
||||
const PsfSlpIndexer = require('./psf-slp-indexer')
|
||||
|
||||
@@ -83,9 +82,6 @@ class BCHJS {
|
||||
|
||||
// console.log(`apiToken: ${this.apiToken}`)
|
||||
|
||||
// Bitcoin.com Ninsight indexer
|
||||
this.Ninsight = new Ninsight(config)
|
||||
|
||||
// ElectrumX indexer
|
||||
this.Electrumx = new Electrumx(libConfig)
|
||||
|
||||
|
||||
+15
-2
@@ -638,13 +638,24 @@ class ElectrumX {
|
||||
// Sort confirmed Transactions by the block height
|
||||
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
|
||||
try {
|
||||
// console.log(`sortConfTxs txs: ${JSON.stringify(txs, null, 2)}`)
|
||||
|
||||
// Filter out unconfirmed transactions, with a height of 0 or less.
|
||||
txs = txs.filter(elem => elem.height > 0)
|
||||
|
||||
if (sortingOrder === 'DESCENDING') {
|
||||
return txs.sort((a, b) => b.height - a.height)
|
||||
// console.log('Sorting in descending order')
|
||||
return txs.sort((a, b) => {
|
||||
// console.log(`descending b.height: ${b.height}, a.height: ${a.height}`)
|
||||
return b.height - a.height
|
||||
})
|
||||
}
|
||||
return txs.sort((a, b) => a.height - b.height)
|
||||
|
||||
// console.log('Sorting in ascending order')
|
||||
return txs.sort((a, b) => {
|
||||
// console.log(`ascending b.height: ${b.height}, a.height: ${a.height}`)
|
||||
return a.height - b.height
|
||||
})
|
||||
} catch (err) {
|
||||
console.log('Error in util.js/sortConfTxs()')
|
||||
throw err
|
||||
@@ -685,6 +696,8 @@ class ElectrumX {
|
||||
// Substitute zero-conf txs with the current block-height + 1
|
||||
async sortAllTxs (txs, sortingOrder = 'DESCENDING') {
|
||||
try {
|
||||
// console.log(`sortingOrder: ${sortingOrder}`)
|
||||
|
||||
// Calculate the height of the next block
|
||||
const nextBlock = (await this.blockchain.getBlockCount()) + 1
|
||||
|
||||
|
||||
-319
@@ -1,319 +0,0 @@
|
||||
/*
|
||||
This library interacts with the ninsight REST API endpoints operated by
|
||||
Bitcoin.com
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
// let _this
|
||||
|
||||
class Ninsight {
|
||||
constructor (config) {
|
||||
// this.restURL = config.restURL
|
||||
// this.apiToken = config.apiToken
|
||||
|
||||
// this.ninsightURL = `https://bch-explorer.api.bitcoin.com/v1/`
|
||||
if (config) {
|
||||
this.ninsightURL = config.ninsightURL
|
||||
? config.ninsightURL
|
||||
: 'https://rest.bitcoin.com/v2'
|
||||
}
|
||||
|
||||
// Add JWT token to the authorization header.
|
||||
this.axiosOptions = {
|
||||
headers: {
|
||||
authorization: `Token ${this.apiToken}`,
|
||||
timeout: 15000
|
||||
}
|
||||
}
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Ninsight.utxo() utxo()
|
||||
* @apiName Ninsight Utxo
|
||||
* @apiGroup Ninsight
|
||||
* @apiDescription Return list of uxto for address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxo = await bchjs.Ninsight.utxo('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
|
||||
* console.log(utxo);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
|
||||
* // "vout": 1,
|
||||
* // "amount": 0.03608203,
|
||||
* // "satoshis": 3608203,
|
||||
* // "height": 585570,
|
||||
* // "confirmations": 10392
|
||||
* // },
|
||||
* // {
|
||||
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
|
||||
* // "vout": 0,
|
||||
* // "amount": 0.03608203,
|
||||
* // "satoshis": 3608203,
|
||||
* // "height": 577125,
|
||||
* // "confirmations": 18837
|
||||
* // }
|
||||
* // ]
|
||||
*
|
||||
*/
|
||||
async utxo (address) {
|
||||
try {
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/utxo`,
|
||||
{
|
||||
addresses: [address]
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
return response.data
|
||||
|
||||
// Handle array of addresses.
|
||||
} else if (Array.isArray(address)) {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/utxo`,
|
||||
{
|
||||
addresses: address
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input address must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Ninsight.unconfirmed() unconfirmed()
|
||||
* @apiName Ninsight Unconfirmed Utxo
|
||||
* @apiGroup Ninsight
|
||||
* @apiDescription Returns a list of unconfirmed UTXOs for an address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxo = await bchjs.Ninsight.unconfirmed('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
|
||||
* console.log(utxo);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "txid": "d31dc2cf66fe4d3d3ae18e1065def58a64920746b1702b52f060e5edeea9883b",
|
||||
* // "vout": 1,
|
||||
* // "amount": 0.03608203,
|
||||
* // "satoshis": 3608203,
|
||||
* // "confirmations": 0
|
||||
* // "ts": 1559670801
|
||||
* // },
|
||||
* // {
|
||||
* // "txid": "41e9a118765ecf7a1ba4487c0863e23dba343cc5880381a72f0365ac2546c5fa",
|
||||
* // "vout": 0,
|
||||
* // "amount": 0.03608203,
|
||||
* // "satoshis": 3608203,
|
||||
* // "confirmations": 0
|
||||
* // "ts": 1559670902
|
||||
* // }
|
||||
* // ]
|
||||
*
|
||||
*/
|
||||
async unconfirmed (address) {
|
||||
try {
|
||||
if (typeof address === 'string') {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/unconfirmed`,
|
||||
{
|
||||
addresses: [address]
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} else if (Array.isArray(address)) {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/unconfirmed`,
|
||||
{
|
||||
addresses: address
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input address must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Ninsight.transactions() transactions()
|
||||
* @apiName Ninsight TX History
|
||||
* @apiGroup Ninsight
|
||||
* @apiDescription Return transactions history for address.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let txs = await bchjs.Ninsight.transactions('bitcoincash:qzs02v05l7qs5s24srqju498qu55dwuj0cx5ehjm2c');
|
||||
* console.log(txs);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "pagesTotal": 1,
|
||||
* // "txs": [
|
||||
* // {
|
||||
* // "txid": "ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b",
|
||||
* // "version": 2,
|
||||
* // "locktime": 0,
|
||||
* // "vin": [
|
||||
* // {
|
||||
* // "txid": "4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0",
|
||||
* // "vout": 0,
|
||||
* // "sequence": 4294967295,
|
||||
* // "n": 0,
|
||||
* // ...
|
||||
* // }
|
||||
* // ...
|
||||
* // ]
|
||||
* // ...
|
||||
* // }
|
||||
* // ],
|
||||
* // "legacyAddress": "1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP",
|
||||
* // "cashAddress": "bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n",
|
||||
* // "currentPage": 0
|
||||
* // }
|
||||
* // ]
|
||||
* //
|
||||
*
|
||||
*/
|
||||
async transactions (address) {
|
||||
try {
|
||||
if (typeof address === 'string') {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/transactions`,
|
||||
{
|
||||
addresses: [address]
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} else if (Array.isArray(address)) {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/address/transactions`,
|
||||
{
|
||||
addresses: address
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Input address must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Ninsight.txDetails() txDetails()
|
||||
* @apiName Ninsight TX Details
|
||||
* @apiGroup Ninsight
|
||||
* @apiDescription Return transactions details for address(es).
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let result = await bchjs.Ninsight.txDetails('fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33');
|
||||
* console.log(result);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
* }
|
||||
* })()
|
||||
*
|
||||
* // [
|
||||
* // {
|
||||
* // "txid": "fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33",
|
||||
* // "version": 1,
|
||||
* // "locktime": 0,
|
||||
* // "vin": [
|
||||
* // {
|
||||
* // "sequence": 4294967295,
|
||||
* // "n": 0
|
||||
* // }
|
||||
* // ],
|
||||
* // "vout": [
|
||||
* // ...
|
||||
* // ],
|
||||
* // "blockhash": "00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09",
|
||||
* // "blockheight": 1000,
|
||||
* // "confirmations": 659909,
|
||||
* // "time": 1232346882,
|
||||
* // "blocktime": 1232346882,
|
||||
* // "firstSeenTime": null,
|
||||
* // "valueOut": 50,
|
||||
* // "size": 135
|
||||
* // }
|
||||
* // ]
|
||||
*
|
||||
*/
|
||||
async txDetails (txid) {
|
||||
try {
|
||||
if (typeof txid === 'string') {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/transaction/details`,
|
||||
{
|
||||
txids: [txid]
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
} else if (Array.isArray(txid)) {
|
||||
const response = await axios.post(
|
||||
`${this.ninsightURL}/transaction/details`,
|
||||
{
|
||||
txids: txid
|
||||
},
|
||||
this.axiosOptions
|
||||
)
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
throw new Error('Transaction ID must be a string or array of strings.')
|
||||
} catch (error) {
|
||||
if (error.response && error.response.data) throw error.response.data
|
||||
else throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Ninsight
|
||||
+46
-7
@@ -11,11 +11,12 @@ const axios = require('axios')
|
||||
|
||||
// Local libraries
|
||||
const RawTransaction = require('./raw-transactions')
|
||||
const SlpUtils = require('./slp/utils')
|
||||
|
||||
// let _this
|
||||
|
||||
class PsfSlpIndexer {
|
||||
constructor (config) {
|
||||
constructor (config = {}) {
|
||||
this.restURL = config.restURL
|
||||
this.apiToken = config.apiToken
|
||||
this.authToken = config.authToken
|
||||
@@ -38,6 +39,7 @@ class PsfSlpIndexer {
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.rawTransaction = new RawTransaction(config)
|
||||
this.slpUtils = new SlpUtils(config)
|
||||
|
||||
// _this = this
|
||||
}
|
||||
@@ -130,6 +132,8 @@ class PsfSlpIndexer {
|
||||
*/
|
||||
async balance (address) {
|
||||
try {
|
||||
// console.log('balance() address: ', address)
|
||||
|
||||
// Handle single address.
|
||||
if (typeof address === 'string') {
|
||||
const response = await axios.post(
|
||||
@@ -308,16 +312,18 @@ class PsfSlpIndexer {
|
||||
// 'TX not found in psf-slp-indexer. Retrieving from full node.'
|
||||
// )
|
||||
|
||||
// Check if this txid belongs to a blacklisted token.
|
||||
const isInBlacklist = await this.checkBlacklist(txid)
|
||||
|
||||
// Get the TX Details from the full node.
|
||||
const txDetails = await this.rawTransaction.getTxData(txid)
|
||||
// console.log(`txDetails: ${JSON.stringify(txDetails, null, 2)}`)
|
||||
|
||||
// TODO: Run the TX through decodeOpReturn() to see if it has a tokenID
|
||||
// that is in the blacklist. If it is, then set isValidSlp = null to
|
||||
// signal that it's state can not be determined.
|
||||
|
||||
// Assumption: transaction is not a valid SLP UTXO.
|
||||
txDetails.isValidSlp = false
|
||||
if (isInBlacklist) {
|
||||
txDetails.isValidSlp = null
|
||||
} else {
|
||||
txDetails.isValidSlp = false
|
||||
}
|
||||
|
||||
const outObj = {
|
||||
txData: txDetails
|
||||
@@ -327,6 +333,39 @@ class PsfSlpIndexer {
|
||||
} else throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the txid has an OP_RETURN containing a tokenID that is in the
|
||||
// blacklist. In that case, the isValidSlp property should be marked as
|
||||
// null, and not false.
|
||||
async checkBlacklist (txid) {
|
||||
try {
|
||||
// TODO: Add endpoint to psf-slp-indexer to retrieve current blacklist.
|
||||
// This should be done once at startup, and not each time this function
|
||||
// is called.
|
||||
const blacklist = [
|
||||
'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9'
|
||||
]
|
||||
|
||||
const outTokenData = await this.slpUtils.decodeOpReturn(txid)
|
||||
// console.log('outTokenData: ', outTokenData)
|
||||
|
||||
// Loop through each token in the blacklist.
|
||||
for (let i = 0; i < blacklist.length; i++) {
|
||||
// If a match is found, return true.
|
||||
if (outTokenData.tokenId === blacklist[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// By default, return false.
|
||||
return false
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
// Exit quietly.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PsfSlpIndexer
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ const Util = require('../util')
|
||||
let _this
|
||||
|
||||
class Utils {
|
||||
constructor (config) {
|
||||
constructor (config = {}) {
|
||||
this.restURL = config.restURL
|
||||
this.apiToken = config.apiToken
|
||||
this.slpParser = slpParser
|
||||
|
||||
+50
@@ -150,6 +150,56 @@ class Util {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Util.chunk100() chunk100()
|
||||
* @apiName chunk100
|
||||
* @apiGroup Util
|
||||
* @apiDescription chunk up an array into multiple arrays of 100 elements each.
|
||||
* Input: arrayToSlice - a one-dimensional array of elements.
|
||||
* Returns a two-dimensional array. An array of 100-element arrays.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,...,148, 149, 150]
|
||||
*
|
||||
* 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,...,98,99],
|
||||
* [100,101,102,...,148,149,150]
|
||||
* ]
|
||||
*/
|
||||
chunk100 (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 100 elements.
|
||||
while (offset < arrayToSlice.length) {
|
||||
const chunk = arrayToSlice.slice(offset, offset + 100)
|
||||
result.push(chunk)
|
||||
offset = offset + 100
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Error in chunk100()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Util.sleep() sleep()
|
||||
* @apiName sleep
|
||||
|
||||
+229
-6
@@ -8,19 +8,23 @@
|
||||
// Local libraries
|
||||
const Electrumx = require('./electrumx')
|
||||
const Slp = require('./slp/slp')
|
||||
const PsfSlpIndexer = require('./psf-slp-indexer')
|
||||
const BigNumber = require('bignumber.js')
|
||||
|
||||
class UTXO {
|
||||
constructor (config) {
|
||||
constructor (config = {}) {
|
||||
// Encapsulate dependencies for easier mocking.
|
||||
this.electrumx = new Electrumx(config)
|
||||
this.slp = new Slp(config)
|
||||
this.psfSlpIndexer = new PsfSlpIndexer(config)
|
||||
this.BigNumber = BigNumber
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.get() get()
|
||||
* @apiName get
|
||||
* @api Utxo.getOld() getOld()
|
||||
* @apiName getOld
|
||||
* @apiGroup UTXO
|
||||
* @apiDescription Get UTXOs for an address
|
||||
* @apiDescription Get UTXOs for an address (from SLPDB)
|
||||
*
|
||||
* Given an address, this function will return an object with thre following
|
||||
* properties:
|
||||
@@ -45,7 +49,7 @@ class UTXO {
|
||||
* @apiExample Example usage:
|
||||
* (async () => {
|
||||
* try {
|
||||
* let utxos = await bchjs.Utxo.get('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
|
||||
* let utxos = await bchjs.Utxo.getOld('simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj');
|
||||
* console.log(utxos);
|
||||
* } catch(error) {
|
||||
* console.error(error)
|
||||
@@ -181,7 +185,7 @@ class UTXO {
|
||||
*
|
||||
*
|
||||
*/
|
||||
async get (address, useWhitelist = false) {
|
||||
async getOld (address, useWhitelist = false) {
|
||||
try {
|
||||
if (!address) {
|
||||
throw new Error('Address must be an array or a string')
|
||||
@@ -286,6 +290,225 @@ class UTXO {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.get() get()
|
||||
* @apiName get
|
||||
* @apiGroup UTXO
|
||||
* @apiDescription Get UTXOs for an address (from psf-slp-indexer)
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
* @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
|
||||
* }
|
||||
* ],
|
||||
*/
|
||||
// This version of get() uses the psf-slp-indexer. It will replace the older
|
||||
// get() function that uses SLPDB.
|
||||
// TODO: NFT UTXOs are identified as non-token UTXOs, which will cause a wallet
|
||||
// to burn them. The psf-slp-indexer needs to be updated to mark these UTXOs.
|
||||
async get (address) {
|
||||
try {
|
||||
// Convert address to an array if it is a string.
|
||||
if (typeof address !== 'string') {
|
||||
throw new Error('address input must be a string')
|
||||
}
|
||||
|
||||
// Ensure the address is a BCH address.
|
||||
const addr = this.slp.Address.toCashAddress(address)
|
||||
|
||||
// Get the UTXOs associated with the address.
|
||||
const utxoData = await this.electrumx.utxo(addr)
|
||||
// console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`)
|
||||
const utxos = utxoData.utxos
|
||||
|
||||
let slpUtxos = []
|
||||
|
||||
// Get SLP UTXOs from the psf-slp-indexer
|
||||
try {
|
||||
const slpUtxoData = await this.psfSlpIndexer.balance(addr)
|
||||
// console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`)
|
||||
|
||||
slpUtxos = slpUtxoData.balance.utxos
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
|
||||
// Exit quietly if address has no SLP UTXOs. Otherwise, throw the error.
|
||||
if (err.error && !err.error.includes('Key not found in database')) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through the Fulcrum UTXOs.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const thisUtxo = utxos[i]
|
||||
|
||||
// Loop through the UTXOs from psf-slp-indexer.
|
||||
for (let j = 0; j < slpUtxos.length; j++) {
|
||||
const thisSlpUtxo = slpUtxos[j]
|
||||
|
||||
// If the non-hydrated UTXO matches the SLP UTXO, then combine the data
|
||||
// and mark the UTXO as an SLP token.
|
||||
if (
|
||||
thisUtxo.tx_hash === thisSlpUtxo.txid &&
|
||||
thisUtxo.tx_pos === thisSlpUtxo.vout
|
||||
) {
|
||||
thisUtxo.txid = thisUtxo.tx_hash
|
||||
thisUtxo.vout = thisUtxo.tx_pos
|
||||
thisUtxo.isSlp = true
|
||||
thisUtxo.type = thisSlpUtxo.type
|
||||
thisUtxo.qty = thisSlpUtxo.qty
|
||||
thisUtxo.tokenId = thisSlpUtxo.tokenId
|
||||
thisUtxo.address = thisSlpUtxo.address
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If there was no match, then this is a normal BCH UTXO. Mark it as such.
|
||||
if (!thisUtxo.isSlp) {
|
||||
thisUtxo.txid = thisUtxo.tx_hash
|
||||
thisUtxo.vout = thisUtxo.tx_pos
|
||||
thisUtxo.address = addr
|
||||
|
||||
// Check the transaction to see if its a 'null' token, ignored by
|
||||
// the indexer.
|
||||
const txData = await this.psfSlpIndexer.tx(thisUtxo.tx_hash)
|
||||
// console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
|
||||
if (txData.txData.isValidSlp === null) {
|
||||
thisUtxo.isSlp = null
|
||||
} else {
|
||||
thisUtxo.isSlp = false
|
||||
}
|
||||
// console.log(`thisUtxo.isSlp: ${thisUtxo.isSlp}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Get token UTXOs
|
||||
let type1TokenUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'token'
|
||||
)
|
||||
|
||||
// Hydrate the UTXOs with additional token data.
|
||||
type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
|
||||
|
||||
const bchUtxos = utxos.filter(x => x.isSlp === false)
|
||||
const type1BatonUtxos = utxos.filter(
|
||||
x => x.isSlp === true && x.type === 'baton'
|
||||
)
|
||||
const nullUtxos = utxos.filter(x => x.isSlp === null)
|
||||
|
||||
const outObj = {
|
||||
address: addr,
|
||||
bchUtxos,
|
||||
slpUtxos: {
|
||||
type1: {
|
||||
tokens: type1TokenUtxos,
|
||||
mintBatons: type1BatonUtxos
|
||||
},
|
||||
nft: {} // Allocated for future support of NFT spec.
|
||||
},
|
||||
nullUtxos
|
||||
}
|
||||
|
||||
return outObj
|
||||
} catch (err) {
|
||||
console.error('Error in bchjs.Utxo.get(): ', err)
|
||||
|
||||
if (err.error) throw new Error(err.error)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Hydrate an array of token UTXOs with token information.
|
||||
// Returns an array of token UTXOs with additional data.
|
||||
async hydrateTokenData (utxoAry) {
|
||||
try {
|
||||
// console.log('utxoAry: ', utxoAry)
|
||||
|
||||
// Create a list of token IDs without duplicates.
|
||||
let tokenIds = utxoAry.map(x => x.tokenId)
|
||||
|
||||
// Remove duplicates. https://stackoverflow.com/questions/9229645/remove-duplicate-values-from-js-array
|
||||
tokenIds = [...new Set(tokenIds)]
|
||||
// console.log('tokenIds: ', tokenIds)
|
||||
|
||||
// Get Genesis data for each tokenId
|
||||
const genesisData = []
|
||||
for (let i = 0; i < tokenIds.length; i++) {
|
||||
const thisTokenId = tokenIds[i]
|
||||
const thisTokenData = await this.psfSlpIndexer.tokenStats(thisTokenId)
|
||||
// console.log('thisTokenData: ', thisTokenData)
|
||||
|
||||
genesisData.push(thisTokenData)
|
||||
}
|
||||
// console.log('genesisData: ', genesisData)
|
||||
|
||||
// Hydrate each token UTXO with data from the genesis transaction.
|
||||
for (let i = 0; i < utxoAry.length; i++) {
|
||||
const thisUtxo = utxoAry[i]
|
||||
|
||||
// Get the genesis data for this token.
|
||||
const genData = genesisData.filter(
|
||||
x => x.tokenData.tokenId === thisUtxo.tokenId
|
||||
)
|
||||
// console.log('genData: ', genData)
|
||||
|
||||
thisUtxo.ticker = genData[0].tokenData.ticker
|
||||
thisUtxo.name = genData[0].tokenData.name
|
||||
thisUtxo.documentUri = genData[0].tokenData.documentUri
|
||||
thisUtxo.documentHash = genData[0].tokenData.documentHash
|
||||
thisUtxo.decimals = genData[0].tokenData.decimals
|
||||
|
||||
// Calculate the real token quantity
|
||||
const qty = new BigNumber(thisUtxo.qty).dividedBy(
|
||||
10 ** parseInt(thisUtxo.decimals)
|
||||
)
|
||||
thisUtxo.qtyStr = qty.toString()
|
||||
}
|
||||
|
||||
return utxoAry
|
||||
} catch (err) {
|
||||
console.log('Error in hydrateTokenData()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
|
||||
* @apiName findBiggestUtxo
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
Integration tests for the utxo.js library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
// const assert = require('chai').assert
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
// const BCHJS = require('../../../../src/bch-js')
|
||||
// const bchjs = new BCHJS()
|
||||
|
||||
describe('#UTXO', () => {
|
||||
beforeEach(async () => {
|
||||
@@ -14,6 +14,7 @@ describe('#UTXO', () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
|
||||
})
|
||||
|
||||
/*
|
||||
describe('#get', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
@@ -31,6 +32,7 @@ describe('#UTXO', () => {
|
||||
assert.isArray(result[0].nullUtxos)
|
||||
})
|
||||
})
|
||||
*/
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -56,7 +56,7 @@ describe('#psf-slp-indexer', () => {
|
||||
describe('#tx', () => {
|
||||
it('should get hydrated tx data for an SLP transaction', async () => {
|
||||
const txid =
|
||||
'83361c34cac2ea7f9ca287fca57a96cc0763719f0cdf4850f9696c1e68eb635c'
|
||||
'947ccb2a0d62ca287bc4b0993874ab0f9f6afd454193e631e2bf84dca66731fc'
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.tx(txid)
|
||||
// console.log('result: ', result)
|
||||
@@ -67,7 +67,7 @@ describe('#psf-slp-indexer', () => {
|
||||
assert.equal(result.txData.isValidSlp, true)
|
||||
})
|
||||
|
||||
it('should handle non-SLP transaction', async () => {
|
||||
it('should mark non-SLP transaction as false', async () => {
|
||||
const txid =
|
||||
'03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('#psf-slp-indexer', () => {
|
||||
// Currently FlexUSD UTXOs are reported as invalid SLP UTXOs, which means
|
||||
// the wallet will burn them. There is a TODO in the code. This test will
|
||||
// need to be changed when it is done.
|
||||
it('should handle a blacklisted token', async () => {
|
||||
it('should mark blacklisted token as null', async () => {
|
||||
const txid =
|
||||
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
|
||||
|
||||
@@ -94,7 +94,23 @@ describe('#psf-slp-indexer', () => {
|
||||
assert.property(result.txData, 'vin')
|
||||
assert.property(result.txData, 'vout')
|
||||
assert.property(result.txData, 'isValidSlp')
|
||||
assert.equal(result.txData.isValidSlp, false)
|
||||
assert.equal(result.txData.isValidSlp, null)
|
||||
})
|
||||
|
||||
it('should throw error for non-existent txid', async () => {
|
||||
try {
|
||||
const txid =
|
||||
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85e'
|
||||
|
||||
await bchjs.PsfSlpIndexer.tx(txid)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
|
||||
assert.include(err.message, 'No such mempool or blockchain transaction')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Integration tests for the transaction.js library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
describe('#Transaction', () => {
|
||||
beforeEach(async () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
it('should get a tx details for a non-SLP TX with an OP_RETURN', async () => {
|
||||
const txid =
|
||||
'01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
|
||||
const result = await bchjs.Transaction.get(txid)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result.txData, 'txid')
|
||||
assert.property(result.txData, 'vin')
|
||||
assert.property(result.txData, 'vout')
|
||||
assert.equal(result.txData.isValidSlp, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -5,7 +5,8 @@
|
||||
const assert = require('chai').assert
|
||||
|
||||
const BCHJS = require('../../../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
// const bchjs = new BCHJS()
|
||||
const bchjs = new BCHJS({ restURL: 'http://192.168.2.129:3000/v5/' })
|
||||
|
||||
describe('#UTXO', () => {
|
||||
beforeEach(async () => {
|
||||
@@ -14,108 +15,218 @@ describe('#UTXO', () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
|
||||
})
|
||||
|
||||
if (process.env.TESTSLP) {
|
||||
describe('#getOld', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
const result = await bchjs.Utxo.getOld(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.getOld(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.getOld(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.getOld(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.getOld(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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('#hydrateTokenData', () => {
|
||||
it('should hydrate token UTXOs', async () => {
|
||||
const utxos = [
|
||||
{
|
||||
txid:
|
||||
'384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '10000000',
|
||||
tokenId:
|
||||
'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '100000000',
|
||||
tokenId:
|
||||
'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
|
||||
vout: 5,
|
||||
type: 'token',
|
||||
qty: '4764',
|
||||
tokenId:
|
||||
'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
|
||||
vout: 5,
|
||||
type: 'token',
|
||||
qty: '238',
|
||||
tokenId:
|
||||
'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
}
|
||||
]
|
||||
|
||||
const result = await bchjs.Utxo.hydrateTokenData(utxos)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result[0], 'ticker')
|
||||
assert.property(result[0], 'name')
|
||||
assert.property(result[0], 'qtyStr')
|
||||
assert.property(result[0], 'documentUri')
|
||||
assert.property(result[0], 'documentHash')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
it('should hydrate address with BCH and SLP UTXOs', async () => {
|
||||
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)
|
||||
// Assert expected properties exist.
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'bchUtxos')
|
||||
assert.property(result, 'slpUtxos')
|
||||
assert.property(result.slpUtxos, 'type1')
|
||||
assert.property(result.slpUtxos, 'nft')
|
||||
assert.property(result, 'nullUtxos')
|
||||
|
||||
assert.isAbove(result.bchUtxos.length, 0)
|
||||
assert.isAbove(result.slpUtxos.type1.tokens.length, 0)
|
||||
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
|
||||
})
|
||||
|
||||
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 () => {
|
||||
// TODO: NFTs are currently not identified as different than normal BCH UTXOs.
|
||||
// The psf-slp-indexer needs to be updated to fix this issue.
|
||||
it('should handle 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)
|
||||
// Assert that minting batons are correctly identified.
|
||||
assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0)
|
||||
})
|
||||
|
||||
it('should use the whitelist when flag is set', async () => {
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
const useWhitelist = true
|
||||
it('should return UTXOs for address with no SLP tokens', async () => {
|
||||
const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr, useWhitelist)
|
||||
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)
|
||||
|
||||
// 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)
|
||||
assert.isAbove(result.bchUtxos.length, 0)
|
||||
assert.equal(result.slpUtxos.type1.tokens.length, 0)
|
||||
})
|
||||
|
||||
it('should sort UTXOs from Utxos.get()', async () => {
|
||||
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
|
||||
it('should handle Group NFTs', async () => {
|
||||
const addr = 'bitcoincash:qrnghwrfgccf3s5e9wnglzxegcnhje9rkcwv2eka33'
|
||||
|
||||
const utxos = await bchjs.Utxo.get(addr)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'satoshis')
|
||||
assert.equal(result.satoshis, 800)
|
||||
assert.isAbove(result.nullUtxos.length, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,72 +11,74 @@ describe('#Transaction', () => {
|
||||
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
|
||||
})
|
||||
|
||||
describe('#getOld', () => {
|
||||
it('should get details about a non-SLP transaction', async () => {
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
if (process.env.TESTSLP) {
|
||||
describe('#getOld', () => {
|
||||
it('should get details about a non-SLP transaction', async () => {
|
||||
const txid =
|
||||
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
|
||||
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[0].scriptPubKey, 'addresses')
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[0].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, false)
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, false)
|
||||
})
|
||||
|
||||
it('should get details about a SLP transaction', async () => {
|
||||
const txid =
|
||||
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
|
||||
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[1].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vout[0], 'tokenQty')
|
||||
assert.equal(result.vout[0].tokenQty, null)
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result.vin[0], 'tokenQty')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, true)
|
||||
})
|
||||
|
||||
// it('should get problematic transaction', async () => {
|
||||
// const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
|
||||
//
|
||||
// const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// })
|
||||
|
||||
// TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
|
||||
// an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
|
||||
// that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
|
||||
// but for separate tokens.
|
||||
it('should get problematic transaction', async () => {
|
||||
const txid =
|
||||
'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
|
||||
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// The token ID should equal the txid for this Vin.
|
||||
assert.equal(result.vin[2].txid, result.vin[2].tokenId)
|
||||
})
|
||||
})
|
||||
|
||||
it('should get details about a SLP transaction', async () => {
|
||||
const txid =
|
||||
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
|
||||
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert that there are stanardized properties.
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vin')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result.vout[0], 'value')
|
||||
assert.property(result.vout[1].scriptPubKey, 'addresses')
|
||||
|
||||
// Assert that added properties exist.
|
||||
assert.property(result.vout[0], 'tokenQty')
|
||||
assert.equal(result.vout[0].tokenQty, null)
|
||||
assert.property(result.vin[0], 'address')
|
||||
assert.property(result.vin[0], 'value')
|
||||
assert.property(result.vin[0], 'tokenQty')
|
||||
assert.property(result, 'isValidSLPTx')
|
||||
assert.equal(result.isValidSLPTx, true)
|
||||
})
|
||||
|
||||
// it('should get problematic transaction', async () => {
|
||||
// const txid = 'a55515de32577e296c512840bcaabed5823bb773fb4f8fd8e5197cc96cbc54d1'
|
||||
//
|
||||
// const result = await bchjs.Transaction.get(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
// })
|
||||
|
||||
// TX a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe has
|
||||
// an input TXID (bd84bc1dd5ecd976165892306992401272f6bedeb37d7b2cdbf74fc4a55967a6)
|
||||
// that is also a valid SLP tx, but is unrelated. Both TXs pass DAG validation,
|
||||
// but for separate tokens.
|
||||
it('should get problematic transaction', async () => {
|
||||
const txid =
|
||||
'a19f2f395a8b0e15b6202944c56834367d128f1e3630486a4756de53424a46fe'
|
||||
|
||||
const result = await bchjs.Transaction.getOld(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// The token ID should equal the txid for this Vin.
|
||||
assert.equal(result.vin[2].txid, result.vin[2].tokenId)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
Mocking data for unit tests for the Ninsight library.
|
||||
*/
|
||||
|
||||
const utxo = {
|
||||
utxos: [
|
||||
{
|
||||
txid: '2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7',
|
||||
vout: 0,
|
||||
amount: 0.00001,
|
||||
satoshis: 1000,
|
||||
height: 602405,
|
||||
confirmations: 36459
|
||||
}
|
||||
],
|
||||
legacyAddress: '15NCRBJsHaJy8As5bX1oh2YauRejnZ1MKF',
|
||||
cashAddress: 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9',
|
||||
slpAddress: 'simpleledger:qqh793x9au6ehvh7r2zflzguanlme760wuwzunhjfm',
|
||||
scriptPubKey: '76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac',
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 2fe2c4c5ef359bb2fe1a849f891cecffbcfb4f77 OP_EQUALVERIFY OP_CHECKSIG'
|
||||
}
|
||||
|
||||
const unconfirmed = {
|
||||
utxos: [
|
||||
{
|
||||
txid: '3904ffe6f8fba4ceda5e887130f60fcb18bdc7dcee10392a57f89475c5c108f1',
|
||||
vout: 0,
|
||||
amount: 0.03608203,
|
||||
satoshis: 3608203,
|
||||
confirmations: 0,
|
||||
ts: 1559670801
|
||||
}
|
||||
],
|
||||
legacyAddress: '1AyWs8U4HUnTLmxxFiGoJbsXauRsvBrcKW',
|
||||
cashAddress: 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
|
||||
slpAddress: 'simpleledger:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rkuk8mdxlf2',
|
||||
scriptPubKey: '76a9146d695af951760b837c8e58b045b098f9c17e23b788ac'
|
||||
}
|
||||
|
||||
const utxoPost = [utxo, utxo]
|
||||
const unconfirmedPost = [unconfirmed, unconfirmed]
|
||||
|
||||
const transactions = {
|
||||
pagesTotal: 1,
|
||||
txs: [
|
||||
{
|
||||
txid: 'ec7bc8349386e3e1939bbdc4f8092fdbdd6a380734e68486b558cd594c451d5b',
|
||||
version: 2,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid:
|
||||
'4f1fc57c33659628938db740449bf92fb75799e1d5750a4aeef80eb52d6df1e0',
|
||||
vout: 0,
|
||||
sequence: 4294967295,
|
||||
n: 0,
|
||||
scriptSig: {
|
||||
hex:
|
||||
'483045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
|
||||
asm:
|
||||
'3045022100a3662a19ae384a1ceddea57765e425e61b04823e976d574da3911ac6b55d7f9b02200e571d985bce987675a2d58587a346fa40c39f4df13dc88548a92c52d5b24422[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
|
||||
},
|
||||
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
|
||||
valueSat: 1111,
|
||||
value: 0.00001111,
|
||||
doubleSpentTxID: null
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'126d62c299e7e14c66fe0b485d13082c23641f003690462046bc24ad2d1180c1',
|
||||
vout: 0,
|
||||
sequence: 4294967295,
|
||||
n: 1,
|
||||
scriptSig: {
|
||||
hex:
|
||||
'47304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda412103f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f',
|
||||
asm:
|
||||
'304402203e3f923207111ff9bbd2fb5ab1a49a9145ad809ee0cad0e0ddaed64bfe38dc16022012ee288fb413bd500c63f8bb95e46b6b57d34762decd46b7188478a1c398eeda[ALL|FORKID] 03f953f7630acc15bd3f5078c698f3af777286ae955b57e4857c158f75d87adb5f'
|
||||
},
|
||||
addr: '17HPz8RQ4XM6mjre6aspvqyj1j648CZidM',
|
||||
valueSat: 1000,
|
||||
value: 0.00001,
|
||||
doubleSpentTxID: null
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: '0.00001736',
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
hex: '76a914d96ac75ca8df9729d278da50ccd7355c5785444e88ac',
|
||||
asm:
|
||||
'OP_DUP OP_HASH160 d96ac75ca8df9729d278da50ccd7355c5785444e OP_EQUALVERIFY OP_CHECKSIG',
|
||||
addresses: ['1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP'],
|
||||
type: 'pubkeyhash'
|
||||
},
|
||||
spentTxId: null,
|
||||
spentIndex: null,
|
||||
spentHeight: null
|
||||
}
|
||||
],
|
||||
blockheight: -1,
|
||||
confirmations: 0,
|
||||
time: 1559673337,
|
||||
valueOut: 0.00001736,
|
||||
size: 339,
|
||||
valueIn: 0.00002111,
|
||||
fees: 0.00000375
|
||||
}
|
||||
],
|
||||
legacyAddress: '1LpbYkEM5cryfhs58tH8c93p4SGzit7UrP',
|
||||
cashAddress: 'bitcoincash:qrvk436u4r0ew2wj0rd9pnxhx4w90p2yfc29ta0d2n',
|
||||
currentPage: 0
|
||||
}
|
||||
|
||||
const transactionsPost = [transactions, transactions]
|
||||
|
||||
const details = {
|
||||
txid: 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
|
||||
version: 1,
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
coinbase: '04ffff001d02fd04',
|
||||
sequence: 4294967295,
|
||||
n: 0
|
||||
}
|
||||
],
|
||||
vout: [
|
||||
{
|
||||
value: '50.00000000',
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
hex:
|
||||
'4104f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446aac',
|
||||
asm:
|
||||
'04f5eeb2b10c944c6b9fbcfff94c35bdeecd93df977882babc7f3a2cf7f5c81d3b09a68db7f0e04f21de5d4230e75e6dbe7ad16eefe0d4325a62067dc6f369446a OP_CHECKSIG',
|
||||
addresses: ['1BW18n7MfpU35q4MTBSk8pse3XzQF8XvzT'],
|
||||
type: 'pubkeyhash',
|
||||
cashAddrs: ['bitcoincash:qpej6mkrwca4tvy2snq4crhrf88v4ljspysx0ueetk']
|
||||
},
|
||||
spentTxId: null,
|
||||
spentIndex: null,
|
||||
spentHeight: null
|
||||
}
|
||||
],
|
||||
blockhash:
|
||||
'00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09',
|
||||
blockheight: 1000,
|
||||
confirmations: 585610,
|
||||
time: 1232346882,
|
||||
blocktime: 1232346882,
|
||||
isCoinBase: true,
|
||||
valueOut: 50,
|
||||
size: 135
|
||||
}
|
||||
|
||||
const detailsPost = [details, details]
|
||||
|
||||
module.exports = {
|
||||
utxo,
|
||||
utxoPost,
|
||||
unconfirmed,
|
||||
unconfirmedPost,
|
||||
transactions,
|
||||
transactionsPost,
|
||||
details,
|
||||
detailsPost
|
||||
}
|
||||
@@ -21,7 +21,8 @@ const tokenStats = {
|
||||
totalMinted: '1',
|
||||
txs: [
|
||||
{
|
||||
txid: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
|
||||
txid:
|
||||
'13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
|
||||
height: 722420,
|
||||
type: 'GENESIS',
|
||||
qty: '1'
|
||||
@@ -39,11 +40,14 @@ const txData = {
|
||||
locktime: 0,
|
||||
vin: [
|
||||
{
|
||||
txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
|
||||
txid:
|
||||
'8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
|
||||
vout: 0,
|
||||
scriptSig: {
|
||||
asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
|
||||
hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
|
||||
asm:
|
||||
'304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
|
||||
hex:
|
||||
'47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
|
||||
},
|
||||
sequence: 4294967295,
|
||||
address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
|
||||
@@ -58,16 +62,20 @@ const txData = {
|
||||
value: 0,
|
||||
n: 0,
|
||||
scriptPubKey: {
|
||||
asm: 'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
|
||||
hex: '6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
|
||||
asm:
|
||||
'OP_RETURN 5262419 1 47454e45534953 54524f5554 54726f75742773207465737420746f6b656e 74726f757473626c6f672e636f6d 0 2 2 000000174876e800',
|
||||
hex:
|
||||
'6a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e800',
|
||||
type: 'nulldata'
|
||||
},
|
||||
tokenQtyStr: '0',
|
||||
tokenQty: 0
|
||||
}
|
||||
],
|
||||
hex: '0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
|
||||
blockhash: '0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
|
||||
hex:
|
||||
'0200000001a7654579abb8faf6f013c6c82561bf51115422cd1eb7119aab6147d930db7083000000006a47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851ffffffff040000000000000000476a04534c500001010747454e455349530554524f55541254726f75742773207465737420746f6b656e0e74726f757473626c6f672e636f6d4c000102010208000000174876e80022020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088ac22020000000000001976a914db4d39ceb7794ffe5d06855f249e1d3a7f1b024088accec20000000000001976a9145904159f2f69bfa63eefa712633a0d96dc2e7e8888ac00000000',
|
||||
blockhash:
|
||||
'0000000000000000009f65225a3e12e23a7ea057c869047e0f36563a1f410267',
|
||||
confirmations: 97398,
|
||||
time: 1581773131,
|
||||
blocktime: 1581773131,
|
||||
@@ -88,27 +96,32 @@ const balance = {
|
||||
balance: {
|
||||
utxos: [
|
||||
{
|
||||
txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
||||
txid:
|
||||
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '1800',
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
|
||||
}
|
||||
],
|
||||
txs: [
|
||||
{
|
||||
txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
|
||||
txid:
|
||||
'078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
|
||||
height: 717796
|
||||
},
|
||||
{
|
||||
txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
||||
txid:
|
||||
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
|
||||
height: 717832
|
||||
}
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
qty: '1800'
|
||||
}
|
||||
]
|
||||
@@ -122,9 +135,19 @@ const status = {
|
||||
chainBlockHeight: 722679
|
||||
}
|
||||
}
|
||||
|
||||
const tokenData01 = {
|
||||
tokenType: 1,
|
||||
txType: 'MINT',
|
||||
tokenId: 'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9',
|
||||
mintBatonVout: 2,
|
||||
qty: {}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
tokenStats,
|
||||
txData,
|
||||
balance,
|
||||
status
|
||||
status,
|
||||
tokenData01
|
||||
}
|
||||
|
||||
@@ -280,10 +280,214 @@ const electrumxUtxos = {
|
||||
]
|
||||
}
|
||||
|
||||
const fulcrumUtxos01 = {
|
||||
success: true,
|
||||
utxos: [
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
tx_pos: 2,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674512,
|
||||
tx_hash:
|
||||
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 1,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 2,
|
||||
value: 546
|
||||
},
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 3,
|
||||
value: 38134
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const fulcrumUtxos02 = {
|
||||
success: true,
|
||||
utxos: [
|
||||
{
|
||||
height: 674513,
|
||||
tx_hash:
|
||||
'705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04',
|
||||
tx_pos: 3,
|
||||
value: 38134
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const psfSlpIndexerUtxos01 = {
|
||||
balance: {
|
||||
utxos: [
|
||||
{
|
||||
txid:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '10000000000',
|
||||
tokenId:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
|
||||
},
|
||||
{
|
||||
txid:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
vout: 2,
|
||||
type: 'baton',
|
||||
tokenId:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
address: 'bitcoincash:qrm0c67wwqh0w7wjxua2gdt2xggnm90xws00a3lezv'
|
||||
}
|
||||
],
|
||||
txs: [
|
||||
{
|
||||
txid:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
height: 674512
|
||||
}
|
||||
],
|
||||
balances: [
|
||||
{
|
||||
tokenId:
|
||||
'acbb0d3ceef55aa3e5fafc19335ae4bf2f8edba3c0567547dfd402391db32230',
|
||||
qty: '10000000000'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const tokenUtxos01 = [
|
||||
{
|
||||
txid: '384e1b8197e8de7d38f98317af2cf5f6bcb50007c46943b3498a6fab6e8aeb7c',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '10000000',
|
||||
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid: '4fc789405d58ec612c69eba29aa56cf0c7f228349801114138424eb68df4479d',
|
||||
vout: 1,
|
||||
type: 'token',
|
||||
qty: '100000000',
|
||||
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid: '42054bba4d69bfe7801ece0cffc754194b04239034fdfe9dbe321ef76c9a2d93',
|
||||
vout: 5,
|
||||
type: 'token',
|
||||
qty: '4764',
|
||||
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
},
|
||||
{
|
||||
txid: '06938d0a0d15aa76524ffe61fe111d6d2b2ea9dd8dcd4c7c7744614ced370861',
|
||||
vout: 5,
|
||||
type: 'token',
|
||||
qty: '238',
|
||||
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
|
||||
address: 'bitcoincash:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvg8nfhq4m'
|
||||
}
|
||||
]
|
||||
|
||||
const genesisData01 = {
|
||||
tokenData: {
|
||||
type: 1,
|
||||
ticker: 'sleven',
|
||||
name: 'sleven',
|
||||
tokenId: 'a436c8e1b6bee3d701c6044d190f76f774be83c36de8d34a988af4489e86dd37',
|
||||
documentUri: 'sleven',
|
||||
documentHash: '',
|
||||
decimals: 7,
|
||||
mintBatonIsActive: false,
|
||||
tokensInCirculationBN: '770059999999',
|
||||
tokensInCirculationStr: '770059999999',
|
||||
blockCreated: 555483,
|
||||
totalBurned: '7711234568',
|
||||
totalMinted: '777771234567'
|
||||
}
|
||||
}
|
||||
|
||||
const genesisData02 = {
|
||||
tokenData: {
|
||||
type: 1,
|
||||
ticker: 'NAKAMOTO',
|
||||
name: 'NAKAMOTO',
|
||||
tokenId: 'df808a41672a0a0ae6475b44f272a107bc9961b90f29dc918d71301f24fe92fb',
|
||||
documentUri: '',
|
||||
documentHash: '',
|
||||
decimals: 8,
|
||||
mintBatonIsActive: false,
|
||||
tokensInCirculationBN: '2099260968799900',
|
||||
tokensInCirculationStr: '2099260968799900',
|
||||
blockCreated: 555671,
|
||||
totalBurned: '739031200100',
|
||||
totalMinted: '2100000000000000'
|
||||
}
|
||||
}
|
||||
|
||||
const genesisData03 = {
|
||||
tokenData: {
|
||||
type: 1,
|
||||
ticker: 'AUDC',
|
||||
name: 'AUD Coin',
|
||||
tokenId: 'f05faf13a29c7f5e54ab921750aafb6afaa953db863bd2cf432e918661d4132f',
|
||||
documentUri: 'audcoino@gmail.com',
|
||||
documentHash: '',
|
||||
decimals: 6,
|
||||
mintBatonIsActive: false,
|
||||
tokensInCirculationBN: '974791786216512742',
|
||||
tokensInCirculationStr: '974791786216512742',
|
||||
blockCreated: 603311,
|
||||
totalBurned: '1025208213783487258',
|
||||
totalMinted: '2000000000000000000'
|
||||
}
|
||||
}
|
||||
|
||||
const noUtxoErr = {
|
||||
success: false,
|
||||
error:
|
||||
'Key not found in database [bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7]'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mockUtxoData,
|
||||
mockHydratedUtxos,
|
||||
mockTwoHydratedAddrs,
|
||||
mockEveryUtxoType,
|
||||
electrumxUtxos
|
||||
electrumxUtxos,
|
||||
fulcrumUtxos01,
|
||||
fulcrumUtxos02,
|
||||
psfSlpIndexerUtxos01,
|
||||
tokenUtxos01,
|
||||
genesisData01,
|
||||
genesisData02,
|
||||
genesisData03,
|
||||
noUtxoErr
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
const chai = require('chai')
|
||||
const assert = chai.assert
|
||||
const axios = require('axios')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const BCHJS = require('../../src/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
const mockData = require('./fixtures/ninsight-mock')
|
||||
|
||||
describe('#Ninsight', () => {
|
||||
let sandbox
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#utxo', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
|
||||
await bchjs.Ninsight.utxo(addr)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input address must be a string or array of strings.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should GET utxos for a single address', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.utxo })
|
||||
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.Ninsight.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'utxos')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'scriptPubKey')
|
||||
assert.property(result, 'asm')
|
||||
|
||||
assert.isArray(result.utxos)
|
||||
|
||||
assert.property(result.utxos[0], 'txid')
|
||||
assert.property(result.utxos[0], 'vout')
|
||||
assert.property(result.utxos[0], 'amount')
|
||||
assert.property(result.utxos[0], 'satoshis')
|
||||
assert.property(result.utxos[0], 'height')
|
||||
assert.property(result.utxos[0], 'confirmations')
|
||||
})
|
||||
|
||||
it('should POST utxo details for an array of addresses', async () => {
|
||||
// Mock the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.utxoPost })
|
||||
|
||||
const addr = [
|
||||
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
|
||||
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.utxo(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.isArray(result[0].utxos)
|
||||
|
||||
assert.property(result[0], 'utxos')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'slpAddress')
|
||||
assert.property(result[0], 'scriptPubKey')
|
||||
assert.property(result[0], 'asm')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#unconfirmed', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
|
||||
await bchjs.Ninsight.unconfirmed(addr)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input address must be a string or array of strings.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should POST utxos for a single address', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmed })
|
||||
|
||||
const addr = 'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5'
|
||||
|
||||
const result = await bchjs.Ninsight.unconfirmed(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.property(result, 'utxos')
|
||||
assert.property(result, 'legacyAddress')
|
||||
assert.property(result, 'cashAddress')
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.property(result, 'scriptPubKey')
|
||||
|
||||
assert.isArray(result.utxos)
|
||||
|
||||
assert.property(result.utxos[0], 'txid')
|
||||
assert.property(result.utxos[0], 'vout')
|
||||
assert.property(result.utxos[0], 'amount')
|
||||
assert.property(result.utxos[0], 'satoshis')
|
||||
assert.property(result.utxos[0], 'confirmations')
|
||||
assert.property(result.utxos[0], 'ts')
|
||||
})
|
||||
|
||||
it('should POST utxo details for an array of addresses', async () => {
|
||||
// Mock the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.unconfirmedPost })
|
||||
|
||||
const addr = [
|
||||
'bitcoincash:qpkkjkhe29mqhqmu3evtq3dsnruuzl3rku6usknlh5',
|
||||
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.unconfirmed(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.isArray(result[0].utxos)
|
||||
|
||||
assert.property(result[0], 'utxos')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'slpAddress')
|
||||
assert.property(result[0], 'scriptPubKey')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
|
||||
await bchjs.Ninsight.transactions(addr)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input address must be a string or array of strings.'
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should POST transaction history for a single address', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
|
||||
|
||||
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
|
||||
const result = await bchjs.Ninsight.transactions(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'txs')
|
||||
assert.isArray(result[0].txs)
|
||||
assert.property(result[0].txs[0], 'txid')
|
||||
assert.property(result[0].txs[0], 'vin')
|
||||
assert.property(result[0].txs[0], 'vout')
|
||||
})
|
||||
it('should POST transaction history for an array of addresses', async () => {
|
||||
// Mock the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.transactionsPost })
|
||||
|
||||
const addr = [
|
||||
'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7',
|
||||
'bitcoincash:qz0us0z6ucpqt07jgpad0shgh7xmwxyr3ynlcsq0wr'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.transactions(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'cashAddress')
|
||||
assert.property(result[0], 'legacyAddress')
|
||||
assert.property(result[0], 'txs')
|
||||
assert.isArray(result[0].txs)
|
||||
assert.property(result[0].txs[0], 'txid')
|
||||
})
|
||||
})
|
||||
describe('#txDetails', () => {
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const txid = 12345
|
||||
|
||||
await bchjs.Ninsight.txDetails(txid)
|
||||
assert.equal(true, false, 'Unexpected result!')
|
||||
} catch (err) {
|
||||
// console.log(`err: `, err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Transaction ID must be a string or array of strings.'
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should POST transaction details for a single TxID', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
|
||||
|
||||
const txid = 'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
|
||||
|
||||
const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'txid')
|
||||
assert.property(result[0], 'version')
|
||||
assert.property(result[0], 'locktime')
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'vout')
|
||||
assert.property(result[0], 'blockhash')
|
||||
assert.property(result[0], 'blockheight')
|
||||
assert.property(result[0], 'confirmations')
|
||||
assert.property(result[0], 'time')
|
||||
assert.property(result[0], 'blocktime')
|
||||
assert.property(result[0], 'isCoinBase')
|
||||
assert.property(result[0], 'valueOut')
|
||||
assert.property(result[0], 'size')
|
||||
})
|
||||
it('should POST transaction details for an array of TxIDs', async () => {
|
||||
// Stub the network call.
|
||||
sandbox.stub(axios, 'post').resolves({ data: mockData.detailsPost })
|
||||
|
||||
const txid = [
|
||||
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33',
|
||||
'fe28050b93faea61fa88c4c630f0e1f0a1c24d0082dd0e10d369e13212128f33'
|
||||
]
|
||||
|
||||
const result = await bchjs.Ninsight.txDetails(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[0], 'txid')
|
||||
assert.property(result[0], 'vin')
|
||||
assert.property(result[0], 'vout')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -71,6 +71,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.isArray(result.balance.txs)
|
||||
assert.isArray(result.balance.balances)
|
||||
})
|
||||
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const addr = 12345
|
||||
@@ -82,6 +83,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.include(err.message, 'Input address must be a string.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
@@ -96,6 +98,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle request error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
@@ -139,6 +142,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.property(result.tokenData, 'totalMinted')
|
||||
assert.property(result.tokenData, 'txs')
|
||||
})
|
||||
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const tokenId = 12345
|
||||
@@ -150,6 +154,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.include(err.message, 'Input tokenId must be a string.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
@@ -164,6 +169,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle request error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
@@ -215,6 +221,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.property(result.txData, 'tokenDocHash')
|
||||
assert.property(result.txData, 'isValidSlp')
|
||||
})
|
||||
|
||||
it('should throw an error for improper input', async () => {
|
||||
try {
|
||||
const txid = 12345
|
||||
@@ -226,6 +233,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.include(err.message, 'Input txid must be a string.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Stub the network call.
|
||||
@@ -249,6 +257,7 @@ describe('#PsfSlpIndexer', () => {
|
||||
sandbox.stub(axios, 'post').rejects(testErr)
|
||||
|
||||
// Stub the call to the full node
|
||||
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(false)
|
||||
sandbox
|
||||
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
|
||||
.resolves({ txid: 'fakeTxid' })
|
||||
@@ -259,6 +268,72 @@ describe('#PsfSlpIndexer', () => {
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.txData.txid, 'fakeTxid')
|
||||
assert.equal(result.txData.isValidSlp, false)
|
||||
})
|
||||
|
||||
it('should return isValidSlp=null for blacklisted token', async () => {
|
||||
// Stub the call to the SLP indexer.
|
||||
const testErr = {
|
||||
response: { data: { error: 'Key not found in database' } }
|
||||
}
|
||||
sandbox.stub(axios, 'post').rejects(testErr)
|
||||
|
||||
// Stub the call to the full node
|
||||
sandbox.stub(bchjs.PsfSlpIndexer, 'checkBlacklist').resolves(true)
|
||||
sandbox
|
||||
.stub(bchjs.PsfSlpIndexer.rawTransaction, 'getTxData')
|
||||
.resolves({ txid: 'fakeTxid' })
|
||||
|
||||
const txid =
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
|
||||
const result = await bchjs.PsfSlpIndexer.tx(txid)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.txData.txid, 'fakeTxid')
|
||||
assert.equal(result.txData.isValidSlp, null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#checkBlacklist', () => {
|
||||
it('should return true if txid contains token in blacklist', async () => {
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
|
||||
.resolves(mockData.tokenData01)
|
||||
|
||||
const txid =
|
||||
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return false if there is an error', async () => {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.checkBlacklist()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should return false if there is no tokenId match', async () => {
|
||||
// Mock dependencies
|
||||
mockData.tokenData01.tokenId = 'abc123'
|
||||
sandbox
|
||||
.stub(bchjs.PsfSlpIndexer.slpUtils, 'decodeOpReturn')
|
||||
.resolves(mockData.tokenData01)
|
||||
|
||||
const txid =
|
||||
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
|
||||
|
||||
const result = await bchjs.PsfSlpIndexer.checkBlacklist(txid)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+124
-7
@@ -15,7 +15,7 @@ describe('#utxo', () => {
|
||||
beforeEach(() => (sandbox = sinon.createSandbox()))
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#get', () => {
|
||||
describe('#getOld', () => {
|
||||
it('should get hydrated and filtered UTXOs for an address', async () => {
|
||||
// Mock dependencies.
|
||||
sandbox.stub(bchjs.Utxo.electrumx, 'utxo').resolves(mockData.mockUtxoData)
|
||||
@@ -25,7 +25,7 @@ describe('#utxo', () => {
|
||||
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
const result = await bchjs.Utxo.getOld(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -46,7 +46,7 @@ describe('#utxo', () => {
|
||||
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
|
||||
await bchjs.Utxo.get(addr)
|
||||
await bchjs.Utxo.getOld(addr)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
@@ -66,7 +66,7 @@ describe('#utxo', () => {
|
||||
'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
|
||||
]
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
const result = await bchjs.Utxo.getOld(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -88,7 +88,7 @@ describe('#utxo', () => {
|
||||
addrs.push(addr)
|
||||
}
|
||||
|
||||
await bchjs.Utxo.get(addrs)
|
||||
await bchjs.Utxo.getOld(addrs)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
@@ -105,7 +105,7 @@ describe('#utxo', () => {
|
||||
|
||||
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
const result = await bchjs.Utxo.getOld(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -133,7 +133,7 @@ describe('#utxo', () => {
|
||||
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
|
||||
const useWhitelist = true
|
||||
|
||||
const result = await bchjs.Utxo.get(addr, useWhitelist)
|
||||
const result = await bchjs.Utxo.getOld(addr, useWhitelist)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.isArray(result)
|
||||
@@ -188,4 +188,121 @@ describe('#utxo', () => {
|
||||
assert.equal(result.satoshis, 800)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#hydrateTokenData', () => {
|
||||
it('should hydrate token UTXOs', async () => {
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
|
||||
.onCall(0)
|
||||
.resolves(mockData.genesisData01)
|
||||
.onCall(1)
|
||||
.resolves(mockData.genesisData02)
|
||||
.onCall(2)
|
||||
.resolves(mockData.genesisData03)
|
||||
|
||||
const result = await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.length, 4)
|
||||
assert.property(result[0], 'qtyStr')
|
||||
assert.property(result[0], 'ticker')
|
||||
assert.property(result[0], 'name')
|
||||
assert.property(result[0], 'documentUri')
|
||||
assert.property(result[0], 'documentHash')
|
||||
})
|
||||
|
||||
it('should should catch and throw errors', async () => {
|
||||
try {
|
||||
// Force error
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tokenStats')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await bchjs.Utxo.hydrateTokenData(mockData.tokenUtxos01)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#get', () => {
|
||||
it('should throw an error if input is not a string', async () => {
|
||||
try {
|
||||
const addr = 123
|
||||
|
||||
await bchjs.Utxo.get(addr)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'address input must be a string')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return UTXO information', async () => {
|
||||
// mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.electrumx, 'utxo')
|
||||
.resolves(mockData.fulcrumUtxos01)
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
|
||||
.resolves(mockData.psfSlpIndexerUtxos01)
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
|
||||
.resolves({ txData: { isValidSlp: false } })
|
||||
|
||||
// Mock function to return the same input. Good enough for this test.
|
||||
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x)
|
||||
|
||||
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
// Assert expected properties exist
|
||||
assert.property(result, 'address')
|
||||
assert.property(result, 'bchUtxos')
|
||||
assert.property(result, 'slpUtxos')
|
||||
assert.property(result.slpUtxos, 'type1')
|
||||
assert.property(result.slpUtxos.type1, 'tokens')
|
||||
assert.property(result.slpUtxos.type1, 'mintBatons')
|
||||
assert.property(result.slpUtxos, 'nft')
|
||||
assert.property(result, 'nullUtxos')
|
||||
|
||||
assert.equal(result.bchUtxos.length, 4)
|
||||
assert.equal(result.slpUtxos.type1.tokens.length, 1)
|
||||
assert.equal(result.slpUtxos.type1.mintBatons.length, 1)
|
||||
assert.equal(result.nullUtxos.length, 0)
|
||||
})
|
||||
|
||||
it('should handle an address with no SLP UTXOs', async () => {
|
||||
// mock dependencies
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.electrumx, 'utxo')
|
||||
.resolves(mockData.fulcrumUtxos02)
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
|
||||
.resolves({ txData: { isValidSlp: false } })
|
||||
|
||||
// Force psf-slp-indexer to return no UTXOs
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'balance')
|
||||
.rejects(mockData.noUtxoErr)
|
||||
|
||||
// Mock function to return the same input. Good enough for this test.
|
||||
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => [])
|
||||
|
||||
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.bchUtxos.length, 1)
|
||||
assert.equal(result.slpUtxos.type1.tokens.length, 0)
|
||||
assert.equal(result.slpUtxos.type1.mintBatons.length, 0)
|
||||
assert.equal(result.nullUtxos.length, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user