Merge pull request #8 from Permissionless-Software-Foundation/bot-unstable

Add multi-facilitator support with Dexter integration
This commit is contained in:
Chris Troutner
2026-04-09 06:41:12 -07:00
committed by GitHub
4 changed files with 69 additions and 21 deletions
+9 -3
View File
@@ -34,13 +34,19 @@ export X402_PRICE_USDC=0.1
# Use 'eip155:84532' for Base Sepolia (testnet)
export x402_NETWORK=eip155:8453
# Facilitator URL
# Primary Facilitator
# Options: 'cdp' (Coinbase) or 'dexter' (Dexter.cash - no API keys required)
export PRIMARY_FACILITATOR=cdp
# Facilitator URLs
# For Coinbase CDP (recommended): https://api.cdp.coinbase.com/platform/v2/x402
# For custom/local facilitator: http://localhost:4022
export x402_FACILITATOR_URL=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
# export x402_FACILITATOR_URL=https://api.cdp.coinbase.com/platform/v2/x402
# CDP API Credentials (required when using CDP facilitator)
# Get these from https://cdp.coinbase.com - API Keys section
# Not required for Dexter facilitator
export FACILITATOR_KEY_ID=your_key_id_here
export FACILITATOR_SECRET_KEY=your_secret_key_here
+20 -15
View File
@@ -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 } from '../src/config/x402.js'
import { buildX402Routes, getX402Settings, getBasicAuthSettings, createAuthHeader, getFacilitatorConfig } from '../src/config/x402.js'
import { basicAuthMiddleware } from '../src/middleware/basic-auth.js'
import DiscoveryRouter from '../src/controllers/discovery/router.js'
@@ -125,14 +125,15 @@ class Server {
if (x402Settings.enabled && basicAuthSettings.enabled) {
// X402_ENABLED=true AND USE_BASIC_AUTH=true: Apply x402 conditionally
const routes = buildX402Routes(this.config.apiPrefix)
const facilitatorOptions = x402Settings.facilitatorUrl
? {
url: x402Settings.facilitatorUrl,
createAuthHeaders: createAuthHeader
}
: undefined
wlogger.info(`x402 v2 middleware enabled with basic auth bypass; enforcing ${x402Settings.priceUSDC} USDC per request (unless basic auth provided)`)
const primaryFacilitator = x402Settings.primaryFacilitator || 'cdp'
const facilitatorConfig = getFacilitatorConfig(primaryFacilitator)
const facilitatorOptions = {
url: facilitatorConfig.url,
createAuthHeaders: facilitatorConfig.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}]`)
const facilitatorClient = new HTTPFacilitatorClient(facilitatorOptions)
// x402 v2 exports use lowercase class names (x402ResourceServer).
@@ -141,6 +142,7 @@ class Server {
registerExactEvmScheme(resourceServer, {})
const x402Mw = x402PaymentMiddleware(routes, resourceServer)
// Create conditional x402 middleware that bypasses if basic auth is valid
const conditionalX402Middleware = (req, res, next) => {
if (req.locals?.basicAuthValid === true) {
return next()
@@ -149,16 +151,19 @@ class Server {
}
app.use(conditionalX402Middleware)
// Display facilitator running url
console.log(`Facilitator running on: ${facilitatorOptions.url}`)
} else if (x402Settings.enabled && !basicAuthSettings.enabled) {
const routes = buildX402Routes(this.config.apiPrefix)
const facilitatorOptions = x402Settings.facilitatorUrl
? {
url: x402Settings.facilitatorUrl,
createAuthHeaders: createAuthHeader
}
: undefined
const primaryFacilitator = x402Settings.primaryFacilitator || 'cdp'
const facilitatorConfig = getFacilitatorConfig(primaryFacilitator)
const facilitatorOptions = {
url: facilitatorConfig.url,
createAuthHeaders: facilitatorConfig.requiresAuth ? createAuthHeader : null
}
wlogger.info(`x402 v2 middleware enabled (basic auth disabled); enforcing ${x402Settings.priceUSDC} USDC per request`)
wlogger.info(`x402 v2 middleware enabled (basic auth disabled); enforcing ${x402Settings.priceUSDC} USDC per request [facilitator: ${facilitatorConfig.name}]`)
const facilitatorClient = new HTTPFacilitatorClient(facilitatorOptions)
// eslint-disable-next-line new-cap
+1
View File
@@ -57,6 +57,7 @@ const x402Defaults = {
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',
network: x402Network,
priceUSDC,
// Optional: USDC token contract (0x…). If unset, Base / Base Sepolia use built-in USDC addresses.
+39 -3
View File
@@ -5,9 +5,23 @@ const DEFAULT_DESCRIPTION = 'Access to protected psf-bch-api resources'
const DEFAULT_TIMEOUT_SECONDS = 120
/**
* Resolve USDC contract + EIP-712 domain hints for the configured network.
* @param {string} network CAIP-2
* Facilitator configurations
* Supports multiple facilitators for redundancy and market coverage
*/
const FACILITATORS = {
cdp: {
name: 'Coinbase CDP',
url: 'https://api.cdp.coinbase.com/platform/v2/x402',
requiresAuth: true,
authType: 'jwt'
},
dexter: {
name: 'Dexter',
url: 'https://x402.dexter.cash',
requiresAuth: false,
authType: 'none'
}
}
/** x402 `exact` on EVM expects a checksummable 0x address for `payTo`. */
function assertEvmPayTo (payTo) {
@@ -22,6 +36,7 @@ function assertEvmPayTo (payTo) {
* Builds x402 v2 RoutesConfig for @x402/express (CAIP-2 network + accepts[]).
* Payment asset is explicitly USDC (ERC-20) via AssetAmount, not generic "money".
* @see https://docs.x402.org/guides/migration-v1-to-v2
* @param {string} apiPrefix Express API prefix (e.g., "/v6")
*/
export function buildX402Routes (apiPrefix = '/v6') {
const normalizedPrefix = apiPrefix.endsWith('/')
@@ -65,7 +80,9 @@ export function getX402Settings () {
serverAddress: config.x402?.serverAddress,
priceUSDC: config.x402?.priceUSDC,
usdcAssetAddress: config.x402?.usdcAssetAddress,
network: config.x402?.network
network: config.x402?.network,
facilitators: FACILITATORS,
primaryFacilitator: config.x402?.primaryFacilitator || 'cdp'
}
}
@@ -194,3 +211,22 @@ export async function createAuthHeader () {
}
}
}
/**
* Get facilitator configuration by name
* @param {string} name - Facilitator name ('cdp' or 'dexter')
* @returns {Object} Facilitator config
*/
export function getFacilitatorConfig (name = 'cdp') {
return FACILITATORS[name] || FACILITATORS.cdp
}
/**
* Check if facilitator requires authentication
* @param {string} name - Facilitator name
* @returns {boolean}
*/
export function facilitatorRequiresAuth (name = 'cdp') {
const cfg = FACILITATORS[name]
return cfg ? cfg.requiresAuth : false
}