Compare commits

...
17 Commits
Author SHA1 Message Date
Chris Troutner e822b91e2c Merge pull request #277 from Permissionless-Software-Foundation/ct-unstable
fix(sem ver): Ensuring sem ver is owrking
2025-12-21 06:39:18 -07:00
Chris Troutner 29a5173036 fix(sem ver): Ensuring sem ver is owrking 2025-12-21 06:36:01 -07:00
Chris Troutner 0bffff6f9b Merge pull request #276 from Permissionless-Software-Foundation/ct-unstable
Updating integration test scripts
2025-12-19 10:51:23 -07:00
Chris Troutner 8c603d3886 fix(x402): Clarifying x402 server separate from psf-bch-api 2025-12-19 10:45:51 -07:00
Chris Troutner 542e59461f fix(x402): Adding default server for x402 payments 2025-12-19 10:25:07 -07:00
Chris Troutner 0912aadc1a Updating integration test scripts 2025-12-19 10:13:37 -07:00
Chris Troutner 638804de42 tweeking test scripts 2025-12-09 14:16:29 -07:00
Chris Troutner fe33ba3381 Merge pull request #275 from Permissionless-Software-Foundation/ct-unstable
Compatibility upgrades for minimal-slp-wallet
2025-11-26 14:46:59 -08:00
Chris Troutner 6f913cf3ad Merge branch 'master' into ct-unstable 2025-11-26 14:43:18 -08:00
Chris Troutner 4d94ad13ff fix(getUsd): Updating endpoint 2025-11-26 13:59:38 -08:00
Chris Troutner c02080d12a fix(scripts.js): Removing @psf/bitcoincash-ops as a dependency 2025-11-26 13:27:45 -08:00
Chris Troutner cc1809b242 fix(script.js): Trying to satisfy ESM, linter, and minify packager at the same time 2025-11-26 13:15:07 -08:00
Chris Troutner 13a7917d9b fix(rest URL): Normalizing URL so user can submit with or without trailing forward slash 2025-11-26 12:28:12 -08:00
Chris Troutner 1573e0bc4c feat(REST URL): removing default REST URL anti-pattern. Error thrown if URL is not specified at startup 2025-11-26 10:55:34 -08:00
Chris Troutner 3fb5377acd Merge pull request #274 from Permissionless-Software-Foundation/ct-unstable
fix(package.json): Fixing minor issue with package.json that was caus…
2025-11-26 08:37:15 -08:00
Chris Troutner a1ba2ac2f7 Merge branch 'master' into ct-unstable 2025-11-26 08:33:46 -08:00
Chris Troutner 7dc29230da fix(package.json): Fixing minor issue with package.json that was causing warning 2025-11-26 08:30:50 -08:00
9 changed files with 334 additions and 33 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2022 Permissionless Software Foundation contributors
Copyright 2022-2026 Permissionless Software Foundation contributors
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:
+66
View File
@@ -40,6 +40,72 @@ the [psf-bch-api](https://github.com/Permissionless-Software-Foundation/psf-bch-
- BCHN Mainnet REST API server: https://x402-bch.fullstack.cash/v7/
- Check server status: https://metrics.fullstack.cash
## Configuration
bch-js can be configured through constructor options or environment variables. Configuration options passed to the constructor take precedence over environment variables.
### Constructor Options
When instantiating BCHJS, you can pass a configuration object:
```javascript
import BCHJS from "@psf/bch-js"
const bchjs = new BCHJS({
restURL: 'https://x402-bch.fullstack.cash/v5/',
bearerToken: 'your-bearer-token',
wif: 'your-private-key-wif',
paymentAmountSats: 20000,
bchServerURL: 'https://bch.fullstack.cash'
})
```
### Configuration Options
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `restURL` | string | Yes* | - | The REST API server URL for making API calls. Must include trailing slash. *Required unless `RESTURL` environment variable is set. |
| `bearerToken` | string | No | `''` | Bearer token for authentication with the REST API server. |
| `wif` | string | No | `''` | Private key in WIF format. When provided, enables automatic x402 payment handling. |
| `paymentAmountSats` | number | No | `20000` | Default amount of satoshis to send when making x402 payments. |
| `bchServerURL` | string | No | `'https://bch.fullstack.cash'` | BCH server URL used for broadcasting payment transactions to the blockchain. This is separate from `restURL` and is specifically for x402 payment processing. |
### Environment Variables
You can also configure bch-js using environment variables:
| Environment Variable | Config Option | Description |
|---------------------|---------------|-------------|
| `RESTURL` | `restURL` | REST API server URL for making API calls. |
| `BCHJSBEARERTOKEN` | `bearerToken` | Bearer token for API authentication. |
| `BCHJSWIF` | `wif` | Private key in WIF format for x402 payments. |
| `BCHJSBCHSERVERURL` | `bchServerURL` | BCH server URL for x402 payment transactions. |
### Understanding restURL vs bchServerURL
These two configuration options serve different purposes:
- **`restURL`**: The REST API server used for all regular API calls (utxo queries, transaction history, etc.). This can be any bch-api compatible server, such as `https://x402-bch.fullstack.cash/v5/` or `https://bch.fullstack.cash/v5/`.
- **`bchServerURL`**: The BCH infrastructure server used specifically for broadcasting x402 payment transactions to the blockchain. This defaults to `https://bch.fullstack.cash` and should typically remain unchanged unless you have specific infrastructure requirements.
**Example Use Case**: Most users will use `https://x402-bch.fullstack.cash/v5/` as their `restURL` to access x402-protected APIs. However, when bch-js needs to make an x402 payment, it uses the `bchServerURL` (default: `https://bch.fullstack.cash`) to broadcast the payment transaction. This ensures payment transactions are sent through a reliable BCH infrastructure endpoint.
```javascript
// Use x402-bch server for API calls, but bch.fullstack.cash for payments
const bchjs = new BCHJS({
restURL: 'https://x402-bch.fullstack.cash/v5/',
wif: 'your-private-key-wif'
// bchServerURL defaults to 'https://bch.fullstack.cash'
})
// Or explicitly set both
const bchjs2 = new BCHJS({
restURL: 'https://x402-bch.fullstack.cash/v5/',
bchServerURL: 'https://bch.fullstack.cash',
wif: 'your-private-key-wif'
})
```
### Web Apps
+2 -3
View File
@@ -1,17 +1,16 @@
{
"name": "@psf/bch-js",
"version": "6.0.0",
"version": "7.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@psf/bch-js",
"version": "6.0.0",
"version": "7.0.0",
"license": "MIT",
"dependencies": {
"@chris.troutner/bip32-utils": "1.0.5",
"@psf/bip21": "2.0.1",
"@psf/bitcoincash-ops": "2.0.0",
"@psf/bitcoincashjs-lib": "4.0.3",
"@psf/coininfo": "4.0.0",
"axios": "1.13.2",
+9 -10
View File
@@ -2,22 +2,22 @@
"name": "@psf/bch-js",
"version": "7.0.0",
"type": "module",
"description": "A JavaScript library for working with Bitcoin Cash, eCash, and SLP Tokens",
"description": "A JavaScript library for working with Bitcoin Cash and SLP Tokens",
"author": "Chris Troutner <chris.troutner@gmail.com>",
"contributors": [
"Gabriel Cardona <gabriel@bitcoin.com>",
"Stoyan Zhekov <zh@zhware.net>"
],
"main": "src/bch-js",
"main": "src/bch-js.js",
"scripts": {
"test": "c8 mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
"test": "export RESTURL=http://localhost:5942/v6 && c8 mocha --trace-warnings --unhandled-rejections=strict --timeout 30000 test/unit/",
"test:integration": "npm run test:integration:local:noauth",
"test:integration:nft": "export RESTURL=https://bchn.fullstack.cash/v6/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 -g '#nft1' test/integration/slp.js",
"test:integration:bchn": "export RESTURL=https://bchn.fullstack.cash/v6/ && export IS_USING_FREE_TIER=true && mocha --timeout 30000 test/integration/",
"test:integration:local:noauth": "export RESTURL=http://localhost:5942/v6/ && mocha --timeout 30000 test/integration/",
"test:integration:local:auth": "export RESTURL=http://5.78.147.3:5942/v6/ && export BCHJSBEARERTOKEN=temp01 && mocha --timeout 30000 test/integration/",
"test:integration:decatur": "export RESTURL=http://192.168.2.127:5942/v6/ && mocha --timeout 30000 test/integration/",
"test:integration:x402": "export RESTURL=http://localhost:5942/v6/ && export BCHJSWIF=L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4 && mocha --timeout 30000 test/integration/",
"test:integration:fullstack:free": "export RESTURL=https://bch.fullstack.cash/v6 && mocha --timeout 60000 test/integration/",
"test:integration:fullstack:x402": "export RESTURL=https://x402-bch.fullstack.cash/v6 && export BCHJSWIF=L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4 && mocha --timeout 30000 test/integration/",
"test:integration:local:noauth": "export RESTURL=http://localhost:5942/v6 && mocha --timeout 30000 test/integration/",
"test:integration:local:auth": "export RESTURL=http://192.168.1.115:5942/v6 && export BCHJSBEARERTOKEN=temp01 && mocha --timeout 30000 test/integration/",
"test:integration:decatur": "export RESTURL=http://192.168.2.127:5942/v6 && mocha --timeout 30000 test/integration/",
"test:integration:x402": "export RESTURL=http://localhost:5942/v6 && export BCHJSWIF=L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4 && mocha --timeout 30000 test/integration/",
"coverage": "nyc --reporter=html mocha --timeout 25000 test/unit/",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs && ./fix-docs-contrast.sh",
"lint": "standard --env mocha --fix"
@@ -31,7 +31,6 @@
"dependencies": {
"@chris.troutner/bip32-utils": "1.0.5",
"@psf/bip21": "2.0.1",
"@psf/bitcoincash-ops": "2.0.0",
"@psf/bitcoincashjs-lib": "4.0.3",
"@psf/coininfo": "4.0.0",
"axios": "1.13.2",
+20 -3
View File
@@ -45,7 +45,6 @@ import Ecash from './ecash.js'
// Indexers
import Electrumx from './electrumx.js'
import PsfSlpIndexer from './psf-slp-indexer.js'
const DEFAULT_REST_API = 'https://api.fullstack.cash/v6/'
class BCHJS {
constructor (config) {
@@ -54,7 +53,16 @@ class BCHJS {
this.restURL = config.restURL
} else if (process.env.RESTURL && process.env.RESTURL !== '') {
this.restURL = process.env.RESTURL
} else this.restURL = DEFAULT_REST_API
} else {
throw new Error(
'REST API URL is required. Provide config.restURL or set the RESTURL environment variable.'
)
}
// Normalize restURL to always have a trailing slash
if (!this.restURL.endsWith('/')) {
this.restURL = this.restURL + '/'
}
// Retrieve the Bearer token for simple token authentication.
this.bearerToken = '' // default value.
@@ -79,7 +87,16 @@ class BCHJS {
this.wif = process.env.BCHJSWIF
}
this.paymentAmountSats = (config && config.paymentAmountSats) || 2000 * 10
this.bchServerURL = (config && config.bchServerURL) || 'https://free-bch.fullstack.cash'
// BCH server URL for x402 payments (separate from REST API server)
// This is used when broadcasting payment transactions to the blockchain
if (config && config.bchServerURL && config.bchServerURL !== '') {
this.bchServerURL = config.bchServerURL
} else if (process.env.BCHJSBCHSERVERURL && process.env.BCHJSBCHSERVERURL !== '') {
this.bchServerURL = process.env.BCHJSBCHSERVERURL
} else {
this.bchServerURL = 'https://bch.fullstack.cash'
}
const libConfig = {
restURL: this.restURL,
+1 -1
View File
@@ -40,7 +40,7 @@ class Price {
async getUsd () {
try {
const response = await this.axios.get(
`${this.restURL}price/usd`,
`${this.restURL}price/bchusd`,
this.axiosOptions
)
// console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
+126 -3
View File
@@ -1,7 +1,130 @@
import Bitcoin from '@psf/bitcoincashjs-lib'
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const opcodes = require('@psf/bitcoincash-ops')
// Inline opcodes data to avoid fs/import.meta issues in browser builds.
// This data is from @psf/bitcoincash-ops/index.json
const opcodes = {
OP_FALSE: 0,
OP_0: 0,
OP_PUSHDATA1: 76,
OP_PUSHDATA2: 77,
OP_PUSHDATA4: 78,
OP_1NEGATE: 79,
OP_RESERVED: 80,
OP_TRUE: 81,
OP_1: 81,
OP_2: 82,
OP_3: 83,
OP_4: 84,
OP_5: 85,
OP_6: 86,
OP_7: 87,
OP_8: 88,
OP_9: 89,
OP_10: 90,
OP_11: 91,
OP_12: 92,
OP_13: 93,
OP_14: 94,
OP_15: 95,
OP_16: 96,
OP_NOP: 97,
OP_VER: 98,
OP_IF: 99,
OP_NOTIF: 100,
OP_VERIF: 101,
OP_VERNOTIF: 102,
OP_ELSE: 103,
OP_ENDIF: 104,
OP_VERIFY: 105,
OP_RETURN: 106,
OP_TOALTSTACK: 107,
OP_FROMALTSTACK: 108,
OP_2DROP: 109,
OP_2DUP: 110,
OP_3DUP: 111,
OP_2OVER: 112,
OP_2ROT: 113,
OP_2SWAP: 114,
OP_IFDUP: 115,
OP_DEPTH: 116,
OP_DROP: 117,
OP_DUP: 118,
OP_NIP: 119,
OP_OVER: 120,
OP_PICK: 121,
OP_ROLL: 122,
OP_ROT: 123,
OP_SWAP: 124,
OP_TUCK: 125,
OP_CAT: 126,
OP_SPLIT: 127,
OP_NUM2BIN: 128,
OP_BIN2NUM: 129,
OP_SIZE: 130,
OP_INVERT: 131,
OP_AND: 132,
OP_OR: 133,
OP_XOR: 134,
OP_EQUAL: 135,
OP_EQUALVERIFY: 136,
OP_RESERVED1: 137,
OP_RESERVED2: 138,
OP_1ADD: 139,
OP_1SUB: 140,
OP_2MUL: 141,
OP_2DIV: 142,
OP_NEGATE: 143,
OP_ABS: 144,
OP_NOT: 145,
OP_0NOTEQUAL: 146,
OP_ADD: 147,
OP_SUB: 148,
OP_MUL: 149,
OP_DIV: 150,
OP_MOD: 151,
OP_LSHIFT: 152,
OP_RSHIFT: 153,
OP_BOOLAND: 154,
OP_BOOLOR: 155,
OP_NUMEQUAL: 156,
OP_NUMEQUALVERIFY: 157,
OP_NUMNOTEQUAL: 158,
OP_LESSTHAN: 159,
OP_GREATERTHAN: 160,
OP_LESSTHANOREQUAL: 161,
OP_GREATERTHANOREQUAL: 162,
OP_MIN: 163,
OP_MAX: 164,
OP_WITHIN: 165,
OP_RIPEMD160: 166,
OP_SHA1: 167,
OP_SHA256: 168,
OP_HASH160: 169,
OP_HASH256: 170,
OP_CODESEPARATOR: 171,
OP_CHECKSIG: 172,
OP_CHECKSIGVERIFY: 173,
OP_CHECKMULTISIG: 174,
OP_CHECKMULTISIGVERIFY: 175,
OP_NOP1: 176,
OP_NOP2: 177,
OP_CHECKLOCKTIMEVERIFY: 177,
OP_NOP3: 178,
OP_CHECKSEQUENCEVERIFY: 178,
OP_NOP4: 179,
OP_NOP5: 180,
OP_NOP6: 181,
OP_NOP7: 182,
OP_NOP8: 183,
OP_NOP9: 184,
OP_NOP10: 185,
OP_CHECKDATASIG: 186,
OP_CHECKDATASIGVERIFY: 187,
OP_REVERSEBYTES: 188,
OP_PUBKEYHASH: 253,
OP_PUBKEY: 254,
OP_INVALIDOPCODE: 255
}
class Script {
constructor () {
+6
View File
@@ -20,6 +20,12 @@ class UTXO {
this.psfSlpIndexer = new PsfSlpIndexer(config)
this.BigNumber = BigNumber
this.blockchain = new Blockchain(config)
// Bind 'this' object to all subfunctions.
this.get = this.get.bind(this)
this.hydrateTokenData = this.hydrateTokenData.bind(this)
this.findBiggestUtxo = this.findBiggestUtxo.bind(this)
this.isValid = this.isValid.bind(this)
}
/**
+103 -12
View File
@@ -15,16 +15,19 @@ describe('#X402 Integration', () => {
describe('#Constructor Configuration', () => {
it('should initialize without x402 by default', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(bchjs.wif, '')
assert.strictEqual(bchjs.paymentAmountSats, 20000)
assert.strictEqual(bchjs.bchServerURL, 'https://free-bch.fullstack.cash')
assert.strictEqual(bchjs.bchServerURL, 'https://bch.fullstack.cash')
})
it('should accept wif in config', () => {
const testWif = 'L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4'
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
wif: testWif
})
@@ -33,6 +36,7 @@ describe('#X402 Integration', () => {
it('should accept paymentAmountSats in config', () => {
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
paymentAmountSats: 5000
})
@@ -42,6 +46,7 @@ describe('#X402 Integration', () => {
it('should accept bchServerURL in config', () => {
const customUrl = 'http://localhost:5000'
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
bchServerURL: customUrl
})
@@ -55,7 +60,7 @@ describe('#X402 Integration', () => {
assert.strictEqual(bchjs.wif, '')
assert.strictEqual(bchjs.paymentAmountSats, 20000)
assert.strictEqual(bchjs.bchServerURL, 'https://free-bch.fullstack.cash')
assert.strictEqual(bchjs.bchServerURL, 'https://bch.fullstack.cash')
})
it('should read WIF from BCHJSWIF environment variable', () => {
@@ -64,7 +69,9 @@ describe('#X402 Integration', () => {
process.env.BCHJSWIF = testWif
try {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(bchjs.wif, testWif)
} finally {
// Restore original env value
@@ -83,7 +90,10 @@ describe('#X402 Integration', () => {
process.env.BCHJSWIF = envWif
try {
const bchjs = new BCHJS({ wif: configWif })
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
wif: configWif
})
assert.strictEqual(bchjs.wif, configWif)
} finally {
// Restore original env value
@@ -94,29 +104,103 @@ describe('#X402 Integration', () => {
}
}
})
it('should read bchServerURL from BCHJSBCHSERVERURL environment variable', () => {
const testUrl = 'https://custom-bch-server.example.com'
const originalEnv = process.env.BCHJSBCHSERVERURL
process.env.BCHJSBCHSERVERURL = testUrl
try {
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(bchjs.bchServerURL, testUrl)
} finally {
// Restore original env value
if (originalEnv === undefined) {
delete process.env.BCHJSBCHSERVERURL
} else {
process.env.BCHJSBCHSERVERURL = originalEnv
}
}
})
it('should prefer config.bchServerURL over BCHJSBCHSERVERURL environment variable', () => {
const configUrl = 'https://config-server.example.com'
const envUrl = 'https://env-server.example.com'
const originalEnv = process.env.BCHJSBCHSERVERURL
process.env.BCHJSBCHSERVERURL = envUrl
try {
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
bchServerURL: configUrl
})
assert.strictEqual(bchjs.bchServerURL, configUrl)
} finally {
// Restore original env value
if (originalEnv === undefined) {
delete process.env.BCHJSBCHSERVERURL
} else {
process.env.BCHJSBCHSERVERURL = originalEnv
}
}
})
it('should keep restURL and bchServerURL independent', () => {
const customRestURL = 'https://x402-bch.fullstack.cash/v5/'
const customBchServerURL = 'https://bch.fullstack.cash'
const bchjs = new BCHJS({
restURL: customRestURL,
bchServerURL: customBchServerURL
})
assert.strictEqual(bchjs.restURL, customRestURL)
assert.strictEqual(bchjs.bchServerURL, customBchServerURL)
// Verify they are different values
assert.notStrictEqual(bchjs.restURL, bchjs.bchServerURL)
})
it('should use default bchServerURL when restURL is customized', () => {
const customRestURL = 'https://x402-bch.fullstack.cash/v5/'
const bchjs = new BCHJS({
restURL: customRestURL
})
assert.strictEqual(bchjs.restURL, customRestURL)
assert.strictEqual(bchjs.bchServerURL, 'https://bch.fullstack.cash')
})
})
describe('#x402 Helper Functions', () => {
it('should expose createSigner function', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(typeof bchjs.x402.createSigner, 'function')
})
it('should expose withPaymentInterceptor function', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(typeof bchjs.x402.withPaymentInterceptor, 'function')
})
it('should expose createPaymentHeader function', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(typeof bchjs.x402.createPaymentHeader, 'function')
})
it('should expose selectPaymentRequirements function', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
assert.strictEqual(typeof bchjs.x402.selectPaymentRequirements, 'function')
})
@@ -124,7 +208,9 @@ describe('#X402 Integration', () => {
describe('#Axios Instance', () => {
it('should have axios available in sub-modules', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
// Check that sub-modules have axios available (from their own import or config)
assert.ok(bchjs.Control.axios)
@@ -138,6 +224,7 @@ describe('#X402 Integration', () => {
it('should pass x402-wrapped axios instance when WIF is provided', () => {
const testWif = 'L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4'
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
wif: testWif
})
@@ -151,7 +238,9 @@ describe('#X402 Integration', () => {
describe('#selectPaymentRequirements', () => {
it('should select BCH utxo payment requirements from accepts array', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
const accepts = [
{
@@ -175,7 +264,9 @@ describe('#X402 Integration', () => {
})
it('should throw an error when no BCH requirements found', () => {
const bchjs = new BCHJS()
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/'
})
const accepts = [
{