@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
812 lines • 35.8 kB
JavaScript
"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