Compare commits

...
16 Commits
Author SHA1 Message Date
Chris Troutner 338788c46d Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
fix(sort0ConfTxs): Switching to Descending by default
2021-01-27 06:57:15 -08:00
Chris Troutner cad2c6378e fix(sort0ConfTxs): Switching to Descending by default 2021-01-27 06:51:36 -08:00
Chris Troutner 2e6938e21f Merge pull request #88 from Permissionless-Software-Foundation/ct-unstable
feat(tx sorting): added Electrumx.sort0ConfTxs()
2021-01-26 20:28:40 -08:00
Chris Troutner 1e13c71715 Added integration test 2021-01-26 20:25:03 -08:00
Chris Troutner d4b0807d16 feat(tx sorting): added Electrumx.sort0ConfTxs() 2021-01-26 20:20:49 -08:00
Chris Troutner 611c7977ef Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
fix(license): Updating license
2021-01-26 08:06:20 -08:00
Chris Troutner 39ac634675 fix(license): Updating license 2021-01-26 08:00:07 -08:00
Chris Troutner 278a54bd9b Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
Ct unstable
2021-01-19 14:08:03 -08:00
Chris Troutner 30b1f71d81 feat(OpenBazaar): Removing support for OpenBazaar API since it's been taken down 2021-01-19 13:43:07 -08:00
Chris Troutner e7d7fc1019 Merge branch 'master' into ct-unstable 2021-01-19 13:11:59 -08:00
Chris Troutner 524da532e2 Merge pull request #85 from Permissionless-Software-Foundation/fix/generateBurnOpReturn-tokenQty
fix(generateBurnOpReturn): Added handler when burning amount is higher than token amount
2021-01-16 11:14:42 -08:00
Gary Nadir aef56c5c14 fix(generateBurnOpReturn): Added handler when burning amount is higher than token amount 2021-01-16 14:22:02 -04:00
Chris Troutner e2ba265878 Updating README 2021-01-11 16:45:47 -08:00
Chris Troutner 2472dc3606 Updating bch-api paths to v4 in readme 2021-01-09 15:48:02 -08:00
Chris Troutner 2df36124b4 Merge pull request #84 from Permissionless-Software-Foundation/ct-unstable
fix(tokenUtxoDetails): Removing requirement to provide satoshis or va…
2021-01-09 15:45:43 -08:00
Chris Troutner c422d8db34 fix(tokenUtxoDetails): Removing requirement to provide satoshis or value in input 2021-01-09 15:43:29 -08:00
16 changed files with 520 additions and 1273 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2020 Chris Troutner
Copyright 2021 Chris Troutner
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+6 -6
View File
@@ -1,9 +1,9 @@
# bch-js
[![Build Status](https://travis-ci.org/Permissionless-Software-Foundation/bch-js.svg?branch=master)](https://travis-ci.org/Permissionless-Software-Foundation/bch-js)
[![Version](https://img.shields.io/npm/v/@psf/bch-js)](https://www.npmjs.com/package/@psf/bch-js)
[![Downloads/week](https://img.shields.io/npm/dw/@psf/bch-js)](https://npmjs.org/package/@psf/bch-js)
[![License](https://img.shields.io/npm/l/@psf/bch-js)](https://github.com/Permissionless-Software-Foundation/bch-js/blob/master/LICENSE.md)
[![js-standard-style](https://img.shields.io/badge/javascript-standard%20code%20style-green.svg?style=flat-square)](https://github.com/feross/standard)
[bch-js](https://www.npmjs.com/package/@psf/bch-js) is a JavaScript npm library for creating web and mobile apps that can interact with the Bitcoin Cash (BCH) blockchains. It can be used for free, but requires an account on [FullStack.cash](https://fullstack.cash) for increased rate limits. Learn more from [this article](https://troutsblog.com/research/bitcoin-cash/how-to-bch-full-stack-developer) about Full Stack Bitcoin Cash development.
@@ -35,15 +35,15 @@ const BCHJS = require("@psf/bch-js")
let bchjs = new BCHJS() // Defaults to BCHN network.
// testnet
bchjs = new BCHJS({ restURL: 'https://testnet3.fullstack.cash/v3/' })
bchjs = new BCHJS({ restURL: 'https://testnet3.fullstack.cash/v4/' })
```
This library is intended to be paired with
the [bch-api](https://github.com/Permissionless-Software-Foundation/bch-api) REST API, and the infrastructure provided by [FullStack.cash](https://fullstack.cash). The `restURL` property can be changed to work with different Bitcoin Cash networks:
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v3/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v3/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v3/
- BCHN Mainnet REST API server: https://bchn.fullstack.cash/v4/
- ABC Mainnet REST API server: https://abc.fullstack.cash/v4/
- Testnet3 REST API server: https://testnet3.fullstack.cash/v4/
- Check server status: https://metrics.fullstack.cash
### API Key
@@ -117,7 +117,7 @@ Have questions? Need help? Join our community support
Copies of this repository are also published on [IPFS](https://ipfs.io).
- v3.8.10: QmauhggzKDo9A9je5y2K6WRAp32JmGRFCw2ZFTiSnW8yot
- v4.5.4: QmWv3pxJy3MH8vU5nLUVyzqFxfNKXLQQEnz1rgStNuQijd
## License
+188 -748
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -16,8 +16,8 @@
"test:integration:local": "RESTURL=http://localhost:3000/v4/ mocha --timeout 30000 test/integration",
"test:integration:local:bchn": "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/testnet",
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#tokenUtxoDetails' test/integration/",
"test:temp2": "mocha --timeout 30000 -g '#generateSendOpReturn' test/unit/",
"test:temp": "export RESTURL=https://bchn.fullstack.cash/v4/ && mocha --timeout 30000 -g '#sort0ConfTxs' test/integration/",
"test:temp2": "mocha --timeout=30000 -g '#sortConfTxs' test/unit/",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage:report": "nyc --reporter=html mocha --timeout 25000 test/unit/",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
@@ -32,7 +32,6 @@
"dependencies": {
"@uppy/core": "^1.10.4",
"@uppy/tus": "^1.5.12",
"apidoc": "^0.25.0",
"assert": "^2.0.0",
"axios": "^0.21.1",
"bc-bip68": "^1.0.5",
@@ -76,6 +75,7 @@
"wif": "^2.0.6"
},
"devDependencies": {
"apidoc": "^0.17.7",
"chai": "^4.1.2",
"coveralls": "^3.0.2",
"eslint": "5.16.0",
+15 -7
View File
@@ -33,22 +33,33 @@ const IPFS = require('./ipfs')
const Encryption = require('./encryption')
// Indexers
const OpenBazaar = require('./openbazaar')
const Ninsight = require('./ninsight')
const Electrumx = require('./electrumx')
class BCHJS {
constructor (config) {
// Try to retrieve the REST API URL from different sources.
if (config && config.restURL && config.restURL !== '') { this.restURL = config.restURL } else if (process.env.RESTURL && process.env.RESTURL !== '') { this.restURL = process.env.RESTURL } else this.restURL = DEFAULT_REST_API
if (config && config.restURL && config.restURL !== '') {
this.restURL = config.restURL
} else if (process.env.RESTURL && process.env.RESTURL !== '') {
this.restURL = process.env.RESTURL
} else this.restURL = DEFAULT_REST_API
// Retrieve the apiToken
this.apiToken = '' // default value.
if (config && config.apiToken && config.apiToken !== '') { this.apiToken = config.apiToken } else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') { this.apiToken = process.env.BCHJSTOKEN }
if (config && config.apiToken && config.apiToken !== '') {
this.apiToken = config.apiToken
} else if (process.env.BCHJSTOKEN && process.env.BCHJSTOKEN !== '') {
this.apiToken = process.env.BCHJSTOKEN
}
// Retrieve the Basic Authentication password.
this.authPass = '' // default value.
if (config && config.authPass && config.authPass !== '') { this.authPass = config.authPass } else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') { this.authPass = process.env.BCHJSAUTHPASS }
if (config && config.authPass && config.authPass !== '') {
this.authPass = config.authPass
} else if (process.env.BCHJSAUTHPASS && process.env.BCHJSAUTHPASS !== '') {
this.authPass = process.env.BCHJSAUTHPASS
}
// Generate a Basic Authentication token from an auth password
this.authToken = ''
@@ -68,9 +79,6 @@ class BCHJS {
// console.log(`apiToken: ${this.apiToken}`)
// Populate OpenBazaar endpoints
this.OpenBazaar = new OpenBazaar(libConfig)
// Bitcoin.com Ninsight indexer
this.Ninsight = new Ninsight(config)
+108 -2
View File
@@ -2,9 +2,12 @@
This library interacts with the ElectrumX bch-api REST API endpoints operated
by FullStack.cash
*/
// Public npm libraries
const axios = require('axios')
// Local libraries.
const Blockchain = require('./blockchain')
let _this
class ElectrumX {
@@ -29,6 +32,8 @@ class ElectrumX {
}
}
this.blockchain = new Blockchain(config)
_this = this
}
@@ -452,7 +457,9 @@ class ElectrumX {
} catch (error) {
// console.log("error: ", error)
if (error.response && error.response.data) {
if (error.response && error.response.data) { throw new Error(error.response.data.error) } else throw error.response.data
if (error.response && error.response.data) {
throw new Error(error.response.data.error)
} else throw error.response.data
} else {
throw error
}
@@ -596,6 +603,105 @@ class ElectrumX {
else throw error
}
}
/**
* @api Electrumx.sortConfTxs() sortConfTxs()
* @apiName ElectrumX sortConfTxs
* @apiGroup ElectrumX
* @apiDescription Sort the output of Electrum.transactions() by block height.
*
* A simple sort function for the output of Electrum.transactions(). Ignores
* unconfirmed transactions.
*
* Sorts in 'DESCENDING' order by default, or 'ASCENDING' can be specified.
* Descending makes the first element the newest (largest block height).
*
* @apiExample Example usage:
* (async () => {
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
* const sortedTxs = bchjs.Electrumx.sortConfTxs(txs.transactions, 'ASCENDING')
* console.log(sortedTxs)
* })()
*
* // [
* // {
* // "height": 560430,
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
* // },
* // {
* // "height": 560534,
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
* // }
* // ]
*/
// Sort confirmed Transactions by the block height
sortConfTxs (txs, sortingOrder = 'DESCENDING') {
try {
// Filter out unconfirmed transactions, with a height of 0 or less.
txs = txs.filter(elem => elem.height > 0)
if (sortingOrder === 'DESCENDING') {
return txs.sort((a, b) => b.height - a.height)
}
return txs.sort((a, b) => a.height - b.height)
} catch (err) {
console.log('Error in util.js/sortConfTxs()')
throw err
}
}
/**
* @api Electrumx.sort0ConfTxs() sort0ConfTxs()
* @apiName ElectrumX sort0ConfTxs
* @apiGroup ElectrumX
* @apiDescription Sort the output of Electrum.transactions() by block height.
*
* A simple sort function for the output of Electrum.transactions().
* Assumes that unconfirmed transactions will make it into the next block. Any
* unconfirmed transactions have their block height with the height of the next
* block. Returns a Promise.
*
* Sorts in 'ASCENDING' order by default, or 'DESCENDING' can be specified.
*
* @apiExample Example usage:
* (async () => {
* const txs = await bchjs.Electrumx.transactions('bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v')
* const sortedTxs = await bchjs.Electrumx.sort0ConfTxs(txs.transactions, 'ASCENDING')
* console.log(sortedTxs)
* })()
*
* // [
* // {
* // "height": 560430,
* // "tx_hash": "3e1f3e882be9c03897eeb197224bf87f312be556a89f4308fabeeeabcf9bc851"
* // },
* // {
* // "height": 560534,
* // "tx_hash": "4ebbeaac51ce141e262964e3a0ce11b96ca72c0dffe9b4127ce80135f503a280"
* // }
* // ]
*/
// Substitute zero-conf txs with the current block-height + 1
async sort0ConfTxs (txs, sortingOrder = 'DESCENDING') {
try {
// Calculate the height of the next block
const nextBlock = (await this.blockchain.getBlockCount()) + 1
// Replace the height of any zero-conf transactions with the height of
// the next block.
const modifiedTxs = txs.map(elem => {
if (elem.height <= 0) elem.height = nextBlock
return elem
})
// console.log(`modifiedTxs: ${JSON.stringify(modifiedTxs, null, 2)}`)
// Sort the modified array of transactions.
return this.sortConfTxs(modifiedTxs, sortingOrder)
} catch (err) {
console.log('Error in util.js/sort0ConfTxs')
throw err
}
}
}
module.exports = ElectrumX
-229
View File
@@ -1,229 +0,0 @@
/*
This library interacts with the public REST API endpoints that OpenBazaar
provides for their application.
*/
const axios = require('axios')
let _this
class OpenBazaar {
constructor (config) {
this.restURL = config.restURL
this.apiToken = config.apiToken
// Add JWT token to the authorization header.
this.axiosOptions = {
headers: {
authorization: `Token ${this.apiToken}`
}
}
_this = this
}
/**
* @api OpenBazaar.balance() balance()
* @apiName OpenBazaar Balance
* @apiGroup OpenBazaar
* @apiDescription Return Balance about an address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let balance = await bchjs.OpenBazaar.balance('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
* console.log(balance)
* } catch(error) {
* console.error(error)
* }
* })()
*
* {
* page: 1,
* totalPages: 1,
* itemsOnPage: 1000,
* addrStr: "bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9",
* balance: "0.00001",
* totalReceived: "0.00001",
* totalSent: "0",
* unconfirmedBalance: "0",
* unconfirmedTxApperances: 0,
* txApperances: 1,
* transactions: [
* "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7"
* ]
* }
*
*/
async balance (address) {
try {
// Handle single address.
if (typeof address === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/address/${address}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/address/${address}`,
_this.axiosOptions
)
}
return response.data
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api OpenBazaar.utxo() utxo()
* @apiName OpenBazaar Utxo
* @apiGroup OpenBazaar
* @apiDescription Return list of uxto for address.
*
* @apiExample Example usage:
* (async () => {
* try {
* let utxo = await bchjs.OpenBazaar.utxo('bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9');
* console.log(utxo);
* } catch(error) {
* console.error(error)
* }
* })()
*
* [
* {
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
* vout: 0,
* amount: "0.00001",
* satoshis: 1000,
* height: 602405,
* confirmations: 11
* }
* ]
*/
async utxo (address) {
try {
// Handle single address.
if (typeof address === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/utxo/${address}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/utxo/${address}`,
_this.axiosOptions
)
}
return response.data
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
/**
* @api OpenBazaar.tx() tx()
* @apiName OpenBazaar Tx
* @apiGroup OpenBazaar
* @apiDescription Get details on a transaction.
*
* @apiExample Example usage:
* (async () => {
* try {
* let txDetails = await bchjs.OpenBazaar.tx(`2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7`);
* console.log(txDetails);
* } catch(error) {
* console.error(error)
* }
* })()
*
*{
* txid: "2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7",
* version: 2,
* vin: [
* {
* txid: "5f09d317e24c5d376f737a2711f3bd1d381abdb41743fff3819b4f76382e1eac",
* vout: 1,
* sequence: 4294967295,
* n: 0,
* scriptSig: {
* hex:
* "473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9"
* },
* addresses: ["bitcoincash:qqxy8hycqe89j7wa79gnggq6z3gaqu2uvqy26xehfe"],
* value: "0.00047504"
* }
* ],
* vout: [
* {
* value: "0.00001",
* n: 0,
* scriptPubKey: {
* hex: "76a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788ac",
* addresses: ["bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9"]
* },
* spent: false
* },
* {
* value: "0.00046256",
* n: 1,
* scriptPubKey: {
* hex: "76a9142dbf5e1804c39a497b908c876097d63210c8490288ac",
* addresses: ["bitcoincash:qqkm7hscqnpe5jtmjzxgwcyh6ceppjzfqg3jdn422e"]
* },
* spent: false
* }
* ],
* blockhash: "0000000000000000010903a1fc4274499037c9339be9ec7338ee980331c20ce5",
* blockheight: 602405,
* confirmations: 11,
* blocktime: 1569792892,
* valueOut: "0.00047256",
* valueIn: "0.00047504",
* fees: "0.00000248",
* hex:
* "0200000001ac1e2e38764f9b81f3ff4317b4bd1a381dbdf311277a736f375d4ce217d3095f010000006a473044022000dd11c41a472f2e54348db996e60864d489429f12d1e044d49ff600b880c9590220715a926404bb0e2731a3795afb341ec1dad3f84ead7d27cd31fcc59abb14738c4121038476128287ac37c7a3cf7e8625fd5f024db1bc3d8e37395abe7bf42fda78d0d9ffffffff02e8030000000000001976a9142fe2c4c5ef359bb2fe1a849f891cecffbcfb4f7788acb0b40000000000001976a9142dbf5e1804c39a497b908c876097d63210c8490288ac00000000"
* }
*
*/
async tx (txid) {
try {
// Handle single txid.
if (typeof txid === 'string') {
let response
if (process.env.NETWORK === 'testnet') {
response = await axios.get(
`https://tbch.blockbook.api.openbazaar.org/api/tx/${txid}`,
_this.axiosOptions
)
} else {
response = await axios.get(
`https://bch.blockbook.api.openbazaar.org/api/tx/${txid}`,
_this.axiosOptions
)
}
return response.data
}
throw new Error('Input txid must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
else throw error
}
}
}
module.exports = OpenBazaar
+6 -1
View File
@@ -220,7 +220,12 @@ class TokenType1 {
// Calculate the total amount of tokens owned by the wallet.
let totalTokens = 0
for (let i = 0; i < tokenUtxos.length; i++) {
totalTokens += tokenUtxos[i].tokenQty
totalTokens += parseFloat(tokenUtxos[i].tokenQty)
}
// Make sure burn quantity isn't bigger than the total amount in tokens
if (burnQty > totalTokens) {
burnQty = totalTokens
}
const remainder = totalTokens - burnQty
+12 -11
View File
@@ -1073,17 +1073,18 @@ class Utils {
for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i]
if (!utxo.satoshis) {
// If Electrumx, convert the value to satoshis.
if (utxo.value) {
utxo.satoshis = utxo.value
} else {
// If there is neither a satoshis or value property, throw an error.
throw new Error(
`utxo ${i} does not have a satoshis or value property.`
)
}
}
// CT 1/9/21: This code can be removed after 2/1/21
// if (!utxo.satoshis) {
// // If Electrumx, convert the value to satoshis.
// if (utxo.value) {
// utxo.satoshis = utxo.value
// } else {
// // If there is neither a satoshis or value property, throw an error.
// throw new Error(
// `utxo ${i} does not have a satoshis or value property.`
// )
// }
// }
if (!utxo.txid) {
// If Electrumx, convert the tx_hash property to txid.
+28 -4
View File
@@ -63,7 +63,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
// const result = await bchjs.Electrumx.utxo(addr)
await bchjs.Electrumx.utxo(addr)
@@ -116,7 +118,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
// const result = await bchjs.Electrumx.balance(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -172,7 +176,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrehqueqhw629p6e57994436w730t4rzasnly00ht0')
}
// const result = await bchjs.Electrumx.transactions(addr)
// console.log(`result: ${util.inspect(result)}`)
@@ -235,7 +241,9 @@ describe('#ElectrumX', () => {
it('should throw error on array size rate limit', async () => {
try {
const addr = []
for (let i = 0; i < 25; i++) { addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf') }
for (let i = 0; i < 25; i++) {
addr.push('bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf')
}
await bchjs.Electrumx.unconfirmed(addr)
@@ -360,6 +368,22 @@ describe('#ElectrumX', () => {
}
})
})
describe('#sort0ConfTxs', () => {
it('should GET transaction history for a single address', async () => {
const addr = 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
const txs = await bchjs.Electrumx.transactions(addr)
const sortedTransactions = await bchjs.Electrumx.sort0ConfTxs(
txs.transactions
)
// console.log(
// `sortedTransactions: ${JSON.stringify(sortedTransactions, null, 2)}`
// )
assert.isAbove(sortedTransactions[0].height, sortedTransactions[1].height)
})
})
})
function sleep (ms) {
-89
View File
@@ -1,89 +0,0 @@
/*
tests for OpenBazaar library.
*/
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
describe('#OpenBazaar', () => {
beforeEach(async () => {
if (process.env.IS_USING_FREE_TIER) await sleep(1000)
})
describe('#Balance', () => {
it('should GET balance for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.balance(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'page',
'totalPages',
'itemsOnPage',
'addrStr',
'balance',
'totalReceived',
'totalSent',
'unconfirmedBalance',
'unconfirmedTxApperances',
'txApperances',
'transactions'
])
assert.isArray(result.transactions)
})
})
describe('#utxo', () => {
it('should GET utxos for a single address', async () => {
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
'txid',
'vout',
'amount',
'height',
'confirmations',
'satoshis'
])
})
})
describe('#tx', () => {
it('should GET transactions for a single address', async () => {
const addr =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.OpenBazaar.tx(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'txid',
'version',
'vin',
'vout',
'blockhash',
'blockheight',
'confirmations',
'blocktime',
'valueOut',
'valueIn',
'fees',
'hex',
'time'
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
+73
View File
@@ -390,6 +390,7 @@ describe('#ElectrumX', () => {
assert.equal(result.transactions.length, 2, '2 outputs for 2 inputs')
})
})
describe('#broadcast', () => {
it('should throw an error for improper input', async () => {
try {
@@ -402,6 +403,7 @@ describe('#ElectrumX', () => {
assert.include(err.message, 'Input txHex must be a string.')
}
})
it('should broadcast a single transaction', async () => {
// Stub the network call.
sandbox.stub(axios, 'post').resolves({ data: mockData.broadcast })
@@ -418,4 +420,75 @@ describe('#ElectrumX', () => {
assert.equal(result.txid, txid)
})
})
describe('#sortConfTxs', () => {
it('should sort in ascending order', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions, 'ASCENDING')
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isBelow(result[0].height, result[1].height)
})
it('should sort in descending order by default', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.transaction.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[0].height, result[1].height)
})
it('should ignore unconfirmed txs', () => {
const result = bchjs.Electrumx.sortConfTxs(mockData.txHistoryWithUnconfirmed.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[0].height, 0)
})
it('should handle errors', () => {
try {
bchjs.Electrumx.sortConfTxs('abc')
assert.fail('Unexpected result')
} catch (err) {
// console.log(err.message)
assert.equal(err.message, 'txs.filter is not a function')
}
})
})
// These tests use mocked data that contains unconfirmed transactions.
describe('#sort0ConfTxs', () => {
it('should sort in ascending', async () => {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
const result = await bchjs.Electrumx.sort0ConfTxs(mockData.txHistoryWithUnconfirmed.transactions, 'ASCENDING')
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isBelow(result[0].height, result[1].height)
})
it('should sort in descending order by default', async () => {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
const result = await bchjs.Electrumx.sort0ConfTxs(mockData.txHistoryWithUnconfirmed.transactions)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isAbove(result[1].height, result[2].height)
})
it('should handle errors', async () => {
try {
// Stub network calls
sandbox.stub(bchjs.Electrumx.blockchain, 'getBlockCount').resolves(672141)
await bchjs.Electrumx.sort0ConfTxs('abc')
assert.fail('Unexpected result')
} catch (err) {
// console.log(err.message)
assert.equal(err.message, 'txs.map is not a function')
}
})
})
})
+44 -8
View File
@@ -168,7 +168,8 @@ const txDetails = {
blocktime: 1578327094,
confirmations: 31861,
hash: '4db095f34d632a4daf942142c291f1f2abb5ba2e1ccac919d85bdc2f671fb251',
hex: '020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
hex:
'020000000265d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667010000006441dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309ffffffff65d13ef402840c8a51f39779afb7ae4d49e4b0a3c24a3d0e7742038f2c679667000000006441347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954ffffffff035ac355000000000017a914189ce02e332548f4804bac65cba68202c9dbf822878dfd0800000000001976a914285bb350881b21ac89724c6fb6dc914d096cd53b88acf9ef3100000000001976a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac00000000',
locktime: 0,
size: 392,
time: 1578327094,
@@ -177,8 +178,10 @@ const txDetails = {
vin: [
{
scriptSig: {
asm: 'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
hex: '41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
asm:
'dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e[ALL|FORKID] 020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309',
hex:
'41dd1dd72770cadede1a7fd0363574846c48468a398ddfa41a9677c74cac8d2652b682743725a3b08c6c2021a629011e11a264d9036e9d5311e35b5f4937ca7b4e4121020797d8fd4d2fa6fd7cdeabe2526bfea2b90525d6e8ad506ec4ee3c53885aa309'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
@@ -186,8 +189,10 @@ const txDetails = {
},
{
scriptSig: {
asm: '347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
hex: '41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
asm:
'347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f77[ALL|FORKID] 028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954',
hex:
'41347d7f218c11c04487c1ad8baac28928fb10e5054cd4494b94d078cfa04ccf68e064fb188127ff656c0b98e9ce87f036d183925d0d0860605877d61e90375f774121028a53f95eb631b460854fc836b2e5d31cad16364b4dc3d970babfbdcc3f2e4954'
},
sequence: 4294967295,
txid: '6796672c8f0342770e3d4ac2a3b0e4494daeb7af7997f3518a0c8402f43ed165',
@@ -210,7 +215,8 @@ const txDetails = {
n: 1,
scriptPubKey: {
addresses: ['bitcoincash: qq59hv6s3qdjrtyfwfxxldkuj9xsjmx48vrz882knz'],
asm: 'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 285bb350881b21ac89724c6fb6dc914d096cd53b OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a914285bb350881b21ac89724c6fb6dc914d096cd53b88ac',
reqSigs: 1,
type: 'pubkeyhash'
@@ -221,7 +227,8 @@ const txDetails = {
n: 2,
scriptPubKey: {
addresses: ['bitcoincash: qpzlruwy4xu5rxjs3z37nsj29y7h59gwvsu4ddp0u4'],
asm: 'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
asm:
'OP_DUP OP_HASH160 45f1f1c4a9b9419a5088a3e9c24a293d7a150e64 OP_EQUALVERIFY OP_CHECKSIG',
hex: '76a91445f1f1c4a9b9419a5088a3e9c24a293d7a150e6488ac',
reqSigs: 1,
type: 'pubkeyhash'
@@ -255,6 +262,34 @@ const broadcast = {
txid: txDetails.txid
}
const txHistoryWithUnconfirmed = {
success: true,
transactions: [
{
height: 668539,
tx_hash:
'fd9bffca3dd7a2d801c87cb36e8b43949d2e171bb8e25e3d92cf39ac5e2ba179'
},
{
height: 668544,
tx_hash:
'9d1b438aa55d75da8ebe9dcdfa4fa96714042ce0a19c8f3c10d86e1e3dcbccf5'
},
{
fee: 440,
height: 0,
tx_hash:
'30d3fa9bf78eab2162d4f1894223e417e7275fdf0d590fe5085381dd475ff077'
},
{
fee: 468,
height: -1,
tx_hash:
'4b93c26a4612b6819fde2bf5d1f6387bf92f49a3375c53a472001e7d76a80e59'
}
]
}
module.exports = {
utxo,
utxos,
@@ -267,5 +302,6 @@ module.exports = {
blockHeaders,
details,
detailsArray,
broadcast
broadcast,
txHistoryWithUnconfirmed
}
-129
View File
@@ -1,129 +0,0 @@
const chai = require('chai')
const assert = chai.assert
const BCHJS = require('../../src/bch-js')
const bchjs = new BCHJS()
const axios = require('axios')
const sinon = require('sinon')
const mockData = require('./fixtures/openbazaar-mock')
describe('#OpenBazaar', () => {
let sandbox
beforeEach(() => (sandbox = sinon.createSandbox()))
afterEach(() => sandbox.restore())
describe('#Balance', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.OpenBazaar.balance(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input address must be a string')
}
})
it('should GET balance for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.balance })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.balance(addr)
// console.log(`result: ${util.inspect(result)}`)
assert.hasAllKeys(result, [
'page',
'totalPages',
'itemsOnPage',
'addrStr',
'balance',
'totalReceived',
'totalSent',
'unconfirmedBalance',
'unconfirmedTxApperances',
'txApperances',
'transactions'
])
assert.isArray(result.transactions)
})
})
describe('#utxo', () => {
it('should throw an error for improper input', async () => {
try {
const addr = 12345
await bchjs.OpenBazaar.utxo(addr)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input address must be a string')
}
})
it('should GET utxos for a single address', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.utxo })
const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9'
const result = await bchjs.OpenBazaar.utxo(addr)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.isArray(result)
assert.hasAllKeys(result[0], [
'txid',
'vout',
'amount',
'height',
'confirmations',
'satoshis'
])
})
})
describe('#tx', () => {
it('should throw an error for improper input', async () => {
try {
const txid = 12345
await bchjs.OpenBazaar.tx(txid)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
// console.log(`err: `, err)
assert.include(err.message, 'Input txid must be a string')
}
})
it('should GET tx details for a single txid', async () => {
// Stub the network call.
sandbox.stub(axios, 'get').resolves({ data: mockData.tx })
const txid =
'2b37bdb3b63dd0bca720437754a36671431a950e684b64c44ea910ea9d5297c7'
const result = await bchjs.OpenBazaar.tx(txid)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.hasAllKeys(result, [
'txid',
'version',
'vin',
'vout',
'blockhash',
'blockheight',
'confirmations',
'blocktime',
'valueOut',
'valueIn',
'fees',
'hex'
])
assert.isArray(result.vin)
assert.isArray(result.vout)
})
})
})
+2 -2
View File
@@ -1217,11 +1217,11 @@ describe('#SLP TokenType1', () => {
tokenDocumentUrl: '',
tokenDocumentHash: '',
decimals: 8,
tokenQty: 7
tokenQty: '7'
}
]
const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 1)
const result = bchjs.SLP.TokenType1.generateBurnOpReturn(tokenUtxos, 8)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
// console.log(`result: `, result)
+34 -33
View File
@@ -497,39 +497,40 @@ describe('#SLP Utils', () => {
}
})
it('should throw error if utxo does not have satoshis or value property.', async () => {
try {
const utxos = [
{
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 3,
amount: 0.00002015,
satoshis: 2015,
height: 594892,
confirmations: 5
},
{
txid:
'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
vout: 2,
amount: 0.00000546,
height: 594892,
confirmations: 5
}
]
await uut.Utils.tokenUtxoDetails(utxos)
assert.equal(true, false, 'Unexpected result.')
} catch (err) {
assert.include(
err.message,
'utxo 1 does not have a satoshis or value property',
'Expected error message.'
)
}
})
// CT 1/9/21: This test can be removed after 2/1/21
// it('should throw error if utxo does not have satoshis or value property.', async () => {
// try {
// const utxos = [
// {
// txid:
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
// vout: 3,
// amount: 0.00002015,
// satoshis: 2015,
// height: 594892,
// confirmations: 5
// },
// {
// txid:
// 'bd158c564dd4ef54305b14f44f8e94c44b649f246dab14bcb42fb0d0078b8a90',
// vout: 2,
// amount: 0.00000546,
// height: 594892,
// confirmations: 5
// }
// ]
//
// await uut.Utils.tokenUtxoDetails(utxos)
//
// assert.equal(true, false, 'Unexpected result.')
// } catch (err) {
// assert.include(
// err.message,
// 'utxo 1 does not have a satoshis or value property',
// 'Expected error message.'
// )
// }
// })
it('should throw error if utxo does not have txid or tx_hash property.', async () => {
try {