Skip to content

Commit e2b215c

Browse files
committed
attest and install modules in single userOp
1 parent 45d4694 commit e2b215c

File tree

5 files changed

+166
-79
lines changed

5 files changed

+166
-79
lines changed

advanced/wallets/react-wallet-v2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@nextui-org/react": "1.0.8-beta.5",
2929
"@polkadot/keyring": "^10.1.2",
3030
"@polkadot/types": "^9.3.3",
31-
"@rhinestone/module-sdk": "0.1.15",
31+
"@rhinestone/module-sdk": "0.1.16",
3232
"@solana/web3.js": "1.89.2",
3333
"@taquito/signer": "^15.1.0",
3434
"@taquito/taquito": "^15.1.0",

advanced/wallets/react-wallet-v2/src/lib/smart-accounts/SafeSmartAccountLib.ts

Lines changed: 131 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,26 @@ import {
1212
WalletGrantPermissionsParameters,
1313
createWalletClient,
1414
encodeFunctionData,
15+
getAddress,
1516
http,
17+
parseAbi,
1618
type WalletGrantPermissionsReturnType
1719
} from 'viem'
1820
import { MultiKeySigner } from 'viem/_types/experimental/erc7715/types/signer'
1921
import { ModuleType } from 'permissionless/actions/erc7579'
2022
import { MOCK_VALIDATOR_ADDRESSES } from './builders/SmartSessionUtil'
2123
import { Permission } from '@/data/EIP7715Data'
2224
import { getSmartSessionContext } from './builders/ContextBuilderUtil'
23-
const { SMART_SESSIONS_ADDRESS, getAccount } =
24-
require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk')
25+
import { readContract } from 'viem/actions'
26+
import { Execution, Module } from '@rhinestone/module-sdk'
27+
28+
const {
29+
SMART_SESSIONS_ADDRESS,
30+
REGISTRY_ADDRESS,
31+
getTrustAttestersAction,
32+
getAccount,
33+
getSmartSessionsValidator
34+
} = require('@rhinestone/module-sdk') as typeof import('@rhinestone/module-sdk')
2535
export class SafeSmartAccountLib extends SmartAccountLib {
2636
protected ERC_7579_LAUNCHPAD_ADDRESS: Address = '0xEBe001b3D534B9B6E2500FB78E67a1A137f561CE'
2737
protected SAFE_4337_MODULE_ADDRESS: Address = '0x3Fdb5BC686e861480ef99A6E3FaAe03c0b9F32e2'
@@ -102,21 +112,22 @@ export class SafeSmartAccountLib extends SmartAccountLib {
102112
/**
103113
* Check Safe7579 Account is ready for processing this RPC request
104114
* - Check Account is deployed
115+
* - Check SmartSession Attesters are trusted
105116
* - Check Permission Validator & Mock Validator modules are installed
106-
* If not, Deploy and installed all necessary module for processing this RPC request
117+
* If not, Deploy and installed all necessary module and enable trusted attester if not trusted for processing this RPC request
107118
* @returns
108119
*/
109120
private async ensureAccountReadyForGrantPermissions(): Promise<void> {
110121
if (!this.client?.account) {
111122
throw new Error('Client not initialized')
112123
}
113124
try {
114-
const isAccountDeployed = await isSmartAccountDeployed(
115-
this.publicClient,
116-
this.client.account.address
117-
)
118-
// TODO: check if account trust the attesters of the module
119-
await this.trustAttesters()
125+
const setUpSmartAccountForSmartSession: Execution[] = []
126+
127+
const [isAccountDeployed, doesSmartAccountTrustSmartSessionAttesters] = await Promise.all([
128+
isSmartAccountDeployed(this.publicClient, this.client.account.address),
129+
this.isSmartAccountTrustSmartSessionAttesters()
130+
])
120131

121132
let smartSessionValidatorInstalled = false
122133
let mockValidatorInstalled = false
@@ -130,36 +141,64 @@ export class SafeSmartAccountLib extends SmartAccountLib {
130141
}
131142
console.log({ smartSessionValidatorInstalled, mockValidatorInstalled })
132143

133-
if (isAccountDeployed && smartSessionValidatorInstalled && mockValidatorInstalled) {
144+
if (
145+
isAccountDeployed &&
146+
smartSessionValidatorInstalled &&
147+
mockValidatorInstalled &&
148+
doesSmartAccountTrustSmartSessionAttesters
149+
) {
134150
console.log('Account is already set up with required modules')
135151
return
136152
}
137153

138154
console.log('Setting up the Account with required modules')
139155

140-
const installModules: {
141-
address: Address
142-
type: ModuleType
143-
context: Hex
144-
}[] = []
145-
146156
if (!isAccountDeployed || !smartSessionValidatorInstalled) {
147-
installModules.push({
148-
address: SMART_SESSIONS_ADDRESS,
149-
type: 'validator',
150-
context: '0x'
151-
})
157+
const smartSessionValidator: Module = {
158+
module: SMART_SESSIONS_ADDRESS,
159+
type: 'validator'
160+
}
161+
const installSmartSessionValidatorAction = this.getInstallModuleAction(
162+
this.client.account.address,
163+
smartSessionValidator
164+
)
165+
setUpSmartAccountForSmartSession.push(installSmartSessionValidatorAction)
152166
}
153167

154168
if (!isAccountDeployed || !mockValidatorInstalled) {
155-
installModules.push({
156-
address: MOCK_VALIDATOR_ADDRESSES[this.chain.id],
157-
type: 'validator',
158-
context: '0x'
169+
const mockSignatureValidator: Module = {
170+
module: MOCK_VALIDATOR_ADDRESSES[this.chain.id],
171+
type: 'validator'
172+
}
173+
const installMockSignatureValidatorAction = this.getInstallModuleAction(
174+
this.client.account.address,
175+
mockSignatureValidator
176+
)
177+
setUpSmartAccountForSmartSession.push(installMockSignatureValidatorAction)
178+
}
179+
180+
if (!doesSmartAccountTrustSmartSessionAttesters) {
181+
console.log('Smart Account do not trusted the attesters of the smartsessions module')
182+
console.log('Enable trusting the attesters of the smartsessions module')
183+
const trustAttestersAction = getTrustAttestersAction({
184+
attesters: ['0xA4C777199658a41688E9488c4EcbD7a2925Cc23A'],
185+
threshold: 1
159186
})
187+
setUpSmartAccountForSmartSession.push(trustAttestersAction)
160188
}
161189

162-
await this.installModules(installModules)
190+
console.log('Setting up the Account with Executions', { setUpSmartAccountForSmartSession })
191+
const userOpHash = await this.sendBatchTransaction(
192+
setUpSmartAccountForSmartSession.map(action => {
193+
return {
194+
to: action.target,
195+
value: action.value.valueOf(),
196+
data: action.callData
197+
}
198+
})
199+
)
200+
const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash })
201+
console.log(`Account setup receipt:`, receipt)
163202
console.log('Account setup completed')
164203
} catch (error) {
165204
console.error(`Error ensuring account is ready for grant permissions: ${error}`)
@@ -179,52 +218,82 @@ export class SafeSmartAccountLib extends SmartAccountLib {
179218
})
180219
}
181220

182-
private async installModules(
183-
modules: {
184-
address: Address
185-
type: ModuleType
186-
context: Hex
187-
}[]
188-
): Promise<void> {
189-
if (!this.client?.account) {
190-
throw new Error('Client not initialized')
191-
}
192-
const userOpHash = await this.client.installModules({
193-
account: this.client.account,
194-
modules: modules
195-
})
196-
const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash })
197-
console.log(`Module installation receipt:`, receipt)
198-
}
199-
200-
private async trustAttesters(): Promise<void> {
201-
if (!this.client?.account) {
202-
throw new Error('Client not initialized')
203-
}
204-
const trustAttestersAction = {
205-
to: '0x000000000069E2a187AEFFb852bF3cCdC95151B2' as Address, // mock-registry
221+
private getInstallModuleAction(accountAddress: Address, module: Module): Execution {
222+
return {
223+
target: accountAddress,
206224
value: BigInt(0),
207-
data: encodeFunctionData({
225+
callData: encodeFunctionData({
208226
abi: [
209227
{
210-
inputs: [
211-
{ type: 'uint8', name: 'threshold' },
212-
{ type: 'address[]', name: 'attesters' }
213-
],
214-
name: 'trustAttesters',
228+
name: 'installModule',
215229
type: 'function',
216230
stateMutability: 'nonpayable',
231+
inputs: [
232+
{
233+
type: 'uint256',
234+
name: 'moduleTypeId'
235+
},
236+
{
237+
type: 'address',
238+
name: 'module'
239+
},
240+
{
241+
type: 'bytes',
242+
name: 'initData'
243+
}
244+
],
217245
outputs: []
218246
}
219247
],
220-
functionName: 'trustAttesters',
221-
args: [1, ['0xA4C777199658a41688E9488c4EcbD7a2925Cc23A']]
248+
functionName: 'installModule',
249+
args: [
250+
this.parseModuleTypeId(module.type),
251+
getAddress(module.module),
252+
module.initData || '0x'
253+
]
222254
})
223255
}
256+
}
224257

225-
const userOpHash = await this.sendTransaction(trustAttestersAction)
226-
console.log(`Trust Attesters userOpHash:`, userOpHash)
227-
// const receipt = await this.bundlerClient.waitForUserOperationReceipt({ hash: userOpHash })
228-
// console.log(`Trust Attesters receipt:`, receipt)
258+
private async isSmartAccountTrustSmartSessionAttesters(): Promise<boolean> {
259+
if (!this.client?.account) {
260+
throw new Error('Client not initialized')
261+
}
262+
263+
const TRUSTED_SMART_SESSIONS_ATTERSTER_ADDRESS = '0xA4C777199658a41688E9488c4EcbD7a2925Cc23A'
264+
const attesters = await readContract(this.publicClient, {
265+
address: REGISTRY_ADDRESS,
266+
abi: parseAbi([
267+
'function findTrustedAttesters(address smartAccount) view returns (address[])'
268+
]),
269+
functionName: 'findTrustedAttesters',
270+
args: [this.client.account.address]
271+
})
272+
273+
if (attesters.length > 0) {
274+
return Boolean(
275+
attesters.find(
276+
(attester: Address) =>
277+
attester.toLowerCase() === TRUSTED_SMART_SESSIONS_ATTERSTER_ADDRESS.toLowerCase()
278+
)
279+
)
280+
}
281+
282+
return false
283+
}
284+
285+
private parseModuleTypeId(type: ModuleType): bigint {
286+
switch (type) {
287+
case 'validator':
288+
return BigInt(1)
289+
case 'executor':
290+
return BigInt(2)
291+
case 'fallback':
292+
return BigInt(3)
293+
case 'hook':
294+
return BigInt(4)
295+
default:
296+
throw new Error('Invalid module type')
297+
}
229298
}
230299
}

advanced/wallets/react-wallet-v2/src/lib/smart-accounts/builders/ContextBuilderUtil.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import {
66
} from './SmartSessionUtil'
77
import type { Session, ChainSession, Account } from '@rhinestone/module-sdk'
88
const {
9+
SMART_SESSIONS_ADDRESS,
10+
SmartSessionMode,
911
getPermissionId,
1012
getSessionDigest,
1113
getSessionNonce,
12-
SMART_SESSIONS_ADDRESS,
14+
encode1271Hash,
1315
encodeSmartSessionSignature,
14-
SmartSessionMode,
1516
hashChainSessions,
1617
encodeUseOrEnableSmartSessionSignature,
1718
decodeSmartSessionSignature
@@ -115,7 +116,7 @@ export async function getSmartSessionContext({
115116
client: publicClient,
116117
session
117118
})) as Hex
118-
119+
console.log('permissionId', permissionId)
119120
const sessionNonce = await getSessionNonce({
120121
client: publicClient,
121122
account,
@@ -150,23 +151,32 @@ export async function getSmartSessionContext({
150151
}
151152
]
152153
const permissionEnableHash = hashChainSessions(chainSessions)
154+
155+
// const formattedHash = encode1271Hash({
156+
// account,
157+
// chainId: chainId,
158+
// validator: account.address,
159+
// hash: permissionEnableHash
160+
// })
161+
153162
const permissionEnableSig = await walletClient.signMessage({
154163
account: walletClient.account,
164+
// message: { raw: formattedHash }
155165
message: { raw: permissionEnableHash }
156166
})
157-
167+
158168
const encodedSmartSessionSignature = encodeSmartSessionSignature({
159169
mode: SmartSessionMode.ENABLE,
160170
permissionId,
161-
signature: '0xe8b94748580ca0b4993c9a1b86b5be851bfc076ff5ce3a1ff65bf16392acfcb800f9b4f1aef1555c7fce5599fffb17e7c635502154a0333ba21f3ae491839af51c',
171+
signature: '0x',
162172
enableSessionData: {
163173
enableSession: {
164174
chainDigestIndex: 0,
165175
hashesAndChainIds: chainDigests,
166176
sessionToEnable: session,
167177
permissionEnableSig
168178
},
169-
validator: MOCK_VALIDATOR_ADDRESSES[chainId], //smartAccountAddress,
179+
validator: MOCK_VALIDATOR_ADDRESSES[chainId], //account.address,
170180
accountType: 'safe'
171181
}
172182
})
@@ -279,6 +289,7 @@ function getSamplePermissions(
279289
chainId: number,
280290
{ permissions, expiry }: { permissions: Permission[]; expiry: number }
281291
): Session {
292+
console.log({expiry})
282293
return {
283294
sessionValidator: MULTIKEY_SIGNER_ADDRESSES[chainId],
284295
sessionValidatorInitData: encodeMultiKeySignerInitData(signers),

advanced/wallets/react-wallet-v2/src/utils/EIP7715RequestHandlerUtils.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import { EIP7715_METHOD } from '@/data/EIP7715Data'
66
import { SafeSmartAccountLib } from '@/lib/smart-accounts/SafeSmartAccountLib'
77
import { web3wallet } from './WalletConnectUtil'
88
import { smartAccountWallets } from './SmartAccountUtil'
9-
import { GrantPermissionsParameters, GrantPermissionsReturnType } from 'viem/experimental'
109
import { KernelSmartAccountLib } from '@/lib/smart-accounts/KernelSmartAccountLib'
1110
import { WalletGrantPermissionsParameters, WalletGrantPermissionsReturnType } from 'viem'
1211
type RequestEventArgs = Omit<SignClientTypes.EventArguments['session_request'], 'verifyContext'>
1312

14-
function getSmartWalletAddressFromSession(requestSession: SessionTypes.Struct) {
15-
const sessionAccounts = requestSession.namespaces['eip155'].accounts
16-
const sessionAccountsAddress = sessionAccounts.map(value => value.split(':').slice(1).join(':'))
13+
function getSmartWalletAddressFromSession(requestSession: SessionTypes.Struct, chainId: string) {
14+
const sessionAccounts = requestSession.namespaces['eip155'].accounts.filter(value =>
15+
value.startsWith(chainId)
16+
)
17+
const sessionAccountsAddresses = sessionAccounts.map(value => value.split(':').slice(1).join(':'))
1718
const smartAccounts = Object.keys(smartAccountWallets)
1819
const smartWalletAddress = smartAccounts.find(smartAccount =>
19-
sessionAccountsAddress.some(address => address.toLowerCase() === smartAccount.toLowerCase())
20+
sessionAccountsAddresses.some(address => address.toLowerCase() === smartAccount.toLowerCase())
2021
)
2122

2223
if (!smartWalletAddress) {
@@ -44,7 +45,7 @@ export async function approveEIP7715Request(requestEvent: RequestEventArgs) {
4445
SettingsStore.setActiveChainId(chainId)
4546
switch (request.method) {
4647
case EIP7715_METHOD.WALLET_GRANT_PERMISSIONS: {
47-
const wallet = getSmartWalletAddressFromSession(requestSession)
48+
const wallet = getSmartWalletAddressFromSession(requestSession, chainId)
4849
let grantPermissionsRequestParams: WalletGrantPermissionsParameters = request.params[0]
4950
if (wallet instanceof SafeSmartAccountLib || wallet instanceof KernelSmartAccountLib) {
5051
const grantPermissionsResponse: WalletGrantPermissionsReturnType =

0 commit comments

Comments
 (0)