Prerequisites
You need a deployed smart account with multi-signature support.Create Smart Account
Add Signers
- ZeroDev
- Biconomy
- Safe
- Alchemy
Copy
Ask AI
import { createMultisigValidator } from "@zerodev/multisig-validator";
import { KERNEL_V3_1 } from "@zerodev/sdk/constants";
async function addSigner(
kernelClient: any,
newSigner: string,
newThreshold?: number
) {
const currentSigners = await kernelClient.account.getSigners();
console.log("Current signers:", currentSigners);
console.log("Adding new signer:", newSigner);
const updatedSigners = [...currentSigners, newSigner];
const threshold = newThreshold || currentSigners.length;
const multisigValidator = await createMultisigValidator(kernelClient.publicClient, {
signers: updatedSigners,
threshold,
entryPoint: kernelClient.account.entryPoint,
kernelVersion: KERNEL_V3_1
});
const userOpHash = await kernelClient.installPlugin({
plugin: multisigValidator
});
await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
console.log("Signer added successfully!");
console.log("New threshold:", threshold, "of", updatedSigners.length);
return userOpHash;
}
Copy
Ask AI
import { encodeFunctionData } from "viem";
async function addSigner(
smartAccount: any,
newSigner: string,
newThreshold?: number
) {
const accountAddress = await smartAccount.getAccountAddress();
const moduleAddress = "0x..."; // Multisig module address
const moduleInterface = new ethers.utils.Interface([
"function addOwner(address owner, uint256 threshold)"
]);
const addOwnerData = moduleInterface.encodeFunctionData("addOwner", [
newSigner,
newThreshold || 1
]);
const transaction = {
to: moduleAddress,
data: addOwnerData,
value: "0"
};
const userOpResponse = await smartAccount.sendTransaction(transaction, {
paymasterServiceData: { mode: "SPONSORED" }
});
console.log("Adding signer:", newSigner);
console.log("UserOp Hash:", userOpResponse.userOpHash);
const receipt = await userOpResponse.wait();
console.log("Signer added:", receipt.receipt.transactionHash);
return receipt;
}
Copy
Ask AI
import Safe from "@safe-global/protocol-kit";
async function addSigner(
protocolKit: Safe,
newOwner: string,
newThreshold?: number
) {
try {
const currentOwners = await protocolKit.getOwners();
const currentThreshold = await protocolKit.getThreshold();
console.log("Current owners:", currentOwners);
console.log("Current threshold:", currentThreshold);
const params = {
ownerAddress: newOwner,
threshold: newThreshold
};
const safeTransaction = await protocolKit.createAddOwnerTx(params);
const safeTxHash = await protocolKit.getTransactionHash(safeTransaction);
const signature = await protocolKit.signTransaction(safeTransaction);
console.log("Transaction hash:", safeTxHash);
console.log("Signature:", signature);
const txResponse = await protocolKit.executeTransaction(safeTransaction);
console.log("Owner added:", txResponse.hash);
console.log("New owner:", newOwner);
return txResponse;
} catch (error) {
console.error("Failed to add owner:", error);
throw error;
}
}
Copy
Ask AI
import { encodeFunctionData, parseAbi } from "viem";
async function addSigner(
multisigClient: any,
newSigner: string,
currentSigners: string[]
) {
const multisigPluginAbi = parseAbi([
"function addSigner(address signer) external"
]);
const addSignerOp = await multisigClient.proposeUserOperation({
uo: {
target: multisigClient.account.address,
data: encodeFunctionData({
abi: multisigPluginAbi,
functionName: "addSigner",
args: [newSigner]
})
}
});
console.log("Proposed add signer operation");
console.log("Operation hash:", addSignerOp.hash);
const signatures = [];
for (const signer of currentSigners.slice(0, 2)) {
const signature = await signer.signMultisigUserOperation(addSignerOp);
signatures.push(signature);
console.log("Collected signature from:", signer.address);
}
const { hash } = await multisigClient.sendUserOperation({
...addSignerOp,
signatures
});
const receipt = await multisigClient.waitForUserOperationReceipt({ hash });
console.log("Signer added:", newSigner);
console.log("Transaction:", receipt.receipt.transactionHash);
return receipt;
}
Remove Signers
- ZeroDev
- Biconomy
- Safe
- Alchemy
Copy
Ask AI
async function removeSigner(
kernelClient: any,
signerToRemove: string,
newThreshold?: number
) {
const currentSigners = await kernelClient.account.getSigners();
if (!currentSigners.includes(signerToRemove)) {
throw new Error("Signer not found");
}
const updatedSigners = currentSigners.filter(s => s !== signerToRemove);
const threshold = newThreshold || Math.min(
currentSigners.length - 1,
await kernelClient.account.getThreshold()
);
if (updatedSigners.length < threshold) {
throw new Error("Threshold would be higher than number of signers");
}
const multisigValidator = await createMultisigValidator(kernelClient.publicClient, {
signers: updatedSigners,
threshold,
entryPoint: kernelClient.account.entryPoint,
kernelVersion: KERNEL_V3_1
});
const userOpHash = await kernelClient.installPlugin({
plugin: multisigValidator
});
await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
console.log("Signer removed:", signerToRemove);
console.log("New threshold:", threshold, "of", updatedSigners.length);
return userOpHash;
}
Copy
Ask AI
async function removeSigner(
smartAccount: any,
signerToRemove: string,
newThreshold: number
) {
const moduleAddress = "0x..."; // Multisig module address
const moduleInterface = new ethers.utils.Interface([
"function removeOwner(address prevOwner, address owner, uint256 threshold)"
]);
const owners = await getOwners(smartAccount);
const prevOwnerIndex = owners.indexOf(signerToRemove) - 1;
const prevOwner = prevOwnerIndex >= 0 ? owners[prevOwnerIndex] : "0x0000000000000000000000000000000000000001";
const removeOwnerData = moduleInterface.encodeFunctionData("removeOwner", [
prevOwner,
signerToRemove,
newThreshold
]);
const transaction = {
to: moduleAddress,
data: removeOwnerData,
value: "0"
};
const userOpResponse = await smartAccount.sendTransaction(transaction, {
paymasterServiceData: { mode: "SPONSORED" }
});
console.log("Removing signer:", signerToRemove);
console.log("UserOp Hash:", userOpResponse.userOpHash);
const receipt = await userOpResponse.wait();
console.log("Signer removed:", receipt.receipt.transactionHash);
return receipt;
}
Copy
Ask AI
async function removeSigner(
protocolKit: Safe,
ownerToRemove: string,
newThreshold?: number
) {
try {
const currentOwners = await protocolKit.getOwners();
const currentThreshold = await protocolKit.getThreshold();
if (!currentOwners.includes(ownerToRemove)) {
throw new Error("Owner not found");
}
const finalThreshold = newThreshold || Math.max(1, currentThreshold - 1);
const params = {
ownerAddress: ownerToRemove,
newThreshold: finalThreshold
};
const safeTransaction = await protocolKit.createRemoveOwnerTx(params);
const safeTxHash = await protocolKit.getTransactionHash(safeTransaction);
const signature = await protocolKit.signTransaction(safeTransaction);
console.log("Transaction hash:", safeTxHash);
const txResponse = await protocolKit.executeTransaction(safeTransaction);
console.log("Owner removed:", ownerToRemove);
console.log("New threshold:", finalThreshold);
return txResponse;
} catch (error) {
console.error("Failed to remove owner:", error);
throw error;
}
}
Copy
Ask AI
async function removeSigner(
multisigClient: any,
signerToRemove: string,
currentSigners: string[]
) {
const multisigPluginAbi = parseAbi([
"function removeSigner(address signer, uint256 newThreshold) external"
]);
const newThreshold = Math.max(1, currentSigners.length - 2);
const removeSignerOp = await multisigClient.proposeUserOperation({
uo: {
target: multisigClient.account.address,
data: encodeFunctionData({
abi: multisigPluginAbi,
functionName: "removeSigner",
args: [signerToRemove, newThreshold]
})
}
});
console.log("Proposed remove signer operation");
const signatures = [];
for (const signer of currentSigners.slice(0, 2)) {
if (signer.address !== signerToRemove) {
const signature = await signer.signMultisigUserOperation(removeSignerOp);
signatures.push(signature);
}
}
const { hash } = await multisigClient.sendUserOperation({
...removeSignerOp,
signatures
});
const receipt = await multisigClient.waitForUserOperationReceipt({ hash });
console.log("Signer removed:", signerToRemove);
console.log("New threshold:", newThreshold);
return receipt;
}
Update Threshold
- ZeroDev
- Safe
Copy
Ask AI
async function updateThreshold(
kernelClient: any,
newThreshold: number
) {
const currentSigners = await kernelClient.account.getSigners();
if (newThreshold > currentSigners.length) {
throw new Error("Threshold cannot exceed number of signers");
}
if (newThreshold < 1) {
throw new Error("Threshold must be at least 1");
}
const multisigValidator = await createMultisigValidator(kernelClient.publicClient, {
signers: currentSigners,
threshold: newThreshold,
entryPoint: kernelClient.account.entryPoint,
kernelVersion: KERNEL_V3_1
});
const userOpHash = await kernelClient.installPlugin({
plugin: multisigValidator
});
await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
console.log("Threshold updated to:", newThreshold);
console.log("Total signers:", currentSigners.length);
return userOpHash;
}
Copy
Ask AI
async function updateThreshold(
protocolKit: Safe,
newThreshold: number
) {
try {
const currentOwners = await protocolKit.getOwners();
const currentThreshold = await protocolKit.getThreshold();
if (newThreshold > currentOwners.length) {
throw new Error("Threshold cannot exceed number of owners");
}
if (newThreshold < 1) {
throw new Error("Threshold must be at least 1");
}
console.log("Current threshold:", currentThreshold);
console.log("New threshold:", newThreshold);
const safeTransaction = await protocolKit.createChangeThresholdTx({
threshold: newThreshold
});
const safeTxHash = await protocolKit.getTransactionHash(safeTransaction);
const signature = await protocolKit.signTransaction(safeTransaction);
const txResponse = await protocolKit.executeTransaction(safeTransaction);
console.log("Threshold updated:", txResponse.hash);
return txResponse;
} catch (error) {
console.error("Failed to update threshold:", error);
throw error;
}
}
Replace Signer
- ZeroDev
- Safe
Copy
Ask AI
async function replaceSigner(
kernelClient: any,
oldSigner: string,
newSigner: string
) {
const currentSigners = await kernelClient.account.getSigners();
if (!currentSigners.includes(oldSigner)) {
throw new Error("Old signer not found");
}
if (currentSigners.includes(newSigner)) {
throw new Error("New signer already exists");
}
const updatedSigners = currentSigners.map(s =>
s === oldSigner ? newSigner : s
);
const currentThreshold = await kernelClient.account.getThreshold();
const multisigValidator = await createMultisigValidator(kernelClient.publicClient, {
signers: updatedSigners,
threshold: currentThreshold,
entryPoint: kernelClient.account.entryPoint,
kernelVersion: KERNEL_V3_1
});
const userOpHash = await kernelClient.installPlugin({
plugin: multisigValidator
});
await kernelClient.waitForUserOperationReceipt({ hash: userOpHash });
console.log("Signer replaced");
console.log("Old:", oldSigner);
console.log("New:", newSigner);
return userOpHash;
}
Copy
Ask AI
async function replaceSigner(
protocolKit: Safe,
oldOwner: string,
newOwner: string
) {
try {
const currentOwners = await protocolKit.getOwners();
if (!currentOwners.includes(oldOwner)) {
throw new Error("Old owner not found");
}
if (currentOwners.includes(newOwner)) {
throw new Error("New owner already exists");
}
const params = {
oldOwnerAddress: oldOwner,
newOwnerAddress: newOwner
};
const safeTransaction = await protocolKit.createSwapOwnerTx(params);
const safeTxHash = await protocolKit.getTransactionHash(safeTransaction);
const signature = await protocolKit.signTransaction(safeTransaction);
const txResponse = await protocolKit.executeTransaction(safeTransaction);
console.log("Owner swapped:", txResponse.hash);
console.log("Old:", oldOwner);
console.log("New:", newOwner);
return txResponse;
} catch (error) {
console.error("Failed to swap owner:", error);
throw error;
}
}