mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api-base.git
synced 2026-09-21 16:52:00 -07:00
fix(AI endpoints): Adding AI friendly endpoints
This commit is contained in:
+10
-1
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user