mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
Merge pull request #11 from Permissionless-Software-Foundation/dh-new-facilitators
feat(x402): Integrated payai facilitator
This commit is contained in:
+15
-7
@@ -34,16 +34,24 @@ export X402_PRICE_USDC=0.1
|
||||
# Use 'eip155:84532' for Base Sepolia (testnet)
|
||||
export x402_NETWORK=eip155:8453
|
||||
|
||||
# Primary Facilitator
|
||||
# Options: 'cdp' (Coinbase) or 'dexter' (Dexter.cash - no API keys required)
|
||||
export PRIMARY_FACILITATOR=cdp
|
||||
# Primary Facilitator (used when ACTIVE_FACILITATORS is unset — single-facilitator mode)
|
||||
# Options: 'cdp' (Coinbase), 'dexter' (Dexter.cash), or 'payai' (PayAI)
|
||||
export PRIMARY_FACILITATOR=dexter
|
||||
|
||||
# Facilitator URLs
|
||||
# For Coinbase CDP (recommended): https://api.cdp.coinbase.com/platform/v2/x402
|
||||
# For Dexter (no API keys):https://x402.dexter.cash
|
||||
# URL are not used when PRIMARY_FACILITATOR is set
|
||||
# Multi-facilitator (x402 v2): comma-separated *additional* facilitators; PRIMARY_FACILITATOR is always first (precedence).
|
||||
# x402_FACILITATOR_URL applies only in single-facilitator mode.
|
||||
# export ACTIVE_FACILITATORS=dexter,payai
|
||||
|
||||
# Bazaar discovery extension on 402 payment requirements (default on). Set false to omit.
|
||||
# export X402_BAZAAR_ENABLED=true
|
||||
|
||||
# Facilitator URL override (optional). If set, this URL is used regardless of PRIMARY_FACILITATOR.
|
||||
# Otherwise the URL is chosen from PRIMARY_FACILITATOR (built-in defaults per provider).
|
||||
# export x402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
|
||||
|
||||
# PayAI only: optional custom facilitator host (default https://facilitator.payai.network)
|
||||
# export PAYAI_FACILITATOR_URL=https://facilitator.payai.network
|
||||
|
||||
# CDP API Credentials (required when using CDP facilitator)
|
||||
# Get these from https://cdp.coinbase.com - API Keys section
|
||||
# Not required for Dexter facilitator
|
||||
|
||||
@@ -14,6 +14,8 @@ The API surface remains focused on BCH infrastructure (BCH full node, Fulcrum, a
|
||||
- Pricing is configured in `X402_PRICE_USDC` (USDC amount), not BCH satoshis.
|
||||
- x402 network is configured using CAIP-2 format (for example `eip155:8453` for Base mainnet and `eip155:84532` for Base Sepolia).
|
||||
- Facilitator auth headers are generated using Coinbase CDP JWT auth (`FACILITATOR_KEY_ID` and `FACILITATOR_SECRET_KEY`) when using CDP endpoints.
|
||||
- Optional facilitators: **Dexter** (`PRIMARY_FACILITATOR=dexter`) and **PayAI** (`PRIMARY_FACILITATOR=payai`, default URL `https://facilitator.payai.network`, no API keys).
|
||||
- **Bazaar** discovery metadata on payment requirements via `@x402/extensions/bazaar`, and optional **multi-facilitator** mode (`ACTIVE_FACILITATORS=cdp,dexter,payai`) so clients can verify/settle through CDP, Dexter, or PayAI (`@x402/core` picks a matching facilitator).
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -85,7 +87,11 @@ All configuration is loaded from environment variables (typically from `.env`).
|
||||
- `SERVER_BASE_ADDRESS` (EVM address receiving x402 settlements)
|
||||
- `X402_PRICE_USDC` (USDC charged per request)
|
||||
- `x402_NETWORK` (CAIP-2 chain ID; e.g. `eip155:8453` or `eip155:84532`)
|
||||
- `x402_FACILITATOR_URL` (default: `https://api.cdp.coinbase.com/platform/v2/x402`)
|
||||
- `PRIMARY_FACILITATOR` (default: `cdp`) — one of `cdp`, `dexter`, or `payai`. Used for **single-facilitator** mode when `ACTIVE_FACILITATORS` is unset; selects the default facilitator base URL unless `x402_FACILITATOR_URL` is set.
|
||||
- `ACTIVE_FACILITATORS` — optional comma-separated list (e.g. `dexter,payai`). When set with multi-facilitator mode, the server registers **multiple** `HTTPFacilitatorClient` instances. **`PRIMARY_FACILITATOR` is always first** in that list (x402 gives earlier facilitators precedence); remaining names follow in the order listed, deduped. Each facilitator uses its default base URL (`x402_FACILITATOR_URL` applies only in **single-facilitator** mode).
|
||||
- `X402_BAZAAR_ENABLED` (default: `true`) — attach Bazaar **discovery** extension to protected route payment requirements (for facilitator catalogs). Set `false` to disable.
|
||||
- `x402_FACILITATOR_URL` — optional override for the facilitator HTTP base URL (must expose `/verify`, `/settle`, and `/supported` like the x402 reference facilitator). When unset, the URL is derived from `PRIMARY_FACILITATOR`. **Ignored per-facilitator when `ACTIVE_FACILITATORS` lists more than one entry** (each entry uses its provider URL).
|
||||
- `PAYAI_FACILITATOR_URL` — optional; when `PRIMARY_FACILITATOR=payai` and `x402_FACILITATOR_URL` is unset, defaults to `https://facilitator.payai.network`.
|
||||
- `FACILITATOR_KEY_ID` (required for CDP facilitator auth)
|
||||
- `FACILITATOR_SECRET_KEY` (required for CDP facilitator auth)
|
||||
|
||||
@@ -123,17 +129,30 @@ Authorization: Bearer my-secret-token
|
||||
|
||||
### 3) x402 Payments on Base (USDC)
|
||||
|
||||
**Coinbase CDP facilitator**
|
||||
|
||||
```bash
|
||||
X402_ENABLED=true
|
||||
USE_BASIC_AUTH=false
|
||||
SERVER_BASE_ADDRESS=0xYourBaseAddress
|
||||
X402_PRICE_USDC=0.1
|
||||
x402_NETWORK=eip155:8453
|
||||
x402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
|
||||
PRIMARY_FACILITATOR=cdp
|
||||
FACILITATOR_KEY_ID=your_key_id
|
||||
FACILITATOR_SECRET_KEY=your_secret
|
||||
```
|
||||
|
||||
**PayAI facilitator** (no CDP keys; default URL `https://facilitator.payai.network`)
|
||||
|
||||
```bash
|
||||
X402_ENABLED=true
|
||||
USE_BASIC_AUTH=false
|
||||
SERVER_BASE_ADDRESS=0xYourBaseAddress
|
||||
X402_PRICE_USDC=0.1
|
||||
x402_NETWORK=eip155:8453
|
||||
PRIMARY_FACILITATOR=payai
|
||||
```
|
||||
|
||||
Protected endpoints return `402 Payment Required` when no valid payment is attached. x402-capable clients can pay and retry automatically.
|
||||
|
||||
### 4) x402 + Bearer Bypass
|
||||
|
||||
+16
-21
@@ -18,7 +18,7 @@ import { dirname, join } from 'path'
|
||||
import config from '../src/config/index.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import wlogger from '../src/adapters/wlogger.js'
|
||||
import { buildX402Routes, getX402Settings, getBasicAuthSettings, createAuthHeader, getFacilitatorConfig } from '../src/config/x402.js'
|
||||
import { buildX402Routes, getX402Settings, getBasicAuthSettings, createAuthHeader, getFacilitatorConnectionOptions } from '../src/config/x402.js'
|
||||
import { basicAuthMiddleware } from '../src/middleware/basic-auth.js'
|
||||
import DiscoveryRouter from '../src/controllers/discovery/router.js'
|
||||
|
||||
@@ -126,19 +126,17 @@ class Server {
|
||||
// X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally
|
||||
const routes = buildX402Routes(this.config.apiPrefix)
|
||||
|
||||
const primaryFacilitator = x402Settings.primaryFacilitator || 'cdp'
|
||||
const facilitatorConfig = getFacilitatorConfig(primaryFacilitator)
|
||||
const facilitatorOptions = {
|
||||
url: facilitatorConfig.url,
|
||||
createAuthHeaders: facilitatorConfig.requiresAuth ? createAuthHeader : null
|
||||
}
|
||||
const connectionOpts = getFacilitatorConnectionOptions()
|
||||
const facilitatorClients = connectionOpts.map(o => new HTTPFacilitatorClient({
|
||||
url: o.url,
|
||||
createAuthHeaders: o.requiresAuth ? createAuthHeader : null
|
||||
}))
|
||||
|
||||
wlogger.info(`x402 v2 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceUSDC} USDC per request (unless basic auth provided) [facilitator: ${facilitatorConfig.name}]`)
|
||||
wlogger.info(`x402 v2 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceUSDC} USDC per request (unless basic auth provided) [facilitators: ${connectionOpts.map(o => o.name).join(', ')}]`)
|
||||
|
||||
const facilitatorClient = new HTTPFacilitatorClient(facilitatorOptions)
|
||||
// x402 v2 exports use lowercase class names (x402ResourceServer).
|
||||
// eslint-disable-next-line new-cap
|
||||
const resourceServer = new x402ResourceServer(facilitatorClient)
|
||||
const resourceServer = new x402ResourceServer(facilitatorClients)
|
||||
registerExactEvmScheme(resourceServer, {})
|
||||
const x402Mw = x402PaymentMiddleware(routes, resourceServer)
|
||||
|
||||
@@ -152,22 +150,19 @@ class Server {
|
||||
|
||||
app.use(conditionalX402Middleware)
|
||||
|
||||
// Display facilitator running url
|
||||
console.log(`Facilitator running on: ${facilitatorOptions.url}`)
|
||||
console.log(`Facilitator(s): ${connectionOpts.map(o => `${o.name} → ${o.url}`).join(' | ')}`)
|
||||
} else if (x402Settings.enabled && !basicAuthSettings.enabled) {
|
||||
const routes = buildX402Routes(this.config.apiPrefix)
|
||||
const primaryFacilitator = x402Settings.primaryFacilitator || 'cdp'
|
||||
const facilitatorConfig = getFacilitatorConfig(primaryFacilitator)
|
||||
const facilitatorOptions = {
|
||||
url: facilitatorConfig.url,
|
||||
createAuthHeaders: facilitatorConfig.requiresAuth ? createAuthHeader : null
|
||||
}
|
||||
const connectionOpts = getFacilitatorConnectionOptions()
|
||||
const facilitatorClients = connectionOpts.map(o => new HTTPFacilitatorClient({
|
||||
url: o.url,
|
||||
createAuthHeaders: o.requiresAuth ? createAuthHeader : null
|
||||
}))
|
||||
|
||||
wlogger.info(`x402 v2 middleware enabled (basic auth disabled); enforcing ${x402Settings.priceUSDC} USDC per request [facilitator: ${facilitatorConfig.name}]`)
|
||||
wlogger.info(`x402 v2 middleware enabled (basic auth disabled); enforcing ${x402Settings.priceUSDC} USDC per request [facilitators: ${connectionOpts.map(o => o.name).join(', ')}]`)
|
||||
|
||||
const facilitatorClient = new HTTPFacilitatorClient(facilitatorOptions)
|
||||
// eslint-disable-next-line new-cap
|
||||
const resourceServer = new x402ResourceServer(facilitatorClient)
|
||||
const resourceServer = new x402ResourceServer(facilitatorClients)
|
||||
registerExactEvmScheme(resourceServer, {})
|
||||
app.use(x402PaymentMiddleware(routes, resourceServer))
|
||||
} else if (basicAuthSettings.enabled && !x402Settings.enabled) {
|
||||
|
||||
Generated
+1
@@ -15,6 +15,7 @@
|
||||
"@x402/core": "2.6.0",
|
||||
"@x402/evm": "2.6.0",
|
||||
"@x402/express": "2.6.0",
|
||||
"@x402/extensions": "2.6.0",
|
||||
"axios": "1.7.7",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.3.1",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"@x402/core": "2.6.0",
|
||||
"@x402/evm": "2.6.0",
|
||||
"@x402/express": "2.6.0",
|
||||
"@x402/extensions": "2.6.0",
|
||||
"axios": "1.7.7",
|
||||
"cors": "2.8.5",
|
||||
"dotenv": "16.3.1",
|
||||
|
||||
@@ -30,9 +30,23 @@ export X402_ENABLED=true
|
||||
export SERVER_BASE_ADDRESS=0xd32585CE60815654C50CAf350e18de8096061e63
|
||||
export X402_PRICE_USDC=0.1
|
||||
export x402_NETWORK=base
|
||||
export ACTIVE_FACILITATORS=dexter,payai
|
||||
export X402_BAZAAR_ENABLED=true
|
||||
export PRIMARY_FACILITATOR=cdp
|
||||
|
||||
# Coinbase CDP Facilitator (for Bazaar registration)
|
||||
export x402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
|
||||
# Facilitator: cdp (default) | dexter | payai. Default base URL follows PRIMARY_FACILITATOR unless x402_FACILITATOR_URL is set.
|
||||
# export PRIMARY_FACILITATOR=payai
|
||||
# Multi-facilitator (CDP + Dexter + PayAI): unset x402_FACILITATOR_URL or use single-facilitator mode only.
|
||||
# export ACTIVE_FACILITATORS=cdp,dexter,payai
|
||||
# Bazaar discovery on 402 responses (default on)
|
||||
# export X402_BAZAAR_ENABLED=true
|
||||
# Optional PayAI host override (default https://facilitator.payai.network)
|
||||
# export PAYAI_FACILITATOR_URL=https://facilitator.payai.network
|
||||
|
||||
# Optional: force a specific facilitator base URL (overrides PRIMARY_FACILITATOR URL resolution)
|
||||
# export x402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
|
||||
|
||||
# CDP JWT credentials (required only when using Coinbase CDP)
|
||||
export FACILITATOR_KEY_ID=your_key_id_here
|
||||
export FACILITATOR_SECRET_KEY=your_secret_key_here
|
||||
|
||||
|
||||
Vendored
+24
-4
@@ -48,16 +48,36 @@ const x402NetworkRaw =
|
||||
'eip155:8453'
|
||||
const x402Network = toV2Caip2Network(x402NetworkRaw)
|
||||
|
||||
const primaryFacilitatorRaw = (process.env.PRIMARY_FACILITATOR || 'cdp').trim().toLowerCase()
|
||||
const x402PrimaryKeys = new Set(['cdp', 'dexter', 'payai'])
|
||||
const primaryFacilitator = x402PrimaryKeys.has(primaryFacilitatorRaw)
|
||||
? primaryFacilitatorRaw
|
||||
: 'cdp'
|
||||
|
||||
function resolveX402FacilitatorUrl () {
|
||||
const explicit = (process.env.x402_FACILITATOR_URL || '').trim()
|
||||
if (explicit) return explicit.replace(/\/$/, '')
|
||||
|
||||
if (primaryFacilitator === 'dexter') return 'https://x402.dexter.cash'
|
||||
if (primaryFacilitator === 'payai') {
|
||||
return (process.env.PAYAI_FACILITATOR_URL || 'https://facilitator.payai.network')
|
||||
.trim()
|
||||
.replace(/\/$/, '')
|
||||
}
|
||||
return 'https://api.cdp.coinbase.com/platform/v2/x402'
|
||||
}
|
||||
|
||||
const x402Defaults = {
|
||||
enabled: normalizeBoolean(process.env.X402_ENABLED, true),
|
||||
// CDP Facilitator: https://api.cdp.coinbase.com/platform/v2/x402
|
||||
// Custom/Local Facilitator: http://localhost:4022
|
||||
facilitatorUrl: process.env.x402_FACILITATOR_URL || 'https://api.cdp.coinbase.com/platform/v2/x402',
|
||||
// Bazaar discovery extension on payment requirements (for facilitator catalogs). @x402/express loads @x402/extensions/bazaar when enabled.
|
||||
bazaarEnabled: normalizeBoolean(process.env.X402_BAZAAR_ENABLED, true),
|
||||
// Resolved from PRIMARY_FACILITATOR unless x402_FACILITATOR_URL is set (see resolveX402FacilitatorUrl).
|
||||
facilitatorUrl: resolveX402FacilitatorUrl(),
|
||||
// EVM 0x… address for USDC settlement; required for x402 exact scheme (no legacy BCH default).
|
||||
serverAddress: (process.env.SERVER_BASE_ADDRESS || '').trim(),
|
||||
facilitatorKeyId: process.env.FACILITATOR_KEY_ID || '',
|
||||
facilitatorSecretKey: process.env.FACILITATOR_SECRET_KEY || '',
|
||||
primaryFacilitator: process.env.PRIMARY_FACILITATOR || 'cdp',
|
||||
primaryFacilitator,
|
||||
network: x402Network,
|
||||
priceUSDC,
|
||||
// Optional: USDC token contract (0x…). If unset, Base / Base Sepolia use built-in USDC addresses.
|
||||
|
||||
+114
-17
@@ -1,5 +1,6 @@
|
||||
import config from './index.js'
|
||||
import { generateJwt } from '@coinbase/cdp-sdk/auth'
|
||||
import { declareDiscoveryExtension } from '@x402/extensions/bazaar'
|
||||
|
||||
const DEFAULT_DESCRIPTION = 'Access to protected psf-bch-api resources'
|
||||
const DEFAULT_TIMEOUT_SECONDS = 120
|
||||
@@ -20,6 +21,12 @@ const FACILITATORS = {
|
||||
url: 'https://x402.dexter.cash',
|
||||
requiresAuth: false,
|
||||
authType: 'none'
|
||||
},
|
||||
payai: {
|
||||
name: 'PayAI',
|
||||
url: 'https://facilitator.payai.network',
|
||||
requiresAuth: false,
|
||||
authType: 'none'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,26 +61,114 @@ export function buildX402Routes (apiPrefix = '/v6') {
|
||||
if (!payTo) throw new Error('SERVER_BASE_ADDRESS is required for x402 v2 payTo.')
|
||||
assertEvmPayTo(payTo)
|
||||
|
||||
return {
|
||||
[routeKey]: {
|
||||
accepts: [
|
||||
{
|
||||
scheme: 'exact',
|
||||
payTo,
|
||||
price: config.x402.priceUSDC,
|
||||
network,
|
||||
maxTimeoutSeconds: DEFAULT_TIMEOUT_SECONDS
|
||||
}
|
||||
],
|
||||
description: `${DEFAULT_DESCRIPTION} (${config.x402.priceUSDC} USDC)`,
|
||||
mimeType: 'application/json'
|
||||
}
|
||||
const entry = {
|
||||
accepts: [
|
||||
{
|
||||
scheme: 'exact',
|
||||
payTo,
|
||||
price: config.x402.priceUSDC,
|
||||
network,
|
||||
maxTimeoutSeconds: DEFAULT_TIMEOUT_SECONDS
|
||||
}
|
||||
],
|
||||
description: `${DEFAULT_DESCRIPTION} (${config.x402.priceUSDC} USDC)`,
|
||||
mimeType: 'application/json'
|
||||
}
|
||||
|
||||
if (config.x402.bazaarEnabled) {
|
||||
entry.extensions = declareDiscoveryExtension({
|
||||
output: {
|
||||
example: {
|
||||
service: 'psf-bch-api',
|
||||
apiPrefix: prefixWithSlash,
|
||||
description: 'Bitcoin Cash full node, Fulcrum, and SLP indexer REST proxy (x402 USDC on Base)'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
[routeKey]: entry
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default facilitator order when ACTIVE_FACILITATORS lists CDP, Dexter, and PayAI (Bazaar-friendly multi-facilitator).
|
||||
*/
|
||||
export const DEFAULT_MULTI_FACILITATORS = ['cdp', 'dexter', 'payai']
|
||||
|
||||
/**
|
||||
* Per-facilitator HTTP base URL (no trailing slash). Used when multiple facilitators are active.
|
||||
* @param {string} key
|
||||
*/
|
||||
export function getFacilitatorHttpUrl (key) {
|
||||
const k = String(key || 'cdp').trim().toLowerCase()
|
||||
if (k === 'dexter') return 'https://x402.dexter.cash'
|
||||
if (k === 'payai') {
|
||||
return (process.env.PAYAI_FACILITATOR_URL || 'https://facilitator.payai.network')
|
||||
.trim()
|
||||
.replace(/\/$/, '')
|
||||
}
|
||||
return 'https://api.cdp.coinbase.com/platform/v2/x402'
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved primary facilitator key (env PRIMARY_FACILITATOR, then config, then cdp).
|
||||
* @returns {string}
|
||||
*/
|
||||
export function resolvePrimaryFacilitatorKey () {
|
||||
const primaryRaw = (
|
||||
process.env.PRIMARY_FACILITATOR ||
|
||||
config.x402?.primaryFacilitator ||
|
||||
'cdp'
|
||||
).trim().toLowerCase()
|
||||
return FACILITATORS[primaryRaw] ? primaryRaw : 'cdp'
|
||||
}
|
||||
|
||||
/**
|
||||
* Active facilitator keys: PRIMARY_FACILITATOR is always first (x402 core gives earlier
|
||||
* facilitator clients precedence). Remaining keys follow ACTIVE_FACILITATORS order, deduped.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export function getActiveFacilitatorKeys () {
|
||||
const primary = resolvePrimaryFacilitatorKey()
|
||||
const raw = (process.env.ACTIVE_FACILITATORS || '').trim()
|
||||
if (!raw) {
|
||||
return [primary]
|
||||
}
|
||||
const fromEnv = raw.split(',').map(s => s.trim().toLowerCase()).filter(k => FACILITATORS[k])
|
||||
if (fromEnv.length === 0) {
|
||||
return [primary]
|
||||
}
|
||||
const rest = fromEnv.filter(k => k !== primary)
|
||||
return [primary, ...rest]
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for constructing one {@link import('@x402/core/server').HTTPFacilitatorClient} per facilitator.
|
||||
* When only one facilitator is active, `url` is `config.x402.facilitatorUrl` so `x402_FACILITATOR_URL` still applies.
|
||||
* @returns {{ key: string, name: string, url: string, requiresAuth: boolean }[]}
|
||||
*/
|
||||
export function getFacilitatorConnectionOptions () {
|
||||
const keys = getActiveFacilitatorKeys()
|
||||
return keys.map(key => {
|
||||
const cfg = getFacilitatorConfig(key)
|
||||
const url =
|
||||
keys.length === 1 ? config.x402.facilitatorUrl : getFacilitatorHttpUrl(key)
|
||||
return {
|
||||
key,
|
||||
name: cfg.name,
|
||||
url,
|
||||
requiresAuth: Boolean(cfg.requiresAuth)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getX402Settings () {
|
||||
return {
|
||||
enabled: Boolean(config.x402?.enabled),
|
||||
bazaarEnabled: Boolean(config.x402?.bazaarEnabled),
|
||||
activeFacilitatorKeys: getActiveFacilitatorKeys(),
|
||||
facilitatorUrl: config.x402?.facilitatorUrl,
|
||||
facilitatorKeyId: config.x402?.facilitatorKeyId,
|
||||
facilitatorSecretKey: config.x402?.facilitatorSecretKey,
|
||||
@@ -214,11 +309,12 @@ export async function createAuthHeader () {
|
||||
|
||||
/**
|
||||
* Get facilitator configuration by name
|
||||
* @param {string} name - Facilitator name ('cdp' or 'dexter')
|
||||
* @param {string} name - Facilitator name (`cdp`, `dexter`, or `payai`)
|
||||
* @returns {Object} Facilitator config
|
||||
*/
|
||||
export function getFacilitatorConfig (name = 'cdp') {
|
||||
return FACILITATORS[name] || FACILITATORS.cdp
|
||||
const key = String(name || 'cdp').trim().toLowerCase()
|
||||
return FACILITATORS[key] || FACILITATORS.cdp
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,6 +323,7 @@ export function getFacilitatorConfig (name = 'cdp') {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function facilitatorRequiresAuth (name = 'cdp') {
|
||||
const cfg = FACILITATORS[name]
|
||||
const key = String(name || 'cdp').trim().toLowerCase()
|
||||
const cfg = FACILITATORS[key]
|
||||
return cfg ? cfg.requiresAuth : false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Unit tests for x402 facilitator registry (PayAI, Dexter, CDP).
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
getFacilitatorConfig,
|
||||
facilitatorRequiresAuth
|
||||
} from '../../../src/config/x402.js'
|
||||
|
||||
describe('#x402 facilitators', () => {
|
||||
it('should register PayAI without auth', () => {
|
||||
assert.equal(facilitatorRequiresAuth('payai'), false)
|
||||
const cfg = getFacilitatorConfig('payai')
|
||||
assert.equal(cfg.name, 'PayAI')
|
||||
assert.equal(cfg.url, 'https://facilitator.payai.network')
|
||||
assert.equal(cfg.requiresAuth, false)
|
||||
})
|
||||
|
||||
it('should normalize facilitator key case', () => {
|
||||
const cfg = getFacilitatorConfig('PAYAI')
|
||||
assert.equal(cfg.name, 'PayAI')
|
||||
})
|
||||
|
||||
it('should require auth for CDP only', () => {
|
||||
assert.equal(facilitatorRequiresAuth('cdp'), true)
|
||||
assert.equal(facilitatorRequiresAuth('dexter'), false)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Multi-facilitator and Bazaar route config (env-dependent helpers).
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
getActiveFacilitatorKeys,
|
||||
getFacilitatorHttpUrl
|
||||
} from '../../../src/config/x402.js'
|
||||
|
||||
describe('#x402 multi-facilitator + bazaar helpers', () => {
|
||||
const saved = {}
|
||||
|
||||
beforeEach(() => {
|
||||
saved.ACTIVE_FACILITATORS = process.env.ACTIVE_FACILITATORS
|
||||
saved.PRIMARY_FACILITATOR = process.env.PRIMARY_FACILITATOR
|
||||
delete process.env.ACTIVE_FACILITATORS
|
||||
process.env.PRIMARY_FACILITATOR = 'cdp'
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (saved.ACTIVE_FACILITATORS === undefined) delete process.env.ACTIVE_FACILITATORS
|
||||
else process.env.ACTIVE_FACILITATORS = saved.ACTIVE_FACILITATORS
|
||||
if (saved.PRIMARY_FACILITATOR === undefined) delete process.env.PRIMARY_FACILITATOR
|
||||
else process.env.PRIMARY_FACILITATOR = saved.PRIMARY_FACILITATOR
|
||||
})
|
||||
|
||||
it('getActiveFacilitatorKeys puts PRIMARY_FACILITATOR first, then ACTIVE_FACILITATORS', () => {
|
||||
process.env.PRIMARY_FACILITATOR = 'cdp'
|
||||
process.env.ACTIVE_FACILITATORS = 'payai,cdp,dexter'
|
||||
assert.deepEqual(getActiveFacilitatorKeys(), ['cdp', 'payai', 'dexter'])
|
||||
})
|
||||
|
||||
it('getActiveFacilitatorKeys prepends primary when not listed in ACTIVE_FACILITATORS', () => {
|
||||
process.env.PRIMARY_FACILITATOR = 'cdp'
|
||||
process.env.ACTIVE_FACILITATORS = 'dexter,payai'
|
||||
assert.deepEqual(getActiveFacilitatorKeys(), ['cdp', 'dexter', 'payai'])
|
||||
})
|
||||
|
||||
it('getActiveFacilitatorKeys falls back to PRIMARY_FACILITATOR', () => {
|
||||
process.env.PRIMARY_FACILITATOR = 'dexter'
|
||||
assert.deepEqual(getActiveFacilitatorKeys(), ['dexter'])
|
||||
})
|
||||
|
||||
it('getFacilitatorHttpUrl returns known bases', () => {
|
||||
assert.include(getFacilitatorHttpUrl('cdp'), 'cdp.coinbase.com')
|
||||
assert.equal(getFacilitatorHttpUrl('dexter'), 'https://x402.dexter.cash')
|
||||
assert.equal(getFacilitatorHttpUrl('payai'), 'https://facilitator.payai.network')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user