@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
366 lines (365 loc) • 17.8 kB
JavaScript
;
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;