Compare commits

..
34 Commits
Author SHA1 Message Date
Chris Troutner c925bfce46 Merge pull request #212 from Permissionless-Software-Foundation/ct-unstable
Integration Test Maintenance
2022-01-23 07:11:32 -08:00
Chris Troutner 056a47f6b2 fix(integration tests): Got BCHN and ABC integration tests passing 2022-01-23 07:08:41 -08:00
Chris Troutner 51038681d3 fix(transaction.js): Added integrationt test 2022-01-23 06:46:06 -08:00
Chris Troutner 1a2f144baf Merge pull request #211 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Properly handling addresses with no SLP utxos
2022-01-19 14:48:29 -08:00
Chris Troutner 6d70568082 fix(Utxo.get()): Properly handling addresses with no SLP utxos 2022-01-19 14:46:00 -08:00
Chris Troutner 0b1cf57852 fix(integration): Updating integration tests 2022-01-19 12:20:08 -08:00
Chris Troutner 48f7f75c9c Merge pull request #210 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Hydrating SLP UTXOs with additional token data
2022-01-19 10:37:45 -08:00
Chris Troutner 0bc96fded5 fix(Utxo.get()): Hydrating SLP UTXOs with additional token data 2022-01-19 10:33:36 -08:00
Chris Troutner 9869211572 Merge pull request #209 from Permissionless-Software-Foundation/ct-unstable
Big changes
2022-01-18 11:53:27 -08:00
Chris Troutner 1056c3724c feat(Utxo.get()): Moving old function to getOld. New get() uses psf-slp-indexer 2022-01-18 11:41:52 -08:00
Chris Troutner 6b838ef9cd feat(Ninsight): Removing Ninsight libraries and tests 2022-01-18 08:45:20 -08:00
Chris Troutner f09573554d fix(PsfSlpIndexer.tx()): Added detection of blacklisted SLP Tokens 2022-01-18 08:32:45 -08:00
Chris Troutner 4a62ae64de Merge pull request #208 from Permissionless-Software-Foundation/ct-unstable
Replacing Transaction.get() with psf-slp-indexer
2022-01-17 19:21:49 -08:00
Chris Troutner 78de9e390e Merge branch 'master' into ct-unstable 2022-01-17 19:16:21 -08:00
Chris Troutner 7d8d0c2d69 Bumping major version to 5.0.0 2022-01-17 19:12:57 -08:00
Chris Troutner 656b0f6809 feat(Transaction.get()): Replacing call with proxy to psf-slp-indexer 2022-01-17 19:12:12 -08:00
Chris Troutner 48808aec14 Merge pull request #207 from Permissionless-Software-Foundation/ct-unstable
fix(slpdb): Removing slpdb from default integration tests
2022-01-17 15:10:26 -08:00
Chris Troutner d50f4a0d50 fix(slpdb): Removing slpdb from default integration tests 2022-01-17 15:07:06 -08:00
Chris Troutner f09f96d365 Merge pull request #206 from Permissionless-Software-Foundation/dh-slp-indexer
feat(indexer): Added PSF SLP Indexer to bch-js
2022-01-17 14:43:23 -08:00
Chris Troutner 7f4745f83c fix(psf-slp-indexer): Added integration tests 2022-01-17 14:39:52 -08:00
Chris Troutner dd5de1424c fix(PsfSlpIndexer.tx): Renaming txId() to tx() 2022-01-17 14:33:49 -08:00
Daniel Gonzalez 30604591bd feat(indexer): Added PSF SLP Indexer to bch-js 2022-01-17 13:37:29 -04:00
Chris Troutner aeb1efe23e Merge pull request #205 from Permissionless-Software-Foundation/ct-unstable
Adding eCash class library
2022-01-10 19:54:31 -08:00
Chris Troutner 6e43f795d7 feat(eCash): Adding eCash class library 2022-01-10 19:51:12 -08:00
Chris Troutner e07a5b9da0 Adding getBlockchainInfo() integration test 2022-01-10 14:48:43 -08:00
Chris Troutner 5ada76eace Merge branch 'master' into ct-unstable 2021-12-20 12:38:18 -08:00
Chris Troutner e605f6f386 Merge pull request #203 from Permissionless-Software-Foundation/temp
Temp
2021-12-15 05:47:56 -08:00
Chris Troutner eb9d0bea2b Merge pull request #202 from mr-zwets/documentation-toHash160
added documentation to function toHash160()
2021-12-15 05:45:17 -08:00
Chris Troutner f185f5de16 Updating integrat test scripts 2021-12-15 05:44:27 -08:00
Chris Troutner d795174916 Merge branch 'master' into documentation-toHash160 2021-12-15 05:39:47 -08:00
mathi 1294bc2149 added documentation toHash160() 2021-12-15 12:08:58 +01:00
Chris Troutner 8117448fd3 Merge pull request #201 from Permissionless-Software-Foundation/ct-unstable
fix(utxo.get()): Throwing error if address===undefined
2021-12-01 14:48:58 -08:00
Chris Troutner c7b51a38ff fix(utxo.get()): Throwing error if address===undefined 2021-12-01 14:45:39 -08:00
Chris Troutner 07d490134a Merge pull request #198 from Permissionless-Software-Foundation/salemkode
Salemkode
2021-11-30 06:38:15 -08:00
26 changed files with 3141 additions and 2079 deletions
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@psf/bch-js",
"version": "4.0.0",
"version": "5.0.0",
"description": "The FullStack.cash JavaScript library for Bitcoin Cash and SLP Tokens",
"author": "Chris Troutner <chris.troutner@gmail.com>",
"contributors": [
@@ -14,12 +14,12 @@
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/chains/bchn/slp.js",
"test:integration:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/abc/",
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:testnet": "IS_USING_FREE_TIER=true mocha --timeout 30000 test/integration/chains/testnet/",
"test:integration:bchn:slpdb": "export TESTSLP=1 && export RESTURL=https://bchn.fullstack.cash/v5/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"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.0.36:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
"test:integration:decatur:abc": "export RESTURL=http://192.168.0.38:3000/v5/ && mocha --timeout 30000 test/integration && mocha --timeout 30000 test/integration/chains/abc/",
"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/",
"test:temp2": "mocha --timeout=30000 -g '#TransactionLib' test/unit/",
+15
View File
@@ -186,6 +186,21 @@ class Address {
return cashAddress.split(':')[1]
}
/**
* @api Address.toHash160() toHash160()
* @apiName toHash160
* @apiGroup Address
* @apiDescription Converts any address format to hash160
*
* @apiExample Example usage:
* // cash address mainnet p2pkh
* bchjs.Address.toHash160("bitcoincash:qptnmya5wkly7xf97wm5ak23yqdsz3l2cyj7k9vyyh")
* // 573d93b475be4f1925f3b74ed951201b0147eac1
*
* // cash address mainnet p2sh
* bchjs.Address.toHash160("bitcoincash:pp7ushdxf5we8mcpaa3wqgsuqt639cu59ur5xu5fug")
* // 7dc85da64d1d93ef01ef62e0221c02f512e3942f
*/
// Converts any address format to hash160
toHash160 (address) {
const legacyAddress = this.toLegacyAddress(address)
+5 -4
View File
@@ -33,10 +33,11 @@ const Encryption = require('./encryption')
const Utxo = require('./utxo')
const Transaction = require('./transaction')
const DSProof = require('./dsproof')
const Ecash = require('./ecash')
// Indexers
const Ninsight = require('./ninsight')
const Electrumx = require('./electrumx')
const PsfSlpIndexer = require('./psf-slp-indexer')
class BCHJS {
constructor (config) {
@@ -81,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)
@@ -118,6 +116,9 @@ class BCHJS {
this.Transaction = new Transaction(libConfig)
this.DSProof = new DSProof(libConfig)
this.eCash = new Ecash()
this.PsfSlpIndexer = new PsfSlpIndexer(libConfig)
}
}
+47
View File
@@ -0,0 +1,47 @@
/*
Utility library for converting units with eCash.
*/
class eCash {
/**
* @api eCash.toSatoshi() toSatoshi()
* @apiName toSatoshi
* @apiGroup eCash
* @apiDescription
* Convert XEC units into satoshi units
*
* @apiExample Example usage:
* // convert 10,704.35 XEC to satoshis:
* bchjs.eCash.toSatoshi(10704.35)
* // 1070435
*/
toSatoshi (xec) {
if (typeof xec !== 'number') {
throw new Error('input must be a floating number representing XEC')
}
return Math.floor(xec * 100)
}
/**
* @api eCash.toXec() toXec()
* @apiName toXec
* @apiGroup eCash
* @apiDescription
* Convert satoshi units to XEC units
*
* @apiExample Example usage:
* // convert 1,070,435 satoshis to XEC:
* bchjs.eCash.toSatoshi(1070435)
* // 10704.35
*/
toXec (sats) {
if (typeof sats !== 'number') {
throw new Error('input must be a floating number representing satoshis')
}
return sats / 100
}
}
module.exports = eCash
-319
View File
@@ -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
+371
View File
@@ -0,0 +1,371 @@
/*
This library interacts with the PSF slp indexer REST API endpoints operated
by FullStack.cash
TODO:
- detect TXs from tokens in the blacklist.
*/
// Public npm libraries
const axios = require('axios')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
// let _this
class PsfSlpIndexer {
constructor (config = {}) {
this.restURL = config.restURL
this.apiToken = config.apiToken
this.authToken = config.authToken
if (this.authToken) {
// Add Basic Authentication token to the authorization header.
this.axiosOptions = {
headers: {
authorization: this.authToken
}
}
} else {
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`
}
}
}
// Encapsulate dependencies
this.rawTransaction = new RawTransaction(config)
this.slpUtils = new SlpUtils(config)
// _this = this
}
/**
* @api PsfSlpIndexer.status() status()
* @apiName Status
* @apiGroup PSF SLP
* @apiDescription Return status from psf slp indexer.
*
* @apiExample Example usage:
* (async () => {
* try {
* let status = await bchjs.PsfSlpIndexer.status();
* console.log(status);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* "status": {
* "startBlockHeight": 543376,
* "syncedBlockHeight": 723249,
* "chainBlockHeight": 722679
* }
* }
*
*/
async status () {
try {
const response = await axios.get(
`${this.restURL}psf/slp/status`,
this.axiosOptions
)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.balance() balance()
* @apiName SLP Balance
* @apiGroup PSF SLP
* @apiDescription Return slp balance for a single address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let balance = await bchjs.PsfSlpIndexer.balance('bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n');
* console.log(balance);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* balance: {
* utxos: [
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* vout: 1,
* type: 'token',
* qty: '1800',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
* }
* ],
* txs: [
* {
* txid: '078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
* height: 717796
* },
* {
* txid: 'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
* height: 717832
* }
* ],
* balances: [
* {
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* qty: '1800'
* }
* ]
* }
* }
*
*/
async balance (address) {
try {
// console.log('balance() address: ', address)
// Handle single address.
if (typeof address === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/address`,
{ address },
this.axiosOptions
)
return response.data
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tokenStats() tokenStats()
* @apiName Token Stats
* @apiGroup PSF SLP
* @apiDescription Return list stats for a single slp token.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenStats = await bchjs.PsfSlpIndexer.tokenStats('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
* console.log(tokenStats);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* tokenData: {
* type: 1,
* ticker: 'TROUT',
* name: "Trout's test token",
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* documentUri: 'troutsblog.com',
* documentHash: '',
* decimals: 2,
* mintBatonIsActive: true,
* tokensInCirculationBN: '100098953386',
* tokensInCirculationStr: '100098953386',
* blockCreated: 622414,
* totalBurned: '1046614',
* totalMinted: '100100000000'
* txs: [
* {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* height: 622414,
* type: 'GENESIS',
* qty: '100000000000'
* }
* ]
* }
* }
*
*/
async tokenStats (tokenId) {
try {
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/token`,
{ tokenId },
this.axiosOptions
)
return response.data
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api PsfSlpIndexer.tx() tx()
* @apiName SLP Transaction Data
* @apiGroup PSF SLP
* @apiDescription Return slp transaction data.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txData = await bchjs.PsfSlpIndexer.tx('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2');
* console.log(txData);
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* txData: {
* txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* version: 2,
* size: 339,
* locktime: 0,
* vin: [
* {
* txid: '8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
* vout: 0,
* scriptSig: {
* asm: '304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
* hex: '47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
* },
* sequence: 4294967295,
* address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
* value: 0.00051303,
* tokenQty: 0,
* tokenQtyStr: '0',
* tokenId: null
* }
* ],
* vout: [
* {
* value: 0,
* n: 0,
* scriptPubKey: {
* 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',
* confirmations: 97398,
* time: 1581773131,
* blocktime: 1581773131,
* blockheight: 622414,
* isSlpTx: true,
* tokenTxType: 'GENESIS',
* tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
* tokenType: 1,
* tokenTicker: 'TROUT',
* tokenName: "Trout's test token",
* tokenDecimals: 2,
* tokenUri: 'troutsblog.com',
* tokenDocHash: '',
* isValidSlp: true
* }
* }
*
*/
async tx (txid) {
try {
// Handle single address.
if (typeof txid === 'string') {
const response = await axios.post(
`${this.restURL}psf/slp/txid`,
{ txid },
this.axiosOptions
)
return response.data
}
throw new Error('Input txid must be a string.')
} catch (error) {
// console.log('error: ', error)
// Case: txid is not stored in the psf-slp-indexer tx database.
// Response: If it's not in the database, then it can be assumed the TX
// is not a token TX?
if (
error.response &&
error.response.data &&
error.response.data.error &&
error.response.data.error.includes('Key not found in database')
) {
// console.log(
// '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)}`)
if (isInBlacklist) {
txDetails.isValidSlp = null
} else {
txDetails.isValidSlp = false
}
const outObj = {
txData: txDetails
}
return outObj
} 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
View File
@@ -36,7 +36,7 @@ class Address extends BCHJSAddress {
* bchjs.SLP.Address.toSLPAddress('qzm47qz5ue99y9yl4aca7jnz7dwgdenl85jkfx3znl')
* // simpleledger:qzm47qz5ue99y9yl4aca7jnz7dwgdenl857dzayzdp
*
* // tesnet legacy
* // testnet legacy
* bchjs.SLP.Address.toSLPAddress('msDbtTj7kWXPpYaR7PQmMK84i66fJqQMLx')
* // slptest:qzq9je6pntpva3wf6scr7mlnycr54sjgeqauyclpwv
*
+1 -1
View File
@@ -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
+11 -2
View File
@@ -2,18 +2,27 @@
High-level functions for working with Transactions
*/
// Global npm libraries
const BigNumber = require('bignumber.js')
// Local libraries
const RawTransaction = require('./raw-transactions')
const SlpUtils = require('./slp/utils')
const Blockchain = require('./blockchain')
const PsfSlpIndexer = require('./psf-slp-indexer')
class Transaction {
constructor (config) {
constructor (config = {}) {
// Encapsulate dependencies
this.slpUtils = new SlpUtils(config)
this.rawTransaction = new RawTransaction(config)
this.blockchain = new Blockchain(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
}
// Proxy the call to the psf-slp-indexer.
async get (txid) {
return await this.psfSlpIndexer.tx(txid)
}
/**
@@ -42,7 +51,7 @@ class Transaction {
// CT 10/31/21: TODO: this function should be refactored to use get2(), but
// add waterfall validation of the TX and its inputs.
async get (txid) {
async getOld (txid) {
try {
if (typeof txid !== 'string') {
throw new Error(
+223 -6
View File
@@ -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,8 +185,12 @@ 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')
}
// Convert address to an array if it is a string.
if (typeof address === 'string') address = [address]
@@ -282,6 +290,215 @@ 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.isSlp = false
thisUtxo.address = addr
}
}
// 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.get2(): ', 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
+8
View File
@@ -313,6 +313,14 @@ describe('#blockchain', () => {
assert.isString(result)
})
})
describe('#getBlockchainInfo', () => {
it('should get info about the blockchain', async () => {
const result = await bchjs.Blockchain.getBlockchainInfo()
console.log(`blockchain info: ${JSON.stringify(result, null, 2)}`)
})
})
})
function sleep (ms) {
@@ -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) {
@@ -0,0 +1,121 @@
/*
Integration tests for the psf-slp-indexer.js library
*/
const assert = require('chai').assert
const BCHJS = require('../../../../src/bch-js')
let bchjs
describe('#psf-slp-indexer', () => {
beforeEach(async () => {
// Introduce a delay so that the BVT doesn't trip the rate limits.
if (process.env.IS_USING_FREE_TIER) await sleep(3000)
bchjs = new BCHJS()
})
describe('#status', () => {
it('should return the status of the indexer.', async () => {
const result = await bchjs.PsfSlpIndexer.status()
// console.log('result: ', result)
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
})
describe('#balance', () => {
it('should get balance data for an address.', async () => {
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log('result: ', result)
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
})
})
describe('#tokenStats', () => {
it('should get stats on a token', async () => {
const tokenId =
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log('result: ', result)
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'txs')
assert.property(result.tokenData, 'totalBurned')
})
})
describe('#tx', () => {
it('should get hydrated tx data for an SLP transaction', async () => {
const txid =
'947ccb2a0d62ca287bc4b0993874ab0f9f6afd454193e631e2bf84dca66731fc'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, true)
})
it('should mark non-SLP transaction as false', async () => {
const txid =
'03d6e6b63647ce7b02ecc73dc6d41b485be14a3e20eed4474b8a840358ddf14e'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
assert.equal(result.txData.isValidSlp, false)
})
// FlexUSD transactions
// 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 mark blacklisted token as null', async () => {
const txid =
'302113c11b90edc5f36c073d2f8a75e1e0eaf59b56235491a843d3819cd6a85f'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log('result: ', result)
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'isValidSlp')
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')
}
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+241 -237
View File
@@ -30,265 +30,269 @@ describe('#SLP', () => {
bchjs = new BCHJS()
})
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
if (process.env.TESTSLP) {
describe('#util', () => {
describe('#decodeOpReturn', () => {
it('should decode a NFT Child transaction', async () => {
const txid =
'eeddccc4d716f04157ea132ac93a48040fea34a6b57f3d8f0cccb7d1a731ab2b'
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
const data = await bchjs.SLP.Utils.decodeOpReturn(txid)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.property(data, 'tokenType')
assert.property(data, 'txType')
assert.property(data, 'ticker')
assert.property(data, 'name')
assert.property(data, 'tokenId')
assert.property(data, 'documentUri')
assert.property(data, 'documentHash')
assert.property(data, 'decimals')
assert.property(data, 'mintBatonVout')
assert.property(data, 'qty')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
assert.equal(data.tokenType, 65)
assert.equal(data.mintBatonVout, 0)
assert.equal(data.qty, '1')
})
})
})
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
describe('#tokenUtxoDetails', () => {
it('should handle a range of UTXO types', async () => {
const utxos = [
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Malformed SLP tx
{
note: 'Malformed SLP tx',
tx_hash:
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
tx_pos: 1,
value: 546
},
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Normal TX (non-SLP)
{
note: 'Normal TX (non-SLP)',
tx_hash:
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
tx_pos: 0,
value: 400000
},
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Valid PSF SLP tx
{
note: 'Valid PSF SLP tx',
tx_hash:
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
tx_pos: 1,
value: 546
},
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
// Valid SLP token not in whitelist
{
note: 'Valid SLP token not in whitelist',
tx_hash:
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
tx_pos: 1,
value: 546
},
// Token send on BCHN network.
{
note: 'Token send on BCHN network',
tx_hash:
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
tx_pos: 1,
value: 546
},
// Token send on ABC network.
{
note: 'Token send on ABC network',
tx_hash:
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d',
tx_pos: 1,
value: 546
},
// Known invalid SLP token send of PSF tokens.
{
note: 'Known invalid SLP token send of PSF tokens',
tx_hash:
'2bf691ad3679d928fef880b8a45b93b233f8fa0d0a92cf792313dbe77b1deb74',
tx_pos: 1,
value: 546
}
]
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const data = await bchjs.SLP.Utils.tokenUtxoDetails(utxos)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
// Malformed SLP tx
assert.equal(data[0].tx_hash, utxos[0].tx_hash)
assert.equal(data[0].isValid, false)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
// Normal TX (non-SLP)
assert.equal(data[1].tx_hash, utxos[1].tx_hash)
assert.equal(data[1].isValid, false)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
})
})
// Valid PSF SLP tx
assert.equal(data[2].tx_hash, utxos[2].tx_hash)
assert.equal(data[2].isValid, true)
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
// Valid SLP token not in whitelist
assert.equal(data[3].tx_hash, utxos[3].tx_hash)
assert.equal(data[3].isValid, true)
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Token send on BCHN network
assert.equal(data[4].tx_hash, utxos[4].tx_hash)
assert.equal(data[4].isValid, true)
assert.isArray(result)
// Token send on ABC network
assert.equal(data[5].tx_hash, utxos[5].tx_hash)
assert.equal(data[5].isValid, null)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
// Known invalid SLP token send of PSF tokens
assert.equal(data[6].tx_hash, utxos[6].tx_hash)
assert.equal(data[6].isValid, false)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#validateTxid3', () => {
it('should invalidate a known invalid TXID', async () => {
const txid =
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, null)
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId =
'68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
it('should validate a known valid TXID', async () => {
const txid =
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd'
const result = await bchjs.SLP.Utils.validateTxid3(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'txid')
assert.equal(result[0].txid, txid)
assert.property(result[0], 'valid')
assert.equal(result[0].valid, true)
})
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Unprocessed SLP TX
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019'
]
const result = await bchjs.SLP.Utils.validateTxid3(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, null)
// Note: This should change from null to true once SLPDB finishes indexing.
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, null)
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId =
'45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
describe('#validateTxid', () => {
it('should handle a mix of valid, invalid, and non-SLP txs', async () => {
const txids = [
// Malformed SLP tx
'f7e5199ef6669ad4d078093b3ad56e355b6ab84567e59ad0f08a5ad0244f783a',
// Normal TX (non-SLP)
'01cdaec2f8b311fc2d6ecc930247bd45fa696dc204ab684596e281fe1b06c1f0',
// Valid PSF SLP tx
'daf4d8b8045e7a90b7af81bfe2370178f687da0e545511bce1c9ae539eba5ffd',
// Valid SLP token not in whitelist
'3a4b628cbcc183ab376d44ce5252325f042268307ffa4a53443e92b6d24fb488',
// Token send on BCHN network.
'402c663379d9699b6e2dd38737061e5888c5a49fca77c97ab98e79e08959e019',
// Token send on ABC network.
'336bfe2168aac4c3303508a9e8548a0d33797a83b85b76a12d845c8d6674f79d'
]
const result = await bchjs.SLP.Utils.validateTxid(txids)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.equal(result[0].txid, txids[0])
assert.equal(result[0].valid, null)
assert.equal(result[1].txid, txids[1])
assert.equal(result[1].valid, null)
assert.equal(result[2].txid, txids[2])
assert.equal(result[2].valid, true)
// True in validateTxid() but null in validateTxid3()
assert.equal(result[3].txid, txids[3])
assert.equal(result[3].valid, true)
assert.equal(result[4].txid, txids[4])
assert.equal(result[4].valid, true)
assert.equal(result[5].txid, txids[5])
assert.equal(result[5].valid, null)
})
})
})
describe('#nft1', () => {
describe('#listNFTGroupChildren', () => {
it('should return array of children GENESIS transactions IDs', async () => {
const groupId = '68cd33ecd909068fbea318ae5ff1d6207cf754e53b191327d6d73b6916424c0a'
const result = await bchjs.SLP.NFT1.listNFTGroupChildren(groupId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftChildren')
assert.isArray(result.nftChildren)
})
})
describe('#parentNFTGroup', () => {
it('should return parent NFT group information', async () => {
const tokenId = '45a30085691d6ea586e3ec2aa9122e9b0e0d6c3c1fd357decccc15d8efde48a9'
const result = await bchjs.SLP.NFT1.parentNFTGroup(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'nftGroup')
assert.property(result.nftGroup, 'id')
assert.property(result.nftGroup, 'name')
assert.property(result.nftGroup, 'symbol')
assert.property(result.nftGroup, 'documentUri')
assert.property(result.nftGroup, 'versionType')
assert.property(result.nftGroup, 'initialTokenQty')
})
})
})
}
})
// Promise-based sleep function
@@ -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)
})
})
})
+184 -83
View File
@@ -14,108 +14,209 @@ 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)
})
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)
// 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)
})
// 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 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)
// 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)
})
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)
assert.isAbove(result.bchUtxos.length, 0)
assert.equal(result.slpUtxos.type1.tokens.length, 0)
})
})
})
+921 -915
View File
File diff suppressed because it is too large Load Diff
+66 -63
View File
@@ -11,71 +11,74 @@ describe('#Transaction', () => {
if (process.env.IS_USING_FREE_TIER) await bchjs.Util.sleep(1000)
})
describe('#get', () => {
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.get(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.get(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.get(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)
})
})
}
})
+56
View File
@@ -0,0 +1,56 @@
/*
Unit tests for eCash library.
*/
// Global npm libraries
const assert = require('chai').assert
const Ecash = require('../../src/ecash')
const uut = new Ecash()
describe('#eCash', () => {
describe('#toSatoshi', () => {
it('should convert XEC to satoshis', () => {
const xec = 10704.35
const result = uut.toSatoshi(xec)
assert.equal(result, 1070435)
})
it('should throw an error if input is not a number', () => {
try {
uut.toSatoshi('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing XEC'
)
}
})
})
describe('#toXec', () => {
it('should convert satoshis to XEC', () => {
const sats = 1070435
const result = uut.toXec(sats)
assert.equal(result, 10704.35)
})
it('should throw an error if input is not a number', () => {
try {
uut.toXec('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.equal(
err.message,
'input must be a floating number representing satoshis'
)
}
})
})
})
-170
View File
@@ -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
}
+153
View File
@@ -0,0 +1,153 @@
/*
Mocking data for unit tests for the PSF SLP INDEXER library.
*/
'use strict'
const tokenStats = {
tokenData: {
type: 1,
ticker: 'TP03',
name: 'Test Plugin 03',
tokenId: '13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
documentUri: 'fullstack.cash',
documentHash: '',
decimals: 0,
mintBatonIsActive: false,
tokensInCirculationBN: '1',
tokensInCirculationStr: '1',
blockCreated: 722420,
totalBurned: '0',
totalMinted: '1',
txs: [
{
txid:
'13cad617d523c8eb4ab11fff19c010e0e0a4ea4360b58e0c8c955a45a146a669',
height: 722420,
type: 'GENESIS',
qty: '1'
}
]
}
}
const txData = {
txData: {
txid: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
hash: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
version: 2,
size: 339,
locktime: 0,
vin: [
{
txid:
'8370db30d94761ab9a11b71ecd22541151bf6125c8c613f0f6fab8ab794565a7',
vout: 0,
scriptSig: {
asm:
'304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4[ALL|FORKID] 02791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851',
hex:
'47304402207e9631c53dfc8a9a793d1916469628c6b7c5780c01c2f676d51ef21b0ba4926f022069feb471ec869a49f8d108d0aaba04e7cd36e60a7500109d86537f55698930d4412102791b19a39165dbd83403d6df268d44fd621da30581b0b6e5cb15a7101ed58851'
},
sequence: 4294967295,
address: 'bitcoincash:qpvsg9vl9a5mlf37a7n3yce6pktdctn73qwgaqm3wq',
value: 0.00051303,
tokenQty: 0,
tokenQtyStr: '0',
tokenId: null
}
],
vout: [
{
value: 0,
n: 0,
scriptPubKey: {
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',
confirmations: 97398,
time: 1581773131,
blocktime: 1581773131,
blockheight: 622414,
isSlpTx: true,
tokenTxType: 'GENESIS',
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
tokenType: 1,
tokenTicker: 'TROUT',
tokenName: "Trout's test token",
tokenDecimals: 2,
tokenUri: 'troutsblog.com',
tokenDocHash: '',
isValidSlp: true
}
}
const balance = {
balance: {
utxos: [
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
vout: 1,
type: 'token',
qty: '1800',
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
address: 'bitcoincash:qrqy3kj7r822ps6628vwqq5k8hyjl6ey3y4eea2m4s'
}
],
txs: [
{
txid:
'078b2c48ed1db0d5d5996f2889b8d847a49200d0a781f6aa6752f740f312688f',
height: 717796
},
{
txid:
'a24a6a4abf06fabd799ecea4f8fac6a9ff21e6a8dd6169a3c2ebc03665329db9',
height: 717832
}
],
balances: [
{
tokenId:
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: '1800'
}
]
}
}
const status = {
status: {
startBlockHeight: 543376,
syncedBlockHeight: 722860,
chainBlockHeight: 722679
}
}
const tokenData01 = {
tokenType: 1,
txType: 'MINT',
tokenId: 'dd21be4532d93661e8ffe16db6535af0fb8ee1344d1fef81a193e2b4cfa9fbc9',
mintBatonVout: 2,
qty: {}
}
module.exports = {
tokenStats,
txData,
balance,
status,
tokenData01
}
+205 -1
View File
@@ -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
}
-255
View File
@@ -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')
})
})
})
+339
View File
@@ -0,0 +1,339 @@
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/psf-slp-indexer-mock')
describe('#PsfSlpIndexer', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#status', () => {
it('should GET status', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.status })
const result = await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'status')
assert.property(result.status, 'startBlockHeight')
assert.property(result.status, 'syncedBlockHeight')
assert.property(result.status, 'chainBlockHeight')
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'get').throws(new Error('test error'))
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'get').throws(testErr)
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#balance', () => {
it('should GET balance', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.balance })
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'balance')
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
assert.isArray(result.balance.utxos)
assert.isArray(result.balance.txs)
assert.isArray(result.balance.balances)
})
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.PsfSlpIndexer.balance(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input address must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tokenStats', () => {
it('should GET token stats', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenStats })
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'tokenData')
assert.property(result.tokenData, 'type')
assert.property(result.tokenData, 'ticker')
assert.property(result.tokenData, 'name')
assert.property(result.tokenData, 'tokenId')
assert.property(result.tokenData, 'documentUri')
assert.property(result.tokenData, 'documentHash')
assert.property(result.tokenData, 'decimals')
assert.property(result.tokenData, 'mintBatonIsActive')
assert.property(result.tokenData, 'tokensInCirculationBN')
assert.property(result.tokenData, 'tokensInCirculationStr')
assert.property(result.tokenData, 'blockCreated')
assert.property(result.tokenData, 'totalBurned')
assert.property(result.tokenData, 'totalMinted')
assert.property(result.tokenData, 'txs')
})
it('should throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input tokenId must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle request error', async () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#tx', () => {
it('should GET transaction data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.txData })
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'txData')
assert.property(result.txData, 'txid')
assert.property(result.txData, 'hash')
assert.property(result.txData, 'version')
assert.property(result.txData, 'size')
assert.property(result.txData, 'locktime')
assert.property(result.txData, 'vin')
assert.property(result.txData, 'vout')
assert.property(result.txData, 'hex')
assert.property(result.txData, 'blockhash')
assert.property(result.txData, 'confirmations')
assert.property(result.txData, 'time')
assert.property(result.txData, 'blocktime')
assert.property(result.txData, 'blockheight')
assert.property(result.txData, 'isSlpTx')
assert.property(result.txData, 'tokenTxType')
assert.property(result.txData, 'tokenId')
assert.property(result.txData, 'tokenType')
assert.property(result.txData, 'tokenTicker')
assert.property(result.txData, 'tokenName')
assert.property(result.txData, 'tokenDecimals')
assert.property(result.txData, 'tokenUri')
assert.property(result.txData, 'tokenDocHash')
assert.property(result.txData, 'isValidSlp')
})
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.PsfSlpIndexer.tx(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input txid must be a string.')
}
})
it('should handle axios error', async () => {
try {
// Stub the network call.
sandbox.stub(axios, 'post').throws(new Error('test error'))
const txid =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
await bchjs.PsfSlpIndexer.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should get tx from full node if not available from slp indexer', 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(false)
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, 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)
})
})
})
+19 -8
View File
@@ -22,10 +22,10 @@ describe('#TransactionLib', () => {
})
afterEach(() => sandbox.restore())
describe('#get', () => {
describe('#getOld', () => {
it('should throw an error if txid is not specified', async () => {
try {
await bchjs.Transaction.get()
await bchjs.Transaction.getOld()
assert.fail('Unexpected code path!')
} catch (err) {
@@ -45,7 +45,7 @@ describe('#TransactionLib', () => {
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.resolves(mockData.nonSlpTxDetails)
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -84,7 +84,7 @@ describe('#TransactionLib', () => {
const txid =
'266844d53e46bbd7dd37134688dffea6e54d944edff27a0add63dd0908839bc1'
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -114,7 +114,7 @@ describe('#TransactionLib', () => {
.stub(bchjs.Transaction.rawTransaction, 'getTxData')
.rejects(new Error('test error'))
await bchjs.Transaction.get(txid)
await bchjs.Transaction.getOld(txid)
assert.fail('Unexpected code path')
} catch (err) {
@@ -147,7 +147,7 @@ describe('#TransactionLib', () => {
const txid =
'874306bda204d3a5dd15e03ea5732cccdca4c33a52df35162cdd64e30ea7f04e'
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -195,7 +195,7 @@ describe('#TransactionLib', () => {
const txid =
'4640a734063ea79fa587a3cac38a70a2f6f3db0011e23514024185982110d0fa'
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -246,7 +246,7 @@ describe('#TransactionLib', () => {
const txid =
'6bc111fbf5b118021d68355ca19a0e77fa358dd931f284b2550f79a51ab4792a'
const result = await bchjs.Transaction.get(txid)
const result = await bchjs.Transaction.getOld(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Assert that there are stanardized properties.
@@ -705,4 +705,15 @@ describe('#TransactionLib', () => {
assert.equal(result.isSlpTx, true)
})
})
describe('#get', () => {
it('should proxy psf-slp-indexer', async () => {
// console.log('bchjs.Transaction: ', bchjs.Transaction)
sandbox.stub(bchjs.Transaction.psfSlpIndexer, 'tx').resolves('test data')
const result = await bchjs.Transaction.get()
assert.equal(result, 'test data')
})
})
})
+117 -7
View File
@@ -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,114 @@ 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)
// 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)
// 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)
})
})
})