UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

219 lines • 9.62 kB
/** * CDP-opinionated extension wiring for the x402 payment protocol. * * `createX402Server` advertises the extensions below on the routes each one * applies to. Gas-sponsoring extensions are static (presence of the key is * enough) and EVM-only. Bazaar is built per-route from the route key and any * user-provided overrides. Builder code is EVM-only and always injected, for * on-chain attribution of payments received through the CDP SDK; setting * `builderCode` on the server config additionally declares the app's own code. * * | Key | Auto-injected on | Notes | * |-----|------------------|-------| * | `"eip2612GasSponsoring"` | EVM routes | Sponsored Permit2 via EIP-2612 permit | * | `"erc20ApprovalGasSponsoring"` | EVM routes | Sponsored ERC-20 approve tx | * | `"bazaar"` | every route | Minimal discovery metadata built from route pattern | * | `"builder-code"` | EVM routes | ERC-8021 attribution: SDK service code (`s`), plus app code (`a`) when `builderCode` is set | * * Users who need richer Bazaar metadata (queryParams, body example, output * schema, etc.) can override by setting `extensions.bazaar` on the route — * their value takes precedence over the auto-generated one. */ import { ExactEvmScheme } from "@x402/evm/exact/server"; import { UptoEvmScheme } from "@x402/evm/upto/server"; import { bazaarResourceServerExtension } from "@x402/extensions/bazaar"; import { BUILDER_CODE, builderCodeResourceServerExtension } from "@x402/extensions/builder-code"; import { ExactSvmScheme } from "@x402/svm/exact/server"; import { UptoSvmScheme } from "@x402/svm/upto/server"; /* * --------------------------------------------------------------------------- * Extension key constants * --------------------------------------------------------------------------- */ /** * Extension key for EIP-2612 gas-sponsored Permit2 payments. * * When advertised in `PaymentRequired.extensions`, the x402 EVM client * automatically signs an EIP-2612 permit if Permit2 allowance is insufficient * — the CDP Facilitator submits the permit transaction, covering the user's gas. */ export const CDP_EXTENSION_GAS_SPONSORING_EIP2612 = "eip2612GasSponsoring"; /** * Extension key for ERC-20 approval gas-sponsored payments. * * When advertised, the x402 EVM client signs an ERC-20 `approve(Permit2, MaxUint256)` * transaction and the CDP Facilitator broadcasts it, covering the user's gas cost. */ export const CDP_EXTENSION_GAS_SPONSORING_ERC20_APPROVAL = "erc20ApprovalGasSponsoring"; /** * Extension key for Bazaar resource discovery. * * Auto-injected by `createX402Server` with a minimal `DiscoveryExtension` * built from the route key (HTTP method + path template). Override by * providing `extensions.bazaar` in your route config with richer metadata * (queryParams, body example, output schema, etc.). */ export const CDP_EXTENSION_BAZAAR = "bazaar"; /** * Extension key for [builder-code](https://github.com/x402-foundation/x402/blob/main/specs/extensions/builder_code.md) * on-chain attribution (ERC-8021 Schema 2). * * Injected by `createX402Server` on every EVM route, declaring the SDK's own * service code (`s`) in `PaymentRequired.extensions`, plus the app code (`a`) * when `builderCode` is set on the server config. */ export const CDP_EXTENSION_BUILDER_CODE = BUILDER_CODE; /* * --------------------------------------------------------------------------- * Auto-injected extension set * --------------------------------------------------------------------------- */ /** * Static extension declarations that `createX402Server` injects into every * route regardless of route-specific metadata. * * Both gas-sponsoring entries are present. Their presence signals to the x402 * EVM client that the CDP Facilitator can cover Permit2 gas costs; the client * only activates the path when `requirements.extra.assetTransferMethod` is * `"permit2"`, so the declarations are harmless for EIP-3009 and Solana routes. * * Bazaar and builder-code are NOT in this set — both need a per-call declaration * (Bazaar carries HTTP method/path metadata; builder-code carries the SDK's * service code and, when configured, the app's own code). */ export const CDP_SUPPORTED_EXTENSIONS = { [CDP_EXTENSION_GAS_SPONSORING_EIP2612]: {}, [CDP_EXTENSION_GAS_SPONSORING_ERC20_APPROVAL]: {}, }; /* * --------------------------------------------------------------------------- * Bazaar discovery declaration builder * --------------------------------------------------------------------------- */ /** HTTP methods that carry a request body (POST, PUT, PATCH). */ const BODY_METHODS = new Set(["POST", "PUT", "PATCH"]); /** * Builds a minimal Bazaar `DiscoveryExtension` declaration from an HTTP method * and path template. It carries enough information for Bazaar to index the route; * users who need richer metadata (queryParams, body example, output schema, path-param * types) should override by setting `extensions.bazaar` explicitly in their route * config. * * Wire shape follows `github.com/x402-foundation/x402/go/extensions/bazaar`: * - GET/HEAD/DELETE → `QueryInput` (`{ type, method }`) * - POST/PUT/PATCH → `BodyInput` (`{ type, method, bodyType: "json", body: {} }`) * * For body methods `body` is required by the Bazaar `BodyDiscoveryInfo` contract * (`body: Record<string, unknown>`). An empty object is used since the route's * body shape is not known at declaration time; users who need a richer schema * should override `extensions.bazaar` in their route config. * * @param method - Uppercase HTTP verb, e.g. `"GET"` or `"POST"`. * @param path - Path template, e.g. `"/report"` or `"/users/:id"`. * @returns A Bazaar `DiscoveryExtension` declaration object. */ export function buildBazaarDeclaration(method, path) { const isBodyMethod = BODY_METHODS.has(method); const input = { type: "http", method }; if (isBodyMethod) { input.bodyType = "json"; input.body = {}; } const inputSchemaProperties = { type: { type: "string", const: "http" }, method: { type: "string", enum: [method] }, }; const inputSchemaRequired = ["type", "method"]; if (isBodyMethod) { inputSchemaProperties.bodyType = { type: "string", enum: ["json"] }; inputSchemaProperties.body = { type: "object" }; inputSchemaRequired.push("bodyType"); inputSchemaRequired.push("body"); } const schema = { properties: { input: { type: "object", properties: inputSchemaProperties, required: inputSchemaRequired, additionalProperties: false, }, }, }; return { info: { input }, schema, routeTemplate: path, }; } /** * Returns the default CDP scheme registrations — `exact` and `upto` for all * EVM (`eip155:*`) and Solana (`solana:*`) networks. * * Solana `upto` delegates voucher signing to the facilitator. No `rpcUrl` is * passed; the client fetches a blockhash and slot when the challenge does not * include them. * * Pass the result to `paymentMiddlewareFromConfig` (Express / Hono) or * any other framework adapter to replicate the same scheme coverage * when building middleware manually. * * @example * ```typescript * import { getCdpDefaultSchemes, createCdpFacilitatorClient } from "@coinbase/cdp-sdk/x402"; * import { paymentMiddlewareFromConfig } from "@x402/express"; * * app.use(paymentMiddlewareFromConfig(routes, createCdpFacilitatorClient(), getCdpDefaultSchemes())); * ``` * @returns Array of scheme+network registrations covering exact+upto on EVM and Solana. */ export function getCdpDefaultSchemes() { return [ { network: "eip155:*", server: new ExactEvmScheme() }, { network: "eip155:*", server: new UptoEvmScheme() }, { network: "solana:*", server: new ExactSvmScheme() }, { network: "solana:*", server: new UptoSvmScheme() }, ]; } /* * --------------------------------------------------------------------------- * ResourceServerExtension registrations * --------------------------------------------------------------------------- */ /** * Returns `ResourceServerExtension` registrations for all CDP-supported extensions. * * These register enrichment handlers on `x402ResourceServer` so that routes * whose `extensions` include a CDP extension key get the correct * `PaymentRequired.extensions[key]` value. * * `createX402Server()` calls this automatically. Call it manually only when * building a resource server without `createX402Server`: * * ```typescript * import { x402ResourceServer } from "@x402/core/server"; * import { getCdpExtensionRegistrations } from "@coinbase/cdp-sdk/x402"; * * const server = new x402ResourceServer(facilitatorClient); * for (const ext of getCdpExtensionRegistrations()) { * server.registerExtension(ext); * } * ``` * * @returns Array of `ResourceServerExtension` registrations for gas-sponsoring, Bazaar, and builder-code. */ export function getCdpExtensionRegistrations() { return [ { key: CDP_EXTENSION_GAS_SPONSORING_EIP2612, enrichPaymentRequiredResponse: async (declaration) => declaration ?? {}, }, { key: CDP_EXTENSION_GAS_SPONSORING_ERC20_APPROVAL, enrichPaymentRequiredResponse: async (declaration) => declaration ?? {}, }, bazaarResourceServerExtension, builderCodeResourceServerExtension, ]; } //# sourceMappingURL=server-extensions.js.map