UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

173 lines (152 loc) 5.21 kB
/** * @module HTTP */ import { generateWalletJwt, generateJwt } from "./jwt.js"; import { UserInputValidationError } from "../../errors.js"; import { version } from "../../version.js"; /** * Options for generating authentication headers for API requests. */ export interface GetAuthHeadersOptions { /** * The API key ID * * Examples: * 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' * 'organizations/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/apiKeys/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' */ apiKeyId: string; /** * The API key secret * * Examples: * 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==' (Edwards key (Ed25519)) * '-----BEGIN EC PRIVATE KEY-----\n...\n...\n...==\n-----END EC PRIVATE KEY-----\n' (EC key (ES256)) */ apiKeySecret: string; /** * The HTTP method for the request (e.g. 'GET', 'POST') */ requestMethod: string; /** * The host for the request (e.g. 'api.cdp.coinbase.com') */ requestHost: string; /** * The path for the request (e.g. '/platform/v1/wallets') */ requestPath: string; /** * Optional request body data */ requestBody?: unknown; /** * The Wallet Secret for wallet authentication */ walletSecret?: string; /** * The source identifier for the request */ source?: string; /** * The version of the source making the request */ sourceVersion?: string; /** * Optional expiration time in seconds (defaults to 120) */ expiresIn?: number; /** * Optional audience claim for the JWT */ audience?: string[]; } /** * Gets authentication headers for a request. * * @param options - The configuration options for generating auth headers * @returns Object containing the authentication headers */ export async function getAuthHeaders( options: GetAuthHeadersOptions, ): Promise<Record<string, string>> { const headers: Record<string, string> = {}; // Generate and add JWT token const jwt = await generateJwt({ apiKeyId: options.apiKeyId, apiKeySecret: options.apiKeySecret, requestMethod: options.requestMethod, requestHost: options.requestHost, requestPath: options.requestPath, expiresIn: options.expiresIn, audience: options.audience, }); headers["Authorization"] = `Bearer ${jwt}`; headers["Content-Type"] = "application/json"; // Add wallet auth if needed if (requiresWalletAuth(options.requestMethod, options.requestPath)) { if (!options.walletSecret) { throw new UserInputValidationError( "Wallet Secret not configured. Please set the CDP_WALLET_SECRET environment variable, or pass it as an option to the CdpClient constructor.", ); } const walletAuthToken = await generateWalletJwt({ walletSecret: options.walletSecret, requestMethod: options.requestMethod, requestHost: options.requestHost, requestPath: options.requestPath, requestData: options.requestBody || {}, }); headers["X-Wallet-Auth"] = walletAuthToken; } // Add correlation data headers["Correlation-Context"] = getCorrelationData(options.source, options.sourceVersion); return headers; } /** * Returns true if the request indicated by the method and URL requires wallet authentication. * * @param requestMethod - The HTTP method of the request * @param requestPath - The URL path of the request * @returns True if the request requires wallet authentication, false otherwise */ function requiresWalletAuth(requestMethod: string, requestPath: string): boolean { return ( /* * Match the wallet account endpoints (/v2/evm/accounts, /v2/solana/accounts) * but NOT the custodial /v2/accounts endpoint, which authenticates with the * API key alone (see openapi.yaml: POST /v2/accounts declares apiKeyAuth and * no X-Wallet-Auth parameter). A bare includes("/accounts") over-matched it. */ (/\/(evm|solana)\/accounts/.test(requestPath ?? "") || requestPath?.includes("/spend-permissions") || requestPath?.includes("/user-operations/prepare-and-send") || requestPath?.includes("/embedded-wallet-api/") || requestPath?.endsWith("/end-users") || requestPath?.endsWith("/end-users/import") || /\/end-users\/[^/]+\/evm$/.test(requestPath) || /\/end-users\/[^/]+\/evm-smart-account$/.test(requestPath) || /\/end-users\/[^/]+\/solana$/.test(requestPath)) && (requestMethod === "POST" || requestMethod === "DELETE" || requestMethod === "PUT") ); } /** * Returns encoded correlation data including the SDK version and language. * * @param source - The source identifier for the request * @param sourceVersion - The version of the source making the request * @returns Encoded correlation data as a query string */ export function getCorrelationData(source?: string, sourceVersion?: string): string { const data: Record<string, string> = { sdk_version: version, sdk_language: "typescript", source: source || "sdk-auth", }; if (sourceVersion) { data["source_version"] = sourceVersion; } return Object.keys(data) .map(key => `${key}=${encodeURIComponent(data[key])}`) .join(","); }