Skip to content

Commit 73b21ce

Browse files
Merge pull request #408 from LIT-Protocol/feature/drel-1208-aave-ability-for-multiple-smart-accounts
Aave ability for generic smart accounts
2 parents 9a533a4 + 255527d commit 73b21ce

File tree

13 files changed

+171
-232
lines changed

13 files changed

+171
-232
lines changed

docs/smart-account/abilities/aave-smart-account.mdx

Lines changed: 32 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,20 @@ This Ability's `precheck` function validates a UserOperation without actually si
116116
*/
117117
userOp: {
118118
sender: string; // Smart Account address (hex)
119-
nonce?: string; // Account nonce (hex string, e.g., "0x1")
119+
nonce: string; // Account nonce (hex string, e.g., "0x1")
120120
callData: string; // Encoded transaction calls (hex)
121-
callGasLimit?: string; // Gas limit for execution (hex)
122-
verificationGasLimit?: string; // Gas for verification (hex)
123-
preVerificationGas?: string; // Pre-verification gas (hex)
124-
maxFeePerGas?: string; // Max fee per gas (hex, default: "0x59682F00")
125-
maxPriorityFeePerGas?: string; // Priority fee (hex, default: "0x3B9ACA00")
126-
signature?: string; // Can be empty for precheck
121+
callGasLimit: string; // Gas limit for execution (hex)
122+
verificationGasLimit: string; // Gas for verification (hex)
123+
preVerificationGas: string; // Pre-verification gas (hex)
124+
maxFeePerGas: string; // Max fee per gas (hex, default: "0x59682F00")
125+
maxPriorityFeePerGas: string; // Priority fee (hex, default: "0x3B9ACA00")
126+
signature?: string; // Can be empty
127127

128128
// ERC-4337 v0.7.0 Paymaster fields (separate, not combined)
129129
paymaster?: string; // Paymaster address (hex)
130130
paymasterData?: string; // Paymaster-specific data (hex)
131-
paymasterVerificationGasLimit?: string; // Paymaster verification gas (hex)
132-
paymasterPostOpGasLimit?: string; // Paymaster post-op gas (hex)
131+
paymasterVerificationGasLimit: string; // Paymaster verification gas (hex)
132+
paymasterPostOpGasLimit: string; // Paymaster post-op gas (hex)
133133

134134
// Account deployment fields (if account not yet deployed)
135135
factory?: string; // Account factory address (hex)
@@ -142,7 +142,7 @@ This Ability's `precheck` function validates a UserOperation without actually si
142142
/**
143143
* The Alchemy RPC URL for on-chain simulation and validation
144144
*/
145-
rpcUrl: string;
145+
alchemyRpcUrl: string;
146146
}
147147
```
148148

@@ -192,7 +192,7 @@ This Ability's `precheck` function validates a UserOperation without actually si
192192
paymasterPostOpGasLimit: '0xC350', // Optional
193193
},
194194
entryPointAddress: '0x0000000071727De22E5E9d8baF0edAc6f37da032', // EntryPoint v0.7
195-
rpcUrl: 'https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY',
195+
alchemyRpcUrl: 'https://base-sepolia.g.alchemy.com/v2/YOUR_API_KEY',
196196
};
197197

198198
const precheckResult = await abilityClient.precheck(smartAccountParams, {
@@ -202,9 +202,8 @@ This Ability's `precheck` function validates a UserOperation without actually si
202202
if (precheckResult.success) {
203203
console.log('UserOperation validation passed');
204204
// Result contains simulation changes and validated userOp
205-
const { simulationChanges, userOp } = precheckResult.result;
205+
const { simulationChanges } = precheckResult.result;
206206
console.log('Simulation changes:', simulationChanges);
207-
console.log('Validated UserOp:', userOp);
208207
} else {
209208
// Handle different types of failures
210209
if (precheckResult.runtimeError) {
@@ -244,26 +243,6 @@ This Ability's `precheck` function validates a UserOperation without actually si
244243
name?: string | null; // Token name
245244
logo?: string | null; // Token logo URL
246245
}>;
247-
/**
248-
* The validated UserOperation (may be modified during validation)
249-
*/
250-
userOp: {
251-
sender: string;
252-
nonce?: string;
253-
callData: string;
254-
callGasLimit?: string;
255-
verificationGasLimit?: string;
256-
preVerificationGas?: string;
257-
maxFeePerGas?: string;
258-
maxPriorityFeePerGas?: string;
259-
signature?: string;
260-
paymaster?: string;
261-
paymasterData?: string;
262-
paymasterVerificationGasLimit?: string;
263-
paymasterPostOpGasLimit?: string;
264-
factory?: string;
265-
factoryData?: string;
266-
};
267246
}
268247
```
269248

