Skip to content

Commit e49f1f6

Browse files
FedericoAmuraglitch003Copilot
authored
Aave ability for smart accounts (#351)
* feat: initial implementation with precheck of aave ability for smart accounts using alchemys light account as model * fix: make rpc url required * feat: execute method with signing for tested user operation * fix: pnpm lockfile * feat: remove unused libraries and organize code * fix: pkp user op signature handling * fix: tests configuration * fix: more tests configuration * fix: get user operation version based on the entrypoint address * feat: make initCode mandatory for non-initialized accounts to avoid restricting support for specific implementations * fix: lines with expected typescript errors in tests * feat: move simulateUserOp * fix: hex regex filtering values that had uppercase chars * feat: use static json rpc providers * feat: update ability to sign for zerodev v3.3 smart accounts after passing userop calldata decoding and simulation validations * feat: update pnpm lockfile * fix: remove ethers dependency on aave smart accounts ability * feat: update lockfile * fix: pass test when there are none * feat: update lockfile after merge * fix: bundled transactions in user operation decoding * feat: explicitly define alchemy rpc url * feat: add utilities to simulate eoa transactions * fix: build and publish latest version * feat: ensure alchemy rpc url is from alchemy and not a fake one controlled by the user * chore: merge main, run pnpm i * Update packages/apps/ability-aave-smart-account/scripts/deploy-lit-action.js Co-authored-by: Copilot <[email protected]> * Update packages/apps/ability-aave-smart-account/src/lib/helpers/aave.ts Co-authored-by: Copilot <[email protected]> * fix: remove duplicate user --------- Co-authored-by: Chris Cassano <[email protected]> Co-authored-by: Chris Cassano <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent d66a5e9 commit e49f1f6

40 files changed

+2455
-50
lines changed

packages/apps/ability-aave-smart-account/.env.example

Whitespace-only changes.

packages/apps/ability-aave-smart-account/.env.test.example

Whitespace-only changes.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
**/**/vincent-contract-caller-config.json
2+
!.env.test.example
3+
test/test-config.json
4+
#src/generated/**/*
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Contributing to Vincent Ability Aave for Smart Account (EIP-4337)
2+
3+
This document provides guidelines for contributing to the Vincent Ability Aave project.
4+
5+
## Overview
6+
7+
The Vincent Ability Aave is a ability to send deposit, withdraw or redeem Aave transactions from a Vincent app on behalf of the delegator. It's part of the Vincent Abilities ecosystem and is built using the Vincent Ability SDK.
8+
9+
## Setup
10+
11+
1. Follow the global setup instructions in the repository root [CONTRIBUTING.md](../../../CONTRIBUTING.md).
12+
2. Install dependencies:
13+
```bash
14+
pnpm install
15+
```
16+
17+
## Development Workflow
18+
19+
### Testing
20+
21+
Run tests:
22+
23+
```bash
24+
nx test ability-aave-smart-account
25+
```
26+
27+
### Building the Lit Action
28+
29+
Build the ability:
30+
31+
```bash
32+
nx action:build ability-aave-smart-account
33+
```
34+
35+
### Deploying the Lit Action to IPFS
36+
37+
Building will be done automatically. Deploy the ability:
38+
39+
```bash
40+
nx action:deploy ability-aave-smart-account
41+
```
42+
43+
## Project Structure
44+
45+
- `src/`: Source code
46+
- `index.ts`: Main entry point
47+
48+
## Ability Development Guidelines
49+
50+
1. Use the Vincent Ability SDK to create abilities
51+
2. Define clear schemas for ability parameters
52+
3. Implement the ability lifecycle methods (precheck, execute)
53+
4. Handle errors gracefully
54+
5. Write comprehensive tests for all functionality
55+
6. Document the ability's purpose and usage
56+
57+
## Integration with Policies
58+
59+
This ability can be integrated with various Vincent Policies to enforce constraints. When developing or modifying the ability, consider how it will be used with policies such as:
60+
61+
- Vincent Policy Spending Limit
62+
63+
## Testing
64+
65+
Write unit tests for all functionality:
66+
67+
```bash
68+
pnpm test
69+
```
70+
71+
## Documentation
72+
73+
- Document the ability's purpose and usage
74+
- Update README.md when adding new features
75+
- Document the ability's parameters and behavior
76+
77+
## Pull Request Process
78+
79+
1. Ensure your code follows the coding standards
80+
2. Update documentation if necessary
81+
3. Include tests for new functionality
82+
4. Link any related issues in your pull request description
83+
5. Request a review from a maintainer
84+
85+
## Additional Resources
86+
87+
- [Vincent Documentation](https://docs.heyvincent.ai/)
88+
- [Vincent Ability SDK Documentation](../../libs/ability-sdk/README.md)
89+
- [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20)
90+
- [ERC4626 Standard](https://eips.ethereum.org/EIPS/eip-4626)
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Vincent Aave Ability for Smart Accounts (EIP-4337)
2+
3+
# TODO update this and other docs
4+
5+
A comprehensive DeFi ability for interacting with Aave v3 protocol on Ethereum, built for the Vincent
6+
Scaffold SDK and Lit Actions execution environment.
7+
8+
## Overview
9+
10+
The Vincent Aave Ability enables secure, decentralized interactions with the Aave v3 lending protocol
11+
through Lit Actions. It supports all core Aave operations: supplying assets as collateral, borrowing
12+
against collateral, repaying debt, and withdrawing assets.
13+
14+
## Features
15+
16+
- Supply assets as collateral to earn interest
17+
- Withdraw supplied assets
18+
- Borrow against collateral with stable or variable rates
19+
- Repay borrowed assets
20+
- Multi-chain support (Ethereum, Polygon, Arbitrum, Optimism, Base, and more)
21+
- Integrated with Vincent Ability SDK for secure execution
22+
23+
## Installation
24+
25+
```bash
26+
npm install @lit-protocol/vincent-ability-aave
27+
```
28+
29+
## Usage
30+
31+
```typescript
32+
import { getVincentAbilityClient } from '@lit-protocol/vincent-app-sdk';
33+
import { bundledVincentAbility as aaveAbility } from '@lit-protocol/vincent-ability-aave-smart-account';
34+
35+
// Initialize the ability client
36+
const aaveAbilityClient = getVincentAbilityClient({
37+
bundledVincentAbility: aaveAbility,
38+
ethersSigner: yourSigner, // Your ethers signer
39+
});
40+
41+
// Example: Supply WETH as collateral
42+
const supplyParams = {
43+
operation: 'supply',
44+
asset: '0xC558DBdd856501FCd9aaF1E62eae57A9F0629a3c', // WETH on Sepolia
45+
amount: '0.01',
46+
chain: 'sepolia',
47+
};
48+
49+
// First run precheck
50+
const precheckRes = await aaveAbilityClient.precheck(supplyParams, {
51+
delegatorPkpEthAddress: pkpAddress,
52+
});
53+
54+
if (precheckRes.success) {
55+
// Then execute
56+
const executeRes = await aaveAbilityClient.execute(supplyParams, {
57+
delegatorPkpEthAddress: pkpAddress,
58+
});
59+
60+
if (executeRes.success) {
61+
console.log('Transaction hash:', executeRes.result.data.txHash);
62+
}
63+
}
64+
```
65+
66+
## Parameters
67+
68+
| Parameter | Type | Required | Description |
69+
| ------------------ | ----------------------------------------------- | -------- | ------------------------------------------------ |
70+
| `operation` | `"supply" \| "withdraw" \| "borrow" \| "repay"` || The Aave operation to perform |
71+
| `asset` | `string` || Token contract address (0x format) |
72+
| `amount` | `string` || Amount as a string (e.g., "1.5") |
73+
| `chain` | `string` || Chain identifier (e.g., "ethereum", "polygon") |
74+
| `interestRateMode` | `1 \| 2` || 1=Stable, 2=Variable (required for borrow/repay) |
75+
| `rpcUrl` | `string` || Custom RPC URL for precheck validation |
76+
77+
## Prerequisites
78+
79+
- Node.js 16+ and npm/yarn
80+
- Sufficient token balance for the operation
81+
- Token approval for supply/repay operations
82+
- Properly configured PKP wallet with required permissions
83+
84+
## Building
85+
86+
```bash
87+
# Build the ability
88+
npm run build
89+
90+
# Build all abilitys and policies
91+
npm run vincent:build
92+
```
93+
94+
## Testing
95+
96+
```bash
97+
# Run unit tests
98+
npm test
99+
100+
# Run E2E tests
101+
npm run test:e2e
102+
103+
# Reset test state
104+
npm run test:reset
105+
```
106+
107+
## Contributing
108+
109+
Contributions are welcome! Please see [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines on how
110+
to contribute to this project.
111+
112+
## License
113+
114+
This project is licensed under the MIT License - see the [LICENSE](../../LICENSE) file for details.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const denoFetch = (...args) => globalThis.fetch(...args);
2+
export default denoFetch;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const esbuild = require('esbuild');
5+
const { polyfillNode } = require('@lit-protocol/esbuild-plugin-polyfill-node');
6+
const Hash = require('ipfs-only-hash');
7+
8+
function aliasFetch() {
9+
const shim = path.resolve(__dirname, 'deno-fetch-shim.js');
10+
11+
return {
12+
name: 'alias-fetch',
13+
setup(build) {
14+
build.onResolve({ filter: /^node-fetch$/ }, () => ({ path: shim }));
15+
build.onResolve({ filter: /^cross-fetch(\/.*)?$/ }, () => ({ path: shim }));
16+
},
17+
};
18+
}
19+
20+
const ensureDirectoryExistence = (filePath) => {
21+
const dirname = path.dirname(filePath);
22+
if (!fs.existsSync(dirname)) {
23+
fs.mkdirSync(dirname, { recursive: true });
24+
}
25+
};
26+
27+
function getBundledVincentAbilityCode() {
28+
return `/**
29+
* DO NOT EDIT THIS FILE. IT IS GENERATED ON BUILD.
30+
*/
31+
32+
import { asBundledVincentAbility } from '@lit-protocol/vincent-ability-sdk';
33+
import { vincentAbility } from '../lib/vincent-ability';
34+
import metadata from './vincent-ability-metadata.json';
35+
36+
if(!metadata.ipfsCid) {
37+
throw new Error('ipfsCid is not defined in metadata JSON file');
38+
}
39+
40+
export const bundledVincentAbility = asBundledVincentAbility(vincentAbility, metadata.ipfsCid);
41+
`;
42+
}
43+
44+
function litActionModuleCode({ ipfsCid, content }) {
45+
return `/**
46+
* DO NOT EDIT THIS FILE. IT IS GENERATED ON BUILD.
47+
* @type {string}
48+
*/
49+
const code = ${JSON.stringify(content)};
50+
module.exports = {
51+
"code": code,
52+
"ipfsCid": "${ipfsCid}",
53+
};
54+
`;
55+
}
56+
57+
function metadataJsonFile({ ipfsCid }) {
58+
return `{
59+
"ipfsCid": "${ipfsCid}"
60+
}
61+
`;
62+
}
63+
64+
const createBundledAbilityFile = {
65+
name: 'create-vincent-bundled-ability-file',
66+
setup(build) {
67+
build.initialOptions.write = false;
68+
69+
const sourceDir = path.dirname(build.initialOptions.entryPoints[0]);
70+
71+
console.log(sourceDir);
72+
build.onEnd(async (result) => {
73+
if (result.errors.length > 0) {
74+
console.error('Build failed with errors:', result.errors);
75+
return;
76+
}
77+
78+
const outputFile = result.outputFiles[0];
79+
const content = outputFile.text;
80+
const ipfsCid = await Hash.of(content);
81+
82+
// Write bundledAbility wrapper
83+
const bundledSource = getBundledVincentAbilityCode();
84+
const bundledPath = path.join(sourceDir, '../generated/vincent-bundled-ability.ts');
85+
fs.writeFileSync(bundledPath, bundledSource);
86+
87+
// Write metadata JSON
88+
const outputPath = path.dirname(path.resolve(outputFile.path));
89+
const metadataPath = path.join(outputPath, 'vincent-ability-metadata.json');
90+
const metadataContent = metadataJsonFile({ ipfsCid });
91+
fs.writeFileSync(metadataPath, metadataContent);
92+
});
93+
},
94+
};
95+
96+
const wrapIIFEInStringPlugin = {
97+
name: 'wrap-iife-in-string',
98+
setup(build) {
99+
build.initialOptions.write = false;
100+
101+
build.onEnd(async (result) => {
102+
if (result.errors.length > 0) {
103+
console.error('Build failed with errors:', result.errors);
104+
return;
105+
}
106+
107+
const outputFile = result.outputFiles[0];
108+
const content = outputFile.text;
109+
const ipfsCid = await Hash.of(content);
110+
111+
const wrapped = litActionModuleCode({ content, ipfsCid });
112+
const outputPath = path.resolve(outputFile.path);
113+
ensureDirectoryExistence(outputPath);
114+
fs.writeFileSync(outputPath, wrapped);
115+
});
116+
},
117+
};
118+
119+
(async () => {
120+
try {
121+
await esbuild
122+
.build({
123+
tsconfig: './tsconfig.lib.json',
124+
entryPoints: ['./src/lib/lit-action.ts'],
125+
bundle: true,
126+
minify: false,
127+
sourcemap: false,
128+
treeShaking: true,
129+
metafile: true,
130+
outdir: './src/generated/',
131+
plugins: [
132+
aliasFetch(),
133+
polyfillNode({
134+
globals: {
135+
Buffer: true,
136+
process: true,
137+
},
138+
modules: {
139+
crypto: true,
140+
http: true,
141+
https: true,
142+
stream: true,
143+
zlib: true,
144+
url: true,
145+
util: true,
146+
},
147+
}),
148+
wrapIIFEInStringPlugin,
149+
createBundledAbilityFile,
150+
],
151+
platform: 'browser',
152+
write: false,
153+
})
154+
.then((result) => {
155+
result.outputFiles.forEach((file) => {
156+
const bytes = file.text.length;
157+
const mbInBinary = (bytes / (1024 * 1024)).toFixed(4);
158+
const mbInDecimal = (bytes / 1_000_000).toFixed(4);
159+
160+
console.log(
161+
`✅ ${file.path.split('/').pop()}\n- ${mbInDecimal} MB (in decimal)\n- ${mbInBinary} MB (in binary)`,
162+
);
163+
});
164+
});
165+
166+
console.log('✅ Vincent ability built successfully');
167+
} catch (e) {
168+
console.error('❌ Error building Vincent ability: ', e);
169+
process.exit(1);
170+
}
171+
})();

0 commit comments

Comments
 (0)