mirror of
https://github.com/luxfi/eoa-contracts.git
synced 2026-10-30 16:58:32 +00:00
243 lines
6.3 KiB
TypeScript
243 lines
6.3 KiB
TypeScript
import { BigNumber, BytesLike, Contract, Signer } from "ethers";
|
|
import { ethers } from "hardhat";
|
|
import { EntryPoint } from "../../typechain";
|
|
import { UserOperation } from "./userOperation";
|
|
import { fillAndSign, makeEcdsaModuleUserOp } from "./userOp";
|
|
import { hexZeroPad, hexConcat, defaultAbiCoder } from "ethers/lib/utils";
|
|
import MerkleTree from "merkletreejs";
|
|
import { keccak256 } from "ethereumjs-util";
|
|
|
|
export interface SessionKeyParams {
|
|
sessionKeyData: string;
|
|
leafData: string;
|
|
}
|
|
|
|
export async function makeEcdsaSessionKeySignedBatchUserOp(
|
|
functionName: string,
|
|
functionParams: any,
|
|
userOpSender: string,
|
|
sessionKey: Signer,
|
|
entryPoint: EntryPoint,
|
|
sessionKeyManagerAddress: string,
|
|
sessionData: any[],
|
|
sessionRouterAddress: string,
|
|
options?: {
|
|
preVerificationGas?: number;
|
|
}
|
|
): Promise<UserOperation> {
|
|
const SmartAccount = await ethers.getContractFactory("SmartAccount");
|
|
|
|
const txnDataAA1 = SmartAccount.interface.encodeFunctionData(
|
|
functionName,
|
|
functionParams
|
|
);
|
|
|
|
const userOp = await fillAndSign(
|
|
{
|
|
sender: userOpSender,
|
|
callData: txnDataAA1,
|
|
...options,
|
|
},
|
|
sessionKey,
|
|
entryPoint,
|
|
"nonce"
|
|
);
|
|
|
|
const userOpHash = await entryPoint.getUserOpHash(userOp);
|
|
const userOpHashAndModuleAddress = ethers.utils.hexConcat([
|
|
ethers.utils.hexZeroPad(userOpHash, 32),
|
|
ethers.utils.hexZeroPad(sessionKeyManagerAddress, 20),
|
|
]);
|
|
const resultingHash = ethers.utils.keccak256(userOpHashAndModuleAddress);
|
|
const signatureOverUserOpHashAndModuleAddress = await sessionKey.signMessage(
|
|
ethers.utils.arrayify(resultingHash)
|
|
);
|
|
|
|
const paddedSig = defaultAbiCoder.encode(
|
|
[
|
|
"address",
|
|
"tuple(uint48,uint48,address,bytes,bytes32[],bytes)[]",
|
|
"bytes",
|
|
],
|
|
[
|
|
sessionKeyManagerAddress,
|
|
sessionData,
|
|
signatureOverUserOpHashAndModuleAddress,
|
|
]
|
|
);
|
|
|
|
const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode(
|
|
["bytes", "address"],
|
|
[paddedSig, sessionRouterAddress]
|
|
);
|
|
userOp.signature = signatureWithModuleAddress;
|
|
|
|
return userOp;
|
|
}
|
|
|
|
export async function makeEcdsaSessionKeySignedUserOp(
|
|
functionName: string,
|
|
functionParams: any,
|
|
userOpSender: string,
|
|
sessionKey: Signer,
|
|
entryPoint: EntryPoint,
|
|
sessionKeyManagerAddress: string,
|
|
validUntil: number,
|
|
validAfter: number,
|
|
sessionValidationModuleAddress: string,
|
|
sessionKeyParamsData: BytesLike,
|
|
merkleProof: any,
|
|
options?: {
|
|
preVerificationGas?: number;
|
|
}
|
|
): Promise<UserOperation> {
|
|
const SmartAccount = await ethers.getContractFactory("SmartAccount");
|
|
|
|
const txnDataAA1 = SmartAccount.interface.encodeFunctionData(
|
|
functionName,
|
|
functionParams
|
|
);
|
|
|
|
const userOp = await fillAndSign(
|
|
{
|
|
sender: userOpSender,
|
|
callData: txnDataAA1,
|
|
...options,
|
|
},
|
|
sessionKey,
|
|
entryPoint,
|
|
"nonce",
|
|
true
|
|
);
|
|
|
|
const paddedSig = defaultAbiCoder.encode(
|
|
// validUntil, validAfter, sessionVerificationModule address, validationData, merkleProof, signature
|
|
["uint48", "uint48", "address", "bytes", "bytes32[]", "bytes"],
|
|
[
|
|
validUntil,
|
|
validAfter,
|
|
sessionValidationModuleAddress,
|
|
sessionKeyParamsData,
|
|
merkleProof,
|
|
userOp.signature,
|
|
]
|
|
);
|
|
|
|
const signatureWithModuleAddress = ethers.utils.defaultAbiCoder.encode(
|
|
["bytes", "address"],
|
|
[paddedSig, sessionKeyManagerAddress]
|
|
);
|
|
userOp.signature = signatureWithModuleAddress;
|
|
|
|
return userOp;
|
|
}
|
|
|
|
export async function enableNewTreeForSmartAccountViaEcdsa(
|
|
leaves: BytesLike[],
|
|
sessionKeyManager: Contract,
|
|
SmartAccountAddress: string,
|
|
smartAccountOwner: Signer,
|
|
entryPoint: EntryPoint,
|
|
ecdsaModuleAddress: string
|
|
): Promise<MerkleTree> {
|
|
const merkleTree = new MerkleTree(leaves, keccak256, {
|
|
sortPairs: true,
|
|
hashLeaves: false,
|
|
});
|
|
const addMerkleRootUserOp = await makeEcdsaModuleUserOp(
|
|
"execute_ncC",
|
|
[
|
|
sessionKeyManager.address,
|
|
ethers.utils.parseEther("0"),
|
|
sessionKeyManager.interface.encodeFunctionData("setMerkleRoot", [
|
|
merkleTree.getHexRoot(),
|
|
]),
|
|
],
|
|
SmartAccountAddress,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
ecdsaModuleAddress
|
|
);
|
|
const tx = await entryPoint.handleOps(
|
|
[addMerkleRootUserOp],
|
|
await smartAccountOwner.getAddress()
|
|
);
|
|
await tx.wait();
|
|
|
|
return merkleTree;
|
|
}
|
|
|
|
export async function addLeavesForSmartAccountViaEcdsa(
|
|
merkleTree: MerkleTree,
|
|
newLeaves: any[],
|
|
sessionKeyManager: Contract,
|
|
SmartAccountAddress: string,
|
|
smartAccountOwner: Signer,
|
|
entryPoint: EntryPoint,
|
|
ecdsaModuleAddress: string
|
|
): Promise<MerkleTree> {
|
|
// rebuilding the tree instead of doing .addLeaves to make sure tree is always sorted
|
|
// as it is always considered as sorted in OZ Merkle Tree implementation
|
|
const leaves = merkleTree.getHexLeaves();
|
|
const sumLeaves = leaves.concat(newLeaves);
|
|
const newMerkleTree = new MerkleTree(sumLeaves, keccak256, {
|
|
sortPairs: true,
|
|
hashLeaves: false,
|
|
});
|
|
|
|
const addMerkleRootUserOp = await makeEcdsaModuleUserOp(
|
|
"execute_ncC",
|
|
[
|
|
sessionKeyManager.address,
|
|
ethers.utils.parseEther("0"),
|
|
sessionKeyManager.interface.encodeFunctionData("setMerkleRoot", [
|
|
newMerkleTree.getHexRoot(),
|
|
]),
|
|
],
|
|
SmartAccountAddress,
|
|
smartAccountOwner,
|
|
entryPoint,
|
|
ecdsaModuleAddress
|
|
);
|
|
const tx = await entryPoint.handleOps(
|
|
[addMerkleRootUserOp],
|
|
await smartAccountOwner.getAddress()
|
|
);
|
|
await tx.wait();
|
|
|
|
return newMerkleTree;
|
|
}
|
|
|
|
export async function getERC20SessionKeyParams(
|
|
sessionKey: string,
|
|
erc20TokenAddress: string,
|
|
receiverAddress: string,
|
|
maxAmountToTransfer: BigNumber,
|
|
validUntil: number,
|
|
validAfter: number,
|
|
sessionValidationModuleAddress: string
|
|
): Promise<SessionKeyParams> {
|
|
const sessionKeyData = defaultAbiCoder.encode(
|
|
["address", "address", "address", "uint256"],
|
|
[
|
|
sessionKey,
|
|
erc20TokenAddress,
|
|
receiverAddress,
|
|
maxAmountToTransfer.toHexString(),
|
|
]
|
|
);
|
|
|
|
const leafData = hexConcat([
|
|
hexZeroPad(ethers.utils.hexlify(validUntil), 6),
|
|
hexZeroPad(ethers.utils.hexlify(validAfter), 6),
|
|
hexZeroPad(sessionValidationModuleAddress, 20),
|
|
sessionKeyData,
|
|
]);
|
|
|
|
const params: SessionKeyParams = {
|
|
sessionKeyData: sessionKeyData,
|
|
leafData: leafData,
|
|
};
|
|
return params;
|
|
}
|