mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-22 00:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3152e87b5d | ||
|
|
4b66f3841d | ||
|
|
9f98a47682 | ||
|
|
0833e8ca78 | ||
|
|
e01a18b65e | ||
|
|
055cd7feca | ||
|
|
906d9b2094 | ||
|
|
9326fd6e59 | ||
|
|
1d91eed5df | ||
|
|
62e56c832b | ||
|
|
d5b7e4ab1d | ||
|
|
2137a75fce | ||
|
|
8966cac210 | ||
|
|
9d32f828bc | ||
|
|
91a7106818 | ||
|
|
44e671dfbe | ||
|
|
979fb481de | ||
|
|
66253ed5a6 | ||
|
|
6ef5ac0f7c | ||
|
|
21c322d0df | ||
|
|
1b0e9ef0ff | ||
|
|
78776bee93 | ||
|
|
a6e3155f37 | ||
|
|
47f60bccde | ||
|
|
d0a3b27682 | ||
|
|
372d1b099d | ||
|
|
dbfaf9c4ab | ||
|
|
eaa08f74e7 | ||
|
|
9ac620ee64 | ||
|
|
ac0a4b0407 | ||
|
|
686012037f | ||
|
|
cfb7b9f779 | ||
|
|
06e5af4b5d | ||
|
|
249e93a3f4 | ||
|
|
761a2d226c | ||
|
|
5e5e636831 | ||
|
|
2adc2dfebb | ||
|
|
f9bc44ccb7 | ||
|
|
298b8f56ff | ||
|
|
139da8cf7b |
Generated
+8269
-9520
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -12,14 +12,14 @@
|
||||
"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/",
|
||||
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v5/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v5/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||
"test:integration:decatur:bchn": "export RESTURL=http://192.168.2.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:decatur:abc": "export RESTURL=http://192.168.2.142: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/",
|
||||
@@ -40,7 +40,7 @@
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/bitcoincashjs-lib": "4.0.3",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"axios": "0.26.1",
|
||||
"bc-bip68": "1.0.5",
|
||||
|
||||
+30
-1
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -32,6 +32,28 @@ class Encryption {
|
||||
_this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api encryption.getPubKey() getPubKey()
|
||||
* @apiName Encryption getPubKey()
|
||||
* @apiGroup Encryption
|
||||
* @apiDescription Get the public key for an address
|
||||
* Given an address, the command will search the blockchain for a public
|
||||
* key associated with that address. The address needs to have made at least
|
||||
* one spend transaction, in order for its public key to be retrievable.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
*(async () => {
|
||||
* try {
|
||||
* const addr = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
* const pubkey = await bchjs.encryption.getPubKey(addr);
|
||||
* console.log(pubkey);
|
||||
* } catch(err) {
|
||||
* console.error(err)
|
||||
* }
|
||||
*})()
|
||||
*
|
||||
*/
|
||||
|
||||
// Search the blockchain for a public key associated with a BCH address.
|
||||
async getPubKey (addr) {
|
||||
try {
|
||||
|
||||
+13
-13
@@ -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()
|
||||
|
||||
+97
-1
@@ -194,6 +194,7 @@ class PsfSlpIndexer {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
async tokenStats (tokenId, withTxHistory = false) {
|
||||
@@ -420,11 +421,104 @@ class PsfSlpIndexer {
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async getTokenData (tokenId) {
|
||||
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(
|
||||
@@ -435,8 +529,10 @@ class PsfSlpIndexer {
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
+71
-1
@@ -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)
|
||||
@@ -104,6 +109,11 @@ class UTXO {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the sync status of the SLP indexer
|
||||
const syncStatus = await this.psfSlpIndexer.status()
|
||||
const slpIndexerHeight = syncStatus.status.syncedBlockHeight
|
||||
const chainBlockHeight = syncStatus.status.chainBlockHeight
|
||||
|
||||
// Loop through the Fulcrum UTXOs.
|
||||
for (let i = 0; i < utxos.length; i++) {
|
||||
const thisUtxo = utxos[i]
|
||||
@@ -147,6 +157,15 @@ class UTXO {
|
||||
thisUtxo.isSlp = false
|
||||
}
|
||||
// console.log(`thisUtxo.isSlp: ${thisUtxo.isSlp}`)
|
||||
|
||||
// If the SLP indexer more than 1 block behind the full node, then
|
||||
// move any BCH UTXOs of 600 sats or less into the null array. This
|
||||
// protects token UTXOs from being burned accidentally.
|
||||
if (slpIndexerHeight < chainBlockHeight - 1) {
|
||||
if (thisUtxo.value < 601) {
|
||||
thisUtxo.isSlp = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,6 +355,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,27 @@
|
||||
/*
|
||||
This test is used to interrogate the behavior of bch-js when a psf-slp-indexer
|
||||
panicks and starts indexing from SLP genesis. In this corner-case, bch-js
|
||||
should detect the indexer is out of sync, and protect any token UTXOs by
|
||||
moving any UTXO under 600 sats into the null array.
|
||||
|
||||
TO RUN THIS TEST:
|
||||
- Reset a local instance of psf-slp-indexer to sync from genesis.
|
||||
- Start a local copy of bch-api
|
||||
*/
|
||||
|
||||
const BCHJS = require('../../../src/bch-js.js')
|
||||
const bchjs = new BCHJS({
|
||||
restURL: 'http://localhost:3000/v5/'
|
||||
})
|
||||
|
||||
async function startTest () {
|
||||
try {
|
||||
const addr = 'bitcoincash:qzkvas58zag9tjry693mflkjze2m20ps7vx7uw7z9d'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.error('Error in startTest(): ', err)
|
||||
}
|
||||
}
|
||||
startTest()
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -128,8 +131,9 @@ describe('#psf-slp-indexer', () => {
|
||||
describe('#getTokenData', () => {
|
||||
it('should get token data', async () => {
|
||||
const tokenId =
|
||||
'f055256b938f1ecfa270459d6f12c7c8c82b66d3263c03d5074445a2b1a498a3'
|
||||
'd9aafa7acb514c597caf440ae268b5e4e955f2687e05f044cdf8fd9550d9a27b'
|
||||
|
||||
// bchjs.PsfSlpIndexer.restURL = 'http://localhost:3000/v5/'
|
||||
const result = await bchjs.PsfSlpIndexer.getTokenData(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
@@ -138,10 +142,42 @@ describe('#psf-slp-indexer', () => {
|
||||
assert.property(result, 'mutableData')
|
||||
|
||||
assert.isObject(result.genesisData)
|
||||
assert.isObject(result.immutableData)
|
||||
assert.isObject(result.mutableData)
|
||||
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
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('#UTXO', () => {
|
||||
})
|
||||
|
||||
it('should handle Type1 minting batons', async () => {
|
||||
const addr = 'simpleledger:qz5l5yzz9r09hw9aadcz53elp2knx6gyg5qk3s8md7'
|
||||
const addr = 'simpleledger:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpv4awvv7m7'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -104,7 +104,7 @@ describe('#UTXO', () => {
|
||||
})
|
||||
|
||||
it('should filter Group tokens and mint batons', async () => {
|
||||
const addr = 'bitcoincash:qzeqcrpe5fcnslv8rfqjq4gh4gzdwytmdc4qmh0ztv'
|
||||
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -114,7 +114,7 @@ describe('#UTXO', () => {
|
||||
})
|
||||
|
||||
it('should filter NFTs', async () => {
|
||||
const addr = 'simpleledger:qq7vp2kvejsql898a2760kuq6xz00h0a5vuwu9lywu'
|
||||
const addr = 'bitcoincash:qrp4mlmsrtwlvjn4seuchvtmus06tuqmpvex9he79q'
|
||||
|
||||
const result = await bchjs.Utxo.get(addr)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
@@ -122,6 +122,65 @@ describe('#UTXO', () => {
|
||||
assert.isAbove(result.slpUtxos.nft.tokens.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#isValid', () => {
|
||||
it('should return true for valid UTXO with fullnode properties', async () => {
|
||||
const utxo = {
|
||||
txid: '260d4fb4006a330660c805327c840d569d7547b7e3e9659fb423b3a041c2e254',
|
||||
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: '260d4fb4006a330660c805327c840d569d7547b7e3e9659fb423b3a041c2e254',
|
||||
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:qqgahqa5zkknmhmvsc98jndpwn3r77gqgc02x03jce')
|
||||
// console.log(`utxo: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
const result = await bchjs.Utxo.isValid(utxo.bchUtxos[0])
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function sleep (ms) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -146,21 +146,69 @@ const tokenData01 = {
|
||||
|
||||
const tokenData = {
|
||||
genesisData: tokenStats.tokenData,
|
||||
immutableData: {
|
||||
issuer: 'Launchpad Intellectual Property, Inc.',
|
||||
website: 'http://launchpadip.com/',
|
||||
dateCreated: '2022-01-11'
|
||||
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: {
|
||||
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'
|
||||
}
|
||||
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,
|
||||
tokenData
|
||||
tokenData,
|
||||
tokenMedia01
|
||||
}
|
||||
|
||||
+12
-12
@@ -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 () => {
|
||||
|
||||
@@ -350,8 +350,8 @@ describe('#PsfSlpIndexer', () => {
|
||||
assert.property(result, 'mutableData')
|
||||
|
||||
assert.isObject(result.genesisData)
|
||||
assert.isObject(result.immutableData)
|
||||
assert.isObject(result.mutableData)
|
||||
assert.isString(result.immutableData)
|
||||
assert.isString(result.mutableData)
|
||||
|
||||
assert.property(result.genesisData, 'type')
|
||||
assert.property(result.genesisData, 'ticker')
|
||||
@@ -411,4 +411,28 @@ describe('#PsfSlpIndexer', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -121,6 +121,8 @@ describe('#utxo', () => {
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
|
||||
.resolves({ txData: { isValidSlp: false } })
|
||||
sandbox.stub(bchjs.Utxo.psfSlpIndexer, 'status')
|
||||
.resolves({ status: { syncedBlockHeight: 600000, chainBlockHeight: 600000 } })
|
||||
|
||||
// Mock function to return the same input. Good enough for this test.
|
||||
sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(x => x)
|
||||
@@ -154,6 +156,8 @@ describe('#utxo', () => {
|
||||
sandbox
|
||||
.stub(bchjs.Utxo.psfSlpIndexer, 'tx')
|
||||
.resolves({ txData: { isValidSlp: false } })
|
||||
sandbox.stub(bchjs.Utxo.psfSlpIndexer, 'status')
|
||||
.resolves({ status: { syncedBlockHeight: 600000, chainBlockHeight: 600000 } })
|
||||
|
||||
// Force psf-slp-indexer to return no UTXOs
|
||||
sandbox
|
||||
@@ -174,4 +178,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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user