Compare commits

...
35 Commits
Author SHA1 Message Date
Chris Troutner b1745d37e0 Merge pull request #285 from Permissionless-Software-Foundation/ct-unstable
fix(x402): Updating x402-bch-axios with faster payment
2025-12-31 15:33:06 -07:00
Chris Troutner 9733c07e04 fix(x402): Updating x402-bch-axios with faster payment 2025-12-31 15:28:44 -07:00
Chris Troutner 430f5f015e Merge pull request #284 from Permissionless-Software-Foundation/ct-unstable
fix(x402): Changing x402 default payment to 25,000 sats, so tx fee is 1%
2025-12-30 13:37:19 -07:00
Chris Troutner 9e76b34d05 fix(x402): Changing x402 default payment to 25,000 sats, so tx fee is 1% 2025-12-30 13:34:02 -07:00
Chris Troutner 61078b2e91 Merge pull request #283 from Permissionless-Software-Foundation/ct-unstable
fix(x402): Removing leading slash in payment URL
2025-12-29 17:08:41 -07:00
Chris Troutner 82733a7e1a Fixing tests 2025-12-29 17:04:09 -07:00
Chris Troutner 3685ea1541 fix(x402): Removing leading slash in payment URL 2025-12-29 16:43:40 -07:00
Chris Troutner 5f217af3e9 Merge pull request #282 from Permissionless-Software-Foundation/ct-unstable
fix(psf-slp-indexer): Fixing issue with error handling
2025-12-29 13:52:40 -07:00
Chris Troutner e2ca3cfb00 fix(psf-slp-indexer): Fixing issue with error handling 2025-12-29 12:38:30 -07:00
Chris Troutner 690520d72e Merge pull request #281 from Permissionless-Software-Foundation/ct-unstable
fix(x402-bch-axios): Updating to v2.1.1 for better error handling
2025-12-24 15:24:47 -07:00
Chris Troutner 36660f3bc9 Merge branch 'master' into ct-unstable 2025-12-24 15:20:34 -07:00
Chris Troutner 80ee5c8d31 fix(x402-bch-axios): Updating to v2.1.1 for better error handling 2025-12-24 15:19:50 -07:00
Chris Troutner b7e04522ce Merge pull request #280 from Permissionless-Software-Foundation/ct-unstable
fix(x402-bch-axios): Updating from v1.1.2 to v2.1.0
2025-12-24 14:09:54 -07:00
Chris Troutner 19f243428d fix(x402-bch-axios): Updating from v1.1.2 to v2.1.0 2025-12-24 14:06:42 -07:00
Chris Troutner 16079c8d04 Merge pull request #279 from Permissionless-Software-Foundation/ct-unstable
fix(dsproof): Fixing bug with axios instantiation
2025-12-22 09:16:37 -07:00
Chris Troutner 6743ca5fa9 Updating integration test script 2025-12-22 08:58:04 -07:00
Chris Troutner ec046d3ef7 fix(dsproof): Fixing bug with axios instantiation 2025-12-21 15:53:46 -07:00
Chris Troutner 85405d8e70 Merge pull request #278 from Permissionless-Software-Foundation/ct-unstable
fix(x402): Improvements to x402 handling
2025-12-21 14:43:56 -07:00
Chris Troutner 1989b72b2a Updated unit tests 2025-12-21 11:58:23 -07:00
Chris Troutner f1f2bf42d9 linting 2025-12-21 11:46:15 -07:00
Chris Troutner 348a073714 fix(x402): Improvements to x402 handling 2025-12-21 11:45:52 -07:00
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
11 changed files with 1018 additions and 523 deletions
+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
+658 -476
View File
File diff suppressed because it is too large Load Diff
+9 -10
View File
@@ -2,7 +2,7 @@
"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>",
@@ -10,14 +10,14 @@
],
"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 120000 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:local:x402": "export RESTURL=http://localhost:5942/v6 && export BCHJSWIF=L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4 && mocha --timeout 1200000 test/integration/",
"test:integration:decatur": "export RESTURL=http://192.168.2.127:5942/v6 && 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",
@@ -53,7 +52,7 @@
"slp-mdm": "0.0.7",
"slp-parser": "0.0.4",
"wif": "2.0.6",
"x402-bch-axios": "1.1.1"
"x402-bch-axios": "2.2.1"
},
"devDependencies": {
"apidoc": "1.2.0",
+28 -5
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.
@@ -78,8 +86,20 @@ class BCHJS {
} else if (process.env.BCHJSWIF && process.env.BCHJSWIF !== '') {
this.wif = process.env.BCHJSWIF
}
this.paymentAmountSats = (config && config.paymentAmountSats) || 2000 * 10
this.bchServerURL = (config && config.bchServerURL) || 'https://free-bch.fullstack.cash'
// Most TXs cost about 250 sats. So we'll set the default to 25000 sats,
// so the mining fee is 1% of the transaction.
this.paymentAmountSats = (config && config.paymentAmountSats) || 25000
// 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/v6'
}
const libConfig = {
restURL: this.restURL,
@@ -100,7 +120,10 @@ class BCHJS {
axiosInstance = withPaymentInterceptor(
axiosInstance,
signer,
{ bchServerURL: this.bchServerURL }
{
apiType: 'rest-api',
bchServerURL: this.bchServerURL
}
)
libConfig.axios = axiosInstance
+2 -1
View File
@@ -6,7 +6,8 @@ class DSProof {
constructor (config) {
this.restURL = config.restURL
this.authToken = config.authToken
this.axios = axios
// Use the shared axios instance if provided, otherwise fall back to axios
this.axios = config.axios || axios
this.axiosOptions = {
headers: {
+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)}`)
+5 -5
View File
@@ -66,7 +66,7 @@ class PsfSlpIndexer {
)
return response.data
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.response && error.response.data && error.response.data.error) throw new Error(error.response.data.error)
else throw error
}
}
@@ -134,7 +134,7 @@ class PsfSlpIndexer {
}
throw new Error('Input address must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.response && error.response.data && error.response.data.error) throw new Error(error.response.data.error)
else throw error
}
}
@@ -199,7 +199,7 @@ class PsfSlpIndexer {
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.response && error.response.data && error.response.data.error) throw new Error(error.response.data.error)
else throw error
}
}
@@ -427,7 +427,7 @@ class PsfSlpIndexer {
}
throw new Error('Input tokenId must be a string.')
} catch (error) {
if (error.response && error.response.data) throw error.response.data
if (error.response && error.response.data && error.response.data.error) throw new Error(error.response.data.error)
else throw error
}
}
@@ -522,7 +522,7 @@ class PsfSlpIndexer {
throw new Error('Input tokenId must be a string.')
} catch (error) {
// console.log('error: ', error)
if (error.response && error.response.data) throw error.response.data
if (error.response && error.response.data && error.response.data.error) throw new Error(error.response.data.error)
else throw error
}
}
+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)
}
/**
+8 -8
View File
@@ -43,14 +43,14 @@ describe('#PsfSlpIndexer', () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
testErr.response = { data: { error: 'Status 422' } }
sandbox.stub(axios, 'get').throws(testErr)
await bchjs.PsfSlpIndexer.status()
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
assert.equal(err.message, 'Status 422')
}
})
})
@@ -103,7 +103,7 @@ describe('#PsfSlpIndexer', () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
testErr.response = { data: { error: 'Status 422' } }
sandbox.stub(axios, 'post').throws(testErr)
const addr = 'bitcoincash:qzmd5vxgh9m22m6fgvm57yd6kjnjl9qnwywsf3583n'
@@ -112,7 +112,7 @@ describe('#PsfSlpIndexer', () => {
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
assert.equal(err.message, 'Status 422')
}
})
})
@@ -174,7 +174,7 @@ describe('#PsfSlpIndexer', () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
testErr.response = { data: { error: 'Status 422' } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
@@ -182,7 +182,7 @@ describe('#PsfSlpIndexer', () => {
await bchjs.PsfSlpIndexer.tokenStats(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
assert.equal(err.message, 'Status 422')
}
})
})
@@ -399,7 +399,7 @@ describe('#PsfSlpIndexer', () => {
try {
// Stub the network call.
const testErr = new Error()
testErr.response = { data: { status: 422 } }
testErr.response = { data: { error: 'Status 422' } }
sandbox.stub(axios, 'post').throws(testErr)
const tokenId =
@@ -407,7 +407,7 @@ describe('#PsfSlpIndexer', () => {
await bchjs.PsfSlpIndexer.getTokenData(tokenId)
assert.equal(true, false, 'Unexpected result!')
} catch (err) {
assert.equal(err.status, 422)
assert.equal(err.message, 'Status 422')
}
})
})
+109 -14
View File
@@ -15,16 +15,21 @@ 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.paymentAmountSats, 10000)
assert.strictEqual(typeof bchjs.paymentAmountSats, 'number')
assert.ok(Number.isInteger(bchjs.paymentAmountSats) && bchjs.paymentAmountSats > 0)
assert.strictEqual(bchjs.bchServerURL, 'https://bch.fullstack.cash/v6')
})
it('should accept wif in config', () => {
const testWif = 'L1eYaneXDDXy8VDig4Arwe8wYHbhtsA5wuQvwsKwhaYeneoZuKG4'
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
wif: testWif
})
@@ -33,6 +38,7 @@ describe('#X402 Integration', () => {
it('should accept paymentAmountSats in config', () => {
const bchjs = new BCHJS({
restURL: 'http://localhost:3000/v5/',
paymentAmountSats: 5000
})
@@ -42,6 +48,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
})
@@ -54,8 +61,10 @@ describe('#X402 Integration', () => {
})
assert.strictEqual(bchjs.wif, '')
assert.strictEqual(bchjs.paymentAmountSats, 20000)
assert.strictEqual(bchjs.bchServerURL, 'https://free-bch.fullstack.cash')
// assert.strictEqual(bchjs.paymentAmountSats, 10000)
assert.strictEqual(typeof bchjs.paymentAmountSats, 'number')
assert.ok(Number.isInteger(bchjs.paymentAmountSats) && bchjs.paymentAmountSats > 0)
assert.strictEqual(bchjs.bchServerURL, 'https://bch.fullstack.cash/v6')
})
it('should read WIF from BCHJSWIF environment variable', () => {
@@ -64,7 +73,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 +94,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 +108,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/v6')
})
})
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 +212,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 +228,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 +242,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 +268,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 = [
{