@coinbase/cdp-sdk
Version:
SDK for interacting with the Coinbase Developer Platform Wallet API
302 lines (290 loc) • 9.35 kB
text/typescript
import {
type TransactionSerializable,
type TypedDataDefinition,
type TypedData,
type HashTypedDataParameters,
getTypesForEIP712Domain,
serializeTransaction,
hashMessage,
} from "viem";
import { toNetworkScopedEvmServerAccount } from "./toNetworkScopedEvmServerAccount.js";
import {
listTokenBalances,
type ListTokenBalancesResult,
type ListTokenBalancesOptions,
} from "../../actions/evm/listTokenBalances.js";
import {
requestFaucet,
type RequestFaucetOptions,
type RequestFaucetResult,
} from "../../actions/evm/requestFaucet.js";
import { sendTransaction } from "../../actions/evm/sendTransaction.js";
import { useSpendPermission } from "../../actions/evm/spend-permissions/account.use.js";
import { UseSpendPermissionOptions } from "../../actions/evm/spend-permissions/types.js";
import { createSwapQuote } from "../../actions/evm/swap/createSwapQuote.js";
import { sendSwapTransaction } from "../../actions/evm/swap/sendSwapTransaction.js";
import { accountTransferStrategy } from "../../actions/evm/transfer/accountTransferStrategy.js";
import { transfer } from "../../actions/evm/transfer/transfer.js";
import { Analytics } from "../../analytics.js";
import type { EvmServerAccount, NetworkOrRpcUrl } from "./types.js";
import type {
SendTransactionOptions,
TransactionResult,
} from "../../actions/evm/sendTransaction.js";
import type {
AccountSwapOptions,
AccountSwapResult,
AccountQuoteSwapOptions,
AccountQuoteSwapResult,
} from "../../actions/evm/swap/types.js";
import type { CdpOpenApiClientType, EvmAccount } from "../../openapi-client/index.js";
import type { Address, EIP712Domain, Hash, Hex } from "../../types/misc.js";
/**
* Options for converting a pre-existing EvmAccount to a EvmServerAccount.
*/
export type ToEvmServerAccountOptions = {
/** The EvmAccount that was previously created. */
account: EvmAccount;
};
/**
* Creates a Server-managed EvmAccount instance from an existing EvmAccount.
* Use this to interact with previously deployed EvmAccounts, rather than creating new ones.
*
* @param {CdpOpenApiClientType} apiClient - The API client.
* @param {ToEvmServerAccountOptions} options - Configuration options.
* @param {EvmAccount} options.account - The EvmAccount that was previously created.
* @returns {EvmServerAccount} A configured EvmAccount instance ready for signing.
*/
export function toEvmServerAccount(
apiClient: CdpOpenApiClientType,
options: ToEvmServerAccountOptions,
): EvmServerAccount {
const account: EvmServerAccount = {
address: options.account.address as Address,
async signMessage({ message }) {
Analytics.trackAction({
action: "sign_message",
accountType: "evm_server",
});
try {
if (typeof message === "string") {
const result = await apiClient.signEvmMessage(options.account.address, {
message,
});
return result.signature as Hex;
}
const result = await apiClient.signEvmHash(options.account.address, {
hash: hashMessage(message),
});
return result.signature as Hex;
} catch (error) {
Analytics.trackError(error, "signMessage");
throw error;
}
},
async sign(parameters: { hash: Hash }) {
Analytics.trackAction({
action: "sign",
accountType: "evm_server",
});
try {
const result = await apiClient.signEvmHash(options.account.address, {
hash: parameters.hash,
});
return result.signature as Hex;
} catch (error) {
Analytics.trackError(error, "sign");
throw error;
}
},
async signTransaction(transaction: TransactionSerializable) {
Analytics.trackAction({
action: "sign_transaction",
accountType: "evm_server",
});
try {
const result = await apiClient.signEvmTransaction(options.account.address, {
transaction: serializeTransaction(transaction),
});
return result.signedTransaction as Hex;
} catch (error) {
Analytics.trackError(error, "signTransaction");
throw error;
}
},
async signTypedData<
const typedData extends TypedData | Record<string, unknown>,
primaryType extends keyof typedData | "EIP712Domain" = keyof typedData,
>(parameters: TypedDataDefinition<typedData, primaryType>) {
Analytics.trackAction({
action: "sign_typed_data",
accountType: "evm_server",
});
try {
const { domain = {}, message, primaryType } = parameters as HashTypedDataParameters;
const types = {
EIP712Domain: getTypesForEIP712Domain({ domain }),
...parameters.types,
};
const openApiMessage = {
domain: domain as EIP712Domain,
types,
primaryType,
message,
};
const result = await apiClient.signEvmTypedData(options.account.address, openApiMessage);
return result.signature as Hex;
} catch (error) {
Analytics.trackError(error, "signTypedData");
throw error;
}
},
async transfer(transferArgs): Promise<TransactionResult> {
Analytics.trackAction({
action: "transfer",
accountType: "evm_server",
properties: {
network: transferArgs.network,
},
});
try {
return transfer(apiClient, account, transferArgs, accountTransferStrategy);
} catch (error) {
Analytics.trackError(error, "transfer");
throw error;
}
},
async listTokenBalances(
options: Omit<ListTokenBalancesOptions, "address">,
): Promise<ListTokenBalancesResult> {
Analytics.trackAction({
action: "list_token_balances",
accountType: "evm_server",
properties: {
network: options.network,
},
});
try {
return listTokenBalances(apiClient, {
...options,
address: this.address,
});
} catch (error) {
Analytics.trackError(error, "listTokenBalances");
throw error;
}
},
async requestFaucet(
options: Omit<RequestFaucetOptions, "address">,
): Promise<RequestFaucetResult> {
Analytics.trackAction({
action: "request_faucet",
accountType: "evm_server",
properties: {
network: options.network,
},
});
try {
return requestFaucet(apiClient, {
...options,
address: this.address,
});
} catch (error) {
Analytics.trackError(error, "requestFaucet");
throw error;
}
},
async sendTransaction(options: Omit<SendTransactionOptions, "address">) {
Analytics.trackAction({
action: "send_transaction",
accountType: "evm_server",
properties: {
network: options.network,
},
});
try {
return sendTransaction(apiClient, {
...options,
address: this.address,
});
} catch (error) {
Analytics.trackError(error, "sendTransaction");
throw error;
}
},
async quoteSwap(options: AccountQuoteSwapOptions): Promise<AccountQuoteSwapResult> {
Analytics.trackAction({
action: "quote_swap",
accountType: "evm_server",
properties: {
network: options.network,
},
});
try {
return createSwapQuote(apiClient, {
...options,
taker: this.address,
});
} catch (error) {
Analytics.trackError(error, "quoteSwap");
throw error;
}
},
async swap(options: AccountSwapOptions): Promise<AccountSwapResult> {
Analytics.trackAction({
action: "swap",
accountType: "evm_server",
properties: {
network: "network" in options ? options.network : undefined,
},
});
try {
return sendSwapTransaction(apiClient, {
...options,
address: this.address,
taker: this.address, // Always use account's address as taker
});
} catch (error) {
Analytics.trackError(error, "swap");
throw error;
}
},
async useSpendPermission(options: UseSpendPermissionOptions): Promise<TransactionResult> {
Analytics.trackAction({
action: "use_spend_permission",
accountType: "evm_server",
properties: {
network: options.network,
},
});
try {
return useSpendPermission(apiClient, this.address, options);
} catch (error) {
Analytics.trackError(error, "useSpendPermission");
throw error;
}
},
name: options.account.name,
type: "evm-server",
policies: options.account.policies,
useNetwork: async <Network extends NetworkOrRpcUrl>(networkOrRpcUrl: Network) => {
Analytics.trackAction({
action: "use_network",
accountType: "evm_server",
properties: {
network: networkOrRpcUrl,
},
});
try {
return toNetworkScopedEvmServerAccount({
account,
network: networkOrRpcUrl,
});
} catch (error) {
Analytics.trackError(error, "useNetwork");
throw error;
}
},
};
return account;
}