@@ -308,18 +287,18 @@ This Ability's `execute` function performs the same validation as precheck, then
308287
sender: string;
309288
nonce?: string;
310289
callData: string;
311-
callGasLimit?: string;
312-
verificationGasLimit?: string;
313-
preVerificationGas?: string;
314-
maxFeePerGas?: string;
315-
maxPriorityFeePerGas?: string;
290+
callGasLimit: string;
291+
verificationGasLimit: string;
292+
preVerificationGas: string;
293+
maxFeePerGas: string;
294+
maxPriorityFeePerGas: string;
316295
signature?: string;
317296

318297
// v0.7.0 Paymaster fields
319298
paymaster?: string;
320299
paymasterData?: string;
321-
paymasterVerificationGasLimit?: string;
322-
paymasterPostOpGasLimit?: string;
300+
paymasterVerificationGasLimit: string;
301+
paymasterPostOpGasLimit: string;
323302

324303
// Account deployment fields
325304
factory?: string;
@@ -332,19 +311,9 @@ This Ability's `execute` function performs the same validation as precheck, then
332311
/**
333312
* The Alchemy RPC URL for on-chain simulation and validation
334313
*/
335-
rpcUrl: string;
336-
/**
337-
* Serialized ZeroDev permission account (session key) that will sign the UserOperation
338-
* This is the permitted signer configured on the Smart Account
339-
*/
340-
serializedZeroDevPermissionAccount: string;
314+
alchemyRpcUrl: string;
341315
}
342316
```
343-
344-
**Important Notes:**
345-
- `serializedZeroDevPermissionAccount` is **REQUIRED** for execute (not needed for precheck)
346-
- This contains the session key configuration that authorizes the Agent Wallet to sign
347-
- All other parameters are identical to `precheck`
348317
</Tab>
349318

350319
<Tab title="Implementation">
@@ -355,21 +324,25 @@ This Ability's `execute` function performs the same validation as precheck, then
355324
const executeParams = {
356325
userOp: smartAccountParams.userOp,
357326
entryPointAddress: smartAccountParams.entryPointAddress,
358-
rpcUrl: smartAccountParams.rpcUrl,
359-
serializedZeroDevPermissionAccount: 'SERIALIZED_SESSION_KEY_HERE', // From ZeroDev session setup
327+
alchemyRpcUrl: smartAccountParams.rpcUrl,
360328
};
361329

362330
const executeResult = await abilityClient.execute(executeParams, {
363331
delegatorPkpEthAddress: '0x...', // Agent Wallet address
364332
});
365333

366334
if (executeResult.success) {
367-
const { simulationChanges, userOp } = executeResult.result;
335+
const { simulationChanges, signature } = executeResult.result;
368336

369337
console.log('UserOperation signed successfully');
370-
console.log('Signature:', userOp.signature);
338+
console.log('Signature:', signature);
371339
console.log('Simulation changes:', simulationChanges);
372340

341+
const userOp = {
342+
...smartAccountParams.userOp,
343+
signature,
344+
};
345+
373346
// Now broadcast the signed UserOperation via your bundler
374347
const txHash = await bundler.sendUserOperation(userOp);
375348
console.log('Transaction hash:', txHash);
@@ -391,7 +364,7 @@ This Ability's `execute` function performs the same validation as precheck, then
391364
<Tab title="Response">
392365
**Success Response**
393366

394-
A successful `execute` response will contain the same structure as `precheck`, but with the UserOperation now signed:
367+
A successful `execute` response will contain the same structure as `precheck`, but will add the signature:
395368

396369
```typescript
397370
{
@@ -413,25 +386,9 @@ This Ability's `execute` function performs the same validation as precheck, then
413386
logo?: string | null;
414387
}>;
415388
/**
416-
* The signed UserOperation ready for broadcast
389+
* The UserOperation signature by the PKP
417390
*/
418-
userOp: {
419-
sender: string;
420-
nonce?: string;
421-
callData: string;
422-
callGasLimit?: string;
423-
verificationGasLimit?: string;
424-
preVerificationGas?: string;
425-
maxFeePerGas?: string;
426-
maxPriorityFeePerGas?: string;
427-
signature: string; // ✅ Now contains valid signature
428-
paymaster?: string;
429-
paymasterData?: string;
430-
paymasterVerificationGasLimit?: string;
431-
paymasterPostOpGasLimit?: string;
432-
factory?: string;
433-
factoryData?: string;
434-
};
391+
signature: string;
435392
}
436393
```
437394

@@ -449,7 +406,7 @@ This Ability's `execute` function performs the same validation as precheck, then
449406
```
450407

