@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
282 lines (255 loc) • 9.34 kB
text/typescript
import { WaitForTransactionReceiptParameters } from "viem";
import { base, baseSepolia, mainnet, sepolia } from "viem/chains";
import { mapChainToNetwork } from "./chainToNetworkMapper.js";
import { isMethodSupportedOnNetwork } from "./networkCapabilities.js";
import { resolveViemClients } from "./resolveViemClients.js";
import { transferWithViem } from "../../actions/evm/transfer/transferWithViem.js";
import { Analytics } from "../../analytics.js";
import type { EvmServerAccount, NetworkScopedEvmServerAccount } from "./types.js";
import type { EvmFundOptions } from "../../actions/evm/fund/fund.js";
import type { EvmQuoteFundOptions } from "../../actions/evm/fund/quoteFund.js";
import type { ListTokenBalancesOptions } from "../../actions/evm/listTokenBalances.js";
import type { RequestFaucetOptions } from "../../actions/evm/requestFaucet.js";
import type {
SendTransactionOptions,
TransactionResult,
} from "../../actions/evm/sendTransaction.js";
import type { UseSpendPermissionOptions } from "../../actions/evm/spend-permissions/types.js";
import type { AccountQuoteSwapOptions, AccountSwapOptions } from "../../actions/evm/swap/types.js";
import type { TransferOptions } from "../../actions/evm/transfer/types.js";
import type { WaitForFundOperationOptions } from "../../actions/waitForFundOperationReceipt.js";
import type {
ListEvmTokenBalancesNetwork,
SendEvmTransactionBodyNetwork,
SpendPermissionNetwork,
} from "../../openapi-client/index.js";
import type { Address, TransactionRequestEIP1559 } from "../../types/misc.js";
/**
* Options for converting a pre-existing EvmAccount to a NetworkScopedEvmServerAccount.
*/
export type ToNetworkScopedEvmServerAccountOptions = {
/** The EvmAccount that was previously created. */
account: EvmServerAccount;
/** The network to scope the account to. */
network: string;
};
/**
* Creates a Network-scoped Server-managed EvmAccount instance from an existing EvmAccount.
* Use this to interact with previously deployed EvmAccounts on a specific network.
*
* @param {ToNetworkScopedEvmServerAccountOptions} options - Configuration options.
* @param {EvmServerAccount} options.account - The EvmServerAccount that was previously created.
* @param {string} options.network - The network to scope the account to.
* @returns {NetworkScopedEvmServerAccount} A configured NetworkScopedEvmServerAccount instance ready for signing.
*/
export async function toNetworkScopedEvmServerAccount<Network extends string>(
options: ToNetworkScopedEvmServerAccountOptions & { network: Network },
): Promise<NetworkScopedEvmServerAccount<Network>> {
const { publicClient, walletClient, chain } = await resolveViemClients({
networkOrNodeUrl: options.network,
account: options.account,
});
/*
* Determine the actual network name from the resolved chain
* This handles cases where options.network is an RPC URL
*/
const resolvedNetworkName = mapChainToNetwork(chain) ?? options.network;
const shouldUseApiForSends =
chain.id === base.id ||
chain.id === baseSepolia.id ||
chain.id === mainnet.id ||
chain.id === sepolia.id;
const account = {
address: options.account.address as Address,
network: options.network,
signMessage: options.account.signMessage,
sign: options.account.sign,
signTransaction: options.account.signTransaction,
signTypedData: options.account.signTypedData,
name: options.account.name,
type: "evm-server",
policies: options.account.policies,
sendTransaction: async (txOpts: Omit<SendTransactionOptions, "address" | "network">) => {
if (shouldUseApiForSends) {
return options.account.sendTransaction({
...txOpts,
network: mapChainToNetwork(chain) as SendEvmTransactionBodyNetwork,
});
} else {
Analytics.trackAction({
action: "send_transaction",
accountType: "evm_server",
properties: {
network: options.network,
managed: true,
},
});
const hash = await walletClient.sendTransaction(
txOpts.transaction as TransactionRequestEIP1559,
);
return { transactionHash: hash };
}
},
transfer: async (transferArgs: Omit<TransferOptions, "address" | "network">) => {
if (shouldUseApiForSends) {
return options.account.transfer({
...transferArgs,
network: mapChainToNetwork(chain) as SendEvmTransactionBodyNetwork,
});
} else {
Analytics.trackAction({
action: "transfer",
accountType: "evm_server",
properties: {
network: options.network,
managed: true,
},
});
return transferWithViem(walletClient, account, transferArgs);
}
},
waitForTransactionReceipt: async (
waitOptions: WaitForTransactionReceiptParameters | TransactionResult,
) => {
Analytics.trackAction({
action: "wait_for_transaction_receipt",
accountType: "evm_server",
properties: {
managed: true,
},
});
if ("transactionHash" in waitOptions) {
return publicClient.waitForTransactionReceipt({
hash: waitOptions.transactionHash,
});
}
return publicClient.waitForTransactionReceipt(waitOptions);
},
} as NetworkScopedEvmServerAccount<Network>;
if (isMethodSupportedOnNetwork("listTokenBalances", resolvedNetworkName)) {
Object.assign(account, {
listTokenBalances: async (
listTokenBalancesOptions: Omit<ListTokenBalancesOptions, "address" | "network">,
) => {
Analytics.trackAction({
action: "list_token_balances",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.listTokenBalances({
...listTokenBalancesOptions,
network: options.network as ListEvmTokenBalancesNetwork,
});
},
});
}
if (isMethodSupportedOnNetwork("requestFaucet", resolvedNetworkName)) {
Object.assign(account, {
requestFaucet: async (faucetOptions: Omit<RequestFaucetOptions, "address" | "network">) => {
Analytics.trackAction({
action: "request_faucet",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.requestFaucet({
...faucetOptions,
network: chain.id === baseSepolia.id ? "base-sepolia" : "ethereum-sepolia",
});
},
});
}
if (isMethodSupportedOnNetwork("quoteFund", resolvedNetworkName)) {
Object.assign(account, {
quoteFund: async (quoteFundOptions: Omit<EvmQuoteFundOptions, "address">) => {
Analytics.trackAction({
action: "quote_fund",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.quoteFund({
...quoteFundOptions,
});
},
});
}
if (isMethodSupportedOnNetwork("fund", resolvedNetworkName)) {
Object.assign(account, {
fund: async (fundOptions: Omit<EvmFundOptions, "address">) => {
Analytics.trackAction({
action: "fund",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.fund({
...fundOptions,
});
},
waitForFundOperationReceipt: async (waitOptions: WaitForFundOperationOptions) => {
Analytics.trackAction({
action: "wait_for_fund_operation_receipt",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.waitForFundOperationReceipt(waitOptions);
},
});
}
if (isMethodSupportedOnNetwork("quoteSwap", resolvedNetworkName)) {
Object.assign(account, {
quoteSwap: async (quoteSwapOptions: AccountQuoteSwapOptions) => {
Analytics.trackAction({
action: "quote_swap",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.quoteSwap(quoteSwapOptions);
},
});
}
if (isMethodSupportedOnNetwork("swap", resolvedNetworkName)) {
Object.assign(account, {
swap: async (swapOptions: AccountSwapOptions) => {
Analytics.trackAction({
action: "swap",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.swap(swapOptions);
},
});
}
if (isMethodSupportedOnNetwork("useSpendPermission", resolvedNetworkName)) {
Object.assign(account, {
useSpendPermission: async (
spendPermissionOptions: Omit<UseSpendPermissionOptions, "network">,
) => {
Analytics.trackAction({
action: "use_spend_permission",
accountType: "evm_server",
properties: {
managed: true,
},
});
return options.account.useSpendPermission({
...spendPermissionOptions,
network: options.network as SpendPermissionNetwork,
});
},
});
}
return account;
}