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 #8 from Permissionless-Software-Foundation/bot-unstable
Add multi-facilitator support with Dexter integration
This commit is contained in:
+9
-3
@@ -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
@@ -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
|
||||
|
||||
Vendored
+1
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user