mirror of
https://github.com/Permissionless-Software-Foundation/bch-js.git
synced 2026-09-21 16:51:59 -07:00
Merge pull request #105 from Permissionless-Software-Foundation/ct-unstable
Porting bch-util into bch-js
This commit is contained in:
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
"test:integration:local:bchn": "export RESTURL=http://localhost:3000/v4/ && mocha --timeout 30000 test/integration/ && mocha --timeout 30000 test/integration/chains/bchn/",
|
||||||
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
"test:integration:local:testnet": "RESTURL=http://localhost:4000/v4/ mocha --timeout 30000 test/integration/chains/testnet",
|
||||||
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#UTXO' test/integration/chains/bchn/",
|
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#UTXO' test/integration/chains/bchn/",
|
||||||
"test:temp2": "mocha --timeout=30000 -g '#utxo' test/unit/",
|
"test:temp2": "mocha --timeout=30000 -g '#Util' test/unit/",
|
||||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||||
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
|
||||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||||
|
|||||||
+123
@@ -27,6 +27,129 @@ class Util {
|
|||||||
_this = this
|
_this = this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api util.floor8() floor8()
|
||||||
|
* @apiName floor8
|
||||||
|
* @apiGroup Util
|
||||||
|
* @apiDescription Round a number down to 8 decimal places.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* const num = 1.234567891111
|
||||||
|
* const result = bchjs.Util.floor8(num)
|
||||||
|
* console.log(result)
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* // returns
|
||||||
|
* 1.23456789
|
||||||
|
*/
|
||||||
|
// floor8 - round to 8 decimal places
|
||||||
|
// Takes a number and returns it, rounded to the nearest 8 decimal place.
|
||||||
|
floor8 (num) {
|
||||||
|
const thisNum = Number(num)
|
||||||
|
|
||||||
|
if (isNaN(thisNum)) throw new Error('input must be a number')
|
||||||
|
|
||||||
|
let tempNum = thisNum * 100000000
|
||||||
|
tempNum = Math.floor(tempNum)
|
||||||
|
tempNum = tempNum / 100000000
|
||||||
|
|
||||||
|
return tempNum
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api util.floor2() floor2()
|
||||||
|
* @apiName floor2
|
||||||
|
* @apiGroup Util
|
||||||
|
* @apiDescription Round a number down to 2 decimal places.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* const num = 1.234567891111
|
||||||
|
* const result = bchjs.Util.floor2(num)
|
||||||
|
* console.log(result)
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* // returns
|
||||||
|
* 1.23
|
||||||
|
*/
|
||||||
|
// floor2 - round down to 2 decimal places
|
||||||
|
// Takes a number and returns it, rounded to the nearest 2 decimal place.
|
||||||
|
floor2 (num) {
|
||||||
|
const thisNum = Number(num)
|
||||||
|
|
||||||
|
if (isNaN(thisNum)) throw new Error('input must be a number')
|
||||||
|
|
||||||
|
let tempNum = thisNum * 100
|
||||||
|
tempNum = Math.floor(tempNum)
|
||||||
|
tempNum = tempNum / 100
|
||||||
|
|
||||||
|
return tempNum
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api util.chunk20() chunk20()
|
||||||
|
* @apiName chunk20
|
||||||
|
* @apiGroup Util
|
||||||
|
* @apiDescription chunk up an array into multiple arrays of 20 elements each.
|
||||||
|
* Input: arrayToSlice - a one-dimensional array of elements.
|
||||||
|
* Returns a two-dimensional array. An array of 20-element arrays.
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* const bigArray = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]
|
||||||
|
*
|
||||||
|
* const chunked = bchjs.Util.chunk20(bigArray)
|
||||||
|
* console.log(chunked)
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* // returns
|
||||||
|
* [
|
||||||
|
* [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],
|
||||||
|
* [20,21,22,23,24,25,26]
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
// chunk20 - chunk up an array into multiple arrays of 20 elements each.
|
||||||
|
// Input: arrayToSlice - a one-dimensional array of elements.
|
||||||
|
// Returns a two-dimensional array. An array of 20-element arrays.
|
||||||
|
chunk20 (arrayToSlice) {
|
||||||
|
try {
|
||||||
|
// Validate inputs
|
||||||
|
if (!Array.isArray(arrayToSlice)) {
|
||||||
|
throw new Error('input must be an array')
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0
|
||||||
|
const result = []
|
||||||
|
|
||||||
|
// Loop over the array and slice off chunks of 20 elements.
|
||||||
|
while (offset < arrayToSlice.length) {
|
||||||
|
const chunk = arrayToSlice.slice(offset, offset + 20)
|
||||||
|
result.push(chunk)
|
||||||
|
offset = offset + 20
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in chunk20()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api util.validateAddress() validateAddress()
|
* @api util.validateAddress() validateAddress()
|
||||||
* @apiName Validate Address.
|
* @apiName Validate Address.
|
||||||
|
|||||||
+64
@@ -279,6 +279,70 @@ class UTXO {
|
|||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api Utxo.findBiggestUtxo() findBiggestUtxo()
|
||||||
|
* @apiName findBiggestUtxo
|
||||||
|
* @apiGroup UTXO
|
||||||
|
* @apiDescription Get the biggest UTXO in an array.
|
||||||
|
*
|
||||||
|
* Given an array of BCH UTXOs, this method will return the biggest UTXO.
|
||||||
|
* This is often the simplest way to pick a UTXO for generating a transaction.
|
||||||
|
*
|
||||||
|
* @apiExample Example usage:
|
||||||
|
* (async () => {
|
||||||
|
* try {
|
||||||
|
* const utxos = await bchjs.Utxo.get('bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z');
|
||||||
|
* const utxo = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||||
|
* console.log(utxo);
|
||||||
|
* } catch(error) {
|
||||||
|
* console.error(error)
|
||||||
|
* }
|
||||||
|
* })()
|
||||||
|
*
|
||||||
|
* // returns
|
||||||
|
* {
|
||||||
|
* "height": 655431,
|
||||||
|
* "tx_hash": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
|
||||||
|
* "tx_pos": 0,
|
||||||
|
* "value": 800,
|
||||||
|
* "txid": "7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b",
|
||||||
|
* "vout": 0,
|
||||||
|
* "isValid": false,
|
||||||
|
* "satoshis": 800
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
// Returns the utxo with the biggest balance from an array of utxos.
|
||||||
|
findBiggestUtxo (utxos) {
|
||||||
|
let largestAmount = 0
|
||||||
|
let largestIndex = 0
|
||||||
|
|
||||||
|
if (!Array.isArray(utxos)) {
|
||||||
|
throw new Error('utxos input to findBiggestUtxo() must be an array')
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < utxos.length; i++) {
|
||||||
|
const thisUtxo = utxos[i]
|
||||||
|
|
||||||
|
// Give Elecrumx utxos a satoshis property.
|
||||||
|
if (thisUtxo.value) {
|
||||||
|
if (!thisUtxo.satoshis) thisUtxo.satoshis = Number(thisUtxo.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!thisUtxo.satoshis) {
|
||||||
|
throw new Error(
|
||||||
|
'UTXOs require a satoshis or value property for findBiggestUtxo()'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisUtxo.satoshis > largestAmount) {
|
||||||
|
largestAmount = thisUtxo.satoshis
|
||||||
|
largestIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utxos[largestIndex]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = UTXO
|
module.exports = UTXO
|
||||||
|
|||||||
@@ -90,6 +90,34 @@ describe('#UTXO', () => {
|
|||||||
assert.isAbove(result[0].nullUtxos.length, 1)
|
assert.isAbove(result[0].nullUtxos.length, 1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#findBiggestUtxo', () => {
|
||||||
|
it('should sort UTXOs from Electrumx', async () => {
|
||||||
|
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
|
||||||
|
|
||||||
|
const electrumxUtxos = await bchjs.Electrumx.utxo(addr)
|
||||||
|
// console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`)
|
||||||
|
|
||||||
|
const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, 'satoshis')
|
||||||
|
assert.equal(result.satoshis, 800)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sort UTXOs from Utxos.get()', async () => {
|
||||||
|
const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z'
|
||||||
|
|
||||||
|
const utxos = await bchjs.Utxo.get(addr)
|
||||||
|
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||||
|
|
||||||
|
const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos)
|
||||||
|
console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, 'satoshis')
|
||||||
|
assert.equal(result.satoshis, 800)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function sleep (ms) {
|
function sleep (ms) {
|
||||||
|
|||||||
@@ -253,9 +253,37 @@ const mockEveryUtxoType = {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const electrumxUtxos = {
|
||||||
|
success: true,
|
||||||
|
utxos: [
|
||||||
|
{
|
||||||
|
height: 0,
|
||||||
|
tx_hash:
|
||||||
|
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
|
||||||
|
tx_pos: 0,
|
||||||
|
value: 600
|
||||||
|
},
|
||||||
|
{
|
||||||
|
height: 0,
|
||||||
|
tx_hash:
|
||||||
|
'f913646f7c180f8de020cb1387951272e8a3f764a0f88e68f7fc1145a0bf02e9',
|
||||||
|
tx_pos: 0,
|
||||||
|
value: 700
|
||||||
|
},
|
||||||
|
{
|
||||||
|
height: 0,
|
||||||
|
tx_hash:
|
||||||
|
'7a091716f8137e94f87e7760648cd34a17e32754ef95f7c7bda38a635c9b2b1b',
|
||||||
|
tx_pos: 0,
|
||||||
|
value: 800
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mockUtxoData,
|
mockUtxoData,
|
||||||
mockHydratedUtxos,
|
mockHydratedUtxos,
|
||||||
mockTwoHydratedAddrs,
|
mockTwoHydratedAddrs,
|
||||||
mockEveryUtxoType
|
mockEveryUtxoType,
|
||||||
|
electrumxUtxos
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const assert2 = require('chai').assert
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const BCHJS = require('../../src/bch-js')
|
const BCHJS = require('../../src/bch-js')
|
||||||
const bchjs = new BCHJS()
|
const bchjs = new BCHJS()
|
||||||
@@ -32,4 +33,143 @@ describe('#Util', () => {
|
|||||||
.then(done, done)
|
.then(done, done)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#floor8', () => {
|
||||||
|
it('should floor a number to 8 decimals', () => {
|
||||||
|
const num = 1.234567891111
|
||||||
|
|
||||||
|
const result = bchjs.Util.floor8(num)
|
||||||
|
// console.log(result)
|
||||||
|
|
||||||
|
assert2.equal(result, 1.23456789)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error for non-number input', () => {
|
||||||
|
try {
|
||||||
|
const num = 'string'
|
||||||
|
|
||||||
|
bchjs.Util.floor8(num)
|
||||||
|
|
||||||
|
assert2.equal(true, false, 'Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
assert2.include(err.message, 'input must be a number')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not effect a number with less than 8 decimals', () => {
|
||||||
|
const num = 1.23
|
||||||
|
|
||||||
|
const result = bchjs.Util.floor8(num)
|
||||||
|
// console.log(result)
|
||||||
|
|
||||||
|
assert2.equal(result, 1.23)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#floor2', () => {
|
||||||
|
it('should round a number to 2 decimals', () => {
|
||||||
|
const num = 1.234567891111
|
||||||
|
|
||||||
|
const result = bchjs.Util.floor2(num)
|
||||||
|
// console.log(result)
|
||||||
|
|
||||||
|
assert2.equal(result, 1.23)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error for non-number input', () => {
|
||||||
|
try {
|
||||||
|
const num = 'string'
|
||||||
|
|
||||||
|
bchjs.Util.floor2(num)
|
||||||
|
|
||||||
|
assert2.equal(true, false, 'Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
assert2.include(err.message, 'input must be a number')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not effect a number with less than 8 decimals', () => {
|
||||||
|
const num = 1.2
|
||||||
|
|
||||||
|
const result = bchjs.Util.floor2(num)
|
||||||
|
// console.log(result)
|
||||||
|
|
||||||
|
assert2.equal(result, 1.2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#chunk20', () => {
|
||||||
|
const thirtyFiveElements = [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
6,
|
||||||
|
7,
|
||||||
|
8,
|
||||||
|
9,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
13,
|
||||||
|
14,
|
||||||
|
15,
|
||||||
|
16,
|
||||||
|
17,
|
||||||
|
18,
|
||||||
|
19,
|
||||||
|
20,
|
||||||
|
21,
|
||||||
|
22,
|
||||||
|
23,
|
||||||
|
24,
|
||||||
|
25,
|
||||||
|
26,
|
||||||
|
27,
|
||||||
|
28,
|
||||||
|
29,
|
||||||
|
30,
|
||||||
|
31,
|
||||||
|
32,
|
||||||
|
33,
|
||||||
|
34
|
||||||
|
]
|
||||||
|
|
||||||
|
it('should split 35 elements into 2 arrays', () => {
|
||||||
|
const result = bchjs.Util.chunk20(thirtyFiveElements)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert2.isArray(result)
|
||||||
|
assert2.isArray(result[0])
|
||||||
|
assert2.equal(result.length, 2)
|
||||||
|
assert2.equal(result[0].length, 20)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return accurately with an array of less than 20 elements', () => {
|
||||||
|
const elems = [0, 1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
const result = bchjs.Util.chunk20(elems)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert2.isArray(result)
|
||||||
|
assert2.isArray(result[0])
|
||||||
|
assert2.equal(result.length, 1)
|
||||||
|
assert2.equal(result[0].length, 6)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error for non-array input', () => {
|
||||||
|
try {
|
||||||
|
bchjs.Util.chunk20('string')
|
||||||
|
|
||||||
|
assert2.equal(true, false, 'Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(err)
|
||||||
|
assert2.include(err.message, 'input must be an array')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -145,4 +145,47 @@ describe('#utxo', () => {
|
|||||||
assert.isArray(result[0].nullUtxos)
|
assert.isArray(result[0].nullUtxos)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('#findBiggestUtxo', () => {
|
||||||
|
it('should throw error for non-array input', async () => {
|
||||||
|
try {
|
||||||
|
await bchjs.Utxo.findBiggestUtxo({})
|
||||||
|
|
||||||
|
assert.equal(true, false, 'Unexpected result!')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'utxos input to findBiggestUtxo() must be an array'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if input does not have a value or satoshis property', async () => {
|
||||||
|
try {
|
||||||
|
const badUtxos = [
|
||||||
|
{
|
||||||
|
height: 0,
|
||||||
|
tx_hash:
|
||||||
|
'192f5037bb3822afd92d6b6ab51842a5dcfbe6bff783290057342da1f27ed414',
|
||||||
|
tx_pos: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
await bchjs.Utxo.findBiggestUtxo(badUtxos)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'UTXOs require a satoshis or value property for findBiggestUtxo()'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should sort UTXOs from Electrumx', async () => {
|
||||||
|
const result = bchjs.Utxo.findBiggestUtxo(mockData.electrumxUtxos.utxos)
|
||||||
|
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||||
|
|
||||||
|
assert.property(result, 'satoshis')
|
||||||
|
assert.equal(result.satoshis, 800)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user