diff --git a/typescript/agentkit/src/action-providers/aave/README.md b/typescript/agentkit/src/action-providers/aave/README.md new file mode 100644 index 000000000..54aa9cb73 --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/README.md @@ -0,0 +1,146 @@ +# Aave Action Provider + +This directory contains the **AaveActionProvider** implementation, which provides actions to interact with the **Aave V3 Protocol** for lending and borrowing operations. + +## Directory Structure + +``` +aave/ +├── aaveActionProvider.ts # Main provider with Aave functionality +├── aaveActionProvider.test.ts # Test file for Aave provider +├── schemas.ts # Aave action schemas +├── constants.ts # Contract addresses and ABIs +├── index.ts # Main exports +└── README.md # This file +``` + +## Actions + +### Supply + +`supply` - Supply assets to Aave as collateral + +- **assetId**: The asset to supply (`weth`, `usdc`, `cbeth`, `wsteth`, `dai`, `usdt`) +- **amount**: The amount of tokens to supply in human-readable format + +Example: "Supply 1 WETH to Aave" + +### Withdraw + +`withdraw` - Withdraw previously supplied assets from Aave + +- **assetId**: The asset to withdraw +- **amount**: The amount of tokens to withdraw + +Example: "Withdraw 1000 USDC from Aave" + +### Borrow + +`borrow` - Borrow assets from Aave against your collateral + +- **assetId**: The asset to borrow (`weth`, `usdc`, `dai`, `usdt`) +- **amount**: The amount of tokens to borrow +- **interestRateMode**: Either `stable` or `variable` (default: variable) + +Example: "Borrow 500 USDC with variable interest rate" + +### Repay + +`repay` - Repay borrowed assets to Aave + +- **assetId**: The asset to repay +- **amount**: The amount of tokens to repay +- **interestRateMode**: The interest rate mode of the debt + +Example: "Repay 500 USDC variable rate debt" + +### Get User Data + +`get_user_data` - Get the user's account summary from Aave + +Returns: +- Total collateral in USD +- Total debt in USD +- Available borrows in USD +- Loan-to-Value ratio +- Liquidation threshold +- Health factor with status indicator + +## Network Support + +The Aave provider supports: +- **Base Mainnet** (`base-mainnet`) +- **Ethereum Mainnet** (`ethereum-mainnet`) + +## Supported Assets + +| Asset | Symbol | Base | Ethereum | +|-------|--------|------|----------| +| Wrapped ETH | WETH | ✅ | ✅ | +| USD Coin | USDC | ✅ | ✅ | +| Coinbase Wrapped ETH | cbETH | ✅ | ✅ | +| Wrapped stETH | wstETH | ✅ | ✅ | +| DAI Stablecoin | DAI | ✅ | ✅ | +| Tether USD | USDT | ✅ | ✅ | + +## Important Notes + +### Health Factor + +The health factor represents the safety of your position: + +| Health Factor | Status | +|---------------|--------| +| > 2.0 | ✅ Very Safe | +| 1.5 - 2.0 | 🟢 Safe | +| 1.1 - 1.5 | 🟡 Moderate Risk | +| 1.0 - 1.1 | 🟠 High Risk | +| < 1.0 | 🔴 Liquidation Risk | + +### Interest Rate Modes + +- **Variable**: Rate changes based on market supply/demand +- **Stable**: Fixed rate (may be higher than variable) + +### Gas Considerations + +- Supply and repay require token approval before the transaction +- All transactions require sufficient ETH for gas + +## Example Usage + +```typescript +import { aaveActionProvider } from "@coinbase/agentkit"; + +const aave = aaveActionProvider(); + +// Supply collateral +await aave.supply(wallet, { assetId: "weth", amount: "1" }); + +// Check account status +await aave.getUserData(wallet, {}); + +// Borrow against collateral +await aave.borrow(wallet, { + assetId: "usdc", + amount: "1000", + interestRateMode: "variable" +}); + +// Repay debt +await aave.repay(wallet, { + assetId: "usdc", + amount: "1000", + interestRateMode: "variable" +}); + +// Withdraw collateral +await aave.withdraw(wallet, { assetId: "weth", amount: "1" }); +``` + +## References + +- [Aave V3 Documentation](https://docs.aave.com/developers/getting-started/readme) +- [Aave V3 on Base](https://docs.aave.com/developers/deployed-contracts/v3-mainnet/base) +- [Aave Risk Parameters](https://docs.aave.com/risk/asset-risk/risk-parameters) + diff --git a/typescript/agentkit/src/action-providers/aave/aaveActionProvider.test.ts b/typescript/agentkit/src/action-providers/aave/aaveActionProvider.test.ts new file mode 100644 index 000000000..6855ceeaf --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/aaveActionProvider.test.ts @@ -0,0 +1,156 @@ +import { AaveActionProvider } from "./aaveActionProvider"; +import { + AaveSupplySchema, + AaveWithdrawSchema, + AaveBorrowSchema, + AaveRepaySchema, + AaveGetUserDataSchema, +} from "./schemas"; + +describe("Aave Action Provider Input Schemas", () => { + describe("Supply Schema", () => { + it("should successfully parse valid supply input", () => { + const validInput = { + assetId: "weth", + amount: "1.5", + }; + const result = AaveSupplySchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + + it("should fail for invalid asset", () => { + const invalidInput = { + assetId: "invalid", + amount: "1.5", + }; + const result = AaveSupplySchema.safeParse(invalidInput); + expect(result.success).toBe(false); + }); + + it("should fail for invalid amount format", () => { + const invalidInput = { + assetId: "weth", + amount: "not-a-number", + }; + const result = AaveSupplySchema.safeParse(invalidInput); + expect(result.success).toBe(false); + }); + }); + + describe("Withdraw Schema", () => { + it("should successfully parse valid withdraw input", () => { + const validInput = { + assetId: "usdc", + amount: "1000", + }; + const result = AaveWithdrawSchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + }); + + describe("Borrow Schema", () => { + it("should successfully parse valid borrow input with variable rate", () => { + const validInput = { + assetId: "usdc", + amount: "500", + interestRateMode: "variable", + }; + const result = AaveBorrowSchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + + it("should successfully parse valid borrow input with stable rate", () => { + const validInput = { + assetId: "weth", + amount: "0.5", + interestRateMode: "stable", + }; + const result = AaveBorrowSchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + + it("should fail for invalid interest rate mode", () => { + const invalidInput = { + assetId: "usdc", + amount: "500", + interestRateMode: "invalid", + }; + const result = AaveBorrowSchema.safeParse(invalidInput); + expect(result.success).toBe(false); + }); + }); + + describe("Repay Schema", () => { + it("should successfully parse valid repay input", () => { + const validInput = { + assetId: "usdc", + amount: "500", + interestRateMode: "variable", + }; + const result = AaveRepaySchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + }); + + describe("GetUserData Schema", () => { + it("should successfully parse empty input", () => { + const validInput = {}; + const result = AaveGetUserDataSchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + }); +}); + +describe("Aave Action Provider", () => { + let actionProvider: AaveActionProvider; + + beforeEach(() => { + actionProvider = new AaveActionProvider(); + }); + + describe("constructor", () => { + it("should create an instance", () => { + expect(actionProvider).toBeDefined(); + expect(actionProvider).toBeInstanceOf(AaveActionProvider); + }); + }); + + describe("supportsNetwork", () => { + it("should return true for base-mainnet", () => { + expect( + actionProvider.supportsNetwork({ + protocolFamily: "evm", + networkId: "base-mainnet", + }), + ).toBe(true); + }); + + it("should return true for ethereum-mainnet", () => { + expect( + actionProvider.supportsNetwork({ + protocolFamily: "evm", + networkId: "ethereum-mainnet", + }), + ).toBe(true); + }); + + it("should return false for unsupported networks", () => { + expect( + actionProvider.supportsNetwork({ + protocolFamily: "evm", + networkId: "polygon-mainnet", + }), + ).toBe(false); + }); + + it("should return false for non-EVM networks", () => { + expect( + actionProvider.supportsNetwork({ + protocolFamily: "solana", + networkId: "solana-mainnet", + }), + ).toBe(false); + }); + }); +}); + diff --git a/typescript/agentkit/src/action-providers/aave/aaveActionProvider.ts b/typescript/agentkit/src/action-providers/aave/aaveActionProvider.ts new file mode 100644 index 000000000..628dfd82c --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/aaveActionProvider.ts @@ -0,0 +1,505 @@ +import { z } from "zod"; +import { encodeFunctionData, formatUnits, parseUnits, Address } from "viem"; + +import { ActionProvider } from "../actionProvider"; +import { EvmWalletProvider } from "../../wallet-providers"; +import { CreateAction } from "../actionDecorator"; +import { approve } from "../../utils"; +import { Network } from "../../network"; +import { + AAVE_POOL_ABI, + AAVE_POOL_ADDRESSES, + TOKEN_ADDRESSES, + ERC20_ABI, +} from "./constants"; +import { + AaveSupplySchema, + AaveWithdrawSchema, + AaveBorrowSchema, + AaveRepaySchema, + AaveGetUserDataSchema, + AaveGetReserveDataSchema, +} from "./schemas"; + +/** + * Helper function to get the Aave Pool address for a network. + */ +function getPoolAddress(networkId: string): Address { + const address = AAVE_POOL_ADDRESSES[networkId]; + if (!address) { + throw new Error(`Aave Pool not available on network: ${networkId}`); + } + return address; +} + +/** + * Helper function to get token address. + */ +function getTokenAddress(networkId: string, assetId: string): Address { + const networkTokens = TOKEN_ADDRESSES[networkId]; + if (!networkTokens) { + throw new Error(`Network not supported: ${networkId}`); + } + const address = networkTokens[assetId]; + if (!address) { + throw new Error(`Token ${assetId} not available on network: ${networkId}`); + } + return address; +} + +/** + * Convert interest rate mode string to number. + */ +function getInterestRateMode(mode: "stable" | "variable"): bigint { + return mode === "stable" ? 1n : 2n; +} + +/** + * AaveActionProvider is an action provider for Aave V3 protocol interactions. + */ +export class AaveActionProvider extends ActionProvider { + /** + * Constructs a new AaveActionProvider instance. + */ + constructor() { + super("aave", []); + } + + /** + * Supplies assets to Aave as collateral. + * + * @param wallet - The wallet instance to perform the transaction. + * @param args - The input arguments including assetId and amount. + * @returns A message indicating success or an error message. + */ + @CreateAction({ + name: "supply", + description: ` +This tool allows supplying assets to Aave V3 as collateral. +It takes: +- assetId: The asset to supply, one of 'weth', 'usdc', 'cbeth', 'wsteth', 'dai', 'usdt' +- amount: The amount of tokens to supply in human-readable format +Examples: +- 1 WETH +- 1000 USDC +- 0.5 cbETH +Important notes: +- Use the exact amount provided by the user +- The token will be supplied as collateral and can be used to borrow other assets +- Make sure you have sufficient token balance before supplying + `, + schema: AaveSupplySchema, + }) + async supply( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId!; + const poolAddress = getPoolAddress(networkId); + const tokenAddress = getTokenAddress(networkId, args.assetId); + const walletAddress = await wallet.getAddress(); + + // Get token decimals + const decimals = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "decimals", + args: [], + }) as number; + + const amountAtomic = parseUnits(args.amount, decimals); + + // Check wallet balance + const balance = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [walletAddress], + }) as bigint; + + if (balance < amountAtomic) { + const humanBalance = formatUnits(balance, decimals); + return `Error: Insufficient balance. You have ${humanBalance} ${args.assetId.toUpperCase()}, but trying to supply ${args.amount}`; + } + + // Approve Aave Pool to spend tokens + const approvalResult = await approve(wallet, tokenAddress, poolAddress, amountAtomic); + if (approvalResult.startsWith("Error")) { + return `Error approving token: ${approvalResult}`; + } + + // Supply to Aave + const data = encodeFunctionData({ + abi: AAVE_POOL_ABI, + functionName: "supply", + args: [tokenAddress, amountAtomic, walletAddress as Address, 0], + }); + + const txHash = await wallet.sendTransaction({ + to: poolAddress, + data, + }); + await wallet.waitForTransactionReceipt(txHash); + + // Get token symbol + const symbol = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "symbol", + args: [], + }) as string; + + return `Successfully supplied ${args.amount} ${symbol} to Aave.\nTransaction hash: ${txHash}\n\nYour supplied assets can now be used as collateral to borrow other assets.`; + } catch (error) { + return `Error supplying to Aave: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Withdraws assets from Aave. + * + * @param wallet - The wallet instance to perform the transaction. + * @param args - The input arguments including assetId and amount. + * @returns A message indicating success or an error message. + */ + @CreateAction({ + name: "withdraw", + description: ` +This tool allows withdrawing assets from Aave V3. +It takes: +- assetId: The asset to withdraw, one of 'weth', 'usdc', 'cbeth', 'wsteth', 'dai', 'usdt' +- amount: The amount of tokens to withdraw in human-readable format +Examples: +- 1 WETH +- 1000 USDC +Important notes: +- You can only withdraw assets you have previously supplied +- Withdrawing may affect your health factor if you have outstanding borrows +- Use 'max' or a very large number to withdraw all available balance + `, + schema: AaveWithdrawSchema, + }) + async withdraw( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId!; + const poolAddress = getPoolAddress(networkId); + const tokenAddress = getTokenAddress(networkId, args.assetId); + const walletAddress = await wallet.getAddress(); + + // Get token decimals + const decimals = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "decimals", + args: [], + }) as number; + + const amountAtomic = parseUnits(args.amount, decimals); + + // Withdraw from Aave + const data = encodeFunctionData({ + abi: AAVE_POOL_ABI, + functionName: "withdraw", + args: [tokenAddress, amountAtomic, walletAddress as Address], + }); + + const txHash = await wallet.sendTransaction({ + to: poolAddress, + data, + }); + await wallet.waitForTransactionReceipt(txHash); + + // Get token symbol + const symbol = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "symbol", + args: [], + }) as string; + + return `Successfully withdrawn ${args.amount} ${symbol} from Aave.\nTransaction hash: ${txHash}`; + } catch (error) { + return `Error withdrawing from Aave: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Borrows assets from Aave. + * + * @param wallet - The wallet instance to perform the transaction. + * @param args - The input arguments including assetId, amount, and interestRateMode. + * @returns A message indicating success or an error message. + */ + @CreateAction({ + name: "borrow", + description: ` +This tool allows borrowing assets from Aave V3. +It takes: +- assetId: The asset to borrow, one of 'weth', 'usdc', 'dai', 'usdt' +- amount: The amount of tokens to borrow in human-readable format +- interestRateMode: Either 'stable' or 'variable' (default: variable) +Examples: +- Borrow 1000 USDC with variable rate +- Borrow 0.5 WETH with stable rate +Important notes: +- You must have sufficient collateral supplied before borrowing +- Variable rate changes based on market conditions +- Stable rate provides predictable interest but may be higher +- Monitor your health factor to avoid liquidation + `, + schema: AaveBorrowSchema, + }) + async borrow( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId!; + const poolAddress = getPoolAddress(networkId); + const tokenAddress = getTokenAddress(networkId, args.assetId); + const walletAddress = await wallet.getAddress(); + + // Get token decimals + const decimals = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "decimals", + args: [], + }) as number; + + const amountAtomic = parseUnits(args.amount, decimals); + const interestRateMode = getInterestRateMode(args.interestRateMode || "variable"); + + // Borrow from Aave + const data = encodeFunctionData({ + abi: AAVE_POOL_ABI, + functionName: "borrow", + args: [tokenAddress, amountAtomic, interestRateMode, 0, walletAddress as Address], + }); + + const txHash = await wallet.sendTransaction({ + to: poolAddress, + data, + }); + await wallet.waitForTransactionReceipt(txHash); + + // Get token symbol + const symbol = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "symbol", + args: [], + }) as string; + + return `Successfully borrowed ${args.amount} ${symbol} from Aave with ${args.interestRateMode || "variable"} interest rate.\nTransaction hash: ${txHash}\n\n⚠️ Remember to monitor your health factor and repay your debt to avoid liquidation.`; + } catch (error) { + return `Error borrowing from Aave: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Repays borrowed assets to Aave. + * + * @param wallet - The wallet instance to perform the transaction. + * @param args - The input arguments including assetId, amount, and interestRateMode. + * @returns A message indicating success or an error message. + */ + @CreateAction({ + name: "repay", + description: ` +This tool allows repaying borrowed assets to Aave V3. +It takes: +- assetId: The asset to repay, one of 'weth', 'usdc', 'dai', 'usdt' +- amount: The amount of tokens to repay in human-readable format +- interestRateMode: The interest rate mode of the debt, 'stable' or 'variable' +Examples: +- Repay 1000 USDC variable rate debt +- Repay 0.5 WETH stable rate debt +Important notes: +- Make sure you have sufficient balance of the asset to repay +- Repaying improves your health factor +- Use 'max' or a very large number to repay all debt + `, + schema: AaveRepaySchema, + }) + async repay( + wallet: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId!; + const poolAddress = getPoolAddress(networkId); + const tokenAddress = getTokenAddress(networkId, args.assetId); + const walletAddress = await wallet.getAddress(); + + // Get token decimals + const decimals = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "decimals", + args: [], + }) as number; + + const amountAtomic = parseUnits(args.amount, decimals); + const interestRateMode = getInterestRateMode(args.interestRateMode || "variable"); + + // Check wallet balance + const balance = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "balanceOf", + args: [walletAddress], + }) as bigint; + + if (balance < amountAtomic) { + const humanBalance = formatUnits(balance, decimals); + return `Error: Insufficient balance. You have ${humanBalance} ${args.assetId.toUpperCase()}, but trying to repay ${args.amount}`; + } + + // Approve Aave Pool to spend tokens + const approvalResult = await approve(wallet, tokenAddress, poolAddress, amountAtomic); + if (approvalResult.startsWith("Error")) { + return `Error approving token: ${approvalResult}`; + } + + // Repay to Aave + const data = encodeFunctionData({ + abi: AAVE_POOL_ABI, + functionName: "repay", + args: [tokenAddress, amountAtomic, interestRateMode, walletAddress as Address], + }); + + const txHash = await wallet.sendTransaction({ + to: poolAddress, + data, + }); + await wallet.waitForTransactionReceipt(txHash); + + // Get token symbol + const symbol = await wallet.readContract({ + address: tokenAddress, + abi: ERC20_ABI, + functionName: "symbol", + args: [], + }) as string; + + return `Successfully repaid ${args.amount} ${symbol} to Aave.\nTransaction hash: ${txHash}\n\nYour health factor has improved.`; + } catch (error) { + return `Error repaying to Aave: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Gets user account data from Aave. + * + * @param wallet - The wallet instance to fetch data. + * @param _ - No input arguments required. + * @returns User account data including collateral, debt, and health factor. + */ + @CreateAction({ + name: "get_user_data", + description: ` +This tool retrieves the user's account data from Aave V3. +Returns: +- Total collateral in USD +- Total debt in USD +- Available borrows in USD +- Loan-to-Value ratio +- Liquidation threshold +- Health factor + +The health factor indicates the safety of your position: +- Health factor > 1: Position is safe +- Health factor < 1: Position may be liquidated + `, + schema: AaveGetUserDataSchema, + }) + async getUserData( + wallet: EvmWalletProvider, + _: z.infer, + ): Promise { + try { + const network = wallet.getNetwork(); + const networkId = network.networkId!; + const poolAddress = getPoolAddress(networkId); + const walletAddress = await wallet.getAddress(); + + const userData = await wallet.readContract({ + address: poolAddress, + abi: AAVE_POOL_ABI, + functionName: "getUserAccountData", + args: [walletAddress as Address], + }) as [bigint, bigint, bigint, bigint, bigint, bigint]; + + const [ + totalCollateralBase, + totalDebtBase, + availableBorrowsBase, + currentLiquidationThreshold, + ltv, + healthFactor, + ] = userData; + + // Format values (Aave uses 8 decimals for USD values) + const collateralUSD = formatUnits(totalCollateralBase, 8); + const debtUSD = formatUnits(totalDebtBase, 8); + const availableBorrowsUSD = formatUnits(availableBorrowsBase, 8); + const healthFactorFormatted = formatUnits(healthFactor, 18); + + // Build response + let response = `## Aave Account Summary\n\n`; + response += `| Metric | Value |\n`; + response += `|--------|-------|\n`; + response += `| Total Collateral | $${Number(collateralUSD).toLocaleString()} |\n`; + response += `| Total Debt | $${Number(debtUSD).toLocaleString()} |\n`; + response += `| Available to Borrow | $${Number(availableBorrowsUSD).toLocaleString()} |\n`; + response += `| Loan-to-Value (LTV) | ${Number(ltv) / 100}% |\n`; + response += `| Liquidation Threshold | ${Number(currentLiquidationThreshold) / 100}% |\n`; + response += `| Health Factor | ${Number(healthFactorFormatted).toFixed(2)} |\n\n`; + + // Add health status + const hf = Number(healthFactorFormatted); + if (hf > 2) { + response += `✅ **Position Status:** Very Safe`; + } else if (hf > 1.5) { + response += `🟢 **Position Status:** Safe`; + } else if (hf > 1.1) { + response += `🟡 **Position Status:** Moderate Risk`; + } else if (hf > 1) { + response += `🟠 **Position Status:** High Risk - Consider repaying debt`; + } else { + response += `🔴 **Position Status:** DANGER - Liquidation imminent!`; + } + + return response; + } catch (error) { + return `Error getting user data from Aave: ${error instanceof Error ? error.message : String(error)}`; + } + } + + /** + * Checks if the Aave action provider supports the given network. + * + * @param network - The network to check. + * @returns True if the network is supported, false otherwise. + */ + supportsNetwork = (network: Network): boolean => + network.protocolFamily === "evm" && + (network.networkId === "base-mainnet" || network.networkId === "ethereum-mainnet"); +} + +/** + * Factory function to create a new AaveActionProvider instance. + * + * @returns A new AaveActionProvider instance. + */ +export const aaveActionProvider = (): AaveActionProvider => new AaveActionProvider(); + diff --git a/typescript/agentkit/src/action-providers/aave/constants.ts b/typescript/agentkit/src/action-providers/aave/constants.ts new file mode 100644 index 000000000..76050c47a --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/constants.ts @@ -0,0 +1,145 @@ +import { Address } from "viem"; + +/** + * Aave V3 Pool contract addresses by network. + */ +export const AAVE_POOL_ADDRESSES: Record = { + "base-mainnet": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", + "ethereum-mainnet": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", +}; + +/** + * Aave V3 Pool Data Provider contract addresses by network. + */ +export const AAVE_POOL_DATA_PROVIDER_ADDRESSES: Record = { + "base-mainnet": "0x2d8A3C5677189723C4cB8873CfC9C8976FDF38Ac", + "ethereum-mainnet": "0x7B4EB56E7CD4b454BA8ff71E4518426369a138a3", +}; + +/** + * Token addresses by network and asset ID. + */ +export const TOKEN_ADDRESSES: Record> = { + "base-mainnet": { + weth: "0x4200000000000000000000000000000000000006", + usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + cbeth: "0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22", + wsteth: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", + dai: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", + usdt: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", + }, + "ethereum-mainnet": { + weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", + usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + cbeth: "0xBe9895146f7AF43049ca1c1AE358B0541Ea49704", + wsteth: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0", + dai: "0x6B175474E89094C44Da98b954EescdeCB5BDaC8", + usdt: "0xdAC17F958D2ee523a2206206994597C13D831ec7", + }, +}; + +/** + * Aave V3 Pool ABI (minimal for our actions). + */ +export const AAVE_POOL_ABI = [ + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "address", name: "onBehalfOf", type: "address" }, + { internalType: "uint16", name: "referralCode", type: "uint16" }, + ], + name: "supply", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "address", name: "to", type: "address" }, + ], + name: "withdraw", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "uint256", name: "interestRateMode", type: "uint256" }, + { internalType: "uint16", name: "referralCode", type: "uint16" }, + { internalType: "address", name: "onBehalfOf", type: "address" }, + ], + name: "borrow", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "asset", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + { internalType: "uint256", name: "interestRateMode", type: "uint256" }, + { internalType: "address", name: "onBehalfOf", type: "address" }, + ], + name: "repay", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "getUserAccountData", + outputs: [ + { internalType: "uint256", name: "totalCollateralBase", type: "uint256" }, + { internalType: "uint256", name: "totalDebtBase", type: "uint256" }, + { internalType: "uint256", name: "availableBorrowsBase", type: "uint256" }, + { internalType: "uint256", name: "currentLiquidationThreshold", type: "uint256" }, + { internalType: "uint256", name: "ltv", type: "uint256" }, + { internalType: "uint256", name: "healthFactor", type: "uint256" }, + ], + stateMutability: "view", + type: "function", + }, +] as const; + +/** + * ERC20 ABI for token operations. + */ +export const ERC20_ABI = [ + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "decimals", + outputs: [{ internalType: "uint8", name: "", type: "uint8" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "symbol", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, +] as const; + diff --git a/typescript/agentkit/src/action-providers/aave/index.ts b/typescript/agentkit/src/action-providers/aave/index.ts new file mode 100644 index 000000000..9a2544cd4 --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/index.ts @@ -0,0 +1,10 @@ +export { AaveActionProvider, aaveActionProvider } from "./aaveActionProvider"; +export { + AaveSupplySchema, + AaveWithdrawSchema, + AaveBorrowSchema, + AaveRepaySchema, + AaveGetUserDataSchema, + AaveGetReserveDataSchema, +} from "./schemas"; + diff --git a/typescript/agentkit/src/action-providers/aave/schemas.ts b/typescript/agentkit/src/action-providers/aave/schemas.ts new file mode 100644 index 000000000..3e21404ef --- /dev/null +++ b/typescript/agentkit/src/action-providers/aave/schemas.ts @@ -0,0 +1,88 @@ +import { z } from "zod"; + +/** + * Input schema for Aave supply action. + */ +export const AaveSupplySchema = z + .object({ + assetId: z + .enum(["weth", "usdc", "cbeth", "wsteth", "dai", "usdt"]) + .describe("The asset to supply as collateral"), + amount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid integer or decimal value") + .describe("The amount of tokens to supply in human-readable format"), + }) + .describe("Input schema for Aave supply action"); + +/** + * Input schema for Aave withdraw action. + */ +export const AaveWithdrawSchema = z + .object({ + assetId: z + .enum(["weth", "usdc", "cbeth", "wsteth", "dai", "usdt"]) + .describe("The asset to withdraw"), + amount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid integer or decimal value") + .describe("The amount of tokens to withdraw in human-readable format"), + }) + .describe("Input schema for Aave withdraw action"); + +/** + * Input schema for Aave borrow action. + */ +export const AaveBorrowSchema = z + .object({ + assetId: z + .enum(["weth", "usdc", "dai", "usdt"]) + .describe("The asset to borrow"), + amount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid integer or decimal value") + .describe("The amount of tokens to borrow in human-readable format"), + interestRateMode: z + .enum(["stable", "variable"]) + .default("variable") + .describe("The interest rate mode: 'stable' or 'variable' (default: variable)"), + }) + .describe("Input schema for Aave borrow action"); + +/** + * Input schema for Aave repay action. + */ +export const AaveRepaySchema = z + .object({ + assetId: z + .enum(["weth", "usdc", "dai", "usdt"]) + .describe("The asset to repay"), + amount: z + .string() + .regex(/^\d+(\.\d+)?$/, "Must be a valid integer or decimal value") + .describe("The amount of tokens to repay in human-readable format"), + interestRateMode: z + .enum(["stable", "variable"]) + .default("variable") + .describe("The interest rate mode of the debt: 'stable' or 'variable'"), + }) + .describe("Input schema for Aave repay action"); + +/** + * Input schema for Aave get user data action. + */ +export const AaveGetUserDataSchema = z + .object({}) + .describe("Input schema for getting user account data from Aave"); + +/** + * Input schema for Aave get reserve data action. + */ +export const AaveGetReserveDataSchema = z + .object({ + assetId: z + .enum(["weth", "usdc", "cbeth", "wsteth", "dai", "usdt"]) + .describe("The asset to get reserve data for"), + }) + .describe("Input schema for getting reserve data from Aave"); + diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 7f2b0233a..b3bb71b5d 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -3,6 +3,7 @@ export * from "./actionProvider"; export * from "./customActionProvider"; +export * from "./aave"; export * from "./across"; export * from "./alchemy"; export * from "./baseAccount";