Skip to content
Alchemy Logo

How to transfer ownership of a Light Account

Not all smart account implementations support transferring ownership (e.g. SimpleAccount), but LightAccount does. Below shows how to do it using @alchemy/smart-accounts.

LightAccount exposes the following onchain method which allows the current owner to transfer ownership to a new owner address:

function transferOwnership(address newOwner) public virtual onlyOwner

toLightAccount returns an encodeTransferOwnership helper. Encode the calldata for the target new owner, send it as a user operation from the account, and (optionally) re-connect a fresh account using the new owner so you can keep sending UOs after the transfer.

import { createBundlerClient } from "viem/account-abstraction";
import { createClient } from "viem";
import { sepolia } from "viem/chains";
import { mnemonicToAccount, privateKeyToAccount, generatePrivateKey } from "viem/accounts";
import { alchemyTransport } from "@alchemy/common";
import { toLightAccount } from "@alchemy/smart-accounts";
import { estimateFeesPerGas } from "@alchemy/aa-infra";
 
const ALCHEMY_API_KEY = "your-api-key";
const transport = alchemyTransport({ apiKey: ALCHEMY_API_KEY });
const rpcClient = createClient({ chain: sepolia, transport });
 
// Original owner + account.
const originalOwner = privateKeyToAccount(generatePrivateKey());
const account = await toLightAccount({
  client: rpcClient,
  owner: originalOwner,
  version: "v2.0.0",
});
 
const bundlerClient = createBundlerClient({
  account,
  client: rpcClient,
  chain: sepolia,
  transport,
  userOperation: { estimateFeesPerGas },
});
 
// 1) Pick a new owner.
const newOwner = mnemonicToAccount(process.env.NEW_OWNER_MNEMONIC!);
 
// 2) Encode + send the transfer.
const data = account.encodeTransferOwnership(newOwner.address);
const hash = await bundlerClient.sendUserOperation({
  calls: [{ to: account.address, data }],
});
await bundlerClient.waitForUserOperationReceipt({ hash });
 
// 3) After the transfer mines, reconnect to the same account address with the new owner.
//    NOTE: you MUST pass the original accountAddress — otherwise the new owner derives
//    a different counterfactual address.
const transferredAccount = await toLightAccount({
  client: rpcClient,
  owner: newOwner,
  version: "v2.0.0",
  accountAddress: account.address,
});
 
const transferredClient = createBundlerClient({
  account: transferredAccount,
  client: rpcClient,
  chain: sepolia,
  transport,
  userOperation: { estimateFeesPerGas },
});

You can also read the current onchain owner via account.getOwnerAddress() to verify the transfer landed.

See the Light Account overview for more details on the contract.

Was this page helpful?