@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
315 lines • 14.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.signEvmX402Payment = signEvmX402Payment;
exports.signEvmSmartAccountX402Payment = signEvmSmartAccountX402Payment;
exports.signSolanaX402Payment = signSolanaX402Payment;
/*
* Account-level x402 payment signing.
*
* `@x402/*` packages are optional peer dependencies of the SDK — most
* consumers of `@coinbase/cdp-sdk` never touch x402, so every module reached
* from an account's `signX402Payment()` method (this file, and the
* `../../x402/account-signers.js` adapters it uses) loads them lazily via
* dynamic `import()` instead of a static top-level import. That keeps a bare
* `import { CdpClient } from "@coinbase/cdp-sdk"` from requiring `@x402/*` to
* be installed at all; they're only resolved the first time a caller
* actually signs an x402 payment.
*/
const constants_js_1 = require("../../x402/constants.js");
/**
* Dynamically imports an optional `@x402/*` peer dependency (or an internal
* module that itself imports one), wrapping module-not-found failures with
* an actionable install hint instead of a raw resolution error.
*
* @param specifier - The module specifier to import.
* @param loader - Thunk performing the dynamic `import()`, so callers keep accurate typing.
* @returns The imported module's exports.
*/
async function importX402Dependency(specifier, loader) {
try {
return await loader();
}
catch (error) {
throw new Error(`Failed to load "${specifier}", which is required to sign x402 payments. ` +
"Install the CDP SDK's x402 peer dependencies: `@x402/core`, `@x402/evm`, `@x402/svm`.", { cause: error });
}
}
const EVM_SIGNING_CAPABILITY = {
networkNamespace: "eip155",
supportedSchemes: ["exact", "upto"],
label: "EVM",
};
/*
* Smart accounts sign with an ERC-1271/ERC-6492 contract signature, which only
* settles via the EIP-3009 `exact` flow. The `upto` scheme and the Permit2
* transfer method both require an on-chain Permit2 allowance owned by an EOA,
* so they are intentionally unsupported for smart accounts.
*/
const EVM_SMART_ACCOUNT_SIGNING_CAPABILITY = {
networkNamespace: "eip155",
supportedSchemes: ["exact"],
label: "EVM smart",
};
const SVM_SIGNING_CAPABILITY = {
networkNamespace: "solana",
supportedSchemes: ["exact"],
label: "Solana",
};
/**
* Parses JSON from CDP_X402_RPC_URLS and converts it into CAIP-2 keyed RPC config.
*
* Supports this value format:
* - { "eip155:8453": "https://..." }
*
* @returns Parsed CAIP-2 keyed EVM/Solana RPC URL overrides.
*/
function parseRpcUrlsFromEnv() {
const raw = process.env.CDP_X402_RPC_URLS;
if (!raw)
return {};
let parsed;
try {
parsed = JSON.parse(raw);
}
catch {
throw new Error("Invalid CDP_X402_RPC_URLS: expected JSON object mapping CAIP-2 network IDs to RPC URL strings.");
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
throw new Error("Invalid CDP_X402_RPC_URLS: expected JSON object mapping CAIP-2 network IDs to RPC URL strings.");
}
const rpcUrlsByCaip2 = {};
for (const [network, value] of Object.entries(parsed)) {
if (typeof value === "string") {
rpcUrlsByCaip2[network] = { rpcUrl: value };
continue;
}
throw new Error(`Invalid CDP_X402_RPC_URLS entry for "${network}": expected string URL.`);
}
return rpcUrlsByCaip2;
}
/**
* Resolves the final CAIP-2 keyed EVM RPC configuration used for x402 signing.
*
* Defaults come from {@link getDefaultEvmRpcUrls} (Base networks only), with
* CDP_X402_RPC_URLS env values taking precedence.
*
* @returns CAIP-2 keyed RPC configuration map.
*/
async function resolveEvmRpcUrlsByCaip2() {
return {
...(await (0, constants_js_1.getDefaultEvmRpcUrls)()),
...parseRpcUrlsFromEnv(),
};
}
/**
* Converts CAIP-2 keyed RPC config to the numeric-chain-ID map expected by
* Exact/Upto EVM schemes.
*
* @param rpcUrlsByCaip2 - CAIP-2 keyed RPC configuration map.
* @returns Record of numeric EIP-155 chain IDs to RPC URL objects.
*/
function buildEvmRpcUrlsByChainId(rpcUrlsByCaip2) {
const result = {};
for (const [caip2, cfg] of Object.entries(rpcUrlsByCaip2)) {
const [namespace, chainId] = caip2.split(":");
if (namespace !== "eip155" || !chainId)
continue;
const numericChainId = Number(chainId);
if (!Number.isNaN(numericChainId)) {
result[numericChainId] = cfg;
}
}
return result;
}
/**
* Selects a payment requirement by original `paymentRequired.accepts` index and
* returns a normalized PaymentRequired containing only that entry.
*
* This preserves the documented semantics of `acceptedIndex` even though
* `x402Client` internally filters accepts by registered scheme/network before
* invoking selectors.
*
* @param paymentRequired - Original payment requirements from a resource server.
* @param acceptedIndex - Index into the original `paymentRequired.accepts`.
* @param capability - The signing networks/schemes the account type supports.
* @returns A PaymentRequired with exactly one accepted requirement.
*/
function selectAcceptedPaymentRequired(paymentRequired, acceptedIndex, capability) {
const selected = paymentRequired.accepts[acceptedIndex];
if (!selected) {
throw new Error(`acceptedIndex ${acceptedIndex} is out of range — paymentRequired.accepts has ` +
`${paymentRequired.accepts.length} entr${paymentRequired.accepts.length === 1 ? "y" : "ies"}.`);
}
if (!selected.network.startsWith(`${capability.networkNamespace}:`)) {
throw new Error(`acceptedIndex ${acceptedIndex} targets network "${selected.network}", which a ` +
`${capability.label} account cannot sign. Choose an index whose network uses the ` +
`"${capability.networkNamespace}:" namespace.`);
}
if (!capability.supportedSchemes.includes(selected.scheme)) {
throw new Error(`acceptedIndex ${acceptedIndex} uses the "${selected.scheme}" scheme, which is not ` +
`supported for ${capability.label} accounts. Supported schemes: ` +
`${capability.supportedSchemes.join(", ")}.`);
}
return {
...paymentRequired,
accepts: [selected],
};
}
/**
* Throws with an actionable message when an EVM payment requirement targets a
* chain that has no configured RPC URL in the resolved map.
*
* Without an RPC, the x402 EVM scheme cannot sign or submit the payment
* transaction. Base and Base Sepolia resolve an RPC automatically via the
* CDP-authenticated node endpoint; every other chain requires an explicit
* RPC via `CDP_X402_RPC_URLS` or `rpcUrls`.
*
* @param network - The CAIP-2 network string from the selected requirement.
* @param rpcUrlsByChainId - The resolved chain-ID-to-RPC-URL map.
*/
function assertEvmRpcConfigured(network, rpcUrlsByChainId) {
const chainId = Number(network.split(":")[1]);
if (!Number.isNaN(chainId) && rpcUrlsByChainId[chainId] === undefined) {
throw new Error(`No RPC URL configured for ${network}. ` +
`Provide one via CDP_X402_RPC_URLS='{"${network}":"https://your-rpc-url"}' ` +
`or rpcUrls: { "${network}": { rpcUrl: "https://your-rpc-url" } } in the SDK configuration.`);
}
}
/**
* Resolves a per-network RPC URL override for SVM signing from
* `CDP_X402_RPC_URLS`, keyed by the selected requirement's CAIP-2 network.
*
* The SVM exact scheme provides its own public RPC defaults, so only an
* explicit override needs to be wired through.
*
* @param network - CAIP-2 network identifier of the selected requirement.
* @returns The override RPC URL, or undefined when none is configured.
*/
function resolveSvmRpcUrlOverride(network) {
return parseRpcUrlsFromEnv()[network]?.rpcUrl;
}
/**
* Signs an x402 payment payload with an EVM server account (EOA).
*
* Registers the exact and upto EVM schemes locally using the x402 SDK, then
* calls `x402Client.createPaymentPayload`. No CDP signing endpoint is used.
*
* @param account - The CDP EVM server account.
* @param options - The x402 payment requirements and selected accepted index.
* @returns The signed x402 payment payload.
*/
async function signEvmX402Payment(account, options) {
const rpcUrlsByChainId = buildEvmRpcUrlsByChainId(await resolveEvmRpcUrlsByCaip2());
const selectedPaymentRequired = selectAcceptedPaymentRequired(options.paymentRequired, options.acceptedIndex, EVM_SIGNING_CAPABILITY);
const [selected] = selectedPaymentRequired.accepts;
assertEvmRpcConfigured(selected.network, rpcUrlsByChainId);
const [{ x402Client }, { registerExactEvmScheme }, { UptoEvmScheme }] = await Promise.all([
importX402Dependency("@x402/core/client", () => Promise.resolve().then(() => __importStar(require("@x402/core/client")))),
importX402Dependency("@x402/evm/exact/client", () => Promise.resolve().then(() => __importStar(require("@x402/evm/exact/client")))),
importX402Dependency("@x402/evm/upto/client", () => Promise.resolve().then(() => __importStar(require("@x402/evm/upto/client")))),
]);
const client = new x402Client();
registerExactEvmScheme(client, { signer: account, schemeOptions: rpcUrlsByChainId });
client.register("eip155:*", new UptoEvmScheme(account, rpcUrlsByChainId));
return client.createPaymentPayload(selectedPaymentRequired);
}
/**
* Signs an x402 payment payload with an EVM smart account.
*
* The smart-account adapter derives the CDP network from the EIP-712 domain's
* `chainId`, so no explicit network parameter is needed on the call site.
*
* Smart accounts produce an ERC-1271/ERC-6492 contract signature, which only
* settles via the EIP-3009 `exact` flow. The `upto` scheme and the Permit2
* transfer method are rejected because they require an on-chain Permit2
* allowance owned by an EOA.
*
* @param account - The CDP EVM smart account.
* @param options - The x402 payment requirements and selected accepted index.
* @returns The signed x402 payment payload.
*/
async function signEvmSmartAccountX402Payment(account, options) {
const rpcUrlsByChainId = buildEvmRpcUrlsByChainId(await resolveEvmRpcUrlsByCaip2());
const selectedPaymentRequired = selectAcceptedPaymentRequired(options.paymentRequired, options.acceptedIndex, EVM_SMART_ACCOUNT_SIGNING_CAPABILITY);
const [selected] = selectedPaymentRequired.accepts;
assertEvmRpcConfigured(selected.network, rpcUrlsByChainId);
if (selected.extra?.assetTransferMethod === "permit2") {
throw new Error(`acceptedIndex ${options.acceptedIndex} requires the Permit2 transfer method, which is ` +
`not supported for EVM smart accounts. Choose a requirement that uses EIP-3009 ` +
`(transferWithAuthorization).`);
}
const [{ x402Client }, { registerExactEvmScheme }, { fromCdpSmartWallet }] = await Promise.all([
importX402Dependency("@x402/core/client", () => Promise.resolve().then(() => __importStar(require("@x402/core/client")))),
importX402Dependency("@x402/evm/exact/client", () => Promise.resolve().then(() => __importStar(require("@x402/evm/exact/client")))),
importX402Dependency("../../x402/account-signers.js", () => Promise.resolve().then(() => __importStar(require("../../x402/account-signers.js")))),
]);
const signer = fromCdpSmartWallet(account);
const client = new x402Client();
registerExactEvmScheme(client, { signer, schemeOptions: rpcUrlsByChainId });
return client.createPaymentPayload(selectedPaymentRequired);
}
/**
* Signs an x402 payment payload with a Solana account.
*
* @param account - The CDP Solana account.
* @param options - The x402 payment requirements and selected accepted index.
* @returns The signed x402 payment payload.
*/
async function signSolanaX402Payment(account, options) {
const selectedPaymentRequired = selectAcceptedPaymentRequired(options.paymentRequired, options.acceptedIndex, SVM_SIGNING_CAPABILITY);
const [selected] = selectedPaymentRequired.accepts;
const [{ x402Client }, { ExactSvmScheme, registerExactSvmScheme }, { cdpSolanaAccountToSvmSigner },] = await Promise.all([
importX402Dependency("@x402/core/client", () => Promise.resolve().then(() => __importStar(require("@x402/core/client")))),
importX402Dependency("@x402/svm/exact/client", () => Promise.resolve().then(() => __importStar(require("@x402/svm/exact/client")))),
importX402Dependency("../../x402/account-signers.js", () => Promise.resolve().then(() => __importStar(require("../../x402/account-signers.js")))),
]);
const signer = cdpSolanaAccountToSvmSigner(account);
const client = new x402Client();
const rpcUrl = resolveSvmRpcUrlOverride(selected.network);
if (rpcUrl) {
/*
* registerExactSvmScheme ignores RPC overrides, so register the selected
* network directly with the override. An exact-network registration takes
* precedence over the "solana:*" wildcard during scheme selection.
*/
client.register(selected.network, new ExactSvmScheme(signer, { rpcUrl }));
}
else {
registerExactSvmScheme(client, { signer });
}
return client.createPaymentPayload(selectedPaymentRequired);
}
//# sourceMappingURL=signX402Payment.js.map