UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

366 lines (365 loc) 17.8 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.compoundActionProvider = exports.CompoundActionProvider = void 0; const zod_1 = require("zod"); const viem_1 = require("viem"); const actionProvider_1 = require("../actionProvider"); const wallet_providers_1 = require("../../wallet-providers"); const actionDecorator_1 = require("../actionDecorator"); const utils_1 = require("../../utils"); const constants_1 = require("./constants"); const schemas_1 = require("./schemas"); const utils_2 = require("./utils"); /** * CompoundActionProvider is an action provider for Compound protocol interactions. */ class CompoundActionProvider extends actionProvider_1.ActionProvider { /** * Constructs a new CompoundActionProvider instance. */ constructor() { super("compound", []); /** * Checks if the Compound action provider supports the given network. * * @param network - The network to check. * @returns True if the network is supported, false otherwise. */ this.supportsNetwork = (network) => network.protocolFamily === "evm" && (network.networkId === "base-mainnet" || network.networkId === "base-sepolia"); } /** * Supplies collateral assets to Compound. * * @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. */ async supply(wallet, args) { try { const network = wallet.getNetwork(); const cometAddress = (0, utils_2.getCometAddress)(network); const tokenAddress = (0, utils_2.getAssetAddress)(network, args.assetId); if (!tokenAddress) { throw new Error(`Token address undefined for assetId ${args.assetId}`); } const decimals = await (0, utils_2.getTokenDecimals)(wallet, tokenAddress); const amountAtomic = (0, viem_1.parseUnits)(args.amount, decimals); // Check wallet balance before proceeding const walletBalance = await (0, utils_2.getTokenBalance)(wallet, tokenAddress); if (walletBalance < amountAtomic) { const humanBalance = (0, viem_1.formatUnits)(walletBalance, decimals); return `Error: Insufficient balance. You have ${humanBalance}, but trying to supply ${args.amount}`; } // Get current health ratio for reference const currentHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); // Approve Compound to spend tokens const approvalResult = await (0, utils_1.approve)(wallet, tokenAddress, cometAddress, amountAtomic); if (approvalResult.startsWith("Error")) { return `Error approving token: ${approvalResult}`; } // Supply tokens to Compound const data = (0, viem_1.encodeFunctionData)({ abi: constants_1.COMET_ABI, functionName: "supply", args: [tokenAddress, amountAtomic], }); const txHash = await wallet.sendTransaction({ to: cometAddress, data, }); await wallet.waitForTransactionReceipt(txHash); // Get new health ratio and token symbol const newHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const tokenSymbol = await (0, utils_2.getTokenSymbol)(wallet, tokenAddress); // Only add the health ratio message if at least one of the values is not Infinity const healthMessage = currentHealth.eq(Infinity) && newHealth.eq(Infinity) ? "" : `\nHealth ratio changed from ${currentHealth.toFixed(2)} to ${newHealth.toFixed(2)}`; return `Supplied ${args.amount} ${tokenSymbol} to Compound.\nTransaction hash: ${txHash}${healthMessage}`; } catch (error) { return `Error supplying to Compound: ${error instanceof Error ? error.message : error && typeof error === "object" && "message" in error ? `Error: ${error.message}` : error}`; } } /** * Withdraws collateral assets from Compound. * * @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. */ async withdraw(wallet, args) { try { const cometAddress = (0, utils_2.getCometAddress)(wallet.getNetwork()); const tokenAddress = (0, utils_2.getAssetAddress)(wallet.getNetwork(), args.assetId); const decimals = await (0, utils_2.getTokenDecimals)(wallet, tokenAddress); const amountAtomic = (0, viem_1.parseUnits)(args.amount, decimals); // Check that there is enough collateral supplied to withdraw const collateralBalance = await (0, utils_2.getCollateralBalance)(wallet, cometAddress, tokenAddress); if (amountAtomic > collateralBalance) { const humanBalance = (0, viem_1.formatUnits)(collateralBalance, decimals); return `Error: Insufficient balance. Trying to withdraw ${args.amount}, but only have ${humanBalance} supplied`; } // Check if position would be healthy after withdrawal const projectedHealthRatio = await (0, utils_2.getHealthRatioAfterWithdraw)(wallet, cometAddress, tokenAddress, amountAtomic); if (projectedHealthRatio.lessThan(1)) { return `Error: Withdrawing ${args.amount} would result in an unhealthy position. Health ratio would be ${projectedHealthRatio.toFixed(2)}`; } // Withdraw from Compound const data = (0, viem_1.encodeFunctionData)({ abi: constants_1.COMET_ABI, functionName: "withdraw", args: [tokenAddress, amountAtomic], }); const txHash = await wallet.sendTransaction({ to: cometAddress, data, }); await wallet.waitForTransactionReceipt(txHash); // Get current and new health ratios and token symbol const currentHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const newHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const tokenSymbol = await (0, utils_2.getTokenSymbol)(wallet, tokenAddress); return (`Withdrawn ${args.amount} ${tokenSymbol} from Compound.\n` + `Transaction hash: ${txHash}\n` + `Health ratio changed from ${currentHealth.toFixed(2)} to ${newHealth.toFixed(2)}`); } catch (error) { return `Error withdrawing from Compound: ${error instanceof Error ? error : error && typeof error === "object" && "message" in error ? `Error: ${error.message}` : error}`; } } /** * Borrows base assets from Compound. * * @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. */ async borrow(wallet, args) { try { const cometAddress = (0, utils_2.getCometAddress)(wallet.getNetwork()); const baseTokenAddress = await (0, utils_2.getBaseTokenAddress)(wallet, cometAddress); const decimals = await (0, utils_2.getTokenDecimals)(wallet, baseTokenAddress); // Convert human-readable amount to atomic units const amountAtomic = (0, viem_1.parseUnits)(args.amount, decimals); // Get current health ratio for reference const currentHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const currentHealthStr = currentHealth.eq(Infinity) ? "Inf.%" : currentHealth.toFixed(2); // Check if position would be healthy after borrow const projectedHealthRatio = await (0, utils_2.getHealthRatioAfterBorrow)(wallet, cometAddress, amountAtomic); if (projectedHealthRatio.lessThan(1)) { return `Error: Borrowing ${args.amount} USDC would result in an unhealthy position. Health ratio would be ${projectedHealthRatio.toFixed(2)}`; } // Use the withdraw method to borrow from Compound const data = (0, viem_1.encodeFunctionData)({ abi: constants_1.COMET_ABI, functionName: "withdraw", args: [baseTokenAddress, amountAtomic], }); const txHash = await wallet.sendTransaction({ to: cometAddress, data, }); await wallet.waitForTransactionReceipt(txHash); // Get new health ratio const newHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const newHealthStr = newHealth.eq(Infinity) ? "Inf.%" : newHealth.toFixed(2); return (`Borrowed ${args.amount} USDC from Compound.\n` + `Transaction hash: ${txHash}\n` + `Health ratio changed from ${currentHealthStr} to ${newHealthStr}`); } catch (error) { return `Error borrowing from Compound: ${error instanceof Error ? error : error && typeof error === "object" && "message" in error ? `Error: ${error.message}` : error}`; } } /** * Repays borrowed assets to Compound. * * @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. */ async repay(wallet, args) { try { const cometAddress = (0, utils_2.getCometAddress)(wallet.getNetwork()); const tokenAddress = (0, utils_2.getAssetAddress)(wallet.getNetwork(), args.assetId); const tokenDecimals = await (0, utils_2.getTokenDecimals)(wallet, tokenAddress); const amountAtomic = (0, viem_1.parseUnits)(args.amount, tokenDecimals); const tokenBalance = await (0, utils_2.getTokenBalance)(wallet, tokenAddress); if (tokenBalance < amountAtomic) { const humanBalance = (0, viem_1.formatUnits)(tokenBalance, tokenDecimals); return `Error: Insufficient balance. You have ${humanBalance}, but trying to repay ${args.amount}`; } // Get current health ratio for reference const currentHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); // Approve Compound to spend tokens const approvalResult = await (0, utils_1.approve)(wallet, tokenAddress, cometAddress, amountAtomic); if (approvalResult.startsWith("Error")) { return `Error approving token: ${approvalResult}`; } // Repay debt by supplying tokens to Compound const data = (0, viem_1.encodeFunctionData)({ abi: constants_1.COMET_ABI, functionName: "supply", args: [tokenAddress, amountAtomic], }); const txHash = await wallet.sendTransaction({ to: cometAddress, data, }); await wallet.waitForTransactionReceipt(txHash); // Get new health ratio and token symbol const newHealth = await (0, utils_2.getHealthRatio)(wallet, cometAddress); const tokenSymbol = await (0, utils_2.getTokenSymbol)(wallet, tokenAddress); return (`Repaid ${args.amount} ${tokenSymbol} to Compound.\n` + `Transaction hash: ${txHash}\n` + `Health ratio improved from ${currentHealth.toFixed(2)} to ${newHealth.toFixed(2)}`); } catch (error) { return `Error repaying to Compound: ${error instanceof Error ? error : error && typeof error === "object" && "message" in error ? `Error: ${error.message}` : error}`; } } /** * Retrieves portfolio details from Compound. * * @param wallet - The wallet instance to fetch portfolio details. * @param _ - No input is required for this action. * @returns A Markdown formatted string with portfolio details or an error message. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async getPortfolio(wallet, _) { try { const cometAddress = (0, utils_2.getCometAddress)(wallet.getNetwork()); return await (0, utils_2.getPortfolioDetailsMarkdown)(wallet, cometAddress); } catch (error) { return `Error getting portfolio details: ${error && typeof error === "object" && "message" in error ? error.message : error}`; } } } exports.CompoundActionProvider = CompoundActionProvider; __decorate([ (0, actionDecorator_1.CreateAction)({ name: "supply", description: ` This tool allows supplying collateral assets to Compound. It takes: - assetId: The asset to supply, one of 'weth', 'cbeth', 'cbbtc', 'wsteth', or 'usdc' - amount: The amount of tokens to supply in human-readable format Examples: - 1 WETH - 0.1 WETH - 0.01 WETH Important notes: - Use the exact amount provided - The token must be an approved collateral asset for the Compound market `, schema: schemas_1.CompoundSupplySchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], CompoundActionProvider.prototype, "supply", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "withdraw", description: ` This tool allows withdrawing collateral assets from Compound. It takes: - assetId: The asset to withdraw, one of 'weth', 'cbeth', 'cbbtc', 'wsteth', or 'usdc' - amount: The amount of tokens to withdraw in human-readable format Examples: - 1 WETH - 0.1 WETH - 0.01 WETH Important notes: - Use the exact amount provided - The token must be a collateral asset you have supplied to the Compound market `, schema: schemas_1.CompoundWithdrawSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], CompoundActionProvider.prototype, "withdraw", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "borrow", description: ` This tool allows borrowing base assets from Compound. It takes: - assetId: The asset to borrow, either 'weth' or 'usdc' - amount: The amount of base tokens to borrow in human-readable format Examples: - 1000 USDC - 0.5 WETH Important notes: - Use the exact amount provided - Ensure you have sufficient collateral to borrow `, schema: schemas_1.CompoundBorrowSchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], CompoundActionProvider.prototype, "borrow", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "repay", description: ` This tool allows repaying borrowed assets to Compound. It takes: - assetId: The asset to repay, either 'weth' or 'usdc' - amount: The amount of tokens to repay in human-readable format Examples: - 1000 USDC - 0.5 WETH Important notes: - Use the exact amount provided - Ensure you have sufficient balance of the asset to repay `, schema: schemas_1.CompoundRepaySchema, }), __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], CompoundActionProvider.prototype, "repay", null); __decorate([ (0, actionDecorator_1.CreateAction)({ name: "get_portfolio", description: ` This tool allows getting portfolio details from Compound. Returns portfolio details including: - Collateral balances and USD values - Borrowed amounts and USD values Formatted in Markdown for readability. `, schema: schemas_1.CompoundPortfolioSchema, }) // eslint-disable-next-line @typescript-eslint/no-unused-vars , __metadata("design:type", Function), __metadata("design:paramtypes", [wallet_providers_1.EvmWalletProvider, void 0]), __metadata("design:returntype", Promise) ], CompoundActionProvider.prototype, "getPortfolio", null); /** * Factory function to create a new instance of CompoundActionProvider. * * @returns A new CompoundActionProvider instance. */ const compoundActionProvider = () => new CompoundActionProvider(); exports.compoundActionProvider = compoundActionProvider;