@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
568 lines • 23.2 kB
JavaScript
"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