Upgrade LA to MAv2
0. Construct your clients
First we need to check what kind of account address you have! The way client.account.address
is derived depends on the type of account and the salt. We'll have to handle different cases depending on whether your client's
address is a light account address or a modular account address. So we construct two clients:
Our Light Account client:
import { createLightAccountClient } from "@account-kit/smart-contracts";
const lightAccountClient = await createLightAccountClient({
transport: alchemy({ apiKey: "your-api-key" }),
chain: sepolia,
signer: yourSigner
Our Modular Account client:
import { createModularAccountV2Client } from "@account-kit/smart-contracts";
const modularAccountV2Client = await createModularAccountV2Client({
transport: alchemy({ apiKey: "your-api-key" }),
chain: sepolia,
signer: yourSigner,
});
1. Check the implementation of your account (using client.account.getImplementationAddress()
):
Depending on the result, take different actions based on the state of the account.
For your Modular Account client:
import { client } from "docs/shared/infra/client";
const impl = await client.account.getImplementationAddress();
// implementation slot exists and is a deployed modular account. this user has a modular account at the modular account counterfactual address. no work needs to be done.
if (impl == "ModularAccountImpl") {
// done
}
For your Light Account client:
import { client } from "docs/shared/infra/client";
const impl = await client.account.getImplementationAddress();
// implementation slot exists and is a deployed modular account. this user has a modular account at the light account address. no need to upgrade.
if (impl == "ModularAccountImpl") {
// done
}
// implementation slot exists and is a deployed light account. this user has a light account at the light account address. we need to initiate an upgrade for the user.
else if (impl == "LightAccountImpl") {
// go to step A
}
// implementation slot is empty
else if (impl == NullAddress) {
// account is not deployed and has assets at the counterfactual
const balance = await client.getBalance({ address: client.account.address });
if (balance) {
// go to step A
}
// account does not exist, new user!
else {
// go to step B
}
}
2. Upgrade Account!
Case A: Implementation Slot Exists and is a LightAccount
If the implementation slot exists and points to LightAccount, it means the account is a legacy LightAccount. Follow this path if the account has been deployed or if the account hasn’t been deployed but has assets at the counterfactual.
Steps:
- Retrieve the upgrade data
upgradeToData
and methodcreateMAV2Account
to create the MAv2 account usinggetMAV2UpgradeToData
. - Call
upgradeAccount
to prepare the upgrade data for MAv2 with the initializer set to give it the same owner as the LA. - Call
createSmartAccountClient
to create the MAv2 client from your newly upgraded account
const { createMAV2Account, ...upgradeToData } = await getMAV2UpgradeToData(
lightAccountclient,
{ account: lightAccountClient.account }
);
await lightAccountClient.upgradeAccount({
upgradeTo: upgradeToData,
waitForTx: true,
});
const maV2Client = createSmartAccountClient({
client: createBundlerClient({
chain: yourchain,
transport: yourTransport,
}),
account: await createMAV2Account(),
});
Case B: Implementation Slot is Empty (No Account or Account Not Deployed With No Assets)
If the implementation slot is empty, this means that the account doesn’t exist. Or, if the account exists but has no assets, we still want to follow this path.
- Construct a MAv2 client with the signer to handle both cases:
- New user with no account.
- New user with an existing but un-deployed account.
import { createModularAccountV2Client } from "@account-kit/smart-contracts";
import { LocalAccountSigner } from "@aa-sdk/core";
import { sepolia, alchemy } from "@account-kit/infra";
import { generatePrivateKey } from "viem/accounts";
const maV2Client = await createModularAccountV2Client({
mode: "default", // optional param to specify the MAv2 variant (either "default" or "7702")
chain: sepolia,
transport: alchemy({ apiKey: "your-api-key" }), // Get your API key at https://dashboard.alchemy.com/apps or http("RPC_URL") for non-alchemy infra
signer: LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey()),
});
This ensures that operations are executed on the already upgraded MAv2 account.
3. Send a User Operation!
Send a user operation to deploy your upgraded account!
const result = await maV2Client.sendUserOperation({
uo: {
target: target,
value: sendAmount,
data: "0x",
},
});
And now you’ve upgraded your account to MAV2!
You can use a Modular Account v2 client to send user operations or use the React packages directly to simplify integration - this hook defaults to MAv2. Make sure to pass in an accountAddress
param to the client to connect the client to the existing account.