diff --git a/.env-example b/.env-example index b4ac956..dbfa1fe 100644 --- a/.env-example +++ b/.env-example @@ -22,15 +22,12 @@ export LOCAL_RESTURL=http://localhost:5942/v6 export PORT=5942 # x402 payments required to access this API? -# Facilitators https://docs.cdp.coinbase.com/x402/quickstart-for-sellers export X402_ENABLED=true export SERVER_BASE_ADDRESS=0xd32585CE60815654C50CAf350e18de8096061e63 -export FACILITATOR_URL=https://x402.org/facilitator #Testnet Facilitator -export X402_PRICE_USDC=0.01 +export X402_PRICE_USDC=0.1 +export x402_NETWORK=base +export x402_FACILITATOR_URL=http://localhost:4022 -# Mainnet facilitator access keys -export FACILITATOR_KEY_ID= -export FACILITATOR_SECRET_KEY= # Basic Authentication required to access this API? export USE_BASIC_AUTH=true diff --git a/.env-facilitator-example b/.env-facilitator-example new file mode 100644 index 0000000..594b25f --- /dev/null +++ b/.env-facilitator-example @@ -0,0 +1,4 @@ +export EVM_PRIVATE_KEY= +export NETWORK=base + +node facilitator-server.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index 547ee0d..3d12b90 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ logs/ docs/ coverage/ start.sh +start-facilitator.sh diff --git a/bin/server.js b/bin/server.js index be0fb0c..587edeb 100644 --- a/bin/server.js +++ b/bin/server.js @@ -16,7 +16,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 } from '../src/config/x402.js' +import { buildX402Routes, getX402Settings, getBasicAuthSettings, createAuthHeader } from '../src/config/x402.js' import { basicAuthMiddleware } from '../src/middleware/basic-auth.js' // Load environment variables @@ -111,10 +111,7 @@ class Server { if (facilitatorOptions) { facilitatorOptions = { url: x402Settings.facilitatorUrl, - /** TODO : Auth headers for request mainnet facilitator needed */ - createAuthHeaders: async () => { - return {} - } + createAuthHeaders: createAuthHeader } } @@ -122,6 +119,7 @@ class Server { // Create conditional x402 middleware that bypasses if basic auth is valid const conditionalX402Middleware = (req, res, next) => { + // req.headers['accept'] = 'application/json'; // If basic auth is valid, bypass x402 if (req.locals?.basicAuthValid === true) { return next() @@ -144,10 +142,7 @@ class Server { if (facilitatorOptions) { facilitatorOptions = { url: x402Settings.facilitatorUrl, - /** TODO : Auth headers for request mainnet facilitator needed */ - createAuthHeaders: async () => { - return {} - } + createAuthHeaders: createAuthHeader } } diff --git a/examples/08-x402-client.js b/examples/08-x402-axios-client.js similarity index 74% rename from examples/08-x402-client.js rename to examples/08-x402-axios-client.js index 8ecde0f..9a9064e 100644 --- a/examples/08-x402-client.js +++ b/examples/08-x402-axios-client.js @@ -1,26 +1,32 @@ import axios from 'axios' import { withPaymentInterceptor } from 'x402-axios' -import { mnemonicToAccount } from 'viem/accounts' -import { baseSepolia } from 'viem/chains' +import { privateKeyToAccount } from 'viem/accounts' +import { base } from 'viem/chains' +// import { baseSepolia } from 'viem/chains' // Testnet + import { createWalletClient, http } from 'viem' const baseURL = 'http://localhost:5942' const endpointPath = '/v6/full-node/blockchain/getBlockchainInfo' -const mnemonic = process.env.MNEMONIC || '' - -if (!mnemonic) throw new Error('MNEMONIC env required!') +const pKey = process.env.PRIVATE_KEY || '' +// PRIVATE_KEY=0x... node examples/08-x402-axios-client.js +if (!pKey) throw new Error('PRIVATE_KEY env required!') +console.log('pKey', pKey) // Create a wallet client -const account = mnemonicToAccount(mnemonic) +const account = privateKeyToAccount(pKey) const client = createWalletClient({ account, transport: http(), - chain: baseSepolia + chain: base }) const api = withPaymentInterceptor( axios.create({ - baseURL + baseURL, + headers: { + 'Content-Type': 'application/json' + } }), client ) diff --git a/package-lock.json b/package-lock.json index be7f30c..b37825c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,10 @@ "version": "7.0.0", "license": "MIT", "dependencies": { + "@coinbase/cdp-sdk": "1.45.0", "@psf/bch-js": "7.1.11", + "@x402/core": "2.6.0", + "@x402/evm": "2.6.0", "axios": "1.7.7", "cors": "2.8.5", "dotenv": "16.3.1", @@ -19,8 +22,9 @@ "slp-token-media": "1.2.10", "winston": "3.11.0", "winston-daily-rotate-file": "4.7.1", - "x402-axios": "^1.1.0", - "x402-express": "1.1.0" + "x402-axios": "1.1.0", + "x402-express": "1.1.0", + "x402-fetch": "1.1.0" }, "devDependencies": { "apidoc": "1.2.0", @@ -4182,6 +4186,49 @@ "node": ">=20" } }, + "node_modules/@spruceid/siwe-parser": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@spruceid/siwe-parser/-/siwe-parser-2.1.2.tgz", + "integrity": "sha512-d/r3S1LwJyMaRAKQ0awmo9whfXeE88Qt00vRj91q5uv5ATtWIQEGJ67Yr5eSZw5zp1/fZCXZYuEckt8lSkereQ==", + "license": "Apache-2.0", + "dependencies": { + "@noble/hashes": "^1.1.2", + "apg-js": "^4.3.0", + "uri-js": "^4.4.1", + "valid-url": "^1.0.9" + } + }, + "node_modules/@stablelib/binary": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz", + "integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==", + "license": "MIT", + "dependencies": { + "@stablelib/int": "^1.0.1" + } + }, + "node_modules/@stablelib/int": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", + "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==", + "license": "MIT" + }, + "node_modules/@stablelib/random": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", + "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", + "license": "MIT", + "dependencies": { + "@stablelib/binary": "^1.0.1", + "@stablelib/wipe": "^1.0.1" + } + }, + "node_modules/@stablelib/wipe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", + "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==", + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", @@ -5253,6 +5300,64 @@ } } }, + "node_modules/@x402/core": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@x402/core/-/core-2.6.0.tgz", + "integrity": "sha512-ISC/JeVss6xlKvor2rp18tJf9K5OQlIDDfZW1VZJQGDI2F4gy+HWxxkFfcQalCsPp4YUlwqh0YOkUxP+LTZWVg==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^3.24.2" + } + }, + "node_modules/@x402/evm": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@x402/evm/-/evm-2.6.0.tgz", + "integrity": "sha512-wPkNHf483gie1Up2sJSvERnW+VIEvMoT1KRXr09wSoSWgelglsJm+ug3gPPXKnT3C6AcmNAKZ12rBnu9Paff7g==", + "license": "Apache-2.0", + "dependencies": { + "@x402/core": "~2.6.0", + "@x402/extensions": "~2.6.0", + "viem": "^2.39.3", + "zod": "^3.24.2" + } + }, + "node_modules/@x402/extensions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@x402/extensions/-/extensions-2.6.0.tgz", + "integrity": "sha512-aLY9xAOOiRLKDN9HT2r9TYUXbD+IsoBces9qPZNVJGO2TBi2rfmbIBc3pcKCtWKn3iTvG2QFr3gpOFdpJRzqww==", + "license": "Apache-2.0", + "dependencies": { + "@scure/base": "^1.2.6", + "@x402/core": "~2.6.0", + "ajv": "^8.17.1", + "siwe": "^2.3.2", + "tweetnacl": "^1.0.3", + "viem": "^2.43.5", + "zod": "^3.24.2" + } + }, + "node_modules/@x402/extensions/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@x402/extensions/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -5333,6 +5438,13 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", + "license": "MIT", + "peer": true + }, "node_modules/agentkeepalive": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", @@ -5438,6 +5550,12 @@ "node": ">= 8" } }, + "node_modules/apg-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/apg-js/-/apg-js-4.4.0.tgz", + "integrity": "sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q==", + "license": "BSD-2-Clause" + }, "node_modules/apidoc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/apidoc/-/apidoc-1.2.0.tgz", @@ -9010,6 +9128,114 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/ethers": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", + "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", + "license": "MIT", + "peer": true + }, + "node_modules/ethers/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/ethers/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD", + "peer": true + }, + "node_modules/ethers/node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT", + "peer": true + }, + "node_modules/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/eventemitter2": { "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", @@ -13046,7 +13272,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -14173,6 +14398,21 @@ "url": "https://opencollective.com/sinon" } }, + "node_modules/siwe": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/siwe/-/siwe-2.3.2.tgz", + "integrity": "sha512-aSf+6+Latyttbj5nMu6GF3doMfv2UYj83hhwZgUF20ky6fTS83uVhkQABdIVnEuS8y1bBdk7p6ltb9SmlhTTlA==", + "license": "Apache-2.0", + "dependencies": { + "@spruceid/siwe-parser": "^2.1.2", + "@stablelib/random": "^1.0.1", + "uri-js": "^4.4.1", + "valid-url": "^1.0.9" + }, + "peerDependencies": { + "ethers": "^5.6.8 || ^6.0.8" + } + }, "node_modules/slp-mdm": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/slp-mdm/-/slp-mdm-0.0.7.tgz", @@ -14978,6 +15218,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -15377,7 +15623,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -15467,6 +15712,11 @@ "node": ">=10.12.0" } }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==" + }, "node_modules/valtio": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", @@ -16939,6 +17189,17 @@ "node": ">= 0.6" } }, + "node_modules/x402-fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/x402-fetch/-/x402-fetch-1.1.0.tgz", + "integrity": "sha512-cbuUrb258+5n/oZxnvw454+iuhBoFkLptr4WEvlcCi8Hg1utIA6n0O1KqXdBV7GgNmQ8LoUDOiVv9DEyIvnlTw==", + "license": "Apache-2.0", + "dependencies": { + "viem": "^2.21.26", + "x402": "^1.1.0", + "zod": "^3.24.2" + } + }, "node_modules/xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/package.json b/package.json index 1de32e4..7d68700 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "license": "MIT", "description": "REST API proxy to Bitcoin Cash infrastructure", "dependencies": { + "@coinbase/cdp-sdk": "1.45.0", "@psf/bch-js": "7.1.11", + "@x402/core": "2.6.0", + "@x402/evm": "2.6.0", "axios": "1.7.7", "cors": "2.8.5", "dotenv": "16.3.1", @@ -26,7 +29,8 @@ "winston": "3.11.0", "winston-daily-rotate-file": "4.7.1", "x402-axios": "1.1.0", - "x402-express": "1.1.0" + "x402-express": "1.1.0", + "x402-fetch": "1.1.0" }, "devDependencies": { "apidoc": "1.2.0", diff --git a/src/config/env/common.js b/src/config/env/common.js index a8d3869..1d289c3 100644 --- a/src/config/env/common.js +++ b/src/config/env/common.js @@ -33,10 +33,11 @@ const priceUSDC = Number.isFinite(parsedPrice) && parsedPrice > 0 ? parsedPrice const x402Defaults = { enabled: normalizeBoolean(process.env.X402_ENABLED, true), - facilitatorUrl: process.env.FACILITATOR_URL || 'http://localhost:4345/facilitator', + facilitatorUrl: process.env.x402_FACILITATOR_URL || 'http://localhost:4022', serverAddress: process.env.SERVER_BASE_ADDRESS || 'bitcoincash:qqsrke9lh257tqen99dkyy2emh4uty0vky9y0z0lsr', facilitatorKeyId: process.env.FACILITATOR_KEY_ID || '', facilitatorSecretKey: process.env.FACILITATOR_SECRET_KEY || '', + network: process.env.x402_NETWORK || 'base-sepolia', priceUSDC } diff --git a/src/config/x402.js b/src/config/x402.js index b3744dc..d17a700 100644 --- a/src/config/x402.js +++ b/src/config/x402.js @@ -1,8 +1,10 @@ import config from './index.js' +import { generateJwt } from '@coinbase/cdp-sdk/auth' const DEFAULT_DESCRIPTION = 'Access to protected psf-bch-api resources' -const DEFAULT_TIMEOUT_SECONDS = 60 -const NETWORK = 'base-sepolia' +const DEFAULT_TIMEOUT_SECONDS = 120 +const NETWORK = config.x402.network +if (!NETWORK) throw new Error('x402_NETWORK env required!') /** * Builds a route configuration map for x402-bch middleware. @@ -27,7 +29,15 @@ export function buildX402Routes (apiPrefix = '/v6') { network: NETWORK, config: { description: `${DEFAULT_DESCRIPTION} (${config.x402.priceUSDC} USDC)`, - maxTimeoutSeconds: DEFAULT_TIMEOUT_SECONDS + maxTimeoutSeconds: DEFAULT_TIMEOUT_SECONDS, + mimeType: 'application/json', + outputSchema: { + input: { + type: 'http', + method: 'GET', + discoverable: true + } + } } } } @@ -36,7 +46,7 @@ export function buildX402Routes (apiPrefix = '/v6') { export function getX402Settings () { return { enabled: Boolean(config.x402?.enabled), - facilitatorUrl: config.x402?.facilitatorUrl, // https://docs.cdp.coinbase.com/x402/quickstart-for-sellers + facilitatorUrl: config.x402?.facilitatorUrl, // https://docs.cdp.coinbase.com/x402/quickstart-for-sellers facilitatorKeyId: config.x402?.facilitatorKeyId, facilitatorSecretKey: config.x402?.facilitatorSecretKey, serverAddress: config.x402?.serverAddress, @@ -50,3 +60,38 @@ export function getBasicAuthSettings () { token: config.basicAuth?.token || '' } } + +/** + * + * Auth headers for interact with mainnet coin base facilitator endpoints. + * + */ +// https://docs.cdp.coinbase.com/get-started/authentication/jwt-authentication#javascript-2 +// https://docs.cdp.coinbase.com/api-reference/v2/rest-api/x402-facilitator/verify-a-payment +export async function createAuthHeader () { + // /verify endpoint jwt + const verifyToken = await generateJwt({ + apiKeyId: config.x402?.facilitatorKeyId, + apiKeySecret: config.x402?.facilitatorSecretKey, + requestMethod: 'POST', + requestHost: 'api.cdp.coinbase.com', + requestPath: '/platform/v2/x402/verify' + }) + // /settle endpoint jwt + const settleToken = await generateJwt({ + apiKeyId: config.x402?.facilitatorKeyId, + apiKeySecret: config.x402?.facilitatorSecretKey, + requestMethod: 'POST', + requestHost: 'api.cdp.coinbase.com', + requestPath: '/platform/v2/x402/settle' + }) + + return { + verify: { + Authorization: `Bearer ${verifyToken}` + }, + settle: { + Authorization: `Bearer ${settleToken}` + } + } +}