Compare commits

..
28 Commits
Author SHA1 Message Date
Chris Troutner 7c66ce7f73 Merge pull request #33 from Permissionless-Software-Foundation/ct-unstable
Increasing rate limits for hydrateUtxos()
2020-09-28 19:57:55 -07:00
Chris Troutner 9e3dfd9848 Updating package lock 2020-09-28 19:55:28 -07:00
Chris Troutner 10eb4da112 fix(rate limits): Increasing rate limits for internal calls 2020-09-28 19:49:49 -07:00
Chris Troutner f4f5f02a2f fix(bch-js): bumping to version 3.6.3 2020-09-28 19:45:47 -07:00
Chris Troutner bb69a6dee9 Merge pull request #32 from Permissionless-Software-Foundation/ct-unstable
fix(rate limits): Increasing internal rate limits
2020-09-28 18:35:29 -07:00
Chris Troutner d67b3aa052 fix(rate limits): Increasing internal rate limits 2020-09-28 18:32:43 -07:00
Chris Troutner 7e1c3d6a73 Merge pull request #31 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Bumping bch-js version to fix hydateUtxos
2020-09-28 18:13:43 -07:00
Chris Troutner 6165001790 fix(hydrateUtxos): Bumping bch-js version to fix hydateUtxos 2020-09-28 18:10:49 -07:00
Chris Troutner 77ba06d68c Merge pull request #30 from Permissionless-Software-Foundation/ct-unstable
fix(internal calls): Applying paid rate limits to internal calls
2020-09-28 08:48:00 -07:00
Chris Troutner 40f6b17d3b fix(internal calls): Applying paid rate limits to internal calls 2020-09-28 08:37:34 -07:00
Chris Troutner 4d40a7cb01 Merge pull request #29 from Permissionless-Software-Foundation/ct-unstable
fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo()
2020-09-27 17:30:20 -07:00
Chris Troutner 0a14ca08a4 fix(hydrateUtxos): Adjusting input to match output of Electrumx.utxo() 2020-09-27 17:26:34 -07:00
Chris Troutner 198141b75a Merge pull request #28 from Permissionless-Software-Foundation/ct-unstable
feat(hydrateUtxos): Porting hydrateUtxos convenience utility function
2020-09-27 00:36:09 -07:00
Chris Troutner 6f25ec4b68 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:31:38 -07:00
Chris Troutner cb416d26c0 Replacing @chris.troutner/bch-js with @psf/bch-js 2020-09-27 00:26:57 -07:00
Chris Troutner 376094c6c2 feat(hydrateUtxos): Porting hydrateUtxos convenience utility function 2020-09-27 00:21:36 -07:00
Chris Troutner f566d44252 Merge pull request #27 from Permissionless-Software-Foundation/ct-unstable
fix(electrumx): Fixes GET tx details
2020-09-10 10:54:25 -07:00
Chris Troutner 0e7b45214f fix(elecrtrumx): Fixes GET tx details 2020-09-10 10:51:22 -07:00
Chris Troutner 58366b5442 Merge pull request #26 from Permissionless-Software-Foundation/25-fix-txdetails-get
Change url param name
2020-09-10 10:48:32 -07:00
Rosco Kalis f97e7016c5 Change url param name 2020-09-10 19:39:35 +02:00
Chris Troutner f82c85712c Merge pull request #24 from Permissionless-Software-Foundation/ct-unstable
fix(electrum-cash): Upgrading to electrum-cash v2.0.2
2020-08-29 13:28:22 -07:00
Chris Troutner 5f18bc578b fix(electrum-cash): Upgrading to electrum-cash v2.0.2 2020-08-29 13:24:22 -07:00
Chris Troutner e72a6b50e4 Merge pull request #22 from Permissionless-Software-Foundation/7-electrum-block-header
Add /block/headers GET/POST endpoints + tests and mock data
2020-08-27 17:12:02 -07:00
Chris Troutner 66c99ae442 Adding temp.sh to the gitignore 2020-08-27 16:54:59 -07:00
Rosco Kalis 5e04125bc3 Update electrum-cash version 2020-08-26 12:46:53 +02:00
Rosco Kalis fd3fd43800 Add /block/headers GET/POST endpoints + tests and mock data 2020-08-26 12:32:32 +02:00
Chris Troutner 5184da7c26 Merge pull request #23 from Permissionless-Software-Foundation/ct-unstable
fix(webhooks): testing webhooks deployment
2020-08-21 17:28:51 -07:00
Chris Troutner 595caffb51 fix(webhooks): testing webhooks deployment 2020-08-21 17:26:33 -07:00
20 changed files with 1232 additions and 654 deletions
+1
View File
@@ -19,6 +19,7 @@ start-fullstack-test.sh
start-free-main.sh
start-local-mainnet.sh.save
start-local-testnet.sh.save
temp.sh
coverage
start-my-infra
+2 -2
View File
@@ -18,7 +18,7 @@ Both bch-api and bch-js are part of the
- [API Documentation](https://fullstack.cash/documentation)
- [Example Code](https://fullstack.cash/examples)
Have questions? Need help? Join our community support
Have questions? Need help? Join our community support:
[Telegram channel](https://t.me/bch_js_toolkit)
## Features
@@ -94,4 +94,4 @@ Have questions? Need help? Join our community support
[Telegram channel](https://t.me/bch_js_toolkit)
## License
[MIT](LICENSE.md)
[MIT](./LICENSE.md)
+470 -633
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -28,7 +28,7 @@
"node": ">=10.15.1"
},
"dependencies": {
"@chris.troutner/bch-js": "^3.0.1",
"@psf/bch-js": "^3.6.3",
"apidoc": "^0.23.0",
"axios": "^0.19.0",
"bitcore-lib-cash": "^8.20.3",
@@ -37,7 +37,7 @@
"cors": "^2.8.3",
"debug": "~4.1.1",
"dotenv": "^8.0.0",
"electrum-cash": "^1.0.1",
"electrum-cash": "^2.0.2",
"express": "^4.15.5",
"express-basic-auth": "^1.1.3",
"express-rate-limit": "^5.0.0",
+6
View File
@@ -135,6 +135,12 @@ class RateLimits {
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
// Apply paid-access rate limits based on key/IP
if (key.toString().indexOf('172.17.') > -1) {
pointsToConsume = 0.1
res.locals.pointsToConsume = pointsToConsume // Feedback for tests.
}
wlogger.info(
`User ${key} consuming ${pointsToConsume} point for resource ${resource}.`
)
+1 -1
View File
@@ -23,7 +23,7 @@ const router = express.Router()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
+174 -3
View File
@@ -10,7 +10,7 @@ const axios = require('axios')
const util = require('util')
const bitcore = require('bitcore-lib-cash')
const ElectrumCash = require('electrum-cash').Client
const ElectrumCash = require('electrum-cash').ElectrumClient
// const ElectrumCash = require('/home/trout/work/personal/electrum-cash/electrum.js').Client // eslint-disable-line
const wlogger = require('../../util/winston-logging')
@@ -19,7 +19,7 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
@@ -50,8 +50,10 @@ class Electrum {
_this.router.get('/', _this.root)
_this.router.get('/utxos/:address', _this.getUtxos)
_this.router.post('/utxos', _this.utxosBulk)
_this.router.get('/tx/data/:address', _this.getTransactionDetails)
_this.router.get('/tx/data/:txid', _this.getTransactionDetails)
_this.router.post('/tx/data', _this.transactionDetailsBulk)
_this.router.get('/block/headers/:height', _this.getBlockHeaders)
_this.router.post('/block/headers', _this.blockHeadersBulk)
_this.router.get('/balance/:address', _this.getBalance)
_this.router.post('/balance', _this.balanceBulk)
_this.router.get('/transactions/:address', _this.getTransactions)
@@ -489,6 +491,175 @@ class Electrum {
}
}
// Returns a promise that resolves to block header data for a block height.
// Expects input to be a height number, and input validation to have already
// been done by parent, calling function.
async _blockHeadersFromElectrum (height, count = 1) {
try {
if (!_this.isReady) {
throw new Error(
'ElectrumX server connection is not ready. Call await connectToServer() first.'
)
}
// Query the block header from the ElectrumX server.
const electrumResponse = await _this.electrumx.request('blockchain.block.headers', height, count)
// console.log(
// `electrumResponse: ${JSON.stringify(electrumResponse, null, 2)}`
// )
const HEADER_SIZE = 80 * 2
if (!(electrumResponse instanceof Error)) {
const headers = electrumResponse.hex.match(new RegExp(`.{1,${HEADER_SIZE}}`, 'g'))
return headers
}
return electrumResponse
} catch (err) {
// console.log('err: ', err)
// Write out error to error log.
wlogger.error(
'Error in elecrumx.js/_blockHeaderFromElectrum(): ',
err
)
throw err
}
}
/**
* @api {get} /electrumx/block/headers/{height} Get `count` block headers starting at a height
* @apiName Block header data for a `count` blocks starting at a block height
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array with block headers starting at the block height
*
*
* @apiExample Example usage:
* curl -X GET "https://api.fullstack.cash/v3/electrumx/block/header/42?count=2" -H "accept: application/json"
*
*/
// GET handler for single block headers
async getBlockHeaders (req, res, next) {
try {
const height = Number(req.params.height)
const count = req.query.count === undefined ? 1 : Number(req.query.count)
// Reject if height is not a number
if (Number.isNaN(height) || height < 0) {
res.status(400)
return res.json({
success: false,
error: 'height must be a positive number'
})
}
// Reject if height is not a number
if (Number.isNaN(count) || count < 0) {
res.status(400)
return res.json({
success: false,
error: 'count must be a positive number'
})
}
wlogger.debug(
'Executing electrumx/getBlockHeaders with this height: ',
height
)
// Get data from ElectrumX server.
const electrumResponse = await _this._blockHeadersFromElectrum(height, count)
// console.log(`_transactionDetailsFromElectrum(): ${JSON.stringify(electrumResponse, null, 2)}`)
// Pass the error message if ElectrumX reports an error.
if (electrumResponse instanceof Error) {
res.status(400)
return res.json({
success: false,
error: electrumResponse.message
})
}
res.status(200)
return res.json({
success: true,
headers: electrumResponse
})
} catch (err) {
// Write out error to error log.
wlogger.error('Error in elecrumx.js/getBlockHeader().', err)
return _this.errorHandler(err, res)
}
}
/**
* @api {post} /electrumx/block/headers Get block headers for an array of height + count pairs
* @apiName Block headers for an array of height + count pairs
* @apiGroup ElectrumX / Fulcrum
* @apiDescription Returns an array of objects with blockheaders of an array of TXIDs.
* Limited to 20 items per request.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/electrumx/block/headers" -H "accept: application/json" -H "Content-Type: application/json" -d '{"heights":[{ "height": 42, count: 2 }, { "height": 100, count: 5 }]}'
*
*/
// POST handler for bulk queries on block headers
async blockHeadersBulk (req, res, next) {
try {
const heights = req.body.heights
// Reject if heights is not an array.
if (!Array.isArray(heights)) {
res.status(400)
return res.json({
success: false,
error: 'heights needs to be an array. Use GET for single height.'
})
}
// Enforce array size rate limits
if (!_this.routeUtils.validateArraySize(req, heights)) {
res.status(429) // https://github.com/Bitcoin-com/rest.bitcoin.com/issues/330
return res.json({
success: false,
error: 'Array too large.'
})
}
wlogger.debug(
'Executing electrumx.js/blockHeadersBulk with these txids: ',
heights
)
// Loops through each address and creates an array of Promises, querying
// the Electrum server in parallel.
const transactions = heights.map(async (obj) => {
const headers = await _this._blockHeadersFromElectrum(
obj.height,
obj.count
)
return { headers }
})
// Wait for all parallel Electrum requests to return.
const result = await Promise.all(transactions)
// Return the array of retrieved transaction details.
res.status(200)
return res.json({
success: true,
headers: result
})
} catch (err) {
wlogger.error('Error in electrumx.js/blockHeadersBulk().', err)
return _this.errorHandler(err, res)
}
}
// Returns a promise that resolves to a balance for an address. Expects input
// to be a cash address, and input validation to have already been done by
// parent, calling function.
+1 -1
View File
@@ -15,7 +15,7 @@ const config = require('../../../config')
const RouteUtils = require('../../util/route-utils')
const routeUtils = new RouteUtils()
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// Use Blockbook for getting indexer data.
+1 -1
View File
@@ -17,7 +17,7 @@ const routeUtils = new RouteUtils()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
let _this
+1 -1
View File
@@ -11,7 +11,7 @@ const express = require('express')
const router = express.Router()
// const BCHJS = require('@chris.troutner/bch-js')
// const BCHJS = require('@psf/bch-js')
// const bchjs = new BCHJS()
// let _this
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('../../util/winston-logging')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
module.exports = {
+1 -1
View File
@@ -1,6 +1,6 @@
const axios = require('axios')
const SLPSDK = require('@chris.troutner/bch-js')
const SLPSDK = require('@psf/bch-js')
const SLP = new SLPSDK()
class Slpdb {
+79 -1
View File
@@ -13,7 +13,7 @@ const Slpdb = require('./services/slpdb')
// const strftime = require('strftime')
const wlogger = require('../../util/winston-logging')
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// Used to convert error messages to strings, to safely pass to users.
@@ -79,6 +79,7 @@ class Slp {
_this.txsByAddressSingle
)
_this.router.post('/generateSendOpReturn', _this.generateSendOpReturn)
_this.router.post('/hydrateUtxos', _this.hydrateUtxos)
}
// DRY error handler.
@@ -1519,6 +1520,83 @@ class Slp {
})
}
}
/**
* @api {post} /slp/hydrateUtxos/ hydrateUtxos
* @apiName SLP hydrateUtxos
* @apiGroup SLP
* @apiDescription Hydrate UTXO data with SLP information.
*
* Expects an array of UTXO objects as input. Returns an array of equal size.
* Returns UTXO data hydrated with token information. If the UTXO does not
* belong to a SLP transaction, it will return an isValid property set to
* false. If the UTXO is part of an SLP transaction, it will return the UTXO
* object with additional SLP information attached. An isValid property will
* be included. If its value is true, the UTXO is a valid SLP UTXO. If the
* value is null, then SLPDB has not yet processed that txid and validity has
* not been confirmed.
*
* @apiExample Example usage:
* curl -X POST "https://api.fullstack.cash/v3/slp/hydrateUtxos" -H "accept:application/json" -H "Content-Type: application/json" -d '{"utxos":[{"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 3, "value": "6816", "height": 606848, "confirmations": 13, "satoshis": 6816}, {"txid": "d56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56","vout": 2, "value": "546", "height": 606848, "confirmations": 13, "satoshis": 546}]}'
*
*
*/
async hydrateUtxos (req, res, next) {
try {
const utxos = req.body.utxos
// Validate inputs
if (!Array.isArray(utxos)) {
res.status(422)
return res.json({
error: 'Input must be an array.'
})
}
if (!utxos.length) {
res.status(422)
return res.json({
error: 'Array should not be empty'
})
}
if (utxos.length > 20) {
res.status(422)
return res.json({
error: 'Array too long, max length is 20'
})
}
// Loop through each address and query the UTXOs for that element.
for (let i = 0; i < utxos.length; i++) {
const theseUtxos = utxos[i].utxos
// Get SLP token details.
const details = await _this.bchjs.SLP.Utils.tokenUtxoDetails(theseUtxos)
// console.log('details : ', details)
// Replace the original UTXO data with the hydrated data.
utxos[i].utxos = details
}
res.status(200)
return res.json({ slpUtxos: utxos })
} catch (err) {
wlogger.error('Error in slp.js/hydrateUtxos().', err)
// Decode the error message.
const { msg, status } = routeUtils.decodeError(err)
if (msg) {
res.status(status)
return res.json({ error: msg })
}
res.status(500)
return res.json({
error: 'Error in hydrateUtxos()'
})
}
}
}
module.exports = Slp
+1 -1
View File
@@ -12,7 +12,7 @@ const blockbook = new Blockbook()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// const BCHJS_TESTNET = 'https://testnet.bchjs.cash/v3/'
+1 -1
View File
@@ -16,7 +16,7 @@ const router = express.Router()
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// Connect the route endpoints to their handler functions.
+1 -1
View File
@@ -10,7 +10,7 @@ const wlogger = require('./winston-logging')
const util = require('util')
util.inspect.defaultOptions = { depth: 1 }
const BCHJS = require('@chris.troutner/bch-js')
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
// let _this
+242 -2
View File
@@ -96,7 +96,7 @@ describe('#ElectrumX Router', () => {
// A wrapper for stubbing with the Sinon sandbox.
function stubMethodForUnitTests (obj, method, value) {
if (!process.env.TEST === 'unit') return false
if (process.env.TEST !== 'unit') return false
electrumxRoute.isReady = true // Force flag.
@@ -495,7 +495,7 @@ describe('#ElectrumX Router', () => {
})
it('should throw 400 on array input', async () => {
req.params.address = [
req.params.txid = [
'4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251'
]
@@ -666,6 +666,246 @@ describe('#ElectrumX Router', () => {
})
})
describe('#_blockHeadersFromElectrum', () => {
it('should return error object for invalid block height', async () => {
const height = -10
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid height')
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, 2)
assert.instanceOf(result, Error)
assert.include(result.message, 'Invalid height')
})
it('should return error object for invalid count', async () => {
const height = 42
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid count')
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, -1)
assert.instanceOf(result, Error)
assert.include(result.message, 'Invalid count')
})
it('should get block header for a single block height', async () => {
const height = 42
const mockedResponse = { count: 2, hex: mockData.blockHeaders.join(''), max: 2016 }
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
mockedResponse
)
const result = await electrumxRoute._blockHeadersFromElectrum(height, 2)
assert.isArray(result)
assert.deepEqual(result, mockData.blockHeaders)
})
})
describe('#getBlockheaders', () => {
it('should throw 400 if height is not a number', async () => {
req.params.height = 'Hello'
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should throw 400 if height is negative', async () => {
req.params.height = -42
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should throw 400 if count is not a number', async () => {
req.params.height = 42
req.query.count = 'Hello'
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'count must be a positive number')
})
it('should throw 400 if count is negative', async () => {
req.params.height = 42
req.query.count = -10
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'count must be a positive number')
})
it('should throw 400 on array input', async () => {
req.params.height = [42, 42]
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'height must be a positive number')
})
it('should return error object for invalid height', async () => {
req.params.height = 1000000000
stubMethodForUnitTests(
electrumxRoute.electrumx,
'request',
new Error('Invalid height')
)
// Call the details API.
const result = await electrumxRoute.getBlockHeaders(req, res)
expectRouteError(res, result, 'Invalid height')
})
it('should get headers for a single block height with count 2', async () => {
req.params.height = 42
req.query.count = 2
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.getBlockHeaders(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
assert.deepEqual(result.headers, mockData.blockHeaders)
})
})
describe('#blockHeadersBulk', () => {
it('should throw an error for an empty body', async () => {
req.body = {}
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'heights needs to be an array')
})
it('should error on non-array single height', async () => {
req.body = {
heights: 42
}
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'heights needs to be an array')
})
it('should NOT throw 400 error for an invalid height', async () => {
req.body = {
heights: [
{ height: -10, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
const result = await electrumxRoute.blockHeadersBulk(req, res)
// This should probably throw a 400 error, but to be consistent with the other
// bulk endpoints it doesn't throw. This will change in the future
// expectRouteError(res, result, 'Invalid tx hash')
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
})
it('should throw 429 error if heights array is too large', async () => {
const testArray = []
for (var i = 0; i < 25; i++) testArray.push('')
req.body.heights = testArray
const result = await electrumxRoute.blockHeadersBulk(req, res)
expectRouteError(res, result, 'Array too large', 429)
})
it('should get details for a single height', async () => {
req.body = {
heights: [
{ height: 42, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.blockHeadersBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.property(result, 'success')
assert.equal(result.success, true)
assert.property(result, 'headers')
assert.isArray(result.headers)
assert.property(result.headers[0], 'headers')
assert.isArray(result.headers[0].headers)
})
it('should get details for multiple txids', async () => {
req.body = {
heights: [
{ height: 42, count: 2 },
{ height: 42, count: 2 }
]
}
stubMethodForUnitTests(
electrumxRoute,
'_blockHeadersFromElectrum',
mockData.blockHeaders
)
// Call the details API.
const result = await electrumxRoute.blockHeadersBulk(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)'
assert.property(result, 'success')
assert.equal(result.success, true)
assert.isArray(result.headers)
assert.isArray(result.headers[0].headers)
assert.equal(result.headers.length, 2, '2 outputs for 2 inputs')
})
})
describe('#_balanceFromElectrumx', () => {
it('should throw error for invalid address', async () => {
try {
+93 -1
View File
@@ -22,6 +22,9 @@ if (!process.env.SLPDB_URL) {
const SLP = require('../../../src/routes/v3/slp')
const slp = new SLP()
const BCHJS = require('@psf/bch-js')
const bchjs = new BCHJS()
const { mockReq, mockRes } = require('../mocks/express-mocks')
describe('#slp', () => {
@@ -102,7 +105,7 @@ describe('#slp', () => {
})
})
describe('generateSendOpReturn', () => {
describe('#generateSendOpReturn', () => {
it('should return OP_RETURN script', async () => {
req.body.tokenUtxos = [
{
@@ -121,4 +124,93 @@ describe('#slp', () => {
assert.isNumber(result.outputs)
})
})
describe('#hydrateUtxos', () => {
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
req.body.utxos = utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
it('should process data directly from Electrumx', async () => {
const addrs = [
'bitcoincash:qq6mvsm7l92d77zpymmltvaw09p5uzghyuyx7spygg',
'bitcoincash:qpjdrs8qruzh8xvusdfmutjx62awcepnhyperm3g89',
'bitcoincash:qzygn28zpgeemnptkn26xzyuzzfu9l8f9vfvq7kptk'
]
const utxos = await bchjs.Electrumx.utxo(addrs)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
req.body.utxos = utxos.utxos
const result = await slp.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 3)
assert.equal(result.slpUtxos[0].utxos.length, 1)
assert.equal(result.slpUtxos[1].utxos.length, 1)
assert.equal(result.slpUtxos[2].utxos.length, 2)
})
})
})
+7 -1
View File
@@ -107,10 +107,16 @@ const txDetails = {
]
}
const blockHeaders = [
'010000008b52bbd72c2f49569059f559c1b1794de5192e4f7d6d2b03c7482bad0000000083e4f8a9d502ed0c419075c1abb5d56f878a2e9079e5612bfb76a2dc37d9c42741dd6849ffff001d2b909dd6',
'01000000f528fac1bcb685d0cd6c792320af0300a5ce15d687c7149548904e31000000004e8985a786d864f21e9cbb7cbdf4bc9265fe681b7a0893ac55a8e919ce035c2f85de6849ffff001d385ccb7c'
]
module.exports = {
utxos,
balance,
txHistory,
mempool,
txDetails
txDetails,
blockHeaders
}
+147
View File
@@ -888,6 +888,153 @@ describe('#SLP', () => {
assert.isNumber(result.outputs)
})
})
describe('#hydrateUtxos', () => {
it('should throw error if input is not an array.', async () => {
req.body.utxos = 'test'
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: `, result)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Input must be an array')
})
it('should throw error if Array is empty', async () => {
req.body.utxos = []
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array should not be empty')
})
it('should throw error if Array is too long', async () => {
const utxo = {
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
}
const utxos = []
// Populate array with 21 utxos
for (let i = 0; i < 21; i++) {
utxos.push(utxo)
}
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
assert.hasAllKeys(result, ['error'])
assert.include(result.error, 'Array too long, max length is 20')
})
it('should return utxo details', async () => {
const utxos = [
{
utxos: [
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546
}
]
}
]
// Mock the external network call.
sandbox.stub(slpRoute.bchjs.SLP.Utils, 'tokenUtxoDetails').resolves([
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 3,
value: '6816',
height: 606848,
confirmations: 13,
satoshis: 6816,
isValid: false
},
{
txid:
'd56a2b446d8149c39ca7e06163fe8097168c3604915f631bc58777d669135a56',
vout: 2,
value: '546',
height: 606848,
confirmations: 13,
satoshis: 546,
utxoType: 'token',
transactionType: 'send',
tokenId:
'dd84ca78db4d617221b58eabc6667af8fe2f7eadbfcc213d35be9f1b419beb8d',
tokenTicker: 'TAP',
tokenName: 'Thoughts and Prayers',
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 0,
tokenType: 1,
tokenQty: 5,
isValid: true
}
])
req.body.utxos = utxos
const result = await slpRoute.hydrateUtxos(req, res)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// Test the general structure of the output.
assert.isArray(result.slpUtxos)
assert.equal(result.slpUtxos.length, 1)
assert.equal(result.slpUtxos[0].utxos.length, 2)
// Test the non-slp UTXO.
assert.property(result.slpUtxos[0].utxos[0], 'txid')
assert.property(result.slpUtxos[0].utxos[0], 'vout')
assert.property(result.slpUtxos[0].utxos[0], 'value')
assert.property(result.slpUtxos[0].utxos[0], 'height')
assert.property(result.slpUtxos[0].utxos[0], 'confirmations')
assert.property(result.slpUtxos[0].utxos[0], 'satoshis')
assert.property(result.slpUtxos[0].utxos[0], 'isValid')
assert.equal(result.slpUtxos[0].utxos[0].isValid, false)
// Test the slp UTXO.
assert.property(result.slpUtxos[0].utxos[1], 'txid')
assert.property(result.slpUtxos[0].utxos[1], 'vout')
assert.property(result.slpUtxos[0].utxos[1], 'value')
assert.property(result.slpUtxos[0].utxos[1], 'height')
assert.property(result.slpUtxos[0].utxos[1], 'confirmations')
assert.property(result.slpUtxos[0].utxos[1], 'satoshis')
assert.property(result.slpUtxos[0].utxos[1], 'isValid')
assert.equal(result.slpUtxos[0].utxos[1].isValid, true)
assert.property(result.slpUtxos[0].utxos[1], 'transactionType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenId')
assert.property(result.slpUtxos[0].utxos[1], 'tokenTicker')
assert.property(result.slpUtxos[0].utxos[1], 'tokenName')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentUrl')
assert.property(result.slpUtxos[0].utxos[1], 'tokenDocumentHash')
assert.property(result.slpUtxos[0].utxos[1], 'decimals')
assert.property(result.slpUtxos[0].utxos[1], 'tokenType')
assert.property(result.slpUtxos[0].utxos[1], 'tokenQty')
})
})
})
/*