Compare commits

..
6 Commits
6 changed files with 18 additions and 457 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:
+2 -2
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.
@@ -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
+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)
-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
-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))
}
-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)
})
})
})