mirror of
https://github.com/Permissionless-Software-Foundation/x402-base-facilitator.git
synced 2026-09-21 16:52:01 -07:00
feat(x402): Implemented x402 Base facilitator
This commit is contained in:
Vendored
+5
-1
@@ -158,5 +158,9 @@ export default {
|
|||||||
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
|
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
|
||||||
|
|
||||||
// Admin password
|
// Admin password
|
||||||
adminPassword: process.env.ADMIN_PASSWORD
|
adminPassword: process.env.ADMIN_PASSWORD,
|
||||||
|
|
||||||
|
// Facilitator Configuration
|
||||||
|
network: process.env.NETWORK || 'base-sepolia',
|
||||||
|
evmPrivateKey: process.env.EVM_PRIVATE_KEY ? process.env.EVM_PRIVATE_KEY : '0x47e17173e204a3007023e19f210b31e1398d50a1df270243df997e0b57b1208a'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export DISABLE_IPFS=1
|
||||||
|
export NO_MONGO=1
|
||||||
|
export PORT=4022
|
||||||
|
export EVM_PRIVATE_KEY=
|
||||||
|
export NETWORK=base
|
||||||
|
|
||||||
|
npm start
|
||||||
Generated
+3649
-1637
File diff suppressed because it is too large
Load Diff
@@ -25,10 +25,13 @@
|
|||||||
},
|
},
|
||||||
"repository": "Permissionless-Software-Foundation/ipfs-service-provider",
|
"repository": "Permissionless-Software-Foundation/ipfs-service-provider",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@x402/core": "^2.7.0",
|
||||||
|
"@x402/evm": "^2.7.0",
|
||||||
"axios": "0.27.2",
|
"axios": "0.27.2",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"glob": "7.1.6",
|
"glob": "7.1.6",
|
||||||
"helia-coord": "2.0.3",
|
"helia-coord": "2.0.3",
|
||||||
|
"install": "^0.13.0",
|
||||||
"jsonrpc-lite": "2.2.0",
|
"jsonrpc-lite": "2.2.0",
|
||||||
"jsonwebtoken": "8.5.1",
|
"jsonwebtoken": "8.5.1",
|
||||||
"jwt-bch-lib": "1.3.0",
|
"jwt-bch-lib": "1.3.0",
|
||||||
@@ -48,8 +51,10 @@
|
|||||||
"mongoose": "5.13.14",
|
"mongoose": "5.13.14",
|
||||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||||
"nodemailer": "6.7.5",
|
"nodemailer": "6.7.5",
|
||||||
|
"npm": "^11.11.1",
|
||||||
"passport-local": "1.0.0",
|
"passport-local": "1.0.0",
|
||||||
"public-ip": "6.0.1",
|
"public-ip": "6.0.1",
|
||||||
|
"viem": "^2.47.4",
|
||||||
"winston": "3.3.3",
|
"winston": "3.3.3",
|
||||||
"winston-daily-rotate-file": "4.5.0"
|
"winston-daily-rotate-file": "4.5.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
import { x402Facilitator as X402Facilitator } from '@x402/core/facilitator'
|
||||||
|
import { registerExactEvmScheme } from '@x402/evm/exact/facilitator'
|
||||||
|
import { toFacilitatorEvmSigner } from '@x402/evm'
|
||||||
|
import { createWalletClient, http, publicActions } from 'viem'
|
||||||
|
import { privateKeyToAccount } from 'viem/accounts'
|
||||||
|
import { baseSepolia, base } from 'viem/chains'
|
||||||
|
|
||||||
|
class FacilitatorAdapter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
this.config = localConfig
|
||||||
|
|
||||||
|
this.facilitator = new X402Facilitator()
|
||||||
|
|
||||||
|
this.NETWORK = this.config.network || 'base'
|
||||||
|
this.EVM_PRIVATE_KEY = this.config.evmPrivateKey
|
||||||
|
|
||||||
|
this.createWalletClient = createWalletClient
|
||||||
|
this.registerExactEvmScheme = registerExactEvmScheme
|
||||||
|
this.privateKeyToAccount = privateKeyToAccount
|
||||||
|
this.toFacilitatorEvmSigner = toFacilitatorEvmSigner
|
||||||
|
|
||||||
|
this.start = this.start.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async start () {
|
||||||
|
try {
|
||||||
|
if (!this.EVM_PRIVATE_KEY) {
|
||||||
|
throw new Error("Property 'EVM_PRIVATE_KEY' must be a string!")
|
||||||
|
}
|
||||||
|
|
||||||
|
const _chainId = this.NETWORK === 'base' ? base?.id : baseSepolia?.id
|
||||||
|
const chainId = `eip155:${_chainId}`
|
||||||
|
// Initialize the EVM account from private key
|
||||||
|
const evmAccount = this.privateKeyToAccount(this.EVM_PRIVATE_KEY)
|
||||||
|
console.info(`EVM Facilitator account: ${evmAccount.address}`)
|
||||||
|
|
||||||
|
// Create a Viem client with both wallet and public capabilities
|
||||||
|
const viemClient = this.createWalletClient({
|
||||||
|
account: evmAccount,
|
||||||
|
chain: this.NETWORK === 'base' ? base : baseSepolia,
|
||||||
|
transport: http()
|
||||||
|
}).extend(publicActions)
|
||||||
|
|
||||||
|
// Initialize the x402 Facilitator with EVM support
|
||||||
|
|
||||||
|
const evmSigner = this.toFacilitatorEvmSigner({
|
||||||
|
getCode: (args) => viemClient.getCode(args),
|
||||||
|
address: evmAccount.address,
|
||||||
|
readContract: (args) =>
|
||||||
|
viemClient.readContract({
|
||||||
|
...args,
|
||||||
|
args: args.args || []
|
||||||
|
}),
|
||||||
|
verifyTypedData: (args) => viemClient.verifyTypedData(args),
|
||||||
|
writeContract: (args) =>
|
||||||
|
viemClient.writeContract({
|
||||||
|
...args,
|
||||||
|
args: args.args || []
|
||||||
|
}),
|
||||||
|
sendTransaction: (args) => viemClient.sendTransaction(args),
|
||||||
|
waitForTransactionReceipt: (args) => viemClient.waitForTransactionReceipt(args)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.registerExactEvmScheme(this.facilitator, {
|
||||||
|
signer: evmSigner,
|
||||||
|
networks: [chainId]
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
this.facilitator
|
||||||
|
.onBeforeVerify(async (ctx) => { console.log('Before verify', ctx) })
|
||||||
|
.onAfterVerify(async (ctx) => { console.log('After verify', ctx) })
|
||||||
|
.onVerifyFailure(async (ctx) => { console.log('Verify failure', ctx) })
|
||||||
|
.onBeforeSettle(async (ctx) => { console.log('Before settle', ctx) })
|
||||||
|
.onAfterSettle(async (ctx) => { console.log('After settle', ctx) })
|
||||||
|
.onSettleFailure(async (ctx) => { console.log('Settle failure', ctx) })
|
||||||
|
|
||||||
|
return this.facilitator
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in facilitator.js/start()')
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FacilitatorAdapter
|
||||||
@@ -18,6 +18,7 @@ import JSONFiles from './json-files.js'
|
|||||||
import FullStackJWT from './fullstack-jwt.js'
|
import FullStackJWT from './fullstack-jwt.js'
|
||||||
import config from '../../config/index.js'
|
import config from '../../config/index.js'
|
||||||
import Wallet from './wallet.adapter.js'
|
import Wallet from './wallet.adapter.js'
|
||||||
|
import FacilitatorAdapter from './facilitator.js'
|
||||||
|
|
||||||
class Adapters {
|
class Adapters {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -31,7 +32,7 @@ class Adapters {
|
|||||||
this.bchjs = new BCHJS({ restURL: config.apiServer })
|
this.bchjs = new BCHJS({ restURL: config.apiServer })
|
||||||
this.config = config
|
this.config = config
|
||||||
this.wallet = new Wallet(localConfig)
|
this.wallet = new Wallet(localConfig)
|
||||||
|
this.facilitator = new FacilitatorAdapter(config)
|
||||||
// Get a valid JWT API key and instance bch-js.
|
// Get a valid JWT API key and instance bch-js.
|
||||||
this.fullStackJwt = new FullStackJWT(config)
|
this.fullStackJwt = new FullStackJWT(config)
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,10 @@ class Adapters {
|
|||||||
console.log('Not starting IPFS node since this is an e2e test.')
|
console.log('Not starting IPFS node since this is an e2e test.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.facilitator.start()
|
||||||
|
|
||||||
|
console.log('Facilitator Adapter is ready.')
|
||||||
|
|
||||||
console.log('Async Adapters have been started.')
|
console.log('Async Adapters have been started.')
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ import LogsRESTController from './logs/index.js'
|
|||||||
import IpfsRESTController from './ipfs/index.js'
|
import IpfsRESTController from './ipfs/index.js'
|
||||||
import config from '../../../config/index.js'
|
import config from '../../../config/index.js'
|
||||||
import UsageRESTController from './usage/index.js'
|
import UsageRESTController from './usage/index.js'
|
||||||
|
import SettleRouter from './settle/index.js'
|
||||||
|
import VerifyRouter from './verify/index.js'
|
||||||
|
import SupportedRouter from './supported/index.js'
|
||||||
|
|
||||||
class RESTControllers {
|
class RESTControllers {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -54,6 +57,18 @@ class RESTControllers {
|
|||||||
userRouter.attach(app)
|
userRouter.attach(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /settle route
|
||||||
|
const settleRouter = new SettleRouter(dependencies)
|
||||||
|
settleRouter.attach(app)
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /verify route
|
||||||
|
const verifyRouter = new VerifyRouter(dependencies)
|
||||||
|
verifyRouter.attach(app)
|
||||||
|
|
||||||
|
// Attach the REST API Controllers associated with the /supported route
|
||||||
|
const supportedRouter = new SupportedRouter(dependencies)
|
||||||
|
supportedRouter.attach(app)
|
||||||
|
|
||||||
// Attach the REST API Controllers associated with the /contact route
|
// Attach the REST API Controllers associated with the /contact route
|
||||||
const contactRESTController = new ContactRESTController(dependencies)
|
const contactRESTController = new ContactRESTController(dependencies)
|
||||||
contactRESTController.attach(app)
|
contactRESTController.attach(app)
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /settle route
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SettleRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /settle REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantifying /settle REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settle = this.settle.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async settle (ctx) {
|
||||||
|
try {
|
||||||
|
const inputObj = ctx.request.body
|
||||||
|
|
||||||
|
const result = await this.useCases.settle.settle(inputObj)
|
||||||
|
|
||||||
|
ctx.body = result
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err.message: ${err.message}`)
|
||||||
|
// console.log('err: ', err)
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRY error handler
|
||||||
|
handleError (ctx, err) {
|
||||||
|
// If an HTTP status is specified by the buisiness logic, use that.
|
||||||
|
if (err.status) {
|
||||||
|
if (err.message) {
|
||||||
|
ctx.throw(err.status, err.message)
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default use a 422 error if the HTTP status is not specified.
|
||||||
|
ctx.throw(422, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettleRESTControllerLib
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
REST API library for /settle route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import SettleRESTControllerLib from './controller.js'
|
||||||
|
|
||||||
|
import config from '../../../../config/index.js'
|
||||||
|
|
||||||
|
class SettleRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating Settle Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating Settle Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.config = config
|
||||||
|
this.settleRESTController = new SettleRESTControllerLib(dependencies)
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/settle'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (app) {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error(
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the routes and attach the controller.
|
||||||
|
this.router.post('/', this.settleRESTController.settle)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default SettleRouter
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /supported route
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SupportedRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /supported REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantifying /supported REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.supported = this.supported.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async supported (ctx) {
|
||||||
|
try {
|
||||||
|
const result = await this.useCases.supported.supported()
|
||||||
|
ctx.body = result
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err.message: ${err.message}`)
|
||||||
|
// console.log('err: ', err)
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRY error handler
|
||||||
|
handleError (ctx, err) {
|
||||||
|
// If an HTTP status is specified by the buisiness logic, use that.
|
||||||
|
if (err.status) {
|
||||||
|
if (err.message) {
|
||||||
|
ctx.throw(err.status, err.message)
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default use a 422 error if the HTTP status is not specified.
|
||||||
|
ctx.throw(422, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SupportedRESTControllerLib
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
REST API library for /supported route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import SupportedRESTControllerLib from './controller.js'
|
||||||
|
|
||||||
|
import config from '../../../../config/index.js'
|
||||||
|
|
||||||
|
class SupportedRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating Supported Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating Supported Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.config = config
|
||||||
|
this.supportedRESTController = new SupportedRESTControllerLib(dependencies)
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/supported'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (app) {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error(
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the routes and attach the controller.
|
||||||
|
this.router.get('/', this.supportedRESTController.supported)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default SupportedRouter
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
REST API Controller library for the /verify route
|
||||||
|
*/
|
||||||
|
|
||||||
|
class VerifyRESTControllerLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating /verify REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantifying /verify REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.verify = this.verify.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
async verify (ctx) {
|
||||||
|
try {
|
||||||
|
const inputObj = ctx.request.body
|
||||||
|
|
||||||
|
const result = await this.useCases.verify.verify(inputObj)
|
||||||
|
|
||||||
|
ctx.body = result
|
||||||
|
} catch (err) {
|
||||||
|
// console.log(`err.message: ${err.message}`)
|
||||||
|
// console.log('err: ', err)
|
||||||
|
// ctx.throw(422, err.message)
|
||||||
|
this.handleError(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRY error handler
|
||||||
|
handleError (ctx, err) {
|
||||||
|
// If an HTTP status is specified by the buisiness logic, use that.
|
||||||
|
if (err.status) {
|
||||||
|
if (err.message) {
|
||||||
|
ctx.throw(err.status, err.message)
|
||||||
|
} else {
|
||||||
|
ctx.throw(err.status)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// By default use a 422 error if the HTTP status is not specified.
|
||||||
|
ctx.throw(422, err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerifyRESTControllerLib
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
REST API library for /verify route.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries.
|
||||||
|
import Router from 'koa-router'
|
||||||
|
|
||||||
|
// Local libraries.
|
||||||
|
import VerifyRESTControllerLib from './controller.js'
|
||||||
|
|
||||||
|
import config from '../../../../config/index.js'
|
||||||
|
|
||||||
|
class VerifyRouter {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// Dependency Injection.
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Adapters library required when instantiating Verify Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.useCases = localConfig.useCases
|
||||||
|
if (!this.useCases) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of Use Cases library required when instantiating Verify Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const dependencies = {
|
||||||
|
adapters: this.adapters,
|
||||||
|
useCases: this.useCases
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encapsulate dependencies.
|
||||||
|
this.config = config
|
||||||
|
this.verifyRESTController = new VerifyRESTControllerLib(dependencies)
|
||||||
|
|
||||||
|
// Instantiate the router and set the base route.
|
||||||
|
const baseUrl = '/verify'
|
||||||
|
this.router = new Router({ prefix: baseUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
attach (app) {
|
||||||
|
if (!app) {
|
||||||
|
throw new Error(
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the routes and attach the controller.
|
||||||
|
this.router.post('/', this.verifyRESTController.verify)
|
||||||
|
|
||||||
|
// Attach the Controller routes to the Koa app.
|
||||||
|
app.use(this.router.routes())
|
||||||
|
app.use(this.router.allowedMethods())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default VerifyRouter
|
||||||
@@ -7,6 +7,9 @@
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import UserUseCases from './user.js'
|
import UserUseCases from './user.js'
|
||||||
import { UsageUseCases } from './usage-use-cases.js'
|
import { UsageUseCases } from './usage-use-cases.js'
|
||||||
|
import SettleUseCases from './settle.js'
|
||||||
|
import VerifyUseCases from './verify.js'
|
||||||
|
import SupportedUseCases from './supported.js'
|
||||||
|
|
||||||
class UseCases {
|
class UseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
@@ -20,6 +23,9 @@ class UseCases {
|
|||||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||||
this.user = new UserUseCases(localConfig)
|
this.user = new UserUseCases(localConfig)
|
||||||
this.usage = new UsageUseCases(localConfig)
|
this.usage = new UsageUseCases(localConfig)
|
||||||
|
this.settle = new SettleUseCases(localConfig)
|
||||||
|
this.verify = new VerifyUseCases(localConfig)
|
||||||
|
this.supported = new SupportedUseCases(localConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run any startup Use Cases at the start of the app.
|
// Run any startup Use Cases at the start of the app.
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This library contains business-logic for dealing with settle x402 payment transactions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import wlogger from '../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class SettleLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// console.log('User localConfig: ', localConfig)
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Settle Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.facilitator = this.adapters.facilitator.facilitator
|
||||||
|
}
|
||||||
|
|
||||||
|
async settle (inputObj = {}) {
|
||||||
|
try {
|
||||||
|
const { paymentPayload, paymentRequirements } = inputObj
|
||||||
|
|
||||||
|
if (!paymentPayload || !paymentRequirements) {
|
||||||
|
throw new Error('Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.facilitator.settle(
|
||||||
|
paymentPayload,
|
||||||
|
paymentRequirements
|
||||||
|
)
|
||||||
|
console.log('Payment Successfully with txId', response.transaction)
|
||||||
|
return response
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('createUser() error: ', err)
|
||||||
|
wlogger.error('Error in lib/settle.js/settle()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettleLib
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
This library contains business-logic for dealing with supported x402 payment transactions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import wlogger from '../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class SupportedLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// console.log('User localConfig: ', localConfig)
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Supported Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.facilitator = this.adapters.facilitator.facilitator
|
||||||
|
}
|
||||||
|
|
||||||
|
async supported () {
|
||||||
|
try {
|
||||||
|
const response = this.facilitator.getSupported()
|
||||||
|
return response
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('createUser() error: ', err)
|
||||||
|
wlogger.error('Error in lib/supported.js/supported()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SupportedLib
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
This library contains business-logic for dealing with verify x402 payment transactions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import wlogger from '../adapters/wlogger.js'
|
||||||
|
|
||||||
|
class VerifyLib {
|
||||||
|
constructor (localConfig = {}) {
|
||||||
|
// console.log('User localConfig: ', localConfig)
|
||||||
|
this.adapters = localConfig.adapters
|
||||||
|
if (!this.adapters) {
|
||||||
|
throw new Error(
|
||||||
|
'Instance of adapters must be passed in when instantiating Verify Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this.facilitator = this.adapters.facilitator.facilitator
|
||||||
|
}
|
||||||
|
|
||||||
|
async verify (inputObj = {}) {
|
||||||
|
try {
|
||||||
|
const { paymentPayload, paymentRequirements } = inputObj
|
||||||
|
|
||||||
|
if (!paymentPayload || !paymentRequirements) {
|
||||||
|
throw new Error('Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.facilitator.verify(
|
||||||
|
paymentPayload,
|
||||||
|
paymentRequirements
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
} catch (err) {
|
||||||
|
console.log('error: ', err)
|
||||||
|
// console.log('createUser() error: ', err)
|
||||||
|
wlogger.error('Error in lib/verify.js/verify()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerifyLib
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import FacilitatorLib from '../../../src/adapters/facilitator.js'
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
describe('Facilitator', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
uut = new FacilitatorLib()
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('start()', () => {
|
||||||
|
it('should throw error if EVM_PRIVATE_KEY property is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.start()
|
||||||
|
assert(false, 'Unexpected result')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, "Property 'EVM_PRIVATE_KEY' must be a string!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should start the facilitator', async () => {
|
||||||
|
try {
|
||||||
|
uut.EVM_PRIVATE_KEY = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
|
||||||
|
|
||||||
|
const viewClientMock = {
|
||||||
|
getCode: sandbox.stub().resolves('0x1'),
|
||||||
|
readContract: sandbox.stub().resolves({}),
|
||||||
|
verifyTypedData: sandbox.stub().resolves(true),
|
||||||
|
writeContract: sandbox.stub().resolves('0xhash'),
|
||||||
|
sendTransaction: sandbox.stub().resolves('0xhash'),
|
||||||
|
waitForTransactionReceipt: sandbox.stub().resolves({})
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox.stub(uut, 'privateKeyToAccount').returns({
|
||||||
|
address: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
||||||
|
privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
|
||||||
|
})
|
||||||
|
|
||||||
|
sandbox.stub(uut, 'createWalletClient').returns({
|
||||||
|
extend: () => viewClientMock
|
||||||
|
})
|
||||||
|
|
||||||
|
const signerStub = sandbox.stub(uut, 'toFacilitatorEvmSigner').returns({ address: '0x123' })
|
||||||
|
|
||||||
|
await uut.start()
|
||||||
|
|
||||||
|
// Cover all the methods of the signer
|
||||||
|
const signerConfig = signerStub.firstCall.args[0]
|
||||||
|
|
||||||
|
await signerConfig.getCode({ address: '0x1' })
|
||||||
|
await signerConfig.readContract({ address: '0x1', abi: [], functionName: 'test' })
|
||||||
|
await signerConfig.verifyTypedData({})
|
||||||
|
await signerConfig.writeContract({ address: '0x1', abi: [], functionName: 'test' })
|
||||||
|
await signerConfig.sendTransaction({})
|
||||||
|
await signerConfig.waitForTransactionReceipt({})
|
||||||
|
} catch (err) {
|
||||||
|
assert(false, 'Unexpected result')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /settle endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import SettleRESTController from '../../../../../src/controllers/rest-api/settle/controller.js'
|
||||||
|
|
||||||
|
import { context as mockContext } from '../../../mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
describe('#Settle-REST-Controller', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new SettleRESTController({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SettleRESTController()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating /settle REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SettleRESTController({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantifying /settle REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#settle', () => {
|
||||||
|
it('should return the settle result', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
ctx.body = {}
|
||||||
|
const result = {
|
||||||
|
transaction: '1234567890'
|
||||||
|
}
|
||||||
|
sandbox.stub(uut.useCases.settle, 'settle').resolves(result)
|
||||||
|
|
||||||
|
await uut.settle(ctx)
|
||||||
|
|
||||||
|
assert.isObject(ctx.body)
|
||||||
|
assert.property(ctx.body, 'transaction')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw useCases error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut.useCases.settle, 'settle').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.settle(ctx)
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#handleError', () => {
|
||||||
|
it('should still throw error if there is no message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 404
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Not Found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error with message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 422,
|
||||||
|
message: 'test error'
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /settle endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import SettleRouter from '../../../../../src/controllers/rest-api/settle/index.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
// let ctx
|
||||||
|
|
||||||
|
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
describe('#Settle-REST-Router', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new SettleRouter({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
// ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SettleRouter()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating Settle Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SettleRouter({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating Settle Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#attach', () => {
|
||||||
|
it('should throw an error if app is not passed in.', () => {
|
||||||
|
try {
|
||||||
|
uut.attach()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /supported endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import SupportedRESTController from '../../../../../src/controllers/rest-api/supported/controller.js'
|
||||||
|
|
||||||
|
import { context as mockContext } from '../../../mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
describe('#Supported-REST-Controller', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new SupportedRESTController({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SupportedRESTController()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating /supported REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SupportedRESTController({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantifying /supported REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#supported', () => {
|
||||||
|
it('should return the supported payment kinds and extensions', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
ctx.body = {}
|
||||||
|
const result = {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
sandbox.stub(uut.useCases.supported, 'supported').resolves(result)
|
||||||
|
|
||||||
|
await uut.supported(ctx)
|
||||||
|
|
||||||
|
assert.isObject(ctx.body)
|
||||||
|
assert.property(ctx.body, 'paymentKinds')
|
||||||
|
assert.property(ctx.body, 'extensions')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw useCases error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut.useCases.supported, 'supported').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.supported(ctx)
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#handleError', () => {
|
||||||
|
it('should still throw error if there is no message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 404
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Not Found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error with message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 422,
|
||||||
|
message: 'test error'
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /supported endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import SupportedRouter from '../../../../../src/controllers/rest-api/supported/index.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
// let ctx
|
||||||
|
|
||||||
|
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
describe('#Supported-REST-Router', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new SupportedRouter({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
// ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SupportedRouter()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating Supported Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SupportedRouter({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating Supported Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#attach', () => {
|
||||||
|
it('should throw an error if app is not passed in.', () => {
|
||||||
|
try {
|
||||||
|
uut.attach()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /verify endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import VerifyRESTController from '../../../../../src/controllers/rest-api/verify/controller.js'
|
||||||
|
|
||||||
|
import { context as mockContext } from '../../../mocks/ctx-mock.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
let ctx
|
||||||
|
|
||||||
|
describe('#Verify-REST-Controller', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new VerifyRESTController({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new VerifyRESTController()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating /verify REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new VerifyRESTController({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantifying /verify REST Controller.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#verify', () => {
|
||||||
|
it('should return the verify result', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
ctx.body = {}
|
||||||
|
const result = {
|
||||||
|
valid: true
|
||||||
|
}
|
||||||
|
sandbox.stub(uut.useCases.verify, 'verify').resolves(result)
|
||||||
|
|
||||||
|
await uut.verify(ctx)
|
||||||
|
|
||||||
|
assert.isObject(ctx.body)
|
||||||
|
assert.property(ctx.body, 'valid')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should catch and throw useCases error', async () => {
|
||||||
|
try {
|
||||||
|
// Force an error
|
||||||
|
sandbox.stub(uut.useCases.verify, 'verify').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.verify(ctx)
|
||||||
|
} catch (err) {
|
||||||
|
// console.log('err: ', err)
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#handleError', () => {
|
||||||
|
it('should still throw error if there is no message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 404
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Not Found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error with message', () => {
|
||||||
|
try {
|
||||||
|
const err = {
|
||||||
|
status: 422,
|
||||||
|
message: 'test error'
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.handleError(ctx, err)
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the REST API handler for the /verify endpoints.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
// Local support libraries
|
||||||
|
import adapters from '../../../mocks/adapters/index.js'
|
||||||
|
|
||||||
|
import UseCasesMock from '../../../mocks/use-cases/index.js'
|
||||||
|
|
||||||
|
// const app = require('../../../mocks/app-mock')
|
||||||
|
|
||||||
|
import VerifyRouter from '../../../../../src/controllers/rest-api/verify/index.js'
|
||||||
|
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
// let ctx
|
||||||
|
|
||||||
|
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||||
|
|
||||||
|
describe('#Verify-REST-Router', () => {
|
||||||
|
// const testUser = {}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const useCases = new UseCasesMock()
|
||||||
|
uut = new VerifyRouter({ adapters, useCases })
|
||||||
|
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
// Mock the context object.
|
||||||
|
// ctx = mockContext()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new VerifyRouter()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Adapters library required when instantiating Verify Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error if useCases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new VerifyRouter({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Use Cases library required when instantiating Verify Router.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#attach', () => {
|
||||||
|
it('should throw an error if app is not passed in.', () => {
|
||||||
|
try {
|
||||||
|
uut.attach()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Must pass app object when attaching REST API controllers.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -108,4 +108,26 @@ const localdb = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { ipfs, localdb };
|
const facilitator = {
|
||||||
|
facilitator: {
|
||||||
|
getSupported: async () => {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settle: async () => {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verify: async () => {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default { ipfs, localdb, facilitator };
|
||||||
|
|||||||
@@ -57,6 +57,32 @@ class UsageUseCaseMock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SupportedUseCaseMock {
|
||||||
|
async supported() {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettleUseCaseMock {
|
||||||
|
async settle() {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VerifyUseCaseMock {
|
||||||
|
async verify() {
|
||||||
|
return {
|
||||||
|
paymentKinds: ['x402'],
|
||||||
|
extensions: ['x402']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
class UseCasesMock {
|
class UseCasesMock {
|
||||||
constuctor(localConfig = {}) {
|
constuctor(localConfig = {}) {
|
||||||
// this.user = new UserUseCaseMock(localConfig)
|
// this.user = new UserUseCaseMock(localConfig)
|
||||||
@@ -64,6 +90,10 @@ class UseCasesMock {
|
|||||||
|
|
||||||
user = new UserUseCaseMock()
|
user = new UserUseCaseMock()
|
||||||
usage = new UsageUseCaseMock()
|
usage = new UsageUseCaseMock()
|
||||||
|
supported = new SupportedUseCaseMock()
|
||||||
|
settle = new SettleUseCaseMock()
|
||||||
|
verify = new VerifyUseCaseMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default UseCasesMock;
|
export default UseCasesMock;
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the src/use-cases/settle.js business logic library.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import SettleUseCases from '../../../src/use-cases/settle.js'
|
||||||
|
|
||||||
|
import adapters from '../mocks/adapters/index.js'
|
||||||
|
describe('#settle-use-case', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
uut = new SettleUseCases({ adapters })
|
||||||
|
console.log('SupportedLib adapters: ', adapters.facilitatorMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SettleUseCases()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating Settle Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#settle', () => {
|
||||||
|
it('should return the supported payment kinds and extensions', async () => {
|
||||||
|
const result = await uut.settle({ paymentPayload: {}, paymentRequirements: {} })
|
||||||
|
|
||||||
|
assert.isObject(result)
|
||||||
|
})
|
||||||
|
it('should throw an error if paymentPayload are not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.settle({})
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if paymentRequirements are not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.settle({ paymentPayload: {} })
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle facilitator errors', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.facilitator, 'settle').throws(new Error('test error'))
|
||||||
|
await uut.settle({ paymentPayload: {}, paymentRequirements: {} })
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the src/use-cases/supported.js business logic library.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import SupportedUseCases from '../../../src/use-cases/supported.js'
|
||||||
|
|
||||||
|
import adapters from '../mocks/adapters/index.js'
|
||||||
|
describe('#supported-use-case', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
uut = new SupportedUseCases({ adapters })
|
||||||
|
console.log('SupportedLib adapters: ', adapters.facilitatorMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new SupportedUseCases()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating Supported Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#supported', () => {
|
||||||
|
it('should return the supported payment kinds and extensions', async () => {
|
||||||
|
const result = await uut.supported()
|
||||||
|
|
||||||
|
assert.isObject(result)
|
||||||
|
})
|
||||||
|
it('should handle errors', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.facilitator, 'getSupported').throws(new Error('test error'))
|
||||||
|
await uut.supported()
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Unit tests for the src/use-cases/verify.js business logic library.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Public npm libraries
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
import VerifyUseCases from '../../../src/use-cases/verify.js'
|
||||||
|
|
||||||
|
import adapters from '../mocks/adapters/index.js'
|
||||||
|
describe('#verify-use-case', () => {
|
||||||
|
let uut
|
||||||
|
let sandbox
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.createSandbox()
|
||||||
|
|
||||||
|
uut = new VerifyUseCases({ adapters })
|
||||||
|
console.log('SupportedLib adapters: ', adapters.facilitatorMock)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => sandbox.restore())
|
||||||
|
|
||||||
|
describe('#constructor', () => {
|
||||||
|
it('should throw an error if adapters are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new VerifyUseCases()
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of adapters must be passed in when instantiating Verify Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#verify', () => {
|
||||||
|
it('should return the supported payment kinds and extensions', async () => {
|
||||||
|
const result = await uut.verify({ paymentPayload: {}, paymentRequirements: {} })
|
||||||
|
|
||||||
|
assert.isObject(result)
|
||||||
|
})
|
||||||
|
it('should throw an error if paymentPayload are not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.verify({})
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if paymentRequirements are not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.verify({ paymentPayload: {} })
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Missing paymentPayload or paymentRequirements')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle facilitator errors', async () => {
|
||||||
|
try {
|
||||||
|
sandbox.stub(uut.facilitator, 'verify').throws(new Error('test error'))
|
||||||
|
await uut.verify({ paymentPayload: {}, paymentRequirements: {} })
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user