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:

1function transferOwnership(address newOwner) public virtual onlyOwner

There a number of ways you can call this method using Account Kit.

1. Using transferOwnership client action

import { 
import lightAccountClient
lightAccountClient
} 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: any
newOwner
=
any
LocalAccountSigner
.
any
mnemonicToAccountSigner
(
any
NEW_OWNER_MNEMONIC
);
const
const accountAddress: any
accountAddress
=
import lightAccountClient
lightAccountClient
.
any
getAddress
();
const
const hash: any
hash
=
import lightAccountClient
lightAccountClient
.
any
transferOwnership
({
newOwner: any
newOwner
,
waitForTxn: boolean
waitForTxn
: 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: any
transport
:
any
custom
(
any
smartAccountClient
),
chain: any
chain
:
any
smartAccountClient
.
any
chain
,
signer: any
signer
:
const newOwner: any
newOwner
,
accountAddress: any
accountAddress
, // NOTE: you MUST specify the original smart account address to connect using the new owner/signer
version: string
version
: "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): AlchemyTransport

Creates 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: Chain
sepolia
} 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(): Hex
generatePrivateKey
} 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(): Hex
generatePrivateKey
()),
chain: { blockExplorers?: { [key: string]: ChainBlockExplorer; default: ChainBlockExplorer; } | undefined; ... 7 more ...; testnet?: boolean | undefined; } & ChainConfig<...>

Chain for the client.

chain
:
const sepolia: Chain
sepolia
,
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
: "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>): EncodeFunctionDataReturnType
encodeFunctionData
} from "viem";
import {
import lightAccountClient
lightAccountClient
} from "./client";
// this will return the address of the smart account you want to transfer ownerhip of const
const accountAddress: any
accountAddress
=
import lightAccountClient
lightAccountClient
.
any
getAddress
();
const
const newOwner: "0x..."
newOwner
= "0x..."; // the address of the new owner
const
const result: any
result
= await
import lightAccountClient
lightAccountClient
.
any
sendUserOperation
({
to: any
to
:
const accountAddress: any
accountAddress
,
data: any
data
:
import lightAccountClient
lightAccountClient
.
any
encodeTransferOwnership
(
const newOwner: "0x..."
newOwner
),
}); // wait for txn with UO to be mined await
import lightAccountClient
lightAccountClient
.
any
waitForUserOperationTransaction
(
const result: any
result
);
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): AlchemyTransport

Creates 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: Chain
sepolia
} 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(): Hex
generatePrivateKey
} 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(): Hex
generatePrivateKey
()),
chain: { blockExplorers?: { [key: string]: ChainBlockExplorer; default: ChainBlockExplorer; } | undefined; ... 7 more ...; testnet?: boolean | undefined; } & ChainConfig<...>

Chain for the client.

chain
:
const sepolia: Chain
sepolia
,
transport: AlchemyTransport

The RPC transport

transport
:
function alchemy(config: AlchemyTransportConfig): AlchemyTransport

Creates 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: string
apiKey
: "YOUR_API_KEY" }),
});

See the LightAccount docs for more details about our `LightAccount implementation.