451408
**Notes:**
452-
- The response structure is identical to `precheck`, except `userOp.signature` is now populated
409+
- The response structure is identical to `precheck`, except `signature` being added
453410
- The signed UserOperation can be broadcast directly to a bundler
454411
- `simulationChanges` provides the same asset change information
455412
</Tab>
@@ -475,10 +432,6 @@ Network support is limited by Alchemy's `alchemy_simulateUserOperationAssetChang
475432
This ability works with ERC-4337 compliant Smart Accounts. Ensure your Smart Account implementation follows the standard and has proper session key support.
476433
</Accordion>
477434

478-
<Accordion title="Session Key Setup" icon="key">
479-
The Agent Wallet must be properly configured as a session key with appropriate permissions on the Smart Account. Without proper session key setup, signature validation will fail.
480-
</Accordion>
481-
482435
<Accordion title="Validation Strictness" icon="shield-check">
483436
The ability validates that ALL interactions are with authorized Aave contracts. Any unauthorized contract call will cause validation to fail. This is a security feature to prevent malicious operations.
484437
</Accordion>

packages/apps/ability-aave-smart-account/package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
{
22
"name": "@lit-protocol/vincent-ability-aave-smart-account",
3-
"version": "0.0.9-mma",
3+
"version": "1.0.2-mma",
44
"publishConfig": {
55
"access": "public"
66
},
77
"dependencies": {
88
"@bgd-labs/aave-address-book": "^4.19.3",
99
"@lit-protocol/vincent-ability-sdk": "workspace:*",
10-
"@zerodev/permissions": "^5.6.2",
11-
"@zerodev/sdk": "^5.5.4",
1210
"tslib": "2.8.1",
1311
"viem": "^2.38.3",
1412
"zod": "^3.25.64"

packages/apps/ability-aave-smart-account/src/generated/lit-action.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"ipfsCid": "QmYtWuxxsUoRk59TxcN6Z1qHKWBmjiQESAhs6g3tNt7GUy"
2+
"ipfsCid": "QmNNg7ygdUFDMqJC5GadYnLf26o5U6rDiPCmo1CuuoJWUt"
33
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
11
export { bundledVincentAbility } from './generated/vincent-bundled-ability';
2+
export {
3+
AAVE_POOL_ABI,
4+
CHAIN_TO_AAVE_ADDRESS_BOOK,
5+
getAaveAddresses,
6+
getAvailableMarkets,
7+
getATokens,
8+
} from './lib/helpers/aave';

packages/apps/ability-aave-smart-account/src/lib/helpers/aave.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const AAVE_POOL_ABI: Abi = [
115115
/**
116116
* Chain id to Aave Address Book mapping
117117
*/
118-
const CHAIN_TO_AAVE_ADDRESS_BOOK: Record<number, any> = {
118+
export const CHAIN_TO_AAVE_ADDRESS_BOOK: Record<number, any> = {
119119
// Mainnets
120120
1: AaveV3Ethereum,
121121
137: AaveV3Polygon,

packages/apps/ability-aave-smart-account/src/lib/helpers/decoding.ts

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { KernelV3_3AccountAbi } from '@zerodev/sdk';
21
import {
2+
type Abi,
33
type Address,
44
type Hex,
55
decodeAbiParameters,
@@ -16,17 +16,87 @@ import { UserOp } from './userOperation';
1616

1717
type LowLevelCall = { to: Address; value: bigint; data: Hex };
1818

19+
// A big unified ABI of different smart account implementations
20+
const smartAccountsAbi: Abi = [
21+
// Kernel v3.3/v3.1/v3.0
22+
{
23+
type: 'function',
24+
name: 'execute',
25+
inputs: [
26+
{ name: 'execMode', type: 'bytes32', internalType: 'ExecMode' },
27+
{ name: 'executionCalldata', type: 'bytes', internalType: 'bytes' },
28+
],
29+
outputs: [],
30+
stateMutability: 'payable',
31+
},
32+
{
33+
type: 'function',
34+
name: 'executeFromExecutor',
35+
inputs: [
36+
{ name: 'execMode', type: 'bytes32', internalType: 'ExecMode' },
37+
{ name: 'executionCalldata', type: 'bytes', internalType: 'bytes' },
38+
],
39+
outputs: [{ name: 'returnData', type: 'bytes[]', internalType: 'bytes[]' }],
40+
stateMutability: 'payable',
41+
},
42+
{
43+
type: 'function',
44+
name: 'executeUserOp',
45+
inputs: [
46+
{
47+
name: 'userOp',
48+
type: 'tuple',
49+
internalType: 'struct PackedUserOperation',
50+
components: [
51+
{
52+
name: 'sender',
53+
type: 'address',
54+
internalType: 'address',
55+
},
56+
{ name: 'nonce', type: 'uint256', internalType: 'uint256' },
57+
{ name: 'initCode', type: 'bytes', internalType: 'bytes' },
58+
{ name: 'callData', type: 'bytes', internalType: 'bytes' },
59+
{
60+
name: 'accountGasLimits',
61+
type: 'bytes32',
62+
internalType: 'bytes32',
63+
},
64+
{
65+
name: 'preVerificationGas',
66+
type: 'uint256',
67+
internalType: 'uint256',
68+
},
69+
{
70+
name: 'gasFees',
71+
type: 'bytes32',
72+
internalType: 'bytes32',
73+
},
74+
{
75+
name: 'paymasterAndData',
76+
type: 'bytes',
77+
internalType: 'bytes',
78+
},
79+
{ name: 'signature', type: 'bytes', internalType: 'bytes' },
80+
],
81+
},
82+
{ name: 'userOpHash', type: 'bytes32', internalType: 'bytes32' },
83+
],
84+
outputs: [],
85+
stateMutability: 'payable',
86+
},
87+
];
88+
1989
function isDelegatecallOrUnknown(execMode: Hex): boolean {
2090
const lastByte = BigInt(execMode) & 0xffn;
2191
return lastByte !== 0n; // 0x00 == CALL; others => block
2292
}
2393

2494
function tryDecodeExecute(executionCalldata: Hex): LowLevelCall[] | null {
25-
// Because Execute with a single call is just an hex concatenation of the calldata,
26-
// this function might produce invalid decodings. It MUST be called last, only if
27-
// all other, more strict, decodings failed or if we know the calldata is a single call.
28-
// Also, current implementation does not support delegate calls, it will produce
29-
// invalid decodings for them.
95+
// Because Execute with a single call is just a hex concatenation of the calldata,
96+
// this function might produce invalid decoding. It MUST be called last, only if
97+
// all other, stricter, decodings failed or if we know the calldata is a single call.
98+
// Also, the current implementation does not support delegate calls. It will produce
99+
// an invalid decoding for them.
30100
try {
31101
const hex = executionCalldata.slice(2);
32102
const MIN = 20 * 2 + 32 * 2; // address + value
@@ -87,7 +157,7 @@ function tryDecodeBatchWithValues(executionCalldata: Hex): LowLevelCall[] | null
87157
}
88158

89159
function decodeKernelV33ToCalls(callData: Hex): LowLevelCall[] {
90-
const df = decodeFunctionData({ abi: KernelV3_3AccountAbi, data: callData });
160+
const df = decodeFunctionData({ abi: smartAccountsAbi, data: callData });
91161
if (df.functionName !== 'execute' && df.functionName !== 'executeFromExecutor') {
92162
throw new Error('Not a Kernel v3.3 execute/executor call');
93163
}

0 commit comments

Comments
 (0)