UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

812 lines • 35.8 kB
"use strict"; /** * CDP-powered x402 resource server. * * `createX402Server()` is the primary entry point. It accepts a route * configuration (and optional receiver wallet config), provisions EVM and * Solana receiver wallets automatically, and returns a fully initialized * `X402Server`. * * `X402Server` **extends** `x402HTTPResourceServer` — it is a drop-in * replacement and can be passed anywhere an `x402HTTPResourceServer` is * expected (e.g. `paymentMiddlewareFromHTTPServer`, Hono / Next.js adapters). * * Routes may be supplied in either format: * * **Simplified CDP format** (`CdpRouteConfig`) — just `price` and optional * `description` / `networks` / `paymentFlow`. The server fills in `scheme`, * `payTo`, and all x402 internals automatically. `paymentFlow` is exact-scheme * only (`"authorization"` default, or `"upfront"`) and is applied to every * network the route expands to (Base and Solana when `networks` is omitted). * * **Full x402 format** (`RouteConfig`) — the same `accepts` / `description` * shape accepted by `x402HTTPResourceServer`. Vacant `payTo` fields (`""`) * are filled with the provisioned receiver address for that network family. * * Both formats can be mixed within the same `routes` map. * * The `payToConfig` field controls how the receiver wallet address is resolved: * - `{ type: "eoa" }` (default) — provisions a CDP EVM EOA + Solana account * - `{ type: "smart", ownerAccountName }` — provisions a CDP Smart Contract Wallet * - `{ type: "address", evm, solana }` — uses provided addresses directly, no provisioning * * @example Minimal CDP Dev setup: * ```typescript * import { createX402Server } from "@coinbase/cdp-sdk/x402"; * import { paymentMiddlewareFromHTTPServer } from "@x402/express"; * * // Set: CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET * const server = await createX402Server({ * routes: { * "GET /report": { price: "$0.01", description: "AI-generated report" }, * }, * }); * app.use(paymentMiddlewareFromHTTPServer(server)); * console.log("EVM receiver:", server.payToEvmAddress); * console.log("Solana receiver:", server.payToSvmAddress); * ``` * * @example Bring your own addresses (skip wallet provisioning): * ```typescript * const server = await createX402Server({ * routes: { "GET /report": { price: "$0.01" } }, * payToConfig: { type: "address", evm: "0x...", solana: "..." }, * }); * ``` * * @example Full x402 format (interop with x402HTTPResourceServer): * ```typescript * const server = await createX402Server({ * routes: { * "GET /report": { * accepts: [ * { scheme: "exact", price: "$0.01", network: "eip155:8453", payTo: "" }, * { scheme: "exact", price: "$0.01", network: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", payTo: "" }, * ], * }, * }, * }); * ``` */ Object.defineProperty(exports, "__esModule", { value: true }); exports.X402Server = exports.CDP_SERVER_DEVELOPMENT_NETWORKS = exports.CDP_SERVER_DEVELOPMENT_SVM_NETWORKS = exports.CDP_SERVER_DEVELOPMENT_EVM_NETWORKS = exports.CDP_SERVER_DEFAULT_NETWORKS = exports.CDP_SERVER_DEFAULT_SVM_NETWORKS = exports.CDP_SERVER_DEFAULT_EVM_NETWORKS = void 0; exports.createX402Server = createX402Server; const promises_1 = require("node:fs/promises"); const server_1 = require("@x402/core/server"); const builder_code_js_1 = require("./builder-code.js"); const constants_js_1 = require("./constants.js"); const facilitator_js_1 = require("./facilitator.js"); const server_extensions_js_1 = require("./server-extensions.js"); const smart_account_js_1 = require("./smart-account.js"); const cdp_js_1 = require("../client/cdp.js"); /* * --------------------------------------------------------------------------- * Default networks * --------------------------------------------------------------------------- */ const DEFAULT_SERVER_ACCOUNT_NAME = "x402-receiver-wallet-1"; /** * Default EVM networks (production) — Base mainnet (`eip155:8453`). */ exports.CDP_SERVER_DEFAULT_EVM_NETWORKS = [constants_js_1.baseMainnetCaip2]; /** * Default Solana networks (production) — Solana mainnet. */ exports.CDP_SERVER_DEFAULT_SVM_NETWORKS = [constants_js_1.solanaMainnetCaip2]; /** * Default networks for production: Base mainnet + Solana mainnet. */ exports.CDP_SERVER_DEFAULT_NETWORKS = [ ...exports.CDP_SERVER_DEFAULT_EVM_NETWORKS, ...exports.CDP_SERVER_DEFAULT_SVM_NETWORKS, ]; /** * Default EVM networks for development — Base Sepolia (`eip155:84532`). */ exports.CDP_SERVER_DEVELOPMENT_EVM_NETWORKS = [constants_js_1.baseSepoliaCaip2]; /** * Default Solana networks for development — Solana Devnet. */ exports.CDP_SERVER_DEVELOPMENT_SVM_NETWORKS = [constants_js_1.solanaDevnetCaip2]; /** * Default networks for development: Base Sepolia + Solana Devnet. */ exports.CDP_SERVER_DEVELOPMENT_NETWORKS = [ ...exports.CDP_SERVER_DEVELOPMENT_EVM_NETWORKS, ...exports.CDP_SERVER_DEVELOPMENT_SVM_NETWORKS, ]; const CDP_EXACT_PAYMENT_FLOWS = new Set(["authorization", "upfront"]); /** * Resolves server-scoped CDP credentials and environment, falling back from * explicit config to `CDP_*` env vars * * @param config - Optional explicit credential overrides. * @returns Resolved credential strings and environment (any credential may be `undefined` if not found). */ function resolveServerCredentials(config) { const rawEnv = config.environment ?? process.env.CDP_X402_SERVER_ENVIRONMENT; const environment = rawEnv === "development" ? "development" : "production"; return { apiKeyId: config.apiKeyId ?? process.env.CDP_API_KEY_ID, apiKeySecret: config.apiKeySecret ?? process.env.CDP_API_KEY_SECRET, walletSecret: config.walletSecret ?? process.env.CDP_WALLET_SECRET, environment, }; } /** * Provisions CDP EVM and Solana receiver accounts for use as `payTo` addresses. * * @param credentials - CDP API credentials for wallet operations. * @param credentials.apiKeyId - CDP API key ID. * @param credentials.apiKeySecret - CDP API key secret. * @param credentials.walletSecret - CDP wallet secret for signing. * @param payToConfig - Resolved wallet type (`"eoa"` or `"smart"`) and account names. * @param need - Which network families the routes actually reference. Only the * referenced families are provisioned, so an EVM-only server never creates a * Solana account (and vice versa). * @returns Provisioned EVM address, Solana address, and optional owner wallet name. */ async function provisionServerAccounts(credentials, payToConfig, need) { const cdpClient = new cdp_js_1.CdpClient({ apiKeyId: credentials.apiKeyId, apiKeySecret: credentials.apiKeySecret, walletSecret: credentials.walletSecret, }); const accountName = payToConfig.accountName ?? DEFAULT_SERVER_ACCOUNT_NAME; const svmAddress = need.svm ? (await cdpClient.solana.getOrCreateAccount({ name: accountName })).address : ""; if (!need.evm) { return { evmAddress: "", svmAddress }; } if (payToConfig.type === "smart") { const ownerAccount = await cdpClient.evm.getOrCreateAccount({ name: payToConfig.ownerAccountName, }); let smartAccount; try { smartAccount = await cdpClient.evm.getOrCreateSmartAccount({ name: accountName, owner: ownerAccount, }); } catch (error) { if ((0, smart_account_js_1.isOwnerAlreadyHasSmartWalletError)(error)) { const existingAddress = await (0, smart_account_js_1.findSmartAccountByOwner)(cdpClient, ownerAccount.address); if (!existingAddress) throw error; smartAccount = await cdpClient.evm.getSmartAccount({ address: existingAddress, owner: ownerAccount, }); } else { throw error; } } return { evmAddress: smartAccount.address, svmAddress, ownerWallet: payToConfig.ownerAccountName, }; } const evmAccount = await cdpClient.evm.getOrCreateAccount({ name: accountName }); return { evmAddress: evmAccount.address, svmAddress, }; } /* * --------------------------------------------------------------------------- * Route resolution helpers * --------------------------------------------------------------------------- */ /** * Classifies a CAIP-2 network identifier into a receiver-wallet family. * * @param network - CAIP-2 network identifier, e.g. `"eip155:8453"`. * @returns `"evm"` for `eip155:*`, `"svm"` for `solana:*`, otherwise `"other"`. */ function networkFamily(network) { if (network.startsWith("eip155:")) return "evm"; if (network.startsWith("solana:")) return "svm"; return "other"; } /** * Returns the default networks for a simplified route that omits `networks`. * Both supported schemes (`"exact"` and `"upto"`) cover Base + Solana. * * @param environment - Deployment environment controlling mainnet vs testnet defaults. * @returns The default CAIP-2 networks. */ function getDefaultNetworks(environment) { return environment === "development" ? exports.CDP_SERVER_DEVELOPMENT_NETWORKS : exports.CDP_SERVER_DEFAULT_NETWORKS; } /** * Determines which network families a route references. * * @param route - Simplified or full x402 route config. * @param environment - Deployment environment controlling default network selection. * @returns The network families referenced by the route. */ function routeNetworkFamilies(route, environment) { let networks; if ("accepts" in route) { const accepts = Array.isArray(route.accepts) ? route.accepts : [route.accepts]; networks = accepts.map(option => option.network); } else { networks = [...(route.networks ?? getDefaultNetworks(environment))]; } return { evm: networks.some(network => networkFamily(network) === "evm"), svm: networks.some(network => networkFamily(network) === "svm"), }; } /** * Aggregates the network families referenced across all routes. * * @param routes - Map of route patterns to simplified or full x402 route configs. * @param environment - Deployment environment controlling default network selection. * @returns The union of network families referenced by the route set. */ function requiredNetworkFamilies(routes, environment) { const result = { evm: false, svm: false }; for (const route of Object.values(routes)) { const families = routeNetworkFamilies(route, environment); result.evm ||= families.evm; result.svm ||= families.svm; } return result; } /** * Throws when `paymentFlow` is set on a simplified route that cannot use it: * unknown flow names, or any scheme other than `"exact"`. * * @param route - Simplified CDP route config being converted. * @param scheme - Resolved scheme for the route (`"exact"` or `"upto"`). */ function assertPaymentFlowCompatible(route, scheme) { if (route.paymentFlow === undefined) return; if (!CDP_EXACT_PAYMENT_FLOWS.has(route.paymentFlow)) { throw new Error(`Unsupported paymentFlow "${String(route.paymentFlow)}". ` + `The simplified route format only supports "authorization" (default) and "upfront".`); } if (scheme !== "exact") { throw new Error(`paymentFlow is only supported on the "exact" scheme. ` + `Route uses scheme "${scheme}". Omit paymentFlow, or pass a full x402 route ` + `with accepts[].extra.paymentFlow to configure other schemes.`); } } /** * Simplified-route scheme identifiers accepted by {@link validateRouteSemantics}. * Kept separate from the `CdpPaymentScheme` type check since config files are * untyped JSON cast to `CdpX402ServerConfig` — a typo or unsupported scheme * (e.g. `"auth-capture"`) has no compile-time guard and must be rejected here. */ const CDP_SIMPLIFIED_ROUTE_SCHEMES = new Set([ "exact", "upto", ]); /** * Throws when a simplified route's `scheme` isn't one of the values the * simplified route format actually supports. * * @param scheme - The raw `scheme` value from a simplified route config. * @returns The validated scheme, narrowed to {@link CdpPaymentScheme}. */ function assertValidSimplifiedScheme(scheme) { if (typeof scheme !== "string" || !CDP_SIMPLIFIED_ROUTE_SCHEMES.has(scheme)) { throw new Error(`Unsupported simplified route scheme "${String(scheme)}". ` + `Supported values: ${[...CDP_SIMPLIFIED_ROUTE_SCHEMES].map(s => `"${s}"`).join(", ")}.`); } return scheme; } /** * Validates every route's scheme/network and `paymentFlow` compatibility up * front, before any CDP or facilitator I/O. Without this, an invalid route * (e.g. `{ scheme: "upto", paymentFlow: "upfront" }`, an unsupported/typo'd * scheme such as `"auth-capture"`, an empty `networks` list, or a non-EVM/ * non-Solana network) would only be caught during `resolveRoutes` — or not * at all until facilitator init — by which point `provisionServerAccounts` * has already created real receiver wallets for the (wrongly) inferred * network families. * * @param routes - Map of route patterns to simplified or full x402 route configs. * @param environment - Deployment environment controlling default network selection. */ function validateRouteSemantics(routes, environment) { for (const route of Object.values(routes)) { if ("accepts" in route) continue; const scheme = assertValidSimplifiedScheme(route.scheme ?? "exact"); assertPaymentFlowCompatible(route, scheme); const networks = route.networks ?? getDefaultNetworks(environment); if (networks.length === 0) { throw new Error("Simplified route must include at least one network."); } for (const network of networks) { if (networkFamily(network) === "other") { throw new Error(`Unsupported network family for simplified route: "${network}". ` + `Simplified routes only support "eip155:*" (EVM) and "solana:*" networks.`); } } } } /** * Throws when the resolved `payTo` address for a network is empty. This can * happen when `payToConfig: { type: "address" }` is used without providing * an address for the given network family. * * @param payTo - The resolved address (may be empty string). * @param network - The CAIP-2 network identifier for the route option. */ function assertNonEmptyPayTo(payTo, network) { if (payTo === "") { const family = network.startsWith("eip155:") ? "EVM (`evm`)" : network.startsWith("solana:") ? "Solana (`solana`)" : `"${network}"`; throw new Error(`No receiver address for ${family} network "${network}". ` + `Provide a receiver address via payToConfig (e.g. { type: "address", evm: "0x...", solana: "..." }) ` + `or use payToConfig: { type: "eoa" } to auto-provision a CDP wallet.`); } } /** * Fills vacant `payTo` fields (`""`) in a full x402 `RouteConfig` with the * provisioned receiver addresses for each network family. Returns a new object — * the original is not mutated. Non-vacant `payTo` values are left untouched. * * @param route - The x402 `RouteConfig` whose vacant `payTo` fields will be filled. * @param evmAddress - EVM receiver address for `eip155:*` payment options. * @param svmAddress - Solana receiver address for `solana:*` payment options. * @returns A new `RouteConfig` with all blank `payTo` fields resolved. */ function fillX402RoutePayTo(route, evmAddress, svmAddress) { const accepts = Array.isArray(route.accepts) ? route.accepts : [route.accepts]; const filled = accepts.map(option => { const network = option.network; if (typeof option.payTo !== "string" || option.payTo.trim() !== "") { return option; } if (network.startsWith("eip155:")) { assertNonEmptyPayTo(evmAddress, network); return { ...option, payTo: evmAddress }; } if (network.startsWith("solana:")) { assertNonEmptyPayTo(svmAddress, network); return { ...option, payTo: svmAddress }; } throw new Error(`Cannot fill vacant payTo for network "${network}": unrecognised network family. ` + `Provide an explicit payTo address for this route option.`); }); return { ...route, accepts: Array.isArray(route.accepts) ? filled : filled[0], }; } /** * Converts a {@link CdpRouteConfig} into a full x402 `RouteConfig`, wiring * the provisioned receiver addresses into each payment option's `payTo`. * * @param route - Simplified CDP route config with `price` and optional fields. * @param evmAddress - EVM receiver address for `eip155:*` networks (`""` when none). * @param svmAddress - Solana receiver address for `solana:*` networks (`""` when none). * @param environment - Deployment environment controlling default network selection. * @param available - Which receiver families have an address. When the route * relies on default networks, families without a receiver address are dropped * so e.g. an EVM-only `payToConfig` works without listing `networks` explicitly. * @returns A full x402 `RouteConfig` with `accepts`, `payTo`, and `scheme` resolved. */ function convertCdpRoute(route, evmAddress, svmAddress, environment, available) { const scheme = route.scheme ?? "exact"; assertPaymentFlowCompatible(route, scheme); const usingDefaultNetworks = route.networks === undefined; const defaultNetworks = getDefaultNetworks(environment); /* * For default networks, drop families that have no receiver address so a * partial payToConfig (e.g. EVM-only) doesn't fail on an unused default * network. Explicit `networks` are always honored (and validated) as-is. */ const networks = usingDefaultNetworks ? defaultNetworks.filter(network => { const family = networkFamily(network); if (family === "evm") return available.evm; if (family === "svm") return available.svm; return true; }) : route.networks; if (networks.length === 0) { throw new Error(`No receiver address available for any default network of scheme "${scheme}". ` + `Provide a receiver address via payToConfig (e.g. { type: "address", evm: "0x...", solana: "..." }), ` + `set explicit \`networks\` on the route, or use payToConfig: { type: "eoa" } to auto-provision a CDP wallet.`); } const maxTimeoutSeconds = route.maxTimeoutSeconds ?? 300; const accepts = networks.map(network => { const payTo = network.startsWith("eip155:") ? evmAddress : network.startsWith("solana:") ? svmAddress : (() => { throw new Error(`Cannot resolve payTo for network "${network}": unrecognised network family.`); })(); assertNonEmptyPayTo(payTo, network); return { scheme, price: route.price, network: network, payTo, maxTimeoutSeconds, ...(route.paymentFlow === "upfront" ? { extra: { paymentFlow: "upfront" } } : {}), }; }); return { accepts: accepts.length === 1 ? accepts[0] : accepts, ...(route.description !== undefined && { description: route.description }), ...(route.extensions !== undefined && { extensions: route.extensions }), }; } /** * Parses an x402 route pattern key (`"GET /report"`) into its HTTP method and * path components for building a Bazaar discovery declaration. Returns `undefined` * when the pattern lacks a method prefix or uses a wildcard verb. * * @param pattern - Route pattern key, e.g. `"GET /report"` or `"POST /orders/:id"`. * @returns Parsed `{ method, path }` pair, or `undefined` when not parseable. */ function parseRouteKeyForBazaar(pattern) { const spaceIdx = pattern.indexOf(" "); if (spaceIdx === -1) return undefined; const method = pattern.slice(0, spaceIdx).toUpperCase(); const path = pattern.slice(spaceIdx + 1); if (method === "*" || !path.startsWith("/")) return undefined; return { method, path }; } /** * Returns `true` when any payment option on a resolved route targets an EVM * (`eip155:*`) network. Used to gate EVM-specific extensions so a Solana-only * route doesn't advertise EVM gas-sponsoring keys. * * @param route - Resolved x402 `RouteConfig` to inspect. * @returns `true` if at least one accept option is on an EVM network. */ function routeHasEvmAccept(route) { const accepts = Array.isArray(route.accepts) ? route.accepts : [route.accepts]; return accepts.some(option => option.network.startsWith("eip155:")); } /** * Merges CDP auto-injected extensions into a resolved route config. Gas-sponsoring * extensions and builder-code are added only to routes with an EVM (`eip155:*`) * payment option — gas sponsoring is meaningless for Solana-only routes, and * builder-code attribution is an ERC-8021 EVM calldata suffix. The Bazaar * declaration is built from the route pattern for all routes. User-provided * `route.extensions` always win. * * @param pattern - Route key (e.g. `"GET /report"`) used to derive Bazaar metadata. * @param route - Resolved x402 `RouteConfig` to augment with CDP extensions. * @param builderCode - Pre-validated builder-code declaration to advertise. `create()` * always supplies one (at minimum the SDK's own service code); `undefined` here only * occurs in tests that call this function directly. * @returns A new `RouteConfig` with CDP extensions merged in. */ function withAutoInjectedExtensions(pattern, route, builderCode) { const bazaar = parseRouteKeyForBazaar(pattern); const hasEvmAccept = routeHasEvmAccept(route); return { ...route, extensions: { ...(hasEvmAccept && server_extensions_js_1.CDP_SUPPORTED_EXTENSIONS), ...(bazaar && { [server_extensions_js_1.CDP_EXTENSION_BAZAAR]: (0, server_extensions_js_1.buildBazaarDeclaration)(bazaar.method, bazaar.path) }), ...(builderCode && hasEvmAccept && { [server_extensions_js_1.CDP_EXTENSION_BUILDER_CODE]: builderCode }), ...route.extensions, }, }; } /** * Resolves a mixed `Record<string, CdpRouteConfig | RouteConfig>` into the * x402 `RoutesConfig` format. Simplified routes are expanded; full x402 * routes have vacant `payTo` fields filled. All CDP extensions are injected * into every route. * * @param routes - Map of route patterns to simplified or full x402 route configs. * @param evmAddress - EVM receiver address for `eip155:*` payment options (`""` when none). * @param svmAddress - Solana receiver address for `solana:*` payment options (`""` when none). * @param environment - Deployment environment controlling default network selection. * @param builderCode - Builder-code declaration injected on every EVM route. * @returns A fully resolved `RoutesConfig` ready to pass to an HTTP resource server. */ function resolveRoutes(routes, evmAddress, svmAddress, environment, builderCode) { const result = {}; const available = { evm: evmAddress !== "", svm: svmAddress !== "" }; for (const [pattern, route] of Object.entries(routes)) { const resolved = "accepts" in route ? fillX402RoutePayTo(route, evmAddress, svmAddress) : convertCdpRoute(route, evmAddress, svmAddress, environment, available); result[pattern] = withAutoInjectedExtensions(pattern, resolved, builderCode); } return result; } /** * Loads and parses a JSON config file, stripping `configPath` from the result * to prevent circular references when the field is present in the file itself. * * Security: this file may carry credentials (`apiKeySecret` / `walletSecret`). * Prefer supplying credentials via environment variables and reserving the * config file for `routes`; if secrets are stored here, keep the file out of * version control (e.g. add it to `.gitignore`). * * @param filePath - Absolute or relative path to the JSON config file. * @returns Parsed config with `configPath` removed. */ async function loadConfigFile(filePath) { const content = await (0, promises_1.readFile)(filePath, "utf-8"); const parsed = JSON.parse(content); const result = { ...parsed }; delete result.configPath; return result; } /* * --------------------------------------------------------------------------- * X402Server class * --------------------------------------------------------------------------- */ /** * A CDP-powered x402 resource server that **extends** `x402HTTPResourceServer`. * * It is a drop-in replacement anywhere an `x402HTTPResourceServer` is * expected — pass it directly to `paymentMiddlewareFromHTTPServer`, Hono / * Next.js adapters, or any other framework integration. * * Use `createX402Server()` (or `X402Server.create()`) to obtain an initialized * instance. The constructor is intentionally private; call the factory instead. * * In addition to the full `x402HTTPResourceServer` surface (`initialize()`, * `processHTTPRequest()`, `processSettlement()`, `requiresPayment()`, etc.) * this class exposes `payToEvmAddress` and `payToSvmAddress` for the * provisioned receiver wallets. */ class X402Server extends server_1.x402HTTPResourceServer { /** * EVM address of the receiver wallet. `undefined` when no EVM wallet was * provisioned (no `eip155:*` route, and `payToConfig` did not supply an * `evm` address). */ payToEvmAddress; /** * Solana address of the provisioned receiver wallet. `undefined` when no * Solana wallet was provisioned (no `solana:*` route, and `payToConfig` did * not supply a `solana` address). */ payToSvmAddress; /** * Owner account name for `"smart"` receiver wallets, otherwise `undefined`. * Only set when `payToConfig.type` is `"smart"`. */ ownerWallet; _initPromise = null; /** * Private constructor — use `createX402Server()` or `X402Server.create()` instead. * * @param resourceServer - Initialized `x402ResourceServer` with schemes registered. * @param routes - Resolved `RoutesConfig` with `payTo` and extensions filled in. * @param payToEvmAddress - EVM address of the provisioned receiver wallet, * or `undefined` if none was provisioned. * @param payToSvmAddress - Solana address of the provisioned receiver wallet, * or `undefined` if none was provisioned. * @param ownerWallet - Owner account name for smart wallets, otherwise `undefined`. * @internal */ constructor(resourceServer, routes, payToEvmAddress, payToSvmAddress, ownerWallet) { super(resourceServer, routes); this.payToEvmAddress = payToEvmAddress; this.payToSvmAddress = payToSvmAddress; this.ownerWallet = ownerWallet; } /** * The underlying `x402ResourceServer` with EVM and Solana schemes registered. * * @returns The `x402ResourceServer` instance used by this server. */ get resourceServer() { return this.server; } /** * Provisions CDP receiver wallets (unless `payToConfig.type` is `"address"`), * resolves routes, constructs the HTTP resource server, and syncs supported * schemes with the CDP facilitator. * * This is the async entry point for `X402Server`. Prefer the module-level * `createX402Server()` wrapper for convenience. * * @param config - Credential, wallet, and route configuration. * @returns A fully initialized `X402Server` instance ready to be passed to * any framework middleware. */ static async create(config) { // Routes are deep-merged so file and inline routes both survive; inline wins on conflicts. let merged = config; if (config.configPath) { const fileConfig = await loadConfigFile(config.configPath); merged = { ...fileConfig, ...config, routes: { ...fileConfig.routes, ...config.routes }, }; } // Validate before any I/O so a bad config fails before wallet provisioning. const routes = merged.routes; if (!routes || Object.keys(routes).length === 0) { throw new Error("createX402Server requires at least one payment route."); } if (merged.builderCode !== undefined) { (0, builder_code_js_1.assertBuilderCode)(merged.builderCode); } /* * Always declared (not gated on `builderCode`) so every EVM route attaches * this SDK's own service code to `s`, in addition to the developer's own * app code (`a`) when configured. */ const builderCodeDeclaration = (0, builder_code_js_1.declareServerBuilderCodeExtension)(merged.builderCode); const credentials = resolveServerCredentials(merged); const { environment } = credentials; validateRouteSemantics(routes, environment); const facilitatorClient = (0, facilitator_js_1.createCdpFacilitatorClient)({ apiKeyId: credentials.apiKeyId, apiKeySecret: credentials.apiKeySecret, }); const resourceServer = new server_1.x402ResourceServer(facilitatorClient); for (const ext of (0, server_extensions_js_1.getCdpExtensionRegistrations)()) { resourceServer.registerExtension(ext); } const payToConfig = merged.payToConfig; let evmAddress; let svmAddress; let ownerWallet; if (payToConfig?.type === "address") { evmAddress = payToConfig.evm ?? ""; svmAddress = payToConfig.solana ?? ""; } else { const missing = []; if (!credentials.apiKeyId) missing.push("CDP_API_KEY_ID"); if (!credentials.apiKeySecret) missing.push("CDP_API_KEY_SECRET"); if (!credentials.walletSecret) missing.push("CDP_WALLET_SECRET"); if (missing.length > 0) { throw new Error(`Missing required CDP credentials: ${missing.join(", ")}. ` + "Provide them via config options or set the corresponding environment variables " + "(CDP_API_KEY_ID / CDP_API_KEY_SECRET / CDP_WALLET_SECRET). " + "Alternatively, pass payToConfig: { type: 'address', evm: '0x...', solana: '...' } to skip wallet provisioning."); } /* * Only provision the wallet families the routes actually reference, so an * EVM-only server never creates a Solana account (and vice versa). */ const need = requiredNetworkFamilies(routes, environment); const provisioned = await provisionServerAccounts({ apiKeyId: credentials.apiKeyId, apiKeySecret: credentials.apiKeySecret, walletSecret: credentials.walletSecret, }, (payToConfig ?? { type: "eoa" }), need); evmAddress = provisioned.evmAddress; svmAddress = provisioned.svmAddress; ownerWallet = provisioned.ownerWallet; } for (const scheme of (0, server_extensions_js_1.getCdpDefaultSchemes)()) { resourceServer.register(scheme.network, scheme.server); } const resolvedRoutes = resolveRoutes(routes, evmAddress, svmAddress, environment, builderCodeDeclaration); const instance = new X402Server(resourceServer, resolvedRoutes, evmAddress === "" ? undefined : evmAddress, svmAddress === "" ? undefined : svmAddress, ownerWallet); await instance.initialize(); return instance; } /** * Initializes the underlying resource server (facilitator support sync + * route validation) exactly once. * * `createX402Server()` already calls this before returning, and framework * adapters such as `paymentMiddlewareFromHTTPServer` call `initialize()` * again on startup. The base implementation re-fetches facilitator support * on every call, so this override memoizes the first run to avoid a redundant * facilitator round-trip. The promise is cleared on failure so a later call * can retry. * * @returns A promise that resolves once initialization has completed. */ async initialize() { if (!this._initPromise) { this._initPromise = super.initialize().catch(error => { this._initPromise = null; throw error; }); } return this._initPromise; } } exports.X402Server = X402Server; /* * --------------------------------------------------------------------------- * Public factory * --------------------------------------------------------------------------- */ /** * Creates and initializes a CDP-powered x402 resource server. * * Returns an `X402Server` which **extends** `x402HTTPResourceServer` and can * be passed directly to `paymentMiddlewareFromHTTPServer` or any other * framework adapter. * * All credential fields fall back to environment variables; an empty `{}` * with a `routes` map is sufficient in most environments. * * Pass `payToConfig: { type: "address", evm: "0x...", solana: "..." }` to * provide your own receiver addresses without provisioning a CDP wallet. * * @param config - Credential, wallet, and route configuration. All credential fields * fall back to environment variables; `routes` is the only required field (or * `configPath` pointing to a JSON file that supplies routes). * @returns A fully initialized `X402Server` ready to be passed to any framework middleware. * @example Minimal setup (auto-provisions receiver wallet): * ```typescript * // Set: CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET * const server = await createX402Server({ * routes: { * "GET /report": { price: "$0.01", description: "AI-generated report" }, * }, * }); * app.use(paymentMiddlewareFromHTTPServer(server)); * console.log("EVM receiver:", server.payToEvmAddress); * console.log("Solana receiver:", server.payToSvmAddress); * ``` * * @example Bring your own addresses: * ```typescript * const server = await createX402Server({ * routes: { "GET /report": { price: "$0.01" } }, * payToConfig: { type: "address", evm: "0x1234...", solana: "ABC..." }, * }); * ``` * * @example Exact + upfront payment flow (shorthand; applies to Base and Solana): * ```typescript * const server = await createX402Server({ * routes: { * "GET /report": { price: "$0.01", paymentFlow: "upfront" }, * }, * }); * ``` * * @example Full x402 RouteConfig format with vacant payTo: * ```typescript * const server = await createX402Server({ * routes: { * "GET /report": { * accepts: { scheme: "exact", price: "$0.01", network: "eip155:8453", payTo: "" }, * }, * }, * }); * ``` * * @example Config-file variant: * ```typescript * // x402.config.json: { "routes": { "GET /report": { "price": "$0.01" } } } * const server = await createX402Server({ configPath: "./x402.config.json" }); * ``` */ async function createX402Server(config) { return X402Server.create(config); } //# sourceMappingURL=server.js.map