diff --git a/examples/typescript/evm/smart-accounts/multiOwner.ts b/examples/typescript/evm/smart-accounts/multiOwner.ts new file mode 100644 index 000000000..5dc391480 --- /dev/null +++ b/examples/typescript/evm/smart-accounts/multiOwner.ts @@ -0,0 +1,55 @@ +// Usage: pnpm tsx evm/smart-accounts/multiOwner.ts + +import { CdpClient } from "@coinbase/cdp-sdk"; +import "dotenv/config"; + +const cdp = new CdpClient(); + +const cdpOwner1 = await cdp.evm.getOrCreateAccount({ name: "Owner-1" }); +const cdpOwner2 = await cdp.evm.getOrCreateAccount({ name: "Owner-2" }); + +const smartAccount = await ( + await cdp.evm.getOrCreateSmartAccount({ + name: "MultiOwner", + owners: [cdpOwner1, cdpOwner2], + enableSpendPermissions: true, + }) +).useNetwork("base-sepolia"); + +console.log("Smart account address:", smartAccount.address); +console.log("Smart account owners:", smartAccount.owners); + +const { userOpHash } = await smartAccount.sendUserOperation({ + calls: [ + { + to: smartAccount.address, + value: 0n, + }, + ], +}); + +console.log("User operation hash with default owner:", userOpHash); + +const userOpResult = await smartAccount.waitForUserOperation({ + userOpHash, +}); + +console.log("User operation result:", userOpResult); + +const { userOpHash: userOpHash2 } = await smartAccount.sendUserOperation({ + calls: [ + { + to: smartAccount.address, + value: 0n, + }, + ], + signer: cdpOwner2, +}); + +console.log("User operation hash with second owner:", userOpHash2); + +const userOpResult2 = await smartAccount.waitForUserOperation({ + userOpHash: userOpHash2, +}); + +console.log("User operation result with second owner:", userOpResult2); diff --git a/typescript/src/accounts/evm/toEvmSmartAccount.ts b/typescript/src/accounts/evm/toEvmSmartAccount.ts index a56c8f549..68cfc3520 100644 --- a/typescript/src/accounts/evm/toEvmSmartAccount.ts +++ b/typescript/src/accounts/evm/toEvmSmartAccount.ts @@ -43,6 +43,7 @@ import { SignTypedDataOptions, UserOperation, } from "../../client/evm/evm.types.js"; +import { UserInputValidationError } from "../../errors.js"; import { type CdpOpenApiClientType, type EvmSmartAccount as EvmSmartAccountModel, @@ -58,34 +59,44 @@ import type { import type { Address, Hex } from "../../types/misc.js"; /** - * Options for converting a pre-existing EvmSmartAccount and owner to a EvmSmartAccount + * Options for converting a pre-existing EvmSmartAccount and owner(s) to a EvmSmartAccount */ export type ToEvmSmartAccountOptions = { /** The pre-existing EvmSmartAccount. */ smartAccount: EvmSmartAccountModel; - /** The owner of the smart account. */ - owner: EvmAccount; + /** The owner of the smart account (for backwards compatibility). */ + owner?: EvmAccount; + /** The owners of the smart account. If provided, takes precedence over `owner`. */ + owners?: EvmAccount[]; }; /** - * Creates a EvmSmartAccount instance from an existing EvmSmartAccount and owner. + * Creates a EvmSmartAccount instance from an existing EvmSmartAccount and owner(s). * Use this to interact with previously deployed EvmSmartAccounts, rather than creating new ones. * - * The owner must be the original owner of the evm smart account. + * The owner(s) must be among the original owners of the evm smart account. * * @param {CdpOpenApiClientType} apiClient - The API client. * @param {ToEvmSmartAccountOptions} options - Configuration options. * @param {EvmSmartAccount} options.smartAccount - The deployed evm smart account. - * @param {EvmAccount} options.owner - The owner which signs for the smart account. + * @param {EvmAccount} [options.owner] - The owner which signs for the smart account (for backwards compatibility). + * @param {EvmAccount[]} [options.owners] - The owners which can sign for the smart account. Takes precedence over `owner`. * @returns {EvmSmartAccount} A configured EvmSmartAccount instance ready for user operation submission. */ export function toEvmSmartAccount( apiClient: CdpOpenApiClientType, options: ToEvmSmartAccountOptions, ): EvmSmartAccount { + // Handle backwards compatibility: if owners is provided, use it; otherwise fall back to owner + const accountOwners = options.owners || (options.owner ? [options.owner] : []); + + if (accountOwners.length === 0) { + throw new UserInputValidationError("At least one owner must be provided"); + } + const account: EvmSmartAccount = { address: options.smartAccount.address as Address, - owners: [options.owner], + owners: accountOwners, policies: options.smartAccount.policies, async transfer(transferArgs): Promise { Analytics.trackAction({ @@ -287,7 +298,7 @@ export function toEvmSmartAccount( return toNetworkScopedEvmSmartAccount(apiClient, { smartAccount: account, - owner: options.owner, + owners: accountOwners, network, }); }, diff --git a/typescript/src/accounts/evm/toNetworkScopedEvmSmartAccount.ts b/typescript/src/accounts/evm/toNetworkScopedEvmSmartAccount.ts index 5c0a7a27a..01091074a 100644 --- a/typescript/src/accounts/evm/toNetworkScopedEvmSmartAccount.ts +++ b/typescript/src/accounts/evm/toNetworkScopedEvmSmartAccount.ts @@ -44,27 +44,30 @@ import type { } from "../../openapi-client/index.js"; /** - * Options for converting a pre-existing EvmSmartAccount and owner to a NetworkScopedEvmSmartAccount + * Options for converting a pre-existing EvmSmartAccount and owner(s) to a NetworkScopedEvmSmartAccount */ export type ToNetworkScopedEvmSmartAccountOptions = { /** The pre-existing EvmSmartAccount. */ smartAccount: EvmSmartAccount; /** The network to scope the smart account object to. */ network: KnownEvmNetworks; - /** The owner of the smart account. */ - owner: EvmAccount; + /** The owner of the smart account (for backwards compatibility). */ + owner?: EvmAccount; + /** The owners of the smart account. If provided, takes precedence over `owner`. */ + owners?: EvmAccount[]; }; /** - * Creates a NetworkScopedEvmSmartAccount instance from an existing EvmSmartAccount and owner. + * Creates a NetworkScopedEvmSmartAccount instance from an existing EvmSmartAccount and owner(s). * Use this to interact with previously deployed EvmSmartAccounts, rather than creating new ones. * - * The owner must be the original owner of the evm smart account. + * The owner(s) must be among the original owners of the evm smart account. * * @param {CdpOpenApiClientType} apiClient - The API client. * @param {ToNetworkScopedEvmSmartAccountOptions} options - Configuration options. * @param {EvmSmartAccount} options.smartAccount - The deployed evm smart account. - * @param {EvmAccount} options.owner - The owner which signs for the smart account. + * @param {EvmAccount} [options.owner] - The owner which signs for the smart account (for backwards compatibility). + * @param {EvmAccount[]} [options.owners] - The owners which can sign for the smart account. Takes precedence over `owner`. * @param {KnownEvmNetworks} options.network - The network to scope the smart account to. * @returns {NetworkScopedEvmSmartAccount} A configured NetworkScopedEvmSmartAccount instance ready for user operation submission. */ @@ -72,6 +75,12 @@ export async function toNetworkScopedEvmSmartAccount> { + // Handle backwards compatibility: if owners is provided, use it; otherwise fall back to owner + const accountOwners = options.owners || (options.owner ? [options.owner] : []); + + if (accountOwners.length === 0) { + throw new Error("At least one owner must be provided"); + } const paymasterUrl = await (async () => { if (options.network === "base") { return getBaseNodeRpcUrl(options.network); @@ -82,7 +91,7 @@ export async function toNetworkScopedEvmSmartAccount = { paymasterUrl?: string; /** The idempotency key. */ idempotencyKey?: string; + /** Optional signer to use. If not provided, defaults to the first owner of the smart account. */ + signer?: EvmSmartAccount["owners"][0]; }; /** @@ -148,9 +150,9 @@ export async function sendUserOperation( paymasterUrl, }); - const owner = options.smartAccount.owners[0]; + const signer = options.signer || options.smartAccount.owners[0]; - const signature = await owner.sign({ + const signature = await signer.sign({ hash: createOpResponse.userOpHash as Hex, }); diff --git a/typescript/src/actions/evm/spend-permissions/smartAccount.use.ts b/typescript/src/actions/evm/spend-permissions/smartAccount.use.ts index 34069e8e5..c72b9b164 100644 --- a/typescript/src/actions/evm/spend-permissions/smartAccount.use.ts +++ b/typescript/src/actions/evm/spend-permissions/smartAccount.use.ts @@ -27,7 +27,7 @@ export function useSpendPermission( account: EvmSmartAccount, options: UseSpendPermissionOptions, ): Promise { - const { spendPermission, value, network } = options; + const { spendPermission, value, network, signer } = options; const data = encodeFunctionData({ abi: SPEND_PERMISSION_MANAGER_ABI, @@ -38,6 +38,7 @@ export function useSpendPermission( return sendUserOperation(apiClient, { smartAccount: account, network: network as EvmUserOperationNetwork, + signer, calls: [ { to: SPEND_PERMISSION_MANAGER_ADDRESS, diff --git a/typescript/src/actions/evm/spend-permissions/types.ts b/typescript/src/actions/evm/spend-permissions/types.ts index 5f50c63fc..2b93557f3 100644 --- a/typescript/src/actions/evm/spend-permissions/types.ts +++ b/typescript/src/actions/evm/spend-permissions/types.ts @@ -1,3 +1,4 @@ +import type { EvmSmartAccount } from "../../../accounts/evm/types.js"; import type { SpendPermissionNetwork } from "../../../openapi-client/index.js"; import type { SpendPermission } from "../../../spend-permissions/types.js"; @@ -11,4 +12,6 @@ export type UseSpendPermissionOptions = { value: bigint; /** The network to execute the transaction on */ network: SpendPermissionNetwork; + /** The owner to use for signing the transaction */ + signer?: EvmSmartAccount["owners"][0]; }; diff --git a/typescript/src/actions/evm/transfer/smartAccountTransferStrategy.ts b/typescript/src/actions/evm/transfer/smartAccountTransferStrategy.ts index bdfde5703..e5a585879 100644 --- a/typescript/src/actions/evm/transfer/smartAccountTransferStrategy.ts +++ b/typescript/src/actions/evm/transfer/smartAccountTransferStrategy.ts @@ -8,13 +8,14 @@ import type { EvmSmartAccount } from "../../../accounts/evm/types.js"; import type { EvmUserOperationNetwork } from "../../../openapi-client/index.js"; export const smartAccountTransferStrategy: TransferExecutionStrategy = { - executeTransfer: async ({ apiClient, from, to, value, token, network, paymasterUrl }) => { + executeTransfer: async ({ apiClient, from, to, value, token, network, paymasterUrl, signer }) => { const smartAccountNetwork = network as EvmUserOperationNetwork; if (token === "eth") { const result = await sendUserOperation(apiClient, { smartAccount: from, paymasterUrl, + signer, network: smartAccountNetwork, calls: [ { @@ -31,6 +32,7 @@ export const smartAccountTransferStrategy: TransferExecutionStrategy; } diff --git a/typescript/src/client/evm/evm.test.ts b/typescript/src/client/evm/evm.test.ts index c5e12627b..ed47ef556 100644 --- a/typescript/src/client/evm/evm.test.ts +++ b/typescript/src/client/evm/evm.test.ts @@ -282,7 +282,7 @@ describe("EvmClient", () => { ); expect(toEvmSmartAccount).toHaveBeenCalledWith(CdpOpenApiClient, { smartAccount: openApiEvmSmartAccount, - owner, + owners: [owner], }); expect(result).toBe(smartAccount); }); @@ -342,10 +342,88 @@ describe("EvmClient", () => { ); expect(toEvmSmartAccount).toHaveBeenCalledWith(CdpOpenApiClient, { smartAccount: openApiEvmSmartAccount, - owner, + owners: [owner], }); expect(result).toBe(smartAccount); }); + + it("should create a smart account with multiple owners", async () => { + const owner1: EvmAccount = { + address: "0x789", + sign: vi.fn().mockResolvedValue("0xsignature"), + signMessage: vi.fn().mockResolvedValue("0xsignature"), + signTransaction: vi.fn().mockResolvedValue("0xsignature"), + signTypedData: vi.fn().mockResolvedValue("0xsignature"), + }; + + const owner2: EvmAccount = { + address: "0xabc", + sign: vi.fn().mockResolvedValue("0xsignature"), + signMessage: vi.fn().mockResolvedValue("0xsignature"), + signTransaction: vi.fn().mockResolvedValue("0xsignature"), + signTypedData: vi.fn().mockResolvedValue("0xsignature"), + }; + + const name = "test-multi-owner-smart-account"; + const createOptions = { + owners: [owner1, owner2], + name, + }; + + const openApiEvmSmartAccount: OpenApiEvmSmartAccount = { + address: "0xdef", + owners: [owner1.address, owner2.address], + }; + + const smartAccount: EvmSmartAccount = { + address: "0xdef" as Hex, + owners: [owner1, owner2], + type: "evm-smart", + name, + transfer: vi.fn(), + listTokenBalances: vi.fn(), + sendUserOperation: vi.fn(), + waitForUserOperation: vi.fn(), + getUserOperation: vi.fn(), + requestFaucet: vi.fn(), + quoteFund: vi.fn(), + fund: vi.fn(), + waitForFundOperationReceipt: vi.fn(), + }; + + const createEvmSmartAccountMock = CdpOpenApiClient.createEvmSmartAccount as MockedFunction< + typeof CdpOpenApiClient.createEvmSmartAccount + >; + createEvmSmartAccountMock.mockResolvedValue(openApiEvmSmartAccount); + + const toEvmSmartAccountMock = toEvmSmartAccount as MockedFunction; + toEvmSmartAccountMock.mockReturnValue(smartAccount); + + const result = await client.createSmartAccount(createOptions); + + expect(CdpOpenApiClient.createEvmSmartAccount).toHaveBeenCalledWith( + { + owners: [owner1.address, owner2.address], + name, + }, + undefined, + ); + expect(toEvmSmartAccount).toHaveBeenCalledWith(CdpOpenApiClient, { + smartAccount: openApiEvmSmartAccount, + owners: [owner1, owner2], + }); + expect(result).toBe(smartAccount); + }); + + it("should throw an error when no owners are provided", async () => { + const createOptions = { + name: "test-smart-account", + }; + + await expect(client.createSmartAccount(createOptions)).rejects.toThrow( + "At least one owner must be provided", + ); + }); }); describe("createSpendPermission", () => { @@ -538,7 +616,7 @@ describe("EvmClient", () => { expect(CdpOpenApiClient.getEvmSmartAccount).toHaveBeenCalledWith(getOptions.address); expect(toEvmSmartAccountMock).toHaveBeenCalledWith(CdpOpenApiClient, { smartAccount: openApiEvmSmartAccount, - owner, + owners: [owner], }); expect(result).toBe(smartAccount); }); @@ -711,11 +789,11 @@ describe("EvmClient", () => { await expect(client.getOrCreateSmartAccount(getOrCreateOptions)).rejects .toThrowErrorMatchingInlineSnapshot(` - [UserInputValidationError: Owner mismatch: The provided owner address is not an owner of the smart account. Please use a valid owner for this smart account. + [UserInputValidationError: Owner mismatch: The provided owner address "0xowner2" is not an owner of the smart account. Please use valid owners for this smart account. Smart Account Address: 0x456 Smart Account Owners: 0xowner1 - Provided Owner Address: 0xowner2 + Provided Owner Addresses: 0xowner2 ] `); }); @@ -1051,6 +1129,79 @@ describe("EvmClient", () => { userOpHash, }); }); + + it("should send a user operation with custom signer", async () => { + const owner1: EvmAccount = { + address: "0x789", + sign: vi.fn().mockResolvedValue("0xsignature1"), + signMessage: vi.fn().mockResolvedValue("0xsignature1"), + signTransaction: vi.fn().mockResolvedValue("0xsignature1"), + signTypedData: vi.fn().mockResolvedValue("0xsignature1"), + }; + + const owner2: EvmAccount = { + address: "0xabc", + sign: vi.fn().mockResolvedValue("0xsignature2"), + signMessage: vi.fn().mockResolvedValue("0xsignature2"), + signTransaction: vi.fn().mockResolvedValue("0xsignature2"), + signTypedData: vi.fn().mockResolvedValue("0xsignature2"), + }; + + const smartAccount: EvmSmartAccount = { + address: "0xdef", + owners: [owner1, owner2], + type: "evm-smart", + transfer: vi.fn(), + listTokenBalances: vi.fn(), + sendUserOperation: vi.fn(), + waitForUserOperation: vi.fn(), + getUserOperation: vi.fn(), + requestFaucet: vi.fn(), + quoteFund: vi.fn(), + fund: vi.fn(), + waitForFundOperationReceipt: vi.fn(), + }; + + const userOpHash = "0xhash"; + const network: EvmUserOperationNetwork = "base-sepolia"; + const calls: EvmCall[] = [ + { + to: "0x123", + value: BigInt(0), + data: "0x", + }, + ]; + const paymasterUrl = "https://paymaster.url"; + const sendOptions = { + smartAccount, + network, + calls, + paymasterUrl, + signer: owner2, // Use the second owner as signer + }; + + const sendUserOperationMock = sendUserOperation as MockedFunction; + sendUserOperationMock.mockResolvedValue({ + smartAccountAddress: smartAccount.address, + status: "broadcast", + userOpHash, + }); + + const result = await client.sendUserOperation(sendOptions); + + expect(sendUserOperation).toHaveBeenCalledWith(CdpOpenApiClient, { + smartAccount, + network, + calls, + paymasterUrl, + signer: owner2, + }); + expect(result).toEqual({ + smartAccountAddress: smartAccount.address, + status: "broadcast", + userOpHash, + }); + }); }); describe("signHash", () => { @@ -1467,7 +1618,7 @@ describe("EvmClient", () => { ); expect(toEvmSmartAccount).toHaveBeenCalledWith(CdpOpenApiClient, { smartAccount: updatedSmartAccount, - owner, + owners: [owner], }); expect(result).toBe(smartAccount); }); @@ -1531,7 +1682,7 @@ describe("EvmClient", () => { ); expect(toEvmSmartAccount).toHaveBeenCalledWith(CdpOpenApiClient, { smartAccount: updatedSmartAccount, - owner, + owners: [owner], }); expect(result).toBe(smartAccount); }); diff --git a/typescript/src/client/evm/evm.ts b/typescript/src/client/evm/evm.ts index 808e4605b..25855e81d 100644 --- a/typescript/src/client/evm/evm.ts +++ b/typescript/src/client/evm/evm.ts @@ -295,14 +295,15 @@ export class EvmClient implements EvmClientInterface { * Creates a new CDP EVM smart account. * * @param {CreateSmartAccountOptions} options - Parameters for creating the smart account. - * @param {Account} options.owner - The owner of the smart account. - * The owner can be any Ethereum account with signing capabilities, + * @param {Account} [options.owner] - The owner of the smart account (for backwards compatibility). + * @param {Account[]} [options.owners] - The owners of the smart account. If provided, takes precedence over `owner`. + * The owners can be any Ethereum accounts with signing capabilities, * such as a CDP EVM account or a Viem LocalAccount. * @param {string} [options.idempotencyKey] - An idempotency key. * * @returns A promise that resolves to the newly created smart account. * - * @example **With a CDP EVM Account as the owner** + * @example **With a single CDP EVM Account as the owner** * ```ts * const account = await cdp.evm.createAccount(); * const smartAccount = await cdp.evm.createSmartAccount({ @@ -310,6 +311,15 @@ export class EvmClient implements EvmClientInterface { * }); * ``` * + * @example **With multiple owners** + * ```ts + * const account1 = await cdp.evm.createAccount(); + * const account2 = await cdp.evm.createAccount(); + * const smartAccount = await cdp.evm.createSmartAccount({ + * owners: [account1, account2], + * }); + * ``` + * * @example **With a Viem LocalAccount as the owner** * ```ts * // See https://viem.sh/docs/accounts/local/privateKeyToAccount @@ -1024,8 +1034,8 @@ export class EvmClient implements EvmClientInterface { * }); * ``` */ - async sendUserOperation( - options: SendUserOperationOptions, + async sendUserOperation( + options: SendUserOperationOptions, ): Promise { Analytics.trackAction({ action: "send_user_operation", @@ -1040,6 +1050,7 @@ export class EvmClient implements EvmClientInterface { calls: options.calls, paymasterUrl: options.paymasterUrl, idempotencyKey: options.idempotencyKey, + signer: options.signer, }); } @@ -1327,9 +1338,16 @@ export class EvmClient implements EvmClientInterface { options.idempotencyKey, ); + // Handle backwards compatibility: if owners is provided, use it; otherwise fall back to owner + const accountOwners = options.owners || (options.owner ? [options.owner] : []); + + if (accountOwners.length === 0) { + throw new UserInputValidationError("At least one owner must be provided"); + } + const smartAccount = toEvmSmartAccount(CdpOpenApiClient, { smartAccount: openApiSmartAccount, - owner: options.owner, + owners: accountOwners, }); Analytics.wrapObjectMethodsWithErrorTracking(smartAccount); @@ -1447,7 +1465,14 @@ export class EvmClient implements EvmClientInterface { private async _createSmartAccountInternal( options: CreateSmartAccountOptions, ): Promise { - const owners = [options.owner.address]; + // Handle backwards compatibility: if owners is provided, use it; otherwise fall back to owner + const accountOwners = options.owners || (options.owner ? [options.owner] : []); + + if (accountOwners.length === 0) { + throw new UserInputValidationError("At least one owner must be provided"); + } + + const owners = accountOwners.map(owner => owner.address); if (options.enableSpendPermissions) { owners.push(SPEND_PERMISSION_MANAGER_ADDRESS); @@ -1463,7 +1488,7 @@ export class EvmClient implements EvmClientInterface { const smartAccount = toEvmSmartAccount(CdpOpenApiClient, { smartAccount: openApiSmartAccount, - owner: options.owner, + owners: accountOwners, }); Analytics.wrapObjectMethodsWithErrorTracking(smartAccount); @@ -1488,19 +1513,29 @@ export class EvmClient implements EvmClientInterface { throw new UserInputValidationError("Either address or name must be provided"); })(); - if (!openApiSmartAccount.owners.includes(options.owner.address)) { - throw new UserInputValidationError( - `Owner mismatch: The provided owner address is not an owner of the smart account. Please use a valid owner for this smart account. + // Handle backwards compatibility: if owners is provided, use it; otherwise fall back to owner + const accountOwners = options.owners || (options.owner ? [options.owner] : []); + + if (accountOwners.length === 0) { + throw new UserInputValidationError("At least one owner must be provided"); + } + + // Validate that all provided owners are actually owners of the smart account + for (const owner of accountOwners) { + if (!openApiSmartAccount.owners.includes(owner.address)) { + throw new UserInputValidationError( + `Owner mismatch: The provided owner address "${owner.address}" is not an owner of the smart account. Please use valid owners for this smart account. Smart Account Address: ${openApiSmartAccount.address} Smart Account Owners: ${openApiSmartAccount.owners.join(", ")} -Provided Owner Address: ${options.owner.address}\n`, - ); +Provided Owner Addresses: ${accountOwners.map(o => o.address).join(", ")}\n`, + ); + } } const smartAccount = toEvmSmartAccount(CdpOpenApiClient, { smartAccount: openApiSmartAccount, - owner: options.owner, + owners: accountOwners, }); Analytics.wrapObjectMethodsWithErrorTracking(smartAccount); diff --git a/typescript/src/client/evm/evm.types.ts b/typescript/src/client/evm/evm.types.ts index 7590841b4..a021778d3 100644 --- a/typescript/src/client/evm/evm.types.ts +++ b/typescript/src/client/evm/evm.types.ts @@ -99,8 +99,8 @@ export type EvmClientInterface = Omit< prepareUserOperation: (options: PrepareUserOperationOptions) => Promise; requestFaucet: (options: RequestFaucetOptions) => Promise; sendTransaction: (options: SendTransactionOptions) => Promise; - sendUserOperation: ( - options: SendUserOperationOptions, + sendUserOperation: ( + options: SendUserOperationOptions, ) => Promise; signHash: (options: SignHashOptions) => Promise; signMessage: (options: SignMessageOptions) => Promise; @@ -387,8 +387,10 @@ export interface GetServerAccountOptions { export interface GetSmartAccountOptions { /** The address of the account. */ address?: Address; - /** The owner of the account. */ - owner: Account; + /** The owner of the account (for backwards compatibility). */ + owner?: Account; + /** The owners of the account. If provided, takes precedence over `owner`. */ + owners?: Account[]; /** The name of the account. */ name?: string; } @@ -407,8 +409,10 @@ export interface GetOrCreateServerAccountOptions { export interface GetOrCreateSmartAccountOptions { /** The name of the account. */ name: string; - /** The owner of the account. */ - owner: Account; + /** The owner of the account. For backwards compatibility, use a single owner. For multiple owners, use the `owners` property instead. */ + owner?: Account; + /** The owners of the account. If provided, this takes precedence over the `owner` property. */ + owners?: Account[]; /** The flag to enable spend permissions. */ enableSpendPermissions?: boolean; } @@ -454,8 +458,10 @@ export interface UpdateEvmSmartAccountOptions { update: UpdateEvmSmartAccount; /** The idempotency key. */ idempotencyKey?: string; - /** The owner of the account. */ - owner: Account; + /** The owner of the account (for backwards compatibility). */ + owner?: Account; + /** The owners of the account. If provided, takes precedence over `owner`. */ + owners?: Account[]; } /** @@ -500,8 +506,10 @@ export interface ListSmartAccountsOptions { * Options for creating an EVM smart account. */ export interface CreateSmartAccountOptions { - /** The owner of the account. */ - owner: Account; + /** The owner of the account. For backwards compatibility, use a single owner. For multiple owners, use the `owners` property instead. */ + owner?: Account; + /** The owners of the account. If provided, this takes precedence over the `owner` property. */ + owners?: Account[]; /** The idempotency key. */ idempotencyKey?: string; /** The name of the account. */