Compare commits

...
38 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
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
12 changed files with 1020 additions and 525 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
+658 -476
View File
File diff suppressed because it is too large Load Diff
+10 -11
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 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 = [
{