diff --git a/packages/account/src/BaseSmartAccount.ts b/packages/account/src/BaseSmartAccount.ts index 21951550b..bbc5111c8 100644 --- a/packages/account/src/BaseSmartAccount.ts +++ b/packages/account/src/BaseSmartAccount.ts @@ -394,6 +394,7 @@ export abstract class BaseSmartAccount implements IBaseSmartAccount { * return the account's address. * this value is valid even before deploying the contract. */ + // Review: Probably should accept index as well as we rely on factory! async getAccountAddress(): Promise { if (this.accountAddress == null) { // means it needs deployment diff --git a/packages/account/src/BiconomySmartAccountV2.ts b/packages/account/src/BiconomySmartAccountV2.ts index f2d4d8a1a..ac7a89ddb 100644 --- a/packages/account/src/BiconomySmartAccountV2.ts +++ b/packages/account/src/BiconomySmartAccountV2.ts @@ -60,6 +60,7 @@ export class BiconomySmartAccountV2 extends BaseSmartAccount { factory?: SmartAccountFactory_v200 defaultValidationModule: BaseValidationModule + // Review: if it must be provided. default can be used as active activeValidationModule: BaseValidationModule constructor(readonly biconomySmartAccountConfig: BiconomySmartAccountV2Config) { diff --git a/packages/account/src/utils/Constants.ts b/packages/account/src/utils/Constants.ts index 7b5421721..87671e77c 100644 --- a/packages/account/src/utils/Constants.ts +++ b/packages/account/src/utils/Constants.ts @@ -18,17 +18,17 @@ export const ENTRYPOINT_ADDRESSES: EntryPointAddresses = { } // will always be latest factory address -export const DEFAULT_BICONOMY_FACTORY_ADDRESS = '0xD8E65814d5F528fa573eF9bb5Aa22817DEE3E1bf' +export const DEFAULT_BICONOMY_FACTORY_ADDRESS = '0xd4450c80F6D0518a987144e44CEd55ec9CbC7805' export const BICONOMY_FACTORY_ADDRESSES: BiconomyFactories = { '0x000000f9ee1842bb72f6bbdd75e6d3d4e3e9594c': 'V1_0_0', - '0xD8E65814d5F528fa573eF9bb5Aa22817DEE3E1bf': 'V2_0_0' + '0xd4450c80F6D0518a987144e44CEd55ec9CbC7805': 'V2_0_0' } // will always be latest implementation address -export const DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS = '0x9777a082B23C09f81cB23C2635cCb93603D1AF42' +export const DEFAULT_BICONOMY_IMPLEMENTATION_ADDRESS = '0x3FA4BE2a60F2Ac16f96DE47630784dD9FC9De77e' export const BICONOMY_IMPLEMENTATION_ADDRESSES: BiconomyImplementations = { '0x00006b7e42e01957da540dc6a8f7c30c4d816af5': 'V1_0_0', - '0x9777a082B23C09f81cB23C2635cCb93603D1AF42': 'V2_0_0' + '0x3FA4BE2a60F2Ac16f96DE47630784dD9FC9De77e': 'V2_0_0' } export const ENTRYPOINT_ADDRESSES_BY_VERSION: EntryPointAddressesByVersion = { @@ -38,12 +38,12 @@ export const ENTRYPOINT_ADDRESSES_BY_VERSION: EntryPointAddressesByVersion = { export const BICONOMY_FACTORY_ADDRESSES_BY_VERSION: BiconomyFactoriesByVersion = { V1_0_0: '0x000000f9ee1842bb72f6bbdd75e6d3d4e3e9594c', - V2_0_0: '0x2642d30cebafeb1da6bc6e3c2cfce0e3199eff19' + V2_0_0: '0xd4450c80F6D0518a987144e44CEd55ec9CbC7805' } export const BICONOMY_IMPLEMENTATION_ADDRESSES_BY_VERSION: BiconomyImplementationsByVersion = { V1_0_0: '0x00006b7e42e01957da540dc6a8f7c30c4d816af5', - V2_0_0: '0xf1080f5f874ea8170e423738791e0e9a8aad87fd' + V2_0_0: '0x3FA4BE2a60F2Ac16f96DE47630784dD9FC9De77e' } export const EIP1559_UNSUPPORTED_NETWORKS: Array = [97, 56, 1442, 1101] diff --git a/packages/modules/src/ECDSAOwnershipValidationModule.ts b/packages/modules/src/ECDSAOwnershipValidationModule.ts index bebcb26a6..856007bb8 100644 --- a/packages/modules/src/ECDSAOwnershipValidationModule.ts +++ b/packages/modules/src/ECDSAOwnershipValidationModule.ts @@ -46,9 +46,10 @@ export class ECDSAOwnershipValidationModule extends BaseValidationModule { return await Promise.resolve(this.signer) } - // TODO: Actually depends on the module address so maybe we can make it dynamic getDummySignature(): string { - return '0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000F993fc8Dc0EE7aece7abf0d6B6939f9d67875dBa000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000' + const moduleAddress = ethers.utils.getAddress(this.getAddress()) + const dynamicPart = moduleAddress.substring(2).padEnd(40, '0') + return `0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000${dynamicPart}000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000` } // Note: other modules may need additional attributes to build init data diff --git a/packages/modules/src/MultichainValidationModule.ts b/packages/modules/src/MultichainValidationModule.ts index e69de29bb..a5b347746 100644 --- a/packages/modules/src/MultichainValidationModule.ts +++ b/packages/modules/src/MultichainValidationModule.ts @@ -0,0 +1,136 @@ +import { UserOperation } from '@biconomy/core-types' +import { Logger, getUserOpHash } from '@biconomy/common' +import { Signer, ethers } from 'ethers' +import MerkleTree from 'merkletreejs' +import { MULTICHAIN_VALIDATION_MODULE_ADDRESSES_BY_VERSION } from './utils/Constants' +import { + keccak256, + arrayify, + defaultAbiCoder, + hexConcat, + hexZeroPad, + Bytes +} from 'ethers/lib/utils' +import { ModuleVersion, MultiChainUserOpDto, MultiChainValidationModuleConfig } from './utils/Types' +import { BaseValidationModule } from './BaseValidationModule' +export class MultiChainValidationModule extends BaseValidationModule { + signer: Signer + moduleAddress!: string + version: ModuleVersion = 'V1_0_0' + + constructor(moduleConfig: MultiChainValidationModuleConfig) { + super(moduleConfig) + if (moduleConfig.moduleAddress) { + this.moduleAddress = moduleConfig.moduleAddress + } else if (moduleConfig.version) { + const moduleAddr = MULTICHAIN_VALIDATION_MODULE_ADDRESSES_BY_VERSION[moduleConfig.version] + if (!moduleAddr) { + throw new Error(`Invalid version ${moduleConfig.version}`) + } + this.moduleAddress = moduleAddr + this.version = moduleConfig.version as ModuleVersion + } + this.signer = moduleConfig.signer + } + + getAddress(): string { + return this.moduleAddress + } + + async getSigner(): Promise { + return await Promise.resolve(this.signer) + } + + getDummySignature(): string { + const moduleAddress = ethers.utils.getAddress(this.getAddress()) + const dynamicPart = moduleAddress.substring(2).padEnd(40, '0') + return `0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000${dynamicPart}000000000000000000000000000000000000000000000000000000000000004181d4b4981670cb18f99f0b4a66446df1bf5b204d24cfcb659bf38ba27a4359b5711649ec2423c5e1247245eba2964679b6a1dbb85c992ae40b9b00c6935b02ff1b00000000000000000000000000000000000000000000000000000000000000` + } + + // Note: other modules may need additional attributes to build init data + async getInitData(): Promise { + const ecdsaOwnerAddress = await this.signer.getAddress() + const moduleRegistryAbi = 'function initForSmartAccount(address owner)' + const ecdsaModuleRegistryInterface = new ethers.utils.Interface([moduleRegistryAbi]) + const ecdsaOwnershipInitData = ecdsaModuleRegistryInterface.encodeFunctionData( + 'initForSmartAccount', + [ecdsaOwnerAddress] + ) + return ecdsaOwnershipInitData + } + + async signUserOp(userOp: UserOperation): Promise { + Logger.log('userOp', userOp) + throw new Error('Method not implemented.') + } + + async signMessage(message: Bytes | string): Promise { + Logger.log('message', message) + throw new Error('Method not implemented.') + } + + async signUserOps(multiChainUserOps: MultiChainUserOpDto[]): Promise { + try { + const leaves: string[] = [] + + // Iterate over each userOp and process them + for (const multiChainOp of multiChainUserOps) { + const validUntil = multiChainOp.validUntil ?? 0 + const validAfter = multiChainOp.validAfter ?? 0 + const leaf = hexConcat([ + hexZeroPad(ethers.utils.hexlify(validUntil), 6), + hexZeroPad(ethers.utils.hexlify(validAfter), 6), + hexZeroPad( + getUserOpHash(multiChainOp.userOp, this.entryPointAddress, multiChainOp.chainId), + 32 + ) + ]) + + leaves.push(keccak256(leaf)) + } + + // Create a new Merkle tree using the leaves array + const merkleTree = new MerkleTree(leaves, keccak256, { sortPairs: true }) + + const multichainSignature = await this.signer.signMessage(arrayify(merkleTree.getHexRoot())) + + // Create an array to store updated userOps + const updatedUserOps: UserOperation[] = [] + + Logger.log('merkle root ', merkleTree.getHexRoot()) + + for (let i = 0; i < leaves.length; i++) { + const merkleProof = merkleTree.getHexProof(leaves[i]) + + Logger.log('merkle proof ', merkleProof) + + const validUntil = multiChainUserOps[i].validUntil ?? 0 + const validAfter = multiChainUserOps[i].validAfter ?? 0 + + // Create the moduleSignature + const moduleSignature = defaultAbiCoder.encode( + ['uint48', 'uint48', 'bytes32', 'bytes32[]', 'bytes'], + [validUntil, validAfter, merkleTree.getHexRoot(), merkleProof, multichainSignature] + ) + + // add validation module address to the signature + const signatureWithModuleAddress = defaultAbiCoder.encode( + ['bytes', 'address'], + [moduleSignature, this.getAddress()] + ) + + // Update userOp with the final signature + const updatedUserOp: UserOperation = { + ...(multiChainUserOps[i].userOp as UserOperation), + signature: signatureWithModuleAddress + } + + updatedUserOps.push(updatedUserOp) + } + return updatedUserOps + } catch (error) { + Logger.error('Error in signing multi chain userops', error) + throw new Error(JSON.stringify(error)) + } + } +} diff --git a/packages/modules/src/index.ts b/packages/modules/src/index.ts index 21e718eac..d4f43e1b4 100644 --- a/packages/modules/src/index.ts +++ b/packages/modules/src/index.ts @@ -1,6 +1,6 @@ export * from './interfaces/IValidationModule' export * from './BaseValidationModule' export * from './ECDSAOwnershipValidationModule' +export * from './MultichainValidationModule' // export * from './SessionKeyManagerModule' -// export * from './MultiChainValidationModule' // export * from './PasskeyValidationModule' diff --git a/packages/modules/src/utils/Constants.ts b/packages/modules/src/utils/Constants.ts index b7da723c2..47e9563a6 100644 --- a/packages/modules/src/utils/Constants.ts +++ b/packages/modules/src/utils/Constants.ts @@ -13,17 +13,23 @@ export const ENTRYPOINT_ADDRESSES_BY_VERSION = { V0_0_6: '0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789' } -export const DEFAULT_ECDSA_OWNERSHIP_MODULE = '0xF993fc8Dc0EE7aece7abf0d6B6939f9d67875dBa' +export const DEFAULT_ECDSA_OWNERSHIP_MODULE = '0x000D19910aAd41540669EfBf720f5dE69fCAc2e4' export const ECDSA_OWNERSHIP_MODULE_ADDRESSES_BY_VERSION = { - V1_0_0: '0xd9cf3caaa21db25f16ad6db43eb9932ab77c8e76' - // 'V1_0_1' : '0xd9cf3caaa21db25f16ad6db43eb9932ab77c8e76' + V1_0_0: '0x000D19910aAd41540669EfBf720f5dE69fCAc2e4' + // 'V1_0_1' : '0x000D19910aAd41540669EfBf720f5dE69fCAc2e4' // and so on } export const SESSION_MANAGER_MODULE_ADDRESSES_BY_VERSION = { - V1_0_0: '0xd9cf3caaa21db25f16ad6db43eb9932ab77c8e76' - // 'V1_0_1' : '0xd9cf3caaa21db25f16ad6db43eb9932ab77c8e76' + V1_0_0: '0xc1918869FcC4584ec9f29BB82a55aC07B8048cf2' + // 'V1_0_1' : '0xc1918869FcC4584ec9f29BB82a55aC07B8048cf2' + // and so on +} + +export const MULTICHAIN_VALIDATION_MODULE_ADDRESSES_BY_VERSION = { + V1_0_0: '0x2E817fe3749B81dA801fc08B247E081ec20eB080' + // 'V1_0_1' : '0x2E817fe3749B81dA801fc08B247E081ec20eB080' // and so on } diff --git a/packages/modules/src/utils/Types.ts b/packages/modules/src/utils/Types.ts index 0c77a35e6..432a2cbf5 100644 --- a/packages/modules/src/utils/Types.ts +++ b/packages/modules/src/utils/Types.ts @@ -1,4 +1,4 @@ -import { ChainId } from '@biconomy/core-types' +import { ChainId, UserOperation } from '@biconomy/core-types' import { Signer } from 'ethers' export type ModuleVersion = 'V1_0_0' // | 'V1_0_1' @@ -27,5 +27,11 @@ export interface MultiChainValidationModuleConfig extends BaseValidationModuleCo moduleAddress?: string version?: ModuleVersion signer: Signer +} + +export type MultiChainUserOpDto = { + validUntil?: number + validAfter?: number chainId: ChainId + userOp: Partial }