From bfb73c2e1411ab23423b22af1e7b1f7428c7feb0 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 22 Apr 2026 18:01:41 -0700 Subject: [PATCH 1/2] fix(AI endpoints): Adding AI friendly endpoints --- bin/server.js | 11 +++- src/controllers/discovery/controller.js | 53 +++++------------ src/discovery/build-documents.js | 59 +++++++++++++------ .../controllers/discovery-controller-unit.js | 33 +++++++++-- 4 files changed, 96 insertions(+), 60 deletions(-) diff --git a/bin/server.js b/bin/server.js index 95a2939..38e3350 100644 --- a/bin/server.js +++ b/bin/server.js @@ -21,6 +21,7 @@ import wlogger from '../src/adapters/wlogger.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' +import DiscoveryController from '../src/controllers/discovery/controller.js' import { createFacilitatorClient } from '../src/lib/x402/facilitator-client-factory.js' // Load environment variables @@ -259,7 +260,15 @@ class Server { res.sendFile(docsPath) }) - // AI-friendly discovery endpoints + // AI-friendly discovery endpoints (aliases for tools that expect these paths; same payloads as DiscoveryRouter) + const discoveryForAi = new DiscoveryController() + app.get('/.well-known/x402.json', (req, res) => { + discoveryForAi.x402Manifest(req, res) + }) + app.get('/agent.json', (req, res) => { + discoveryForAi.agentManifest(req, res) + }) + app.get('/llms.txt', (req, res) => { const llmsPath = join(__dirname, '..', 'public', 'llms.txt') res.setHeader('Content-Type', 'text/plain') diff --git a/src/controllers/discovery/controller.js b/src/controllers/discovery/controller.js index f5605cf..a276c11 100644 --- a/src/controllers/discovery/controller.js +++ b/src/controllers/discovery/controller.js @@ -3,16 +3,15 @@ */ import config from '../../config/index.js' -import { getX402Settings } from '../../config/x402.js' +import wlogger from '../../adapters/wlogger.js' +import { getX402Settings, getX402WellKnownManifest as defaultGetX402WellKnownManifest } from '../../config/x402.js' import { getDiscoveryDocuments } from '../../discovery/build-documents.js' -const BCH_MAINNET_CAIP2 = 'bip122:000000000000000000651ef99cb9fcbe' -const BCH_NATIVE_ASSET = '0x0000000000000000000000000000000000000001' -const X402_TIMEOUT_SECONDS = 60 - class DiscoveryController { constructor (localConfig = {}) { this.getX402Settings = localConfig.getX402Settings || getX402Settings + this.getX402WellKnownManifest = + localConfig.getX402WellKnownManifest || defaultGetX402WellKnownManifest this.getDiscoveryDocuments = localConfig.getDiscoveryDocuments || getDiscoveryDocuments this.apiPrefix = localConfig.apiPrefix || config.apiPrefix @@ -36,47 +35,25 @@ class DiscoveryController { } /** - * @api {get} /.well-known/x402 x402-bch resource discovery + * @api {get} /.well-known/x402 x402 v2 resource discovery * @apiName X402Discovery * @apiGroup Discovery - * @apiDescription Returns x402-bch v2 resource and payment requirement metadata. + * @apiDescription Returns x402 v2 resource and payment metadata (Base USDC, `exact` scheme) — same payload as `/.well-known/x402.json`. */ x402Manifest (req, res) { if (this.respondNotFoundWhenDisabled(res)) return - const x402 = this.getX402Settings() const apiPrefix = this.apiPrefix || '/v6' - const prefixWithSlash = apiPrefix.startsWith('/') ? apiPrefix : `/${apiPrefix}` - - const payload = { - x402Version: 2, - network: BCH_MAINNET_CAIP2, - facilitator: { - url: x402.facilitatorUrl - }, - resources: [ - { - resource: `${prefixWithSlash}/*`, - type: 'http', - x402Version: 2, - accepts: [ - { - scheme: 'utxo', - network: BCH_MAINNET_CAIP2, - amount: String(x402.priceSat), - description: `Access to protected psf-bch-api resources (${x402.priceSat} satoshis)`, - mimeType: 'application/json', - payTo: x402.serverAddress, - maxTimeoutSeconds: X402_TIMEOUT_SECONDS, - asset: BCH_NATIVE_ASSET, - extra: {} - } - ] - } - ] + try { + const payload = this.getX402WellKnownManifest(apiPrefix) + return res.status(200).json(payload) + } catch (err) { + wlogger.error('x402 well-known manifest error:', err) + return res.status(500).json({ + error: 'x402 configuration error', + message: err instanceof Error ? err.message : String(err) + }) } - - return res.status(200).json(payload) } /** diff --git a/src/discovery/build-documents.js b/src/discovery/build-documents.js index 7bf5ed0..f06b860 100644 --- a/src/discovery/build-documents.js +++ b/src/discovery/build-documents.js @@ -6,9 +6,8 @@ import { readFileSync, existsSync, readdirSync } from 'fs' import { resolve } from 'path' import config from '../config/index.js' -import { getX402Settings } from '../config/x402.js' +import { getX402AgentAuthPricing } from '../config/x402.js' -const BCH_MAINNET_CAIP2 = 'bip122:000000000000000000651ef99cb9fcbe' const X402_SPEC_VERSION = 2 let cachedDocs = null @@ -90,7 +89,7 @@ function normalizePaths (endpoints) { description: 'Payment required when x402 is enabled' } }, - 'x-payment-model': 'x402-bch-v2' + 'x-payment-model': 'x402-v2-exact-usdc' } paths[endpoint.path][endpoint.method.toLowerCase()] = op @@ -134,13 +133,15 @@ function buildLlms (endpoints) { const lines = [ '# psf-bch-api', '', - '> REST API proxy to Bitcoin Cash infrastructure with optional x402-bch monetized access.', + '> REST API proxy to Bitcoin Cash infrastructure with optional x402 (USDC on Base) monetized access.', '', '## Discovery', '- [OpenAPI document](/openapi.json): OpenAPI 3 projection generated from apiDoc annotations.', '- [Swagger document](/swagger.json): Swagger 2.0 compatibility projection.', - '- [x402 manifest](/.well-known/x402): x402-bch v2 payment requirement discovery.', + '- [x402 manifest](/.well-known/x402): x402 v2 payment requirement discovery (Base USDC).', + '- [x402.json](/.well-known/x402.json): same JSON as the x402 manifest (for tools that expect a `.json` path).', '- [Agent manifest](/.well-known/agent.json): draft agent capability surface.', + '- [agent.json](/agent.json): same as the agent manifest (convenience path).', '', '## API Groups' ] @@ -157,7 +158,7 @@ function buildLlms (endpoints) { } function buildAgent (endpoints) { - const x402 = getX402Settings() + const pricing = getX402AgentAuthPricing() const actions = endpoints.map(endpoint => ({ id: endpoint.operationId, description: endpoint.summary, @@ -166,24 +167,48 @@ function buildAgent (endpoints) { method: endpoint.method })) + const auth = pricing + ? { + type: 'x402', + x402Version: X402_SPEC_VERSION, + required_for: actions.map(a => a.id), + pricing: { + scheme: pricing.scheme, + network: pricing.network, + payTo: pricing.payTo, + priceUSDC: String(pricing.priceUSDC), + facilitatorUrl: pricing.facilitatorUrl + } + } + : { + type: 'x402', + x402Version: X402_SPEC_VERSION, + required_for: actions.map(a => a.id), + pricing: { + note: 'Configure SERVER_BASE_ADDRESS, x402_NETWORK, and x402 for full pricing when X402 is enabled' + } + } + return { awp_version: '0.1', - domain: 'localhost', + name: 'psf-bch-api', + description: + 'REST API for BCH full node, Fulcrum, and SLP data; access via x402 USDC on Base or documented bypasses.', + domain: (process.env.PUBLIC_BASE_URL || '').trim() || null, intent: 'Programmatic BCH blockchain API access', + discovery: { + openapi: '/openapi.json', + x402: '/.well-known/x402', + x402_json: '/.well-known/x402.json', + agent: '/.well-known/agent.json', + agent_json: '/agent.json', + llms: '/llms.txt' + }, capabilities: { batch_actions: false, streaming: false }, - auth: { - type: 'x402-bch-v2', - required_for: actions.map(a => a.id), - pricing: { - amountSat: String(x402.priceSat), - payTo: x402.serverAddress, - network: BCH_MAINNET_CAIP2, - x402Version: X402_SPEC_VERSION - } - }, + auth, actions } } diff --git a/test/unit/controllers/discovery-controller-unit.js b/test/unit/controllers/discovery-controller-unit.js index 834cec1..df11c26 100644 --- a/test/unit/controllers/discovery-controller-unit.js +++ b/test/unit/controllers/discovery-controller-unit.js @@ -14,8 +14,33 @@ describe('#discovery-controller.js', () => { getX402Settings: () => ({ enabled, facilitatorUrl: 'http://localhost:4345/facilitator', - serverAddress: 'bitcoincash:qtestaddress', - priceSat: 200 + serverAddress: '0x0000000000000000000000000000000000000001', + priceUSDC: '0.1', + network: 'eip155:8453' + }), + getX402WellKnownManifest: () => ({ + x402Version: 2, + network: 'eip155:8453', + facilitator: { url: 'http://localhost:4345/facilitator' }, + resources: [ + { + resource: '/v6/*', + type: 'http', + x402Version: 2, + accepts: [ + { + scheme: 'exact', + network: 'eip155:8453', + price: '0.1', + payTo: '0x0000000000000000000000000000000000000001', + description: 'test', + mimeType: 'application/json', + maxTimeoutSeconds: 120, + extra: {} + } + ] + } + ] }), getDiscoveryDocuments: () => ({ openapi: { openapi: '3.0.3', paths: { '/v6/price/bchusd': {} } }, @@ -59,8 +84,8 @@ describe('#discovery-controller.js', () => { assert.equal(res.statusValue, 200) assert.equal(res.jsonData.x402Version, 2) assert.isArray(res.jsonData.resources) - assert.equal(res.jsonData.resources[0].accepts[0].scheme, 'utxo') - assert.equal(res.jsonData.resources[0].accepts[0].amount, '200') + assert.equal(res.jsonData.resources[0].accepts[0].scheme, 'exact') + assert.equal(res.jsonData.resources[0].accepts[0].price, '0.1') }) it('should return openapi and swagger docs', async () => { From 7a16aa6278bd1d77f1077a0be8182814ca7125dd Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 22 Apr 2026 18:02:18 -0700 Subject: [PATCH 2/2] fix(Docker): Using master branch in Docker container --- production/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/docker/Dockerfile b/production/docker/Dockerfile index 9ee6952..184f6ce 100644 --- a/production/docker/Dockerfile +++ b/production/docker/Dockerfile @@ -51,7 +51,7 @@ RUN git clone https://github.com/Permissionless-Software-Foundation/psf-bch-api- # and `stage` has the most up-to-date changes. WORKDIR /home/safeuser/psf-bch-api-base -RUN git checkout round-robin-facilitator +#RUN git checkout round-robin-facilitator # Install dependencies RUN npm install