UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

139 lines • 6.12 kB
/* * Pure CDP-account-to-x402-signer adapters. */ import { address as toSolanaAddress, getTransactionEncoder } from "@solana/kit"; import { toClientEvmSigner } from "@x402/evm"; import { CHAIN_ID_TO_CDP_NETWORK } from "./constants.js"; /** * Converts a CDP EVM server account (EOA) into an x402-compatible signer. * * @param account - A CDP EVM account from `cdpClient.evm.getOrCreateAccount()` * @returns A `ClientEvmSigner` compatible with `@x402/evm`'s `registerExactEvmScheme` */ export function fromCdpEvmAccount(account) { return toClientEvmSigner(account); } /** * Resolves a CDP SDK network name from an EIP-712 domain chain ID. * * @param chainId - The EIP-712 domain chain ID to resolve. * @throws {Error} If the chain ID is undefined or not supported. * @returns The CDP SDK network name (e.g. `"base-sepolia"`). */ export function resolveNetworkFromChainId(chainId) { if (chainId === undefined) { throw new Error("Cannot derive CDP network: domain.chainId is missing from the typed data. " + "EIP-712 domain must include chainId when using a Smart Contract Wallet."); } const network = CHAIN_ID_TO_CDP_NETWORK[chainId]; if (!network) { throw new Error(`Unsupported chainId ${chainId} for CDP Smart Contract Wallet. ` + `Supported networks: ${Object.values(CHAIN_ID_TO_CDP_NETWORK).join(", ")}`); } return network; } /** * Converts a CDP Smart Account (EvmSmartAccount) into an x402-compatible signer. * * CDP Smart Accounts differ from EOAs in that their `signTypedData` requires a * `network` parameter. This adapter derives the network automatically from the * EIP-712 domain's `chainId`. * * @param account - A CDP Smart Account from `cdpClient.evm.getOrCreateSmartAccount()` * @returns A `ClientEvmSigner` compatible with `@x402/evm`'s `registerExactEvmScheme` */ export function fromCdpSmartWallet(account) { const signerShape = { address: account.address, // eslint-disable-next-line @typescript-eslint/no-explicit-any async signTypedData({ domain, types, primaryType, message }) { const chainId = domain?.chainId; const network = resolveNetworkFromChainId(chainId); return account.signTypedData({ domain, types, primaryType, message, network }); }, }; return toClientEvmSigner(signerShape); } /** * Decodes a Solana short-vector length prefix from the start of a byte array. * * @param bytes - Raw transaction bytes to decode. * @returns Decoded length value and number of bytes consumed. */ function decodeShortVecLength(bytes) { let value = 0; let shift = 0; let bytesRead = 0; while (bytesRead < bytes.length) { const byte = bytes[bytesRead]; value |= (byte & 0x7f) << shift; bytesRead += 1; if ((byte & 0x80) === 0) { return { value, bytesRead }; } shift += 7; } throw new Error("Invalid Solana transaction wire format: truncated shortvec length."); } /** * Finds the index of a signer in a compiled Solana transaction's signatures map. * * @param transaction - Compiled Solana transaction object. * @param signerAddress - Base58 signer address to locate. * @returns The zero-based index of the signer in the signatures array. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any function resolveSignerIndex(transaction, signerAddress) { const signatures = transaction?.signatures; if (signatures && typeof signatures === "object") { const signerOrder = Object.keys(signatures); const index = signerOrder.indexOf(signerAddress); if (index >= 0) return index; } throw new Error(`Unable to locate signer "${signerAddress}" in transaction signatures. ` + "Expected transaction.signatures to contain the signer address."); } /** * Converts a CDP Solana account into an x402-compatible `TransactionSigner`. * * Handles the translation between CDP's base64-encoded transaction strings and * `@solana/kit`'s compiled transaction objects. * * @param account - A CDP Solana account from `cdpClient.solana.getOrCreateAccount()` * @returns A `TransactionSigner` compatible with `@x402/svm`'s `registerExactSvmScheme` */ export function cdpSolanaAccountToSvmSigner(account) { const signerAddress = toSolanaAddress(account.address); const signerAddressKey = signerAddress; const encoder = getTransactionEncoder(); return { address: signerAddress, // eslint-disable-next-line @typescript-eslint/no-explicit-any async signTransactions(transactions) { const results = []; for (const transaction of transactions) { const signerIndex = resolveSignerIndex(transaction, signerAddressKey); const encodedBytes = encoder.encode(transaction); const base64Transaction = Buffer.from(encodedBytes).toString("base64"); const { signedTransaction } = await account.signTransaction({ transaction: base64Transaction, }); const signedBytes = Buffer.from(signedTransaction, "base64"); const { value: signatureCount, bytesRead: signatureSectionOffset } = decodeShortVecLength(signedBytes); if (signerIndex >= signatureCount) { throw new Error(`Signer index ${signerIndex} is out of bounds for ${signatureCount} signatures.`); } const signatureOffset = signatureSectionOffset + signerIndex * 64; const signatureEnd = signatureOffset + 64; if (signatureEnd > signedBytes.length) { throw new Error("Invalid Solana transaction wire format: signed transaction is missing signature bytes."); } const signature = new Uint8Array(signedBytes.slice(signatureOffset, signatureEnd)); results.push({ [signerAddressKey]: signature }); } return results; }, }; } //# sourceMappingURL=account-signers.js.map