UNPKG

@coinbase/cdp-sdk

Version:

SDK for interacting with the Coinbase Developer Platform Wallet API

568 lines 23.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SolanaClient = void 0; const crypto_1 = require("crypto"); const bs58_1 = __importDefault(require("bs58")); const toSolanaAccount_js_1 = require("../../accounts/solana/toSolanaAccount.js"); const requestFaucet_js_1 = require("../../actions/solana/requestFaucet.js"); const sendTransaction_js_1 = require("../../actions/solana/sendTransaction.js"); const signMessage_js_1 = require("../../actions/solana/signMessage.js"); const signTransaction_js_1 = require("../../actions/solana/signTransaction.js"); const analytics_js_1 = require("../../analytics.js"); const constants_js_1 = require("../../constants.js"); const errors_js_1 = require("../../errors.js"); const errors_js_2 = require("../../openapi-client/errors.js"); const index_js_1 = require("../../openapi-client/index.js"); const export_js_1 = require("../../utils/export.js"); /** * The namespace containing all Solana methods. */ class SolanaClient { /** * Creates a new Solana account. * * @param {CreateAccountOptions} options - Parameters for creating the Solana account. * @param {string} [options.name] - The name of the account. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the newly created account. * * @example **Without arguments** * ```ts * const account = await cdp.solana.createAccount(); * ``` * * @example **With a name** * ```ts * const account = await cdp.solana.createAccount({ name: "MyAccount" }); * ``` * * @example **With an idempotency key** * ```ts * const idempotencyKey = uuidv4(); * * // First call * await cdp.solana.createAccount({ idempotencyKey }); * * // Second call with the same idempotency key will return the same account * await cdp.solana.createAccount({ idempotencyKey }); * ``` */ async createAccount(options = {}) { analytics_js_1.Analytics.trackAction({ action: "create_account", accountType: "solana", }); return this._createAccountInternal(options); } /** * Exports a CDP Solana account's private key. * It is important to store the private key in a secure place after it's exported. * * @param {ExportAccountOptions} options - Parameters for exporting the Solana account. * @param {string} [options.address] - The address of the account. * @param {string} [options.name] - The name of the account. * * @returns A promise that resolves to the exported account's full 64-byte private key as a base58 encoded string. * * @example **With an address** * ```ts * const privateKey = await cdp.solana.exportAccount({ * address: "1234567890123456789012345678901234567890", * }); * ``` * * @example **With a name** * ```ts * const privateKey = await cdp.solana.exportAccount({ * name: "MyAccount", * }); * ``` */ async exportAccount(options) { analytics_js_1.Analytics.trackAction({ action: "export_account", accountType: "solana", }); const { publicKey, privateKey } = await (0, export_js_1.generateExportEncryptionKeyPair)(); const { encryptedPrivateKey } = await (async () => { if (options.address) { return index_js_1.CdpOpenApiClient.exportSolanaAccount(options.address, { exportEncryptionKey: publicKey, }, options.idempotencyKey); } if (options.name) { return index_js_1.CdpOpenApiClient.exportSolanaAccountByName(options.name, { exportEncryptionKey: publicKey, }, options.idempotencyKey); } throw new errors_js_1.UserInputValidationError("Either address or name must be provided"); })(); const decryptedPrivateKey = (0, export_js_1.decryptWithPrivateKey)(privateKey, encryptedPrivateKey); return (0, export_js_1.formatSolanaPrivateKey)(decryptedPrivateKey); } /** * Imports a Solana account using a private key. * The private key will be encrypted before being stored securely. * * @param {ImportAccountOptions} options - Parameters for importing the Solana account. * @param {string} options.privateKey - The private key to import (32 or 64 bytes). Can be a base58 encoded string or raw bytes. * @param {string} [options.name] - The name of the account. * @param {string} [options.encryptionPublicKey] - The RSA public key for encrypting the private key. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the imported account. * * @example **Import with private key only** * ```ts * const account = await cdp.solana.importAccount({ * privateKey: "3Kzjw8qSxx8bQkV7EHrVFWYiPyNLbBVxtVe1Q5h2zKZY8DdcuT2dKxyz9kU5vQrP", * }); * ``` * * @example **Import with name** * ```ts * const account = await cdp.solana.importAccount({ * privateKey: "3Kzjw8qSxx8bQkV7EHrVFWYiPyNLbBVxtVe1Q5h2zKZY8DdcuT2dKxyz9kU5vQrP", * name: "ImportedAccount", * }); * ``` * * @example **Import with idempotency key** * ```ts * const idempotencyKey = uuidv4(); * * const account = await cdp.solana.importAccount({ * privateKey: "3Kzjw8qSxx8bQkV7EHrVFWYiPyNLbBVxtVe1Q5h2zKZY8DdcuT2dKxyz9kU5vQrP", * name: "ImportedAccount", * idempotencyKey, * }); * ``` */ async importAccount(options) { analytics_js_1.Analytics.trackAction({ action: "import_account", accountType: "solana", }); let privateKeyBytes = new Uint8Array(); if (typeof options.privateKey === "string") { privateKeyBytes = bs58_1.default.decode(options.privateKey); } else { privateKeyBytes = options.privateKey; } if (privateKeyBytes.length !== 32 && privateKeyBytes.length !== 64) { throw new errors_js_1.UserInputValidationError("Invalid private key length"); } if (privateKeyBytes.length === 64) { privateKeyBytes = privateKeyBytes.subarray(0, 32); } const encryptionPublicKey = options.encryptionPublicKey || constants_js_1.ImportAccountPublicRSAKey; const encryptedPrivateKey = (0, crypto_1.publicEncrypt)({ key: encryptionPublicKey, padding: crypto_1.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256", }, privateKeyBytes); const openApiAccount = await index_js_1.CdpOpenApiClient.importSolanaAccount({ encryptedPrivateKey: encryptedPrivateKey.toString("base64"), name: options.name, }, options.idempotencyKey); const account = (0, toSolanaAccount_js_1.toSolanaAccount)(index_js_1.CdpOpenApiClient, { account: openApiAccount, }); analytics_js_1.Analytics.wrapObjectMethodsWithErrorTracking(account); return account; } /** * Gets a Solana account by its address. * * @param {GetAccountOptions} options - Parameters for getting the Solana account. * Either `address` or `name` must be provided. * If both are provided, lookup will be done by `address` and `name` will be ignored. * @param {string} [options.address] - The address of the account. * @param {string} [options.name] - The name of the account. * * @returns A promise that resolves to the account. * * @example **Get an account by address** * ```ts * const account = await cdp.solana.getAccount({ * address: "1234567890123456789012345678901234567890", * }); * ``` * * @example **Get an account by name** * ```ts * const account = await cdp.solana.getAccount({ * name: "MyAccount", * }); * ``` */ async getAccount(options) { analytics_js_1.Analytics.trackAction({ action: "get_account", accountType: "solana", }); return this._getAccountInternal(options); } /** * Gets a Solana account by its address. * * @param {GetOrCreateAccountOptions} options - Parameters for getting or creating the Solana account. * @param {string} options.name - The name of the account. * * @returns A promise that resolves to the account. * * @example * ```ts * const account = await cdp.solana.getOrCreateAccount({ * name: "MyAccount", * }); * ``` */ async getOrCreateAccount(options) { analytics_js_1.Analytics.trackAction({ action: "get_or_create_account", accountType: "solana", }); try { const account = await this._getAccountInternal(options); return account; } catch (error) { // If it failed because the account doesn't exist, create it const doesAccountNotExist = error instanceof errors_js_2.APIError && error.statusCode === 404; if (doesAccountNotExist) { try { const account = await this._createAccountInternal(options); return account; } catch (error) { // If it failed because the account already exists, get the existing account const doesAccountAlreadyExist = error instanceof errors_js_2.APIError && error.statusCode === 409; if (doesAccountAlreadyExist) { const account = await this._getAccountInternal(options); return account; } throw error; } } throw error; } } /** * Lists all Solana accounts. * * @param {ListAccountsOptions} options - Parameters for listing the Solana accounts. * @param {number} [options.pageSize] - The number of accounts to return. * @param {string} [options.pageToken] - The page token to begin listing from. * This is obtained by previous calls to this method. * * @returns A promise that resolves to an array of Solana account instances. * * @example **Without arguments** * ```ts * const accounts = await cdp.solana.listAccounts(); * ``` * * @example **With pagination** * ```ts * let page = await cdp.solana.listAccounts(); * * while (page.nextPageToken) { * page = await cdp.solana.listAccounts({ pageToken: page.nextPageToken }); * } * * page.accounts.forEach(account => console.log(account)); * ``` * } * ``` */ async listAccounts(options = {}) { analytics_js_1.Analytics.trackAction({ action: "list_accounts", accountType: "solana", }); const solAccounts = await index_js_1.CdpOpenApiClient.listSolanaAccounts({ pageSize: options.pageSize, pageToken: options.pageToken, }); return { accounts: solAccounts.accounts.map(account => { const solanaAccount = (0, toSolanaAccount_js_1.toSolanaAccount)(index_js_1.CdpOpenApiClient, { account, }); analytics_js_1.Analytics.wrapObjectMethodsWithErrorTracking(solanaAccount); return solanaAccount; }), nextPageToken: solAccounts.nextPageToken, }; } /** * Requests funds from a Solana faucet. * * @param {RequestFaucetOptions} options - Parameters for requesting funds from the Solana faucet. * @param {string} options.address - The address to request funds for. * @param {string} options.token - The token to request funds for. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the transaction signature. * * @example * ```ts * const signature = await cdp.solana.requestFaucet({ * address: "1234567890123456789012345678901234567890", * token: "sol", * }); * ``` */ async requestFaucet(options) { analytics_js_1.Analytics.trackAction({ action: "request_faucet", accountType: "solana", }); return (0, requestFaucet_js_1.requestFaucet)(index_js_1.CdpOpenApiClient, options); } /** * Signs a message. * * @param {SignMessageOptions} options - Parameters for signing the message. * @param {string} options.address - The address to sign the message for. * @param {string} options.message - The message to sign. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the signature. * * @example * ```ts * // Create a Solana account * const account = await cdp.solana.createAccount(); * * // When you want to sign a message, you can do so by address * const signature = await cdp.solana.signMessage({ * address: account.address, * message: "Hello, world!", * }); * ``` */ async signMessage(options) { analytics_js_1.Analytics.trackAction({ action: "sign_message", accountType: "solana", }); return (0, signMessage_js_1.signMessage)(index_js_1.CdpOpenApiClient, options); } /** * Signs a transaction. * * @param {SignTransactionOptions} options - Parameters for signing the transaction. * @param {string} options.address - The address to sign the transaction for. * @param {string} options.transaction - The transaction to sign. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the signature. * * @example * ```ts * // Create a Solana account * const account = await cdp.solana.createAccount(); * * // Add your transaction instructions here * const transaction = new Transaction() * * // Make sure to set requireAllSignatures to false, since signing will be done through the API * const serializedTransaction = transaction.serialize({ * requireAllSignatures: false, * }); * * // Base64 encode the serialized transaction * const transaction = Buffer.from(serializedTransaction).toString("base64"); * * // When you want to sign a transaction, you can do so by address and base64 encoded transaction * const signature = await cdp.solana.signTransaction({ * address: account.address, * transaction, * }); * ``` */ async signTransaction(options) { analytics_js_1.Analytics.trackAction({ action: "sign_transaction", accountType: "solana", }); return (0, signTransaction_js_1.signTransaction)(index_js_1.CdpOpenApiClient, options); } /** * Updates a CDP Solana account. * * @param {UpdateSolanaAccountOptions} [options] - Optional parameters for creating the account. * @param {string} options.address - The address of the account to update * @param {UpdateSolanaAccountBody} options.update - An object containing account fields to update. * @param {string} [options.update.name] - The new name for the account. * @param {string} [options.update.accountPolicy] - The ID of a Policy to apply to the account. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the updated account. * * @example **With a name** * ```ts * const account = await cdp.sol.updateAccount({ address: "...", update: { name: "New Name" } }); * ``` * * @example **With an account policy** * ```ts * const account = await cdp.sol.updateAccount({ address: "...", update: { accountPolicy: "73bcaeeb-d7af-4615-b064-42b5fe83a31e" } }); * ``` * * @example **With an idempotency key** * ```ts * const idempotencyKey = uuidv4(); * * // First call * await cdp.sol.updateAccount({ * address: "0x...", * update: { accountPolicy: "73bcaeeb-d7af-4615-b064-42b5fe83a31e" }, * idempotencyKey, * }); * * // Second call with the same idempotency key will not update * await cdp.sol.updateAccount({ * address: '0x...', * update: { name: "" }, * idempotencyKey, * }); * ``` */ async updateAccount(options) { analytics_js_1.Analytics.trackAction({ action: "update_account", accountType: "solana", }); const openApiAccount = await index_js_1.CdpOpenApiClient.updateSolanaAccount(options.address, options.update, options.idempotencyKey); const account = (0, toSolanaAccount_js_1.toSolanaAccount)(index_js_1.CdpOpenApiClient, { account: openApiAccount, }); analytics_js_1.Analytics.wrapObjectMethodsWithErrorTracking(account); return account; } /** * Sends a Solana transaction using the Coinbase API. * * @param {SendTransactionOptions} options - Parameters for sending the Solana transaction. * @param {string} options.network - The network to send the transaction to. * @param {string} options.transaction - The base64 encoded transaction to send. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the transaction result. * * @example * ```ts * const signature = await cdp.solana.sendTransaction({ * network: "solana-devnet", * transaction: "...", * }); * ``` */ async sendTransaction(options) { analytics_js_1.Analytics.trackAction({ action: "send_transaction", accountType: "solana", properties: { network: options.network, }, }); return (0, sendTransaction_js_1.sendTransaction)(index_js_1.CdpOpenApiClient, options); } /** * Lists the token balances for a Solana account. * * @param {ListTokenBalancesOptions} options - Parameters for listing the Solana token balances. * @param {string} options.address - The address of the account to list token balances for. * @param {string} [options.network] - The network to list token balances for. Defaults to "solana". * @param {number} [options.pageSize] - The number of token balances to return. * @param {string} [options.pageToken] - The page token to begin listing from. * This is obtained by previous calls to this method. * * @returns A promise that resolves to an array of Solana token balance instances. * * @example * ```ts * const balances = await cdp.solana.listTokenBalances({ address: "...", network: "solana-devnet" }); * ``` */ async listTokenBalances(options) { analytics_js_1.Analytics.trackAction({ action: "list_token_balances", accountType: "solana", properties: { network: options.network, }, }); const tokenBalances = await index_js_1.CdpOpenApiClient.listSolanaTokenBalances(options.network || "solana", options.address, { pageSize: options.pageSize, pageToken: options.pageToken, }); return { balances: tokenBalances.balances.map(balance => { return { amount: { amount: BigInt(balance.amount.amount), decimals: balance.amount.decimals, }, token: { mintAddress: balance.token.mintAddress, name: balance.token.name, symbol: balance.token.symbol, }, }; }), nextPageToken: tokenBalances.nextPageToken, }; } /** * Internal method to create a Solana account without tracking analytics. * Used internally by composite operations to avoid double-counting. * * @param {CreateAccountOptions} options - Parameters for creating the account. * @returns {Promise<SolanaAccount>} A promise that resolves to the newly created account. */ async _createAccountInternal(options = {}) { const openApiAccount = await index_js_1.CdpOpenApiClient.createSolanaAccount({ name: options.name, accountPolicy: options.accountPolicy, }, options.idempotencyKey); const account = (0, toSolanaAccount_js_1.toSolanaAccount)(index_js_1.CdpOpenApiClient, { account: openApiAccount, }); analytics_js_1.Analytics.wrapObjectMethodsWithErrorTracking(account); return account; } /** * Internal method to get a Solana account without tracking analytics. * Used internally by composite operations to avoid double-counting. * * @param {GetAccountOptions} options - Parameters for getting the account. * @returns {Promise<SolanaAccount>} A promise that resolves to the account. */ async _getAccountInternal(options) { const openApiAccount = await (() => { if (options.address) { return index_js_1.CdpOpenApiClient.getSolanaAccount(options.address); } if (options.name) { return index_js_1.CdpOpenApiClient.getSolanaAccountByName(options.name); } throw new errors_js_1.UserInputValidationError("Either address or name must be provided"); })(); const account = (0, toSolanaAccount_js_1.toSolanaAccount)(index_js_1.CdpOpenApiClient, { account: openApiAccount, }); analytics_js_1.Analytics.wrapObjectMethodsWithErrorTracking(account); return account; } } exports.SolanaClient = SolanaClient; //# sourceMappingURL=solana.js.map