Skip to content

Instantly share code, notes, and snippets.

@TrejGun
Created February 25, 2025 07:19
Show Gist options
  • Save TrejGun/26420487df5fe5728e83759eb186893e to your computer and use it in GitHub Desktop.
Save TrejGun/26420487df5fe5728e83759eb186893e to your computer and use it in GitHub Desktop.
multisig
import { OperationType } from "@safe-global/types-kit";
import Safe from "@safe-global/protocol-kit";
import SafeApiKit from "@safe-global/api-kit";
import { encodeFunctionData, Hash } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import { waitForTransactionReceipt } from "viem/actions";
const ERC20_TOKEN_ADDRESS = "0x...";
const AMOUNT = 100n ** 18n;
await (async () => {
const deployerPrivateKey = generatePrivateKey();
const { address: _deployerAccount } = privateKeyToAccount(deployerPrivateKey);
const adminPrivateKey1 = generatePrivateKey();
const { address: adminAccount1 } = privateKeyToAccount(adminPrivateKey1);
const adminPrivateKey2 = generatePrivateKey();
const { address: adminAccount2 } = privateKeyToAccount(adminPrivateKey2);
const senderPrivateKey = generatePrivateKey();
const { address: senderAccount } = privateKeyToAccount(senderPrivateKey);
const receiverPrivateKey = generatePrivateKey();
const { address: receiverAccount } = privateKeyToAccount(receiverPrivateKey);
const protocolKitDeployer = await Safe.init({
provider: sepolia.rpcUrls.default.http[0],
signer: deployerPrivateKey,
predictedSafe: {
safeAccountConfig: {
owners: [senderAccount, adminAccount1, adminAccount2],
threshold: 2,
},
},
});
const predictedSafeAddress = await protocolKitDeployer.getAddress();
const deploymentTransaction = await protocolKitDeployer.createSafeDeploymentTransaction();
const client = await protocolKitDeployer.getSafeProvider().getExternalSigner();
if (!client) {
throw new Error();
}
const transactionHash = await client.sendTransaction({
to: deploymentTransaction.to,
value: BigInt(deploymentTransaction.value),
data: deploymentTransaction.data as Hash,
chain: sepolia,
});
await waitForTransactionReceipt(client, { hash: transactionHash });
const newProtocolKit = await protocolKitDeployer.connect({
safeAddress: predictedSafeAddress,
});
const isSafeDeployed = await newProtocolKit.isSafeDeployed();
if (!isSafeDeployed) {
throw new Error("Wallet was not deployed");
}
const actualSafeAddress = await newProtocolKit.getAddress();
if (predictedSafeAddress !== actualSafeAddress) {
throw new Error("Address mismatch");
}
const protocolKitOwner = await Safe.init({
provider: sepolia.rpcUrls.default.http[0],
signer: senderPrivateKey,
safeAddress: actualSafeAddress,
});
const safeTransactionData = {
to: ERC20_TOKEN_ADDRESS,
value: "0",
data: encodeFunctionData({
abi: [
{
name: "transferFrom",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [],
},
],
functionName: "transferFrom",
args: [senderAccount, receiverAccount, AMOUNT],
}),
operation: OperationType.Call,
};
const safeTransaction = await protocolKitOwner.createTransaction({
transactions: [safeTransactionData],
});
const safeTransactionHash = await protocolKitOwner.getTransactionHash(safeTransaction);
const senderSignature = await protocolKitOwner.signHash(safeTransactionHash);
const apiKit = new SafeApiKit({
chainId: BigInt(sepolia.id),
});
await apiKit.proposeTransaction({
safeAddress: actualSafeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash: safeTransactionHash,
senderAddress: senderAccount,
senderSignature: senderSignature.data,
});
const protocolKitAdmin = await Safe.init({
provider: sepolia.rpcUrls.default.http[0],
signer: adminPrivateKey1,
safeAddress: actualSafeAddress,
});
const signature = await protocolKitAdmin.signHash(safeTransactionHash);
await apiKit.confirmTransaction(safeTransactionHash, signature.data);
const executableTransaction = await apiKit.getTransaction(safeTransactionHash);
await protocolKitOwner.executeTransaction(executableTransaction);
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment