diff --git a/advanced/wallets/react-wallet-v2/package.json b/advanced/wallets/react-wallet-v2/package.json index 4193c608d..dc7c4c814 100644 --- a/advanced/wallets/react-wallet-v2/package.json +++ b/advanced/wallets/react-wallet-v2/package.json @@ -28,7 +28,7 @@ "@nextui-org/react": "1.0.8-beta.5", "@polkadot/keyring": "^10.1.2", "@polkadot/types": "^9.3.3", - "@rhinestone/module-sdk": "0.1.15", + "@rhinestone/module-sdk": "0.1.16", "@solana/web3.js": "1.89.2", "@taquito/signer": "^15.1.0", "@taquito/taquito": "^15.1.0", diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts index 48b0d3bd9..efb6a6c12 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts @@ -12,7 +12,9 @@ import { WalletGrantPermissionsParameters, createWalletClient, encodeFunctionData, + getAddress, http, + parseAbi, type WalletGrantPermissionsReturnType } from 'viem' import { MultiKeySigner } from 'viem/_types/experimental/erc7715/types/signer' @@ -20,8 +22,16 @@ import { ModuleType } from 'permissionless/actions/erc7579' import { MOCK_VALIDATOR_ADDRESSES } from './builders/SmartSessionUtil' import { Permission } from '@/data/EIP7715Data' import { getSmartSessionContext } from './builders/ContextBuilderUtil' -const { SMART_SESSIONS_ADDRESS, getAccount } = - require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') +import { readContract } from 'viem/actions' +import { Execution, Module } from '@rhinestone/module-sdk' + +const { + SMART_SESSIONS_ADDRESS, + REGISTRY_ADDRESS, + getTrustAttestersAction, + getAccount, + getSmartSessionsValidator +} = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk') export class SafeSmartAccountLib extends SmartAccountLib { protected ERC_7579_LAUNCHPAD_ADDRESS: Address = '0xEBe001b3D534B9B6E2500FB78E67a1A137f561CE' protected SAFE_4337_MODULE_ADDRESS: Address = '0x3Fdb5BC686e861480ef99A6E3FaAe03c0b9F32e2' @@ -102,8 +112,9 @@ export class SafeSmartAccountLib extends SmartAccountLib { /** * Check Safe7579 Account is ready for processing this RPC request * - Check Account is deployed + * - Check SmartSession Attesters are trusted * - Check Permission Validator & Mock Validator modules are installed - * If not, Deploy and installed all necessary module for processing this RPC request + * If not, Deploy and installed all necessary module and enable trusted attester if not trusted for processing this RPC request * @returns */ private async ensureAccountReadyForGrantPermissions(): Promise { @@ -111,12 +122,12 @@ export class SafeSmartAccountLib extends SmartAccountLib { throw new Error('Client not initialized') } try { - const isAccountDeployed = await isSmartAccountDeployed( - this.publicClient, - this.client.account.address - ) - // TODO: check if account trust the attesters of the module - await this.trustAttesters() + const setUpSmartAccountForSmartSession: Execution[] = [] + + const [isAccountDeployed, doesSmartAccountTrustSmartSessionAttesters] = await Promise.all([ + isSmartAccountDeployed(this.publicClient, this.client.account.address), + this.isSmartAccountTrustSmartSessionAttesters() + ]) let smartSessionValidatorInstalled = false let mockValidatorInstalled = false @@ -130,36 +141,64 @@ export class SafeSmartAccountLib extends SmartAccountLib { } console.log({ smartSessionValidatorInstalled, mockValidatorInstalled }) - if (isAccountDeployed && smartSessionValidatorInstalled && mockValidatorInstalled) { + if ( + isAccountDeployed && + smartSessionValidatorInstalled && + mockValidatorInstalled && + doesSmartAccountTrustSmartSessionAttesters + ) { console.log('Account is already set up with required modules') return } console.log('Setting up the Account with required modules') - const installModules: { - address: Address - type: ModuleType - context: Hex - }[] = [] - if (!isAccountDeployed || !smartSessionValidatorInstalled) { - installModules.push({ - address: SMART_SESSIONS_ADDRESS, - type: 'validator', - context: '0x' - }) + const smartSessionValidator: Module = { + module: SMART_SESSIONS_ADDRESS, + type: 'validator' + } + const installSmartSessionValidatorAction = this.getInstallModuleAction( + this.client.account.address, + smartSessionValidator + ) + setUpSmartAccountForSmartSession.push(installSmartSessionValidatorAction) } if (!isAccountDeployed || !mockValidatorInstalled) { - installModules.push({ - address: MOCK_VALIDATOR_ADDRESSES[this.chain.id], - type: 'validator', - context: '0x' + const mockSignatureValidator: Module = { + module: MOCK_VALIDATOR_ADDRESSES[this.chain.id], + type: 'validator' + } + const installMockSignatureValidatorAction = this.getInstallModuleAction( + this.client.account.address, + mockSignatureValidator + ) + setUpSmartAccountForSmartSession.push(installMockSignatureValidatorAction) + } + + if (!doesSmartAccountTrustSmartSessionAttesters) { + console.log('Smart Account do not trusted the attesters of the smartsessions module') + console.log('Enable trusting the attesters of the smartsessions module') + const trustAttestersAction = getTrustAttestersAction({ + attesters: ['0xA4C777199658a41688E9488c4EcbD7a2925Cc23A'], + threshold: 1 }) + setUpSmartAccountForSmartSession.push(trustAttestersAction) } - await this.installModules(installModules) + console.log('Setting up the Account with Executions', { setUpSmartAccountForSmartSession }) + const userOpHash = await this.sendBatchTransaction( + setUpSmartAccountForSmartSession.map(action => { + return { + to: action.target, + value: action.value.valueOf(), + data: action.callData + } + }) + ) + const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) + console.log(`Account setup receipt:`, receipt) console.log('Account setup completed') } catch (error) { console.error(`Error ensuring account is ready for grant permissions: ${error}`) @@ -179,52 +218,82 @@ export class SafeSmartAccountLib extends SmartAccountLib { }) } - private async installModules( - modules: { - address: Address - type: ModuleType - context: Hex - }[] - ): Promise { - if (!this.client?.account) { - throw new Error('Client not initialized') - } - const userOpHash = await this.client.installModules({ - account: this.client.account, - modules: modules - }) - const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) - console.log(`Module installation receipt:`, receipt) - } - - private async trustAttesters(): Promise { - if (!this.client?.account) { - throw new Error('Client not initialized') - } - const trustAttestersAction = { - to: '0x000000000069E2a187AEFFb852bF3cCdC95151B2' as Address, // mock-registry + private getInstallModuleAction(accountAddress: Address, module: Module): Execution { + return { + target: accountAddress, value: BigInt(0), - data: encodeFunctionData({ + callData: encodeFunctionData({ abi: [ { - inputs: [ - { type: 'uint8', name: 'threshold' }, - { type: 'address[]', name: 'attesters' } - ], - name: 'trustAttesters', + name: 'installModule', type: 'function', stateMutability: 'nonpayable', + inputs: [ + { + type: 'uint256', + name: 'moduleTypeId' + }, + { + type: 'address', + name: 'module' + }, + { + type: 'bytes', + name: 'initData' + } + ], outputs: [] } ], - functionName: 'trustAttesters', - args: [1, ['0xA4C777199658a41688E9488c4EcbD7a2925Cc23A']] + functionName: 'installModule', + args: [ + this.parseModuleTypeId(module.type), + getAddress(module.module), + module.initData || '0x' + ] }) } + } - const userOpHash = await this.sendTransaction(trustAttestersAction) - console.log(`Trust Attesters userOpHash:`, userOpHash) - // const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash }) - // console.log(`Trust Attesters receipt:`, receipt) + private async isSmartAccountTrustSmartSessionAttesters(): Promise { + if (!this.client?.account) { + throw new Error('Client not initialized') + } + + const TRUSTED_SMART_SESSIONS_ATTERSTER_ADDRESS = '0xA4C777199658a41688E9488c4EcbD7a2925Cc23A' + const attesters = await readContract(this.publicClient, { + address: REGISTRY_ADDRESS, + abi: parseAbi([ + 'function findTrustedAttesters(address smartAccount) view returns (address[])' + ]), + functionName: 'findTrustedAttesters', + args: [this.client.account.address] + }) + + if (attesters.length > 0) { + return Boolean( + attesters.find( + (attester: Address) => + attester.toLowerCase() === TRUSTED_SMART_SESSIONS_ATTERSTER_ADDRESS.toLowerCase() + ) + ) + } + + return false + } + + private parseModuleTypeId(type: ModuleType): bigint { + switch (type) { + case 'validator': + return BigInt(1) + case 'executor': + return BigInt(2) + case 'fallback': + return BigInt(3) + case 'hook': + return BigInt(4) + default: + throw new Error('Invalid module type') + } } } diff --git a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts index 09c6ae6f2..a18663ba8 100644 --- a/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts +++ b/advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts @@ -6,12 +6,13 @@ import { } from './SmartSessionUtil' import type { Session, ChainSession, Account } from '@rhinestone/module-sdk' const { + SMART_SESSIONS_ADDRESS, + SmartSessionMode, getPermissionId, getSessionDigest, getSessionNonce, - SMART_SESSIONS_ADDRESS, + encode1271Hash, encodeSmartSessionSignature, - SmartSessionMode, hashChainSessions, encodeUseOrEnableSmartSessionSignature, decodeSmartSessionSignature @@ -115,7 +116,7 @@ export async function getSmartSessionContext({ client: publicClient, session })) as Hex - + console.log('permissionId', permissionId) const sessionNonce = await getSessionNonce({ client: publicClient, account, @@ -150,15 +151,24 @@ export async function getSmartSessionContext({ } ] const permissionEnableHash = hashChainSessions(chainSessions) + + // const formattedHash = encode1271Hash({ + // account, + // chainId: chainId, + // validator: account.address, + // hash: permissionEnableHash + // }) + const permissionEnableSig = await walletClient.signMessage({ account: walletClient.account, + // message: { raw: formattedHash } message: { raw: permissionEnableHash } }) - + const encodedSmartSessionSignature = encodeSmartSessionSignature({ mode: SmartSessionMode.ENABLE, permissionId, - signature: '0xe8b94748580ca0b4993c9a1b86b5be851bfc076ff5ce3a1ff65bf16392acfcb800f9b4f1aef1555c7fce5599fffb17e7c635502154a0333ba21f3ae491839af51c', + signature: '0x', enableSessionData: { enableSession: { chainDigestIndex: 0, @@ -166,7 +176,7 @@ export async function getSmartSessionContext({ sessionToEnable: session, permissionEnableSig }, - validator: MOCK_VALIDATOR_ADDRESSES[chainId], //smartAccountAddress, + validator: MOCK_VALIDATOR_ADDRESSES[chainId], //account.address, accountType: 'safe' } }) @@ -279,6 +289,7 @@ function getSamplePermissions( chainId: number, { permissions, expiry }: { permissions: Permission[]; expiry: number } ): Session { + console.log({expiry}) return { sessionValidator: MULTIKEY_SIGNER_ADDRESSES[chainId], sessionValidatorInitData: encodeMultiKeySignerInitData(signers), diff --git a/advanced/wallets/react-wallet-v2/src/utils/EIP7715RequestHandlerUtils.ts b/advanced/wallets/react-wallet-v2/src/utils/EIP7715RequestHandlerUtils.ts index 8b1c71f8f..aa290dd47 100644 --- a/advanced/wallets/react-wallet-v2/src/utils/EIP7715RequestHandlerUtils.ts +++ b/advanced/wallets/react-wallet-v2/src/utils/EIP7715RequestHandlerUtils.ts @@ -6,17 +6,18 @@ import { EIP7715_METHOD } from '@/data/EIP7715Data' import { SafeSmartAccountLib } from '@/lib/smart-accounts/SafeSmartAccountLib' import { web3wallet } from './WalletConnectUtil' import { smartAccountWallets } from './SmartAccountUtil' -import { GrantPermissionsParameters, GrantPermissionsReturnType } from 'viem/experimental' import { KernelSmartAccountLib } from '@/lib/smart-accounts/KernelSmartAccountLib' import { WalletGrantPermissionsParameters, WalletGrantPermissionsReturnType } from 'viem' type RequestEventArgs = Omit -function getSmartWalletAddressFromSession(requestSession: SessionTypes.Struct) { - const sessionAccounts = requestSession.namespaces['eip155'].accounts - const sessionAccountsAddress = sessionAccounts.map(value => value.split(':').slice(1).join(':')) +function getSmartWalletAddressFromSession(requestSession: SessionTypes.Struct, chainId: string) { + const sessionAccounts = requestSession.namespaces['eip155'].accounts.filter(value => + value.startsWith(chainId) + ) + const sessionAccountsAddresses = sessionAccounts.map(value => value.split(':').slice(1).join(':')) const smartAccounts = Object.keys(smartAccountWallets) const smartWalletAddress = smartAccounts.find(smartAccount => - sessionAccountsAddress.some(address => address.toLowerCase() === smartAccount.toLowerCase()) + sessionAccountsAddresses.some(address => address.toLowerCase() === smartAccount.toLowerCase()) ) if (!smartWalletAddress) { @@ -44,7 +45,7 @@ export async function approveEIP7715Request(requestEvent: RequestEventArgs) { SettingsStore.setActiveChainId(chainId) switch (request.method) { case EIP7715_METHOD.WALLET_GRANT_PERMISSIONS: { - const wallet = getSmartWalletAddressFromSession(requestSession) + const wallet = getSmartWalletAddressFromSession(requestSession, chainId) let grantPermissionsRequestParams: WalletGrantPermissionsParameters = request.params[0] if (wallet instanceof SafeSmartAccountLib || wallet instanceof KernelSmartAccountLib) { const grantPermissionsResponse: WalletGrantPermissionsReturnType = diff --git a/advanced/wallets/react-wallet-v2/yarn.lock b/advanced/wallets/react-wallet-v2/yarn.lock index 7e8c1a43a..99d18514a 100644 --- a/advanced/wallets/react-wallet-v2/yarn.lock +++ b/advanced/wallets/react-wallet-v2/yarn.lock @@ -2077,12 +2077,13 @@ "@react-types/grid" "^3.2.6" "@react-types/shared" "^3.23.1" -"@rhinestone/module-sdk@0.1.15": - version "0.1.15" - resolved "https://registry.yarnpkg.com/@rhinestone/module-sdk/-/module-sdk-0.1.15.tgz#8d49af671ed39881caa28e0a97351a8bc9261580" - integrity sha512-9EHglokR1+NCj7CE37lFbzrsCgI7Anr6KLIwNdJ9Rs7cNAsyDNvbzDaY6FBlPbK9dk3bEZHfyphSVMJVvH+Siw== +"@rhinestone/module-sdk@0.1.16": + version "0.1.16" + resolved "https://registry.yarnpkg.com/@rhinestone/module-sdk/-/module-sdk-0.1.16.tgz#8bbc5ed193ad4f3656a0ac1aa1f438771734d182" + integrity sha512-Cn+JJ/8J6f5rk9ihagCoQ815bqve0YOpPCzkxEIqFzsyVYOuEUrEbX5ED9KJhoyyZSuAk2Ar8yxNGJBlS56RvA== dependencies: solady "^0.0.235" + tslib "^2.7.0" "@rushstack/eslint-patch@^1.1.3": version "1.6.0" @@ -6839,6 +6840,11 @@ tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.6.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"