Skip to content

Getting started with the Multisig Plugin

1. Create a Multisig Account Client

Initialize a Multisig Modular Account client and set the n accounts as signers.

import { LocalAccountSigner } from "@aa-sdk/core";
import { alchemy, sepolia } from "@account-kit/infra";
import { createMultisigAccountAlchemyClient } from "@account-kit/smart-contracts";
 
const MODULAR_MULTISIG_ACCOUNT_OWNER_MNEMONIC = "YOUR MNEMONIC";
 
// Creating a 3/3 multisig account
export const signers = [
  LocalAccountSigner.mnemonicToAccountSigner(
    MODULAR_MULTISIG_ACCOUNT_OWNER_MNEMONIC,
    { accountIndex: 0 }
  ),
  LocalAccountSigner.mnemonicToAccountSigner(
    MODULAR_MULTISIG_ACCOUNT_OWNER_MNEMONIC,
    { accountIndex: 1 }
  ),
  LocalAccountSigner.mnemonicToAccountSigner(
    MODULAR_MULTISIG_ACCOUNT_OWNER_MNEMONIC,
    { accountIndex: 2 }
  ),
];
 
export const threshold = 3n;
 
export const owners = await Promise.all(signers.map((s) => s.getAddress()));
 
export const multisigAccountClient = await createMultisigAccountAlchemyClient({
  chain: sepolia,
  signer: signers[0],
  owners,
  threshold,
  transport: alchemy({
    apiKey: "YOUR_API_KEY",
  }),
});

3. Propose a User Operation

To propose a new user operation for review by the multisig signers, you will use the proposeUserOperation method. This estimates gas, constructs the user operation struct, and if gasManagerConfig is used then it attempts to use a paymaster. Lastly, a signature is generated with the pre-provided signer.

example.ts
import { multisigAccountClient } from "./client";
import { createMultisigAccountAlchemyClient } from "@account-kit/smart-contracts";
 
const {
  request,
  aggregatedSignature,
  signatureObj: firstSig,
} = await multisigAccountClient.proposeUserOperation({
  uo: {
    target: targetAddress,
    data: "0x",
  },
});

4. Get the threshold signatures

Next, you have to collect the next k-2 signatures, excluding the first signature which you already provided and the last signature which we'll deal with in step 5 when we send the user operation. Each member of the multisig can sign with the signMultisigUserOperation method.

example.ts
import { createMultisigAccountAlchemyClient } from "@account-kit/smart-contracts";
import { signers, owners, threshold } from "./client";
 
const multisigAccountClient = await createMultisigAccountAlchemyClient({
  chain,
  signer: signers[1], // using the second signer
  owners,
  threshold,
  apiKey: "YOUR_API_KEY",
});
 
const { aggregatedSignature, signatureObj: secondSig } =
  await multisigAccountClient.signMultisigUserOperation({
    account: multisigAccountClient.account,
    // output from step 1, and from this step if k-2 > 1
    signatures: [previousAggregatedSignature],
    userOperationRequest: request,
  });

5. Send the User Operation

After collecting k-1 signatures, you're ready to collect the last signature and send the user operation. This is done with the sendUserOperation method. sendUserOperation also formats this aggregated signature, sorting its parts in ascending order by owner address as expected by the Multisig Plugin smart contract.

example.ts
import { createMultisigAccountAlchemyClient } from "@account-kit/smart-contracts";
import { signers, owners, threshold } from "./client";
 
const multisigAccountClient = await createMultisigAccountAlchemyClient({
  chain,
  // using the last signer
  signer: signers[2],
  owners,
  threshold,
  apiKey: "YOUR_API_KEY",
});
 
const result = await multisigAccountClient.sendUserOperation({
  uo: request.callData,
  context: {
    aggregatedSignature,
    signatures: [firstSig, secondSig],
    userOpSignatureType: "ACTUAL",
  },
});

By default, we use the variable gas feature in the Multisig Plugin smart contract. For this, we need userOpSignatureType to be set to "ACTUAL". If you do not wish to use this feature, gas overrides should be passed in sendUserOperation, and userOpSignatureType should be set to "UPPERLIMIT".

example.ts
import { multisigAccountClient } from "./client";
 
const result = await multisigAccountClient.sendUserOperation({
  uo: request.callData,
  overrides: {
    callGasLimit: request.callGasLimit,
    verificationGasLimit: request.verificationGasLimit,
    preVerificationGas: request.preVerificationGas,
    maxFeePerGas: request.maxFeePerGas,
    maxPriorityFeePerGas: request.maxPriorityFeePerGas,
  },
  context: {
    aggregatedSignature,
    signatures: [firstSig, secondSig],
    userOpSignatureType: "UPPERLIMIT",
  },
});

Conclusion

That's it! You've initialized a modular account with three multisig members, proposed a user operation, collected the necessary signatures, and sent the user operation to the bundler.

For more info, check out the technical details of the multisig plugin.