Compare commits

..
42 Commits
Author SHA1 Message Date
Chris Troutner 62e56c832b Merge pull request #251 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Removing integration tests that trips rate limit
2022-10-13 10:33:55 -07:00
Chris Troutner d5b7e4ab1d fix(tests): Removing integration tests that trips rate limit 2022-10-13 10:31:22 -07:00
Chris Troutner 2137a75fce Merge pull request #250 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData2): Adding api documentation
2022-10-06 20:43:14 -07:00
Chris Troutner 8966cac210 fix(getTokenData2): Adding api documentation 2022-10-06 20:40:22 -07:00
Chris Troutner 9d32f828bc Cleaning up screen spam 2022-10-06 20:33:36 -07:00
Chris Troutner 91a7106818 Merge pull request #249 from Permissionless-Software-Foundation/ct-unstable
feat(getTokenData2): Adding new endpoint for token icons and other media
2022-10-06 20:27:00 -07:00
Chris Troutner 44e671dfbe fix(price): Removing dead price endpoint tests 2022-10-06 20:24:17 -07:00
Chris Troutner 979fb481de feat(getTokenData2): Adding token media endpoint 2022-10-06 18:30:39 -07:00
Chris Troutner 66253ed5a6 feat(bchjs.Price.current): Removing dead endpoint 2022-10-06 17:58:00 -07:00
Chris Troutner 6ef5ac0f7c feat(getTokenData2): Adding new endpoint for token icons and other media 2022-10-06 17:57:11 -07:00
Chris Troutner 21c322d0df Merge pull request #247 from Permissionless-Software-Foundation/ct-unstable
fix(getTokenData): Adding ability to retrieve tx history
2022-07-12 14:48:22 -07:00
Chris Troutner 1b0e9ef0ff fix(getTokenData): Adding ability to retrieve tx history 2022-07-12 14:24:09 -07:00
Chris Troutner 78776bee93 Merge pull request #246 from Permissionless-Software-Foundation/ct-unstable
fix(deps): npm audit shows now vulns
2022-07-11 07:24:47 -07:00
Chris Troutner a6e3155f37 fix(deps): npm audit shows now vulns 2022-07-11 07:20:20 -07:00
Chris Troutner 47f60bccde Merge pull request #245 from Permissionless-Software-Foundation/ct-unstable
fix(ecash): Adding token support for ecash
2022-07-11 07:11:36 -07:00
Chris Troutner d0a3b27682 fix(ecash): Adding token support for ecash 2022-07-11 07:09:44 -07:00
Chris Troutner 372d1b099d Merge pull request #244 from Permissionless-Software-Foundation/ct-unstable
fix(address): Adding toEtokenAddress() function
2022-07-10 09:50:44 -07:00
Chris Troutner dbfaf9c4ab fix(address): Adding toEtokenAddress() function 2022-07-10 09:47:49 -07:00
Chris Troutner eaa08f74e7 Merge pull request #243 from Permissionless-Software-Foundation/ct-unstable
fix(test): Updating integration test
2022-05-15 07:53:53 -07:00
Chris Troutner 9ac620ee64 fix(test): Updating integration test 2022-05-15 07:50:57 -07:00
Chris Troutner ac0a4b0407 Merge pull request #242 from Permissionless-Software-Foundation/ct-unstable
feat(Utxo.isValid()): can test if a UTXO has been spent
2022-05-10 09:34:37 -07:00
Chris Troutner 686012037f feat(Utxo.isValid()): can test if a UTXO has been spent 2022-05-10 09:30:57 -07:00
Chris Troutner cfb7b9f779 Merge pull request #241 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Fixing broken UTXO.get() test
2022-05-09 10:35:21 -07:00
Chris Troutner 06e5af4b5d fix(tests): Fixing broken UTXO.get() test 2022-05-09 10:33:06 -07:00
Chris Troutner 249e93a3f4 Merge pull request #240 from Permissionless-Software-Foundation/ct-unstable
fix(test): Updating UTXO integration tests
2022-05-09 10:25:58 -07:00
Chris Troutner 761a2d226c fix(test): Updating UTXO integration tests 2022-05-09 10:24:00 -07:00
Chris Troutner 5e5e636831 Merge pull request #239 from Permissionless-Software-Foundation/ct-unstable
fix(tests): Fixing broken integration tests for token data
2022-05-09 09:49:02 -07:00
Chris Troutner 2adc2dfebb fix(tests): Fixing broken integration tests for token data 2022-05-09 09:45:28 -07:00
Chris Troutner f9bc44ccb7 Merge pull request #238 from Permissionless-Software-Foundation/ct-unstable
fix(token data): Returning CID rather than content itself
2022-05-07 20:59:55 -07:00
Chris Troutner 298b8f56ff Merge branch 'temp-token-data' into ct-unstable 2022-05-07 20:54:49 -07:00
Chris Troutner 139da8cf7b fix(token data): Returning CID rather than content itself 2022-05-07 20:54:20 -07:00
Chris Troutner 08e03c14d8 Merge pull request #234 from Permissionless-Software-Foundation/dh-psfSlpIndexer
feat(slpindexer): Added bchjs.PsfSlpIndexer.getTokenData()
2022-05-07 20:25:15 -07:00
Chris Troutner 7da010e7af Using config.restURL instead of hard-coded URL 2022-05-07 20:20:30 -07:00
Chris Troutner 7b6eebe7c7 Merge branch 'master' into dh-psfSlpIndexer 2022-05-07 20:05:25 -07:00
Chris Troutner 843f5eacef Merge pull request #237 from Permissionless-Software-Foundation/ct-unstable
fix(Utxo.get()): Added additional integration tests
2022-05-05 12:08:46 -07:00
Chris Troutner 416e302ca5 fix(Utxo.get()): Added additional integration tests 2022-05-05 12:06:59 -07:00
Chris Troutner 1c66da70b6 Merge pull request #236 from Permissionless-Software-Foundation/ct-unstable
fix(mintNFTGroupOpReturn()): Updating to use UTXOs from psf-slp-indexer
2022-05-05 10:45:59 -07:00
Chris Troutner 59e92adf0b fix(mintNFTGroupOpReturn()): Updating to use UTXOs from psf-slp-indexer 2022-05-05 10:43:40 -07:00
Chris Troutner f369411ccb Merge pull request #235 from Permissionless-Software-Foundation/ct-unstable
Updating Utxo.get()
2022-05-05 10:05:36 -07:00
Chris Troutner 0504cf7b8d fix(Utxo.get()): Adding filtering for Group batons 2022-05-05 10:03:35 -07:00
Chris Troutner 88ae47cade fix(Utxo.get()): Adding prototype NFT filtering 2022-05-04 17:44:53 -07:00
Daniel Gonzalez d144b13500 feat(slpindexer): Added bchjs.PsfSlpIndexer.getTokenData() 2022-05-04 18:29:54 -04:00
18 changed files with 1600 additions and 2157 deletions
+858 -2085
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -12,7 +12,7 @@
"test": "nyc mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
"test:integration": "npm run test:integration:bchn",
"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:abc": "export RESTURL=https://abc.fullstack.cash/v5/ && 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: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/",
+30 -1
View File
@@ -133,7 +133,7 @@ class Address {
* @api Address.toEcashAddress() toEcashAddress()
* @apiName toEcashAddress
* @apiGroup Address
* @apiDescription Convert legacy to cashAddress format
* @apiDescription Convert legacy to eCash (XEC) format
*
* @apiExample Example usage:
* // mainnet
@@ -158,6 +158,35 @@ class Address {
return ecashAddress.split(':')[1]
}
/**
* @api Address.toEtokenAddress() toEtokenAddress()
* @apiName toEtokenAddress
* @apiGroup Address
* @apiDescription Convert legacy to eToken (XEC) format
*
* @apiExample Example usage:
* // mainnet
* bchjs.Address.toEtokenAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2')
* // etoken:qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
*
* // mainnet no prefix
* bchjs.Address.toEtokenAddress('bitcoincash:qq50d800hgunr8u4trz3uuppspk3mds0dy9978plt2', false)
* // qq50d800hgunr8u4trz3uuppspk3mds0dyug2v69da
*
*/
toEtokenAddress (address, prefix = true) {
const decoded = this._decode(address)
const etokenAddress = cashaddr.encode(
'etoken',
decoded.type,
decoded.hash
)
if (prefix) return etokenAddress
return etokenAddress.split(':')[1]
}
/**
* @api Address.ecashtoCashAddress() ecashtoCashAddress()
* @apiName ecashtoCashAddress
+5
View File
@@ -7,6 +7,7 @@ const axios = require('axios')
// Local libraries.
const Blockchain = require('./blockchain')
// const Address = require('./address')
// let _this
@@ -32,7 +33,9 @@ class ElectrumX {
}
}
// Encapsulate dependencies
this.blockchain = new Blockchain(config)
// this.address = new Address(config)
// _this = this
}
@@ -104,6 +107,8 @@ class ElectrumX {
*/
async utxo (address) {
try {
// console.log(`electrumx.js/utxo() restURL: ${this.restURL}`)
// Handle single address.
if (typeof address === 'string') {
const response = await axios.get(
+13 -13
View File
@@ -30,19 +30,19 @@ class Price {
}
// This endpoint is deprecated. Documentation removed.
async current (currency = 'usd') {
try {
const response = await this.axios.get(
`https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}`
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
return response.data.price
} catch (err) {
if (err.response && err.response.data) throw err.response.data
else throw err
}
}
// async current (currency = 'usd') {
// try {
// const response = await this.axios.get(
// `https://index-api.bitcoin.com/api/v0/cash/price/${currency.toLowerCase()}`
// )
// // console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
//
// return response.data.price
// } catch (err) {
// if (err.response && err.response.data) throw err.response.data
// else throw err
// }
// }
/**
* @api price.getUsd() getUsd()
+164 -1
View File
@@ -41,7 +41,7 @@ class PsfSlpIndexer {
this.rawTransaction = new RawTransaction(config)
this.slpUtils = new SlpUtils(config)
// _this = this
// _this = this
}
/**
@@ -194,6 +194,7 @@ class PsfSlpIndexer {
* }
* }
*
*
*/
async tokenStats (tokenId, withTxHistory = false) {
@@ -374,6 +375,168 @@ class PsfSlpIndexer {
return false
}
}
/**
* @api PsfSlpIndexer.getTokenData() getTokenData()
* @apiName Token Data
* @apiGroup PSF SLP
* @apiDescription Get mutable and immutable data if the token contains them.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenData = await bchjs.PsfSlpIndexer.getTokenData('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
* console.log(tokenData)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* genesisData: {
* 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'
* ]
* },
* immutableData :{
* issuer:"FullStack.cash.",
* website:"https://fullstack.cash/",
* dateCreated:"2022-01-11"
* },
* mutableData :{
* "tokenIcon":"https://gateway.ipfs.io/ipfs/bafybeiehitanirn5gmhqjg44xrmdtomn4n5lu5yjoepsvgpswk5mggaw6i/LP_logo-1.png",
* "about":"Mutable data managed with npm package: https://www.npmjs.com/package/slp-mutable-data"
* }
* }
*
*/
async getTokenData (tokenId, withTxHistory = false) {
try {
const url = `${this.restURL}psf/slp/token/data`
// console.log(`url: ${url}`)
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
// 'https://bchn.fullstack.cash/v5/psf/slp/token/data/',
url,
{ tokenId, withTxHistory },
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.getTokenData2() getTokenData2()
* @apiName Token Data
* @apiGroup PSF SLP
* @apiDescription Get token icon and other media associated with a token.
*
* Get the icon for a token, given it's token ID.
* This function expects a string input of a token ID property.
* This function returns an object with a tokenIcon property that contains
* the URL to the icon.
*
* The output object always have these properties:
* - tokenIcon: A url to the token icon, if it exists.
* - tokenStats: Data about the token from psf-slp-indexer.
* - optimizedTokenIcon: An alternative, potentially more optimal, url to the token icon, if it exists.
* - iconRepoCompatible: true if the token icon is available via token.bch.sx
* - ps002Compatible: true if the token icon is compatible with PS007 specification.
*
* @apiExample Example usage:
* (async () => {
* try {
* let tokenData = await bchjs.PsfSlpIndexer.getTokenData2('a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2')
* console.log(tokenData)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* tokenStats: {
* type: 1,
* ticker: 'CTAIA006',
* name: 'CTAIA006 - AI Art by Chris Troutner',
* tokenId: '0e4543f820699294ab57e02ee2b1815a8bbc7b17a4333e4a138034e4b2324a61',
* documentUri: 'ipfs://bafybeia5yuq7rg6jmwquako7t277cwrobcunz7cumqrv4wn6bgfvthemku',
* documentHash: '78a00e9db312b8fff4e5c37cf592be83e6bab7f3bd5a54c9545bad5d4f3ee0f5',
* decimals: 0,
* mintBatonIsActive: false,
* tokensInCirculationBN: '1',
* tokensInCirculationStr: '1',
* blockCreated: 757507,
* totalBurned: '0',
* totalMinted: '1'
* },
* mutableData: {
* tokenIcon: 'https://bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm.ipfs.w3s.link/whale-night-sky-01.png',
* fullSizedUrl: '',
* about: 'This NFT was created using the PSF Token Studio at https://nft-creator.fullstack.cash',
* userData: ''
* },
* immutableData: {
* issuer: 'http://psfoundation.cash',
* website: 'https://nft-creator.fullstack.cash',
* dateCreated: '9/12/2022, 5:17:38 PM',
* userData: '{\n' +
* ' "title": "CTAIA006 - AI Art by Chris Troutner",\n' +
* ' "about": "AI generated art. Generated from DALL-E at https://labs.openai.com",\n' +
* ' "prompt": "whale swimming through a sky full of stars",\n' +
* ' "algorithm": "DALL-E (stable diffusion)",\n' +
* ' "set": "1-of-2"\n' +
* '}'
* },
* tokenIcon: 'https://bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm.ipfs.w3s.link/whale-night-sky-01.png',
* fullSizedUrl: '',
* optimizedTokenIcon: 'https://p2wdb-gateway-678.fullstack.cash/ipfs/bafybeihiv5jvlhoymmbous3h2akotogj6b7hruhjcj3zq7dsfteimuuttm/whale-night-sky-01.png',
* optimizedFullSizedUrl: '',
* iconRepoCompatible: false,
* ps002Compatible: true
* }
*
*/
async getTokenData2 (tokenId) {
try {
const url = `${this.restURL}psf/slp/token/data2`
// console.log(`url: ${url}`)
// Handle single address.
if (typeof tokenId === 'string') {
const response = await axios.post(
// 'https://bchn.fullstack.cash/v5/psf/slp/token/data/',
url,
{ tokenId },
this.axiosOptions
)
return response.data
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
// console.log('error: ', error)
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = PsfSlpIndexer
+1 -1
View File
@@ -147,7 +147,7 @@ class Nft1 {
// Loop through the tokenUtxos array and find the minting baton.
let mintBatonUtxo
for (let i = 0; i < tokenUtxos.length; i++) {
if (tokenUtxos[i].utxoType === 'minting-baton') {
if (tokenUtxos[i].utxoType === 'minting-baton' || tokenUtxos[i].type === 'baton') {
mintBatonUtxo = tokenUtxos[i]
}
}
+95 -12
View File
@@ -10,6 +10,7 @@ const Electrumx = require('./electrumx')
const Slp = require('./slp/slp')
const PsfSlpIndexer = require('./psf-slp-indexer')
const BigNumber = require('bignumber.js')
const Blockchain = require('./blockchain')
class UTXO {
constructor (config = {}) {
@@ -18,6 +19,7 @@ class UTXO {
this.slp = new Slp(config)
this.psfSlpIndexer = new PsfSlpIndexer(config)
this.BigNumber = BigNumber
this.blockchain = new Blockchain(config)
}
/**
@@ -80,7 +82,10 @@ class UTXO {
}
// Ensure the address is a BCH address.
const addr = this.slp.Address.toCashAddress(address)
let addr = address
if (!addr.includes('ecash')) {
addr = this.slp.Address.toCashAddress(address)
}
// Get the UTXOs associated with the address.
const utxoData = await this.electrumx.utxo(addr)
@@ -125,6 +130,7 @@ class UTXO {
thisUtxo.qty = thisSlpUtxo.qty
thisUtxo.tokenId = thisSlpUtxo.tokenId
thisUtxo.address = thisSlpUtxo.address
thisUtxo.tokenType = thisSlpUtxo.tokenType
break
}
@@ -151,19 +157,37 @@ class UTXO {
// Get token UTXOs
let type1TokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token'
x => x.isSlp === true && x.type === 'token' && x.tokenType === 1
)
// Hydrate the UTXOs with additional token data.
type1TokenUtxos = await this.hydrateTokenData(type1TokenUtxos)
// Collect and hydrate any baton UTXOs
// Collect and hydrate any type1 baton UTXOs
const bchUtxos = utxos.filter(x => x.isSlp === false)
let type1BatonUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'baton'
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 1
)
type1BatonUtxos = await this.hydrateTokenData(type1BatonUtxos)
// Collect and hydrate NFT Group tokens
let nftGroupTokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token' && x.tokenType === 129
)
nftGroupTokenUtxos = await this.hydrateTokenData(nftGroupTokenUtxos)
// Collect and hydrate any Group baton UTXOs
let groupBatonUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'baton' && x.tokenType === 129
)
groupBatonUtxos = await this.hydrateTokenData(groupBatonUtxos)
// Collect and hydrate NFT child tokens
let nftChildTokenUtxos = utxos.filter(
x => x.isSlp === true && x.type === 'token' && x.tokenType === 65
)
nftChildTokenUtxos = await this.hydrateTokenData(nftChildTokenUtxos)
// Isolate any UTXOs that are marked null by the SLP indexer.
const nullUtxos = utxos.filter(x => x.isSlp === null)
@@ -175,7 +199,13 @@ class UTXO {
tokens: type1TokenUtxos,
mintBatons: type1BatonUtxos
},
nft: {} // Allocated for future support of NFT spec.
group: {
tokens: nftGroupTokenUtxos,
mintBatons: groupBatonUtxos
},
nft: {
tokens: nftChildTokenUtxos
}
},
nullUtxos
}
@@ -229,14 +259,16 @@ class UTXO {
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()
if (thisUtxo.type !== 'baton') {
// Calculate the real token quantity
const qty = new BigNumber(thisUtxo.qty).dividedBy(
10 ** parseInt(thisUtxo.decimals)
)
thisUtxo.qtyStr = qty.toString()
// tokenQty is property expected by SLP.tokentype1.js library
thisUtxo.tokenQty = thisUtxo.qtyStr
// tokenQty is property expected by SLP.tokentype1.js library
thisUtxo.tokenQty = thisUtxo.qtyStr
}
}
return utxoAry
@@ -309,6 +341,57 @@ class UTXO {
return utxos[largestIndex]
}
/**
* @api Utxo.isValid() isValid()
* @apiName isValid
* @apiGroup UTXO
* @apiDescription Validate that UTXO exists and is still spendable.
*
* Given a UTXO, this method will return true if the UTXO is still in the
* mempool and still valid for spending. It will return false if the UTXO
* has been spent.
*
* @apiExample Example usage:
* (async () => {
* try {
* const utxos = await bchjs.Utxo.get('bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z');
* const isValid = bchjs.Utxo.isValid(utxos.bchUtxos[0])
* console.log(isValid);
* } catch(error) {
* console.error(error)
* }
* })()
*
* // returns
* true
*/
async isValid (utxo) {
try {
// console.log('utxo: ', utxo)
// Convert different properties from different indexers
const txid = utxo.txid || utxo.tx_hash
const vout = utxo.vout | utxo.tx_pos
// Query the full node
const txOut = await this.blockchain.getTxOut(txid, vout, true)
// console.log('txOut: ', txOut)
// Simplify results to either true or false.
let isValid = null
if (txOut === null) {
isValid = false
} else {
isValid = true
}
return isValid
} catch (err) {
console.log('Error in Utxo.isValid()')
throw err
}
}
}
module.exports = UTXO
@@ -0,0 +1,38 @@
/*
Integration tests for the psf-slp-indexer.js library, specific to the eCash
blockchain.
*/
// Global npm libraries
const assert = require('chai').assert
// Local libraries
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('#balance', () => {
it('should get token balance for an ecash address', async () => {
const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09'
const result = await bchjs.PsfSlpIndexer.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result.balance, 'utxos')
assert.property(result.balance, 'txs')
assert.property(result.balance, 'balances')
})
})
})
// Promise-based sleep function
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+12 -14
View File
@@ -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,25 +14,23 @@ 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'
const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const addr = 'ecash:qr5c4hfy52zn87484cucvzle5pljz0gtr5vhtw9z09'
// const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.property(result[0], 'address')
assert.property(result[0], 'bchUtxos')
assert.property(result[0], 'nullUtxos')
assert.property(result[0], 'slpUtxos')
assert.isArray(result[0].bchUtxos)
assert.isArray(result[0].nullUtxos)
// assert.isArray(result)
assert.property(result, 'address')
assert.property(result, 'bchUtxos')
assert.property(result, 'nullUtxos')
assert.property(result, 'slpUtxos')
assert.isArray(result.bchUtxos)
assert.isArray(result.nullUtxos)
})
})
*/
})
function sleep (ms) {
@@ -1,9 +1,12 @@
/*
Integration tests for the psf-slp-indexer.js library
Integration tests for the psf-slp-indexer.js library, specific to the BCH
blockchain.
*/
// Global npm libraries
const assert = require('chai').assert
// Local libraries
const BCHJS = require('../../../../src/bch-js')
let bchjs
@@ -124,6 +127,57 @@ describe('#psf-slp-indexer', () => {
}
})
})
describe('#getTokenData', () => {
it('should get token data', async () => {
const tokenId =
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
// console.log('result: ', result)
assert.property(result, 'genesisData')
assert.property(result, 'immutableData')
assert.property(result, 'mutableData')
assert.isObject(result.genesisData)
assert.isString(result.immutableData)
assert.isString(result.mutableData)
})
it('should get token data with a transaction history', async () => {
const tokenId = '43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d'
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId, true)
// console.log('result: ', result)
assert.isArray(result.genesisData.txs)
})
})
// This test is commented out because it can not succeed in the BVT without
// tripping rate limits.
// describe('#getTokenData2', () => {
// it('should get token data', async () => {
// const tokenId =
// 'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
//
// // bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
// const result = await bchjs.PsfSlpIndexer.getTokenData2(tokenId)
// // console.log('result: ', result)
//
// assert.property(result, 'tokenStats')
// assert.property(result, 'mutableData')
// assert.property(result, 'immutableData')
// assert.property(result, 'tokenIcon')
// assert.property(result, 'fullSizedUrl')
// assert.property(result, 'optimizedTokenIcon')
// assert.property(result, 'optimizedFullSizedUrl')
// assert.property(result, 'iconRepoCompatible')
// assert.property(result, 'ps002Compatible')
// })
// })
})
// Promise-based sleep function
@@ -83,10 +83,8 @@ describe('#UTXO', () => {
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 minting batons', async () => {
const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj'
it('should handle Type1 minting batons', async () => {
const addr = 'simpleledger:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpv4awvv7m7'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
@@ -105,13 +103,82 @@ describe('#UTXO', () => {
assert.equal(result.slpUtxos.type1.tokens.length, 0)
})
it('should handle Group NFTs', async () => {
const addr = 'bitcoincash:qrnghwrfgccf3s5e9wnglzxegcnhje9rkcwv2eka33'
it('should filter Group tokens and mint batons', async () => {
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(result.nullUtxos.length, 0)
assert.isAbove(result.slpUtxos.group.tokens.length, 0)
assert.isAbove(result.slpUtxos.group.mintBatons.length, 0)
})
it('should filter NFTs', async () => {
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
const result = await bchjs.Utxo.get(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result.slpUtxos.nft.tokens.length, 0)
})
})
describe('#isValid', () => {
it('should return true for valid UTXO with fullnode properties', async () => {
const utxo = {
txid: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
vout: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result)
assert.equal(result, true)
})
it('should return true for valid UTXO with fulcrum properties', async () => {
const utxo = {
tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
tx_pos: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result)
assert.equal(result, true)
})
it('should return true for valid UTXO with fullnode properties', async () => {
const utxo = {
txid: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
vout: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result)
assert.equal(result, false)
})
it('should return true for valid UTXO with fulcrum properties', async () => {
const utxo = {
tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
tx_pos: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result
assert.equal(result, false)
})
it('should process output of Utxo.get()', async () => {
const utxo = await bchjs.Utxo.get('bitcoincash:qr4yscpw9jgq8ltajfeknpj32kamkf9knujffcdhyq')
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
const result = await bchjs.Utxo.isValid(utxo.bchUtxos[0])
assert.equal(result, true)
})
})
})
+9 -8
View File
@@ -7,14 +7,14 @@ describe('#price', () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1500)
})
describe('#current', () => {
describe('#single currency', () => {
it('should get current price for single currency', async () => {
const result = await bchjs.Price.current('usd')
assert.notEqual(0, result)
})
})
})
// describe('#current', () => {
// describe('#single currency', () => {
// it('should get current price for single currency', async () => {
// const result = await bchjs.Price.current('usd')
// assert.notEqual(0, result)
// })
// })
// })
describe('#getUsd', () => {
it('should get the USD price of BCH', async () => {
@@ -33,6 +33,7 @@ describe('#price', () => {
assert.isNumber(result)
})
})
describe('#getBchUsd', () => {
it('should get the USD price of BCH', async () => {
const result = await bchjs.Price.getBchUsd()
+40
View File
@@ -935,4 +935,44 @@ describe('#address.js', () => {
})
})
})
describe('#toEcashAddress', () => {
it('should convert a BCH address to an eCash address', () => {
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
const result = bchjs.Address.toEcashAddress(cashAddr)
// console.log('result: ', result)
assert.equal(result, 'ecash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s8f6wu02p')
})
it('should convert a BCH address to an eCash address without a prefix', () => {
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
const result = bchjs.Address.toEcashAddress(cashAddr, false)
// console.log('result: ', result)
assert.equal(result, 'qr24z7q6s26d9wr078lr2pxsmxg22cyn9s8f6wu02p')
})
})
describe('#toEtokenAddress', () => {
it('should convert a BCH address to an eToken address', () => {
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
const result = bchjs.Address.toEtokenAddress(cashAddr)
// console.log('result: ', result)
assert.equal(result, 'etoken:qr24z7q6s26d9wr078lr2pxsmxg22cyn9sfhnv2gwk')
})
it('should convert a BCH address to an eToken address without a prefix', () => {
const cashAddr = 'bitcoincash:qr24z7q6s26d9wr078lr2pxsmxg22cyn9s7yw984vk'
const result = bchjs.Address.toEtokenAddress(cashAddr, false)
// console.log('result: ', result)
assert.equal(result, 'qr24z7q6s26d9wr078lr2pxsmxg22cyn9sfhnv2gwk')
})
})
})
+62 -1
View File
@@ -144,10 +144,71 @@ const tokenData01 = {
qty: {}
}
const tokenData = {
genesisData: tokenStats.tokenData,
immutableData: 'ipfs://bafybeidzfi3sictag4noy746gniyiwwcgio565xuzelmjfwxnga6qyiyai',
mutableData: 'ipfs://bafybeie6t5uyupddc7azms737xg4hxrj7i5t5ov3lb5g2qeehaujj6ak64'
}
const tokenMedia01 = {
tokenStats: {
type: 65,
ticker: 'TEST02',
name: 'How to Send BCH and Tokens',
tokenId: 'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b',
documentUri: 'ipfs://bafybeigjujbb55wqr5lns7z7vleg5cfp4yjwqp4swisxpgxl4xsp5tngai',
documentHash: '845f803d8dd3c2b6be94a6cf3a445b54c36f2e01bd5c9ab4c23f37dbd531489e',
decimals: 0,
mintBatonIsActive: false,
tokensInCirculationBN: '0',
tokensInCirculationStr: '0',
blockCreated: 740089,
totalBurned: '1',
totalMinted: '1',
parentGroupId: 'c9c425f2c6352697c6665a53e035cbad8a44c4b1e36491a1838dc4655479aa09'
},
mutableData: {
updated: '2022-05-14T15:27:44.429Z',
tokenIcon: 'https://bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy.ipfs.dweb.link/send-bch-token-icon.png',
tokenInfo: 'https://token.fullstack.cash/?tokenid=d9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b',
description: 'This is an NFT representing a video. This is a test token.',
issuer: 'Chris Troutner',
forSale: false,
display: true,
currentOwner: {
bchAddr: 'bitcoincash:qqy7jcrm3vmtqs96r878hy2kx90mn84f25ujqw9z5h',
name: 'Chris Troutner',
contactEmail: 'chris.troutner@gmail.com'
},
content: {
youtube: 'https://youtu.be/WZRwkLPtkaI',
rumble: 'https://rumble.com/v14n00h-how-to-send-bch-and-tokens.html',
odysee: 'https://odysee.com/@trout:5/how-to-send-bch-tokens:0',
lbry: 'lbry://@trout#5/how-to-send-bch-tokens#0',
ipfs: 'bafybeigf3ky5i6fyxwk5bjmtsr6urqmlx4zq2lqathgguytey67iinh4be',
filecoin: 'https://bafybeigf3ky5i6fyxwk5bjmtsr6urqmlx4zq2lqathgguytey67iinh4be.ipfs.dweb.link/send-bch-2022-02-20_08.22.45.mp4'
}
},
immutableData: {
creationDate: '2022-05-13T21:32:11.619Z',
issuer: 'Chris Troutner',
license: 'https://bafybeidnkhjfsbihp4gquwqrs6y35jfpcriafymceszwvkundjkwk546pi.ipfs.dweb.link/copyright.txt',
name: 'How to Send BCH and Tokens'
},
tokenIcon: 'https://bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy.ipfs.dweb.link/send-bch-token-icon.png',
fullSizedUrl: '',
optimizedTokenIcon: 'https://p2wdb-gateway-678.fullstack.cash/ipfs/bafybeiefg3nd5iognbqztkpi5hj34dmwkqfe7v7xeayn2nhhatinmjmzcy/send-bch-token-icon.png',
optimizedFullSizedUrl: '',
iconRepoCompatible: false,
ps002Compatible: true
}
module.exports = {
tokenStats,
txData,
balance,
status,
tokenData01
tokenData01,
tokenData,
tokenMedia01
}
+12 -12
View File
@@ -19,18 +19,18 @@ describe('#price', () => {
afterEach(() => sandbox.restore())
describe('#current', () => {
it('should get current price for single currency', async () => {
sandbox
.stub(bchjs.Price.axios, 'get')
.resolves({ data: { price: 24905 } })
const result = await bchjs.Price.current('usd')
// console.log(result)
assert.isNumber(result)
})
})
// describe('#current', () => {
// it('should get current price for single currency', async () => {
// sandbox
// .stub(bchjs.Price.axios, 'get')
// .resolves({ data: { price: 24905 } })
//
// const result = await bchjs.Price.current('usd')
// // console.log(result)
//
// assert.isNumber(result)
// })
// })
describe('#getUsd', () => {
it('should get the USD price of BCH', async () => {
+99
View File
@@ -336,4 +336,103 @@ describe('#PsfSlpIndexer', () => {
assert.equal(result, false)
})
})
describe('#getTokenData', () => {
it('should GET token data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenData })
const tokenId =
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2'
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
assert.property(result, 'genesisData')
assert.property(result, 'immutableData')
assert.property(result, 'mutableData')
assert.isObject(result.genesisData)
assert.isString(result.immutableData)
assert.isString(result.mutableData)
assert.property(result.genesisData, 'type')
assert.property(result.genesisData, 'ticker')
assert.property(result.genesisData, 'name')
assert.property(result.genesisData, 'tokenId')
assert.property(result.genesisData, 'documentUri')
assert.property(result.genesisData, 'documentHash')
assert.property(result.genesisData, 'decimals')
assert.property(result.genesisData, 'mintBatonIsActive')
assert.property(result.genesisData, 'tokensInCirculationBN')
assert.property(result.genesisData, 'tokensInCirculationStr')
assert.property(result.genesisData, 'blockCreated')
assert.property(result.genesisData, 'totalBurned')
assert.property(result.genesisData, 'totalMinted')
})
it('should throw an error for improper input', async () => {
try {
const tokenId = 12345
await bchjs.PsfSlpIndexer.getTokenData(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.getTokenData(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.getTokenData(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
}
})
})
describe('#getTokenData2', () => {
it('should get token data', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.tokenMedia01 })
const tokenId =
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
const result = await bchjs.PsfSlpIndexer.getTokenData2(tokenId)
// console.log('result: ', result)
assert.property(result, 'tokenStats')
assert.property(result, 'mutableData')
assert.property(result, 'immutableData')
assert.property(result, 'tokenIcon')
assert.property(result, 'fullSizedUrl')
assert.property(result, 'optimizedTokenIcon')
assert.property(result, 'optimizedFullSizedUrl')
assert.property(result, 'iconRepoCompatible')
assert.property(result, 'ps002Compatible')
})
})
})
+32
View File
@@ -174,4 +174,36 @@ describe('#utxo', () => {
assert.equal(result.nullUtxos.length, 0)
})
})
describe('#isValid', () => {
it('should return false if getTxOut() returns null', async () => {
// Mock dependencies
sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves(null)
const utxo = {
tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
tx_pos: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result
assert.equal(result, false)
})
it('should return true if getTxOut() returns non-null output', async () => {
// Mock dependencies
sandbox.stub(bchjs.Utxo.blockchain, 'getTxOut').resolves({ a: 'b' })
const utxo = {
tx_hash: 'b94e1ff82eb5781f98296f0af2488ff06202f12ee92b0175963b8dba688d1b40',
tx_pos: 0
}
const result = await bchjs.Utxo.isValid(utxo)
// console.log('result: ', result
assert.equal(result, true)
})
})
})