How to transfer ownership of a Light Account
Not all smart account implementations support transferring the ownership (e.g. SimpleAccount
). However, a number of the accounts in this guide and in Account Kit do, including our LightAccount
! Let’s see a few different ways we can transfer ownership of an Account (using LightAccount
as an example).
Usage
LightAccount
exposes the following method which allows the existing owner to transfer ownership to a new owner address:
There a number of ways you can call this method using Account Kit.
1. Using transferOwnership
client action
import { import lightAccountClientlightAccountClient } from "./client";
import { function createLightAccountClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyLightAccountClientConfig<TSigner> & {
transport: AlchemyTransport;
}): Promise<AlchemySmartAccountClient<Chain | undefined, LightAccount<TSigner>, LightAccountClientActions<TSigner>>> (+1 overload)createLightAccountClient } from "@account-kit/smart-contracts";
// this will return the signer of the smart account you want to transfer ownerhip to
const const newOwner: anynewOwner = anyLocalAccountSigner.anymnemonicToAccountSigner(anyNEW_OWNER_MNEMONIC);
const const accountAddress: anyaccountAddress = import lightAccountClientlightAccountClient.anygetAddress();
const const hash: anyhash = import lightAccountClientlightAccountClient.anytransferOwnership({
newOwner: anynewOwner,
waitForTxn: booleanwaitForTxn: true,
});
// after transaction is mined on the network,
// create a new light account client for the transferred Light Account
const const transferredClient: {
account: LightAccount<any>;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}transferredClient = await createLightAccountClient<Chain | undefined, any, any>(args: any): Promise<{
account: LightAccount<any>;
batch?: {
multicall?: boolean | Prettify<MulticallBatchOptions> | undefined;
} | undefined;
... 84 more ...;
extend: <client>(fn: (client: Client<...>) => client) => Client<...>;
}> (+1 overload)createLightAccountClient({
transport: anytransport: anycustom(anysmartAccountClient),
chain: anychain: anysmartAccountClient.anychain,
signer: anysigner: const newOwner: anynewOwner,
accountAddress: anyaccountAddress, // NOTE: you MUST specify the original smart account address to connect using the new owner/signer
version: stringversion: "v2.0.0", // NOTE: if the version of the light account is not v2.0.0, it must be specified here
});
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createLightAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyLightAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, LightAccount<TSigner>, LightAccountClientActions<TSigner>>>createLightAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const lightAccountClient: {
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 86 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}lightAccountClient = await createLightAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyLightAccountClientConfig<...>): Promise<...>createLightAccountAlchemyClient({
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "YOUR_API_KEY" }),
});
Since @alchemy/aa-accounts
exports a LightAccount
ABI, the above approach makes it easy to transfer ownership. That said, you can also directly call sendUserOperation
to execute the ownership transfer. As you will see below, however, it is a bit verbose:
2. Using sendUserOperation
import { function encodeFunctionData<const abi extends Abi | readonly unknown[], functionName extends ContractFunctionName<abi> | undefined = undefined>(parameters: EncodeFunctionDataParameters<abi, functionName>): EncodeFunctionDataReturnTypeencodeFunctionData } from "viem";
import { import lightAccountClientlightAccountClient } from "./client";
// this will return the address of the smart account you want to transfer ownerhip of
const const accountAddress: anyaccountAddress = import lightAccountClientlightAccountClient.anygetAddress();
const const newOwner: "0x..."newOwner = "0x..."; // the address of the new owner
const const result: anyresult = await import lightAccountClientlightAccountClient.anysendUserOperation({
to: anyto: const accountAddress: anyaccountAddress,
data: anydata: import lightAccountClientlightAccountClient.anyencodeTransferOwnership(const newOwner: "0x..."newOwner),
});
// wait for txn with UO to be mined
await import lightAccountClientlightAccountClient.anywaitForUserOperationTransaction(const result: anyresult);
import { class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner } from "@aa-sdk/core";
import { function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy, const sepolia: Chainsepolia } from "@account-kit/infra";
import { function createLightAccountAlchemyClient<TSigner extends SmartAccountSigner = SmartAccountSigner<any>>(params: AlchemyLightAccountClientConfig<TSigner>): Promise<AlchemySmartAccountClient<Chain | undefined, LightAccount<TSigner>, LightAccountClientActions<TSigner>>>createLightAccountAlchemyClient } from "@account-kit/smart-contracts";
import { function generatePrivateKey(): HexgeneratePrivateKey } from "viem/accounts";
export const const lightAccountClient: {
account: LightAccount<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>;
... 86 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}lightAccountClient = await createLightAccountAlchemyClient<LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>>(params: AlchemyLightAccountClientConfig<...>): Promise<...>createLightAccountAlchemyClient({
signer: LocalAccountSigner<{
address: Address;
nonceManager?: NonceManager | undefined;
sign: (parameters: {
hash: Hash;
}) => Promise<Hex>;
... 6 more ...;
type: "local";
}>signer: class LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>Represents a local account signer and provides methods to sign messages and transactions, as well as static methods to create the signer from mnemonic or private key.
LocalAccountSigner.LocalAccountSigner<T extends HDAccount | PrivateKeyAccount | LocalAccount>.privateKeyToAccountSigner(key: Hex): LocalAccountSigner<PrivateKeyAccount>Creates a LocalAccountSigner
instance using the provided private key.
privateKeyToAccountSigner(function generatePrivateKey(): HexgeneratePrivateKey()),
chain: {
blockExplorers?: {
[key: string]: ChainBlockExplorer;
default: ChainBlockExplorer;
} | undefined;
... 7 more ...;
testnet?: boolean | undefined;
} & ChainConfig<...>Chain for the client.
chain: const sepolia: Chainsepolia,
transport: AlchemyTransportThe RPC transport
transport: function alchemy(config: AlchemyTransportConfig): AlchemyTransportCreates an Alchemy transport with the specified configuration options. When sending all traffic to Alchemy, you must pass in one of rpcUrl, apiKey, or jwt. If you want to send Bundler and Paymaster traffic to Alchemy and Node traffic to a different RPC, you must pass in alchemyConnection and nodeRpcUrl.
alchemy({ apiKey: stringapiKey: "YOUR_API_KEY" }),
});
See the LightAccount
docs for more details about our `LightAccount implementation.