diff --git a/examples/typescript/servers/cloudfront-lambda-edge/README.md b/examples/typescript/servers/cloudfront-lambda-edge/README.md index c8eff1ac90..72d4f02c9a 100644 --- a/examples/typescript/servers/cloudfront-lambda-edge/README.md +++ b/examples/typescript/servers/cloudfront-lambda-edge/README.md @@ -91,12 +91,12 @@ export const NETWORK = 'eip155:84532'; // Base Sepolia (testnet) Define which routes require payment: ```typescript -const ROUTES: RoutesConfig = { +export const ROUTES: RoutesConfig = { '/api/*': { accepts: { scheme: 'exact', - network: 'eip155:84532', - payTo: '0xYourAddress', + network: NETWORK, + payTo: PAY_TO, price: '$0.001', }, description: 'API access', @@ -117,12 +117,52 @@ Bundle and deploy both Lambda functions: import { originRequestHandler, originResponseHandler } from './index'; ``` +--- + ## Networks -| Network | ID | Use | -| ------------ | -------------- | ---------- | -| Base Sepolia | `eip155:84532` | Testing | -| Base Mainnet | `eip155:8453` | Production | +| Network | ID | Use | +| -------------- | ----------------------------------------- | ---------- | +| Base Sepolia | `eip155:84532` | Testing | +| Base Mainnet | `eip155:8453` | Production | +| Solana Devnet | `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1` | Testing | +| Solana Mainnet | `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` | Production | + +--- + +## Running on Mainnet + +To accept real payments, you need a mainnet facilitator. Each facilitator may have different authentication requirements. Browse available facilitators at the [x402 Ecosystem — Facilitators](https://www.x402.org/ecosystem?filter=facilitators). + +Update `config.ts` with your chosen facilitator, a mainnet network, and your wallet address: + +```typescript +export const FACILITATOR_URL = 'https://your-facilitator-url'; +export const NETWORK = 'eip155:8453'; // Base mainnet +export const PAY_TO = '0xYourMainnetWalletAddress'; +``` + +If your facilitator requires authentication, you can pass a `facilitatorConfig` object (with `url` and `createAuthHeaders`) via the middleware config. Update `config.ts`: + +```typescript +// Example: using a facilitator package that provides a config with auth +import { createFacilitatorConfig } from 'your-facilitator-package'; + +export const FACILITATOR_CONFIG = createFacilitatorConfig('api-key-id', 'api-key-secret'); +``` + +Then pass it in `origin-request.ts` and `origin-response.ts`: + +```typescript +const x402 = createX402Middleware({ + facilitatorUrl: FACILITATOR_URL, + network: NETWORK, + routes: ROUTES, + facilitatorConfig: FACILITATOR_CONFIG, // overrides facilitatorUrl when provided +}); +``` + +> **Note**: Lambda@Edge does not support environment variables. If your facilitator reads credentials from `process.env`, you can pass them explicitly via the config function, or fetch them from AWS Secrets Manager at runtime. --- @@ -136,33 +176,44 @@ cloudfront-lambda-edge/ │ ├── origin-response.ts # Handler for origin-response event │ ├── config.ts # Routes, addresses, network config │ └── lib/ # Reusable x402 middleware -│ ├── middleware.ts # createX402Middleware factory +│ ├── index.ts # Package exports +│ ├── middleware.ts # createX402Middleware factory │ ├── server.ts # createX402Server factory │ ├── adapter.ts # CloudFrontHTTPAdapter │ └── responses.ts # Lambda@Edge response helpers ``` +--- + ## Middleware Pattern -The x402 logic is composable middleware, so you can integrate it with your existing Lambda@Edge logic: +The `lib/` folder follows the same pattern as `@x402/express`, `@x402/hono`, etc.: ```typescript -import { createX402Middleware } from './lib'; - -const x402 = createX402Middleware({ getServer: createServer }); +import { createX402Middleware, MiddlewareResultType } from './lib'; + +// Create middleware with config +const x402 = createX402Middleware({ + facilitatorUrl: 'https://x402.org/facilitator', + network: 'eip155:84532', + routes: { + '/api/*': { + accepts: { scheme: 'exact', network: 'eip155:84532', payTo: '0x...', price: '$0.01' }, + description: 'API access', + }, + }, +}); +// Use in handlers export const handler = async (event: CloudFrontRequestEvent) => { const request = event.Records[0].cf.request; + const distributionDomain = event.Records[0].cf.config.distributionDomainName; // Your custom logic first (auth, WAF, logging, etc.) - if (request.headers['x-api-key']?.[0]?.value !== 'secret') { - return { status: '401', body: 'Unauthorized' }; - } - // x402 payment check const result = await x402.processOriginRequest(request, distributionDomain); - if (result.type === 'respond') { + if (result.type === MiddlewareResultType.RESPOND) { return result.response; // 402 Payment Required } @@ -220,13 +271,6 @@ getHeader(name: string): string | undefined { -
-Browser Paywall - -HTML paywall is disabled by default due to Lambda@Edge's 1MB response limit. For browser-based payment flows, consider hosting the paywall HTML on S3 and using CloudFront origin routing to serve it. - -
-
Payment Flow Internals diff --git a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/config.ts b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/config.ts index 6f5304a2b4..efb3456695 100644 --- a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/config.ts +++ b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/config.ts @@ -9,8 +9,8 @@ import type { RoutesConfig } from '@x402/core/server'; // Payment configuration export const FACILITATOR_URL = 'https://x402.org/facilitator'; -export const PAY_TO = '0xD8213b3b85e5bD05D60b6dD89F1cF71fcd5b57B0' //'0xYourPaymentAddressHere'; -export const NETWORK = 'eip155:84532'; // Base Sepolia testnet. Use 'eip155:8453' for mainnet. +export const PAY_TO = '0xYourPaymentAddressHere'; +export const NETWORK = 'eip155:84532'; // Base Sepolia testnet // Route configuration export const ROUTES: RoutesConfig = { diff --git a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/middleware.ts b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/middleware.ts index 63a3349b31..3b07a2a649 100644 --- a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/middleware.ts +++ b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/middleware.ts @@ -60,7 +60,7 @@ const PENDING_SETTLEMENT_HEADER = 'x-x402-pending-settlement'; * * @example * ```typescript - * import { createX402Middleware } from '@x402/lambda-edge'; + * import { createX402Middleware } from './lib'; * * const x402 = createX402Middleware({ * facilitatorUrl: 'https://x402.org/facilitator', diff --git a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/server.ts b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/server.ts index 061bc07ddb..641cc234c4 100644 --- a/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/server.ts +++ b/examples/typescript/servers/cloudfront-lambda-edge/lambda/src/lib/server.ts @@ -1,4 +1,4 @@ -import type { RoutesConfig } from '@x402/core/server'; +import type { RoutesConfig, FacilitatorConfig } from '@x402/core/server'; import { x402ResourceServer, x402HTTPResourceServer, HTTPFacilitatorClient } from '@x402/core/server'; import { ExactEvmScheme } from '@x402/evm/exact/server'; @@ -12,6 +12,8 @@ export interface X402ServerConfig { network: string; /** Route configuration defining which paths require payment */ routes: RoutesConfig; + /** Optional facilitator config with auth headers (for facilitators that require authentication) */ + facilitatorConfig?: FacilitatorConfig; } /** @@ -19,21 +21,28 @@ export interface X402ServerConfig { * * @example * ```typescript + * // Testnet (no auth) * const server = await createX402Server({ * facilitatorUrl: 'https://x402.org/facilitator', * network: 'eip155:84532', - * routes: { - * '/api/*': { - * accepts: { scheme: 'exact', network: 'eip155:84532', payTo: '0x...', price: '$0.01' } - * } - * } + * routes: { ... }, + * }); + * + * // Mainnet with auth (pass a facilitator config from your facilitator package) + * const server = await createX402Server({ + * facilitatorUrl: 'https://your-facilitator-url', + * network: 'eip155:8453', + * routes: { ... }, + * facilitatorConfig: createFacilitatorConfig('api-key-id', 'api-key-secret'), * }); * ``` */ export async function createX402Server(config: X402ServerConfig): Promise { - const facilitator = new HTTPFacilitatorClient({ url: config.facilitatorUrl }); + const facilitator = new HTTPFacilitatorClient( + config.facilitatorConfig ?? { url: config.facilitatorUrl }, + ); const resourceServer = new x402ResourceServer(facilitator) - .register(config.network, new ExactEvmScheme()); + .register(config.network as `${string}:${string}`, new ExactEvmScheme()); const httpServer = new x402HTTPResourceServer(resourceServer, config.routes); await httpServer.initialize();