UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

354 lines (353 loc) 15.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBaseTokenAddress = exports.getAssetAddress = exports.getCometAddress = exports.getPortfolioDetailsMarkdown = exports.getHealthRatioAfterBorrow = exports.getHealthRatioAfterWithdraw = exports.getHealthRatio = exports.getCollateralBalance = exports.getTokenBalance = exports.getTokenSymbol = exports.getTokenDecimals = void 0; const decimal_js_1 = require("decimal.js"); const viem_1 = require("viem"); const constants_1 = require("./constants"); const constants_2 = require("./constants"); /** * Get token decimals from contract * * @param wallet - The wallet provider instance * @param tokenAddress - The address of the token contract * @returns The number of decimals for the token */ const getTokenDecimals = async (wallet, tokenAddress) => { const decimals = await wallet.readContract({ address: tokenAddress, abi: constants_1.ERC20_ABI, functionName: "decimals", }); return Number(decimals); }; exports.getTokenDecimals = getTokenDecimals; /** * Get token symbol from contract * * @param wallet - The wallet provider instance * @param tokenAddress - The address of the token contract * @returns The symbol of the token */ const getTokenSymbol = async (wallet, tokenAddress) => { const symbol = await wallet.readContract({ address: tokenAddress, abi: constants_1.ERC20_ABI, functionName: "symbol", }); return symbol; }; exports.getTokenSymbol = getTokenSymbol; /** * Get token balance for an address * * @param wallet - The wallet provider instance * @param tokenAddress - The address of the token contract * @returns The token balance as a bigint */ const getTokenBalance = async (wallet, tokenAddress) => { const balance = await wallet.readContract({ address: tokenAddress, abi: constants_1.ERC20_ABI, functionName: "balanceOf", args: [wallet.getAddress()], }); return balance; }; exports.getTokenBalance = getTokenBalance; /** * Get collateral balance for an address * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @param tokenAddress - The address of the token contract * @returns The collateral balance as a bigint */ const getCollateralBalance = async (wallet, cometAddress, tokenAddress) => { const balance = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "collateralBalanceOf", args: [(await wallet.getAddress()), tokenAddress], }); return balance; }; exports.getCollateralBalance = getCollateralBalance; /** * Get health ratio for an account * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @returns The health ratio as a Decimal */ const getHealthRatio = async (wallet, cometAddress) => { const borrowDetails = await getBorrowDetails(wallet, cometAddress); const supplyDetails = await getSupplyDetails(wallet, cometAddress); const borrowValue = borrowDetails.borrowAmount.mul(borrowDetails.price); let totalAdjustedCollateral = new decimal_js_1.Decimal(0); for (const supply of supplyDetails) { const collateralValue = supply.supplyAmount.mul(supply.price); const adjustedValue = collateralValue.mul(supply.collateralFactor); totalAdjustedCollateral = totalAdjustedCollateral.add(adjustedValue); } return borrowValue.eq(0) ? new decimal_js_1.Decimal(Infinity) : totalAdjustedCollateral.div(borrowValue); }; exports.getHealthRatio = getHealthRatio; /** * Get health ratio after a hypothetical withdraw * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @param tokenAddress - The address of the token contract * @param amount - The amount to withdraw * @returns The health ratio after withdraw as a Decimal */ const getHealthRatioAfterWithdraw = async (wallet, cometAddress, tokenAddress, amount) => { const borrowDetails = await getBorrowDetails(wallet, cometAddress); const supplyDetails = await getSupplyDetails(wallet, cometAddress); const borrowValue = borrowDetails.borrowAmount.mul(borrowDetails.price); let totalAdjustedCollateral = new decimal_js_1.Decimal(0); for (const supply of supplyDetails) { const supplyTokenSymbol = supply.tokenSymbol; const withdrawTokenSymbol = await (0, exports.getTokenSymbol)(wallet, tokenAddress); if (supplyTokenSymbol === withdrawTokenSymbol) { const decimals = await (0, exports.getTokenDecimals)(wallet, tokenAddress); const withdrawAmountHuman = new decimal_js_1.Decimal((0, viem_1.formatUnits)(amount, decimals)); const newSupplyAmount = supply.supplyAmount.sub(withdrawAmountHuman); const assetValue = newSupplyAmount.mul(supply.price); totalAdjustedCollateral = totalAdjustedCollateral.add(assetValue.mul(supply.collateralFactor)); } else { totalAdjustedCollateral = totalAdjustedCollateral.add(supply.supplyAmount.mul(supply.price).mul(supply.collateralFactor)); } } return borrowValue.eq(0) ? new decimal_js_1.Decimal(Infinity) : totalAdjustedCollateral.div(borrowValue); }; exports.getHealthRatioAfterWithdraw = getHealthRatioAfterWithdraw; /** * Get health ratio after a hypothetical borrow * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @param amount - The amount to borrow * @returns The health ratio after borrow as a Decimal */ const getHealthRatioAfterBorrow = async (wallet, cometAddress, amount) => { const borrowDetails = await getBorrowDetails(wallet, cometAddress); const supplyDetails = await getSupplyDetails(wallet, cometAddress); const baseToken = await (0, exports.getBaseTokenAddress)(wallet, cometAddress); const baseDecimals = await (0, exports.getTokenDecimals)(wallet, baseToken); const additionalBorrow = new decimal_js_1.Decimal((0, viem_1.formatUnits)(amount, baseDecimals)); const newBorrow = borrowDetails.borrowAmount.add(additionalBorrow); const newBorrowValue = newBorrow.mul(borrowDetails.price); let totalAdjustedCollateral = new decimal_js_1.Decimal(0); for (const supply of supplyDetails) { totalAdjustedCollateral = totalAdjustedCollateral.add(supply.supplyAmount.mul(supply.price).mul(supply.collateralFactor)); } return newBorrowValue.eq(0) ? new decimal_js_1.Decimal(Infinity) : totalAdjustedCollateral.div(newBorrowValue); }; exports.getHealthRatioAfterBorrow = getHealthRatioAfterBorrow; /** * Get portfolio details in markdown format * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @returns A markdown formatted string with portfolio details */ const getPortfolioDetailsMarkdown = async (wallet, cometAddress) => { let markdownOutput = "# Portfolio Details\n\n"; markdownOutput += "## Supply Details\n\n"; let totalSupplyValue = new decimal_js_1.Decimal(0); const supplyDetails = await getSupplyDetails(wallet, cometAddress); if (supplyDetails.length > 0) { for (const supply of supplyDetails) { const token = supply.tokenSymbol; const supplyAmount = supply.supplyAmount; const price = supply.price; const decimals = supply.decimals; const collateralFactor = supply.collateralFactor; const assetValue = supplyAmount.mul(price); markdownOutput += `### ${token}\n`; markdownOutput += `- **Supply Amount:** ${supplyAmount.toFixed(decimals)}\n`; markdownOutput += `- **Price:** $${price.toFixed(2)}\n`; markdownOutput += `- **Collateral Factor:** ${collateralFactor.toFixed(2)}\n`; markdownOutput += `- **Asset Value:** $${assetValue.toFixed(2)}\n\n`; totalSupplyValue = totalSupplyValue.add(assetValue); } } else { markdownOutput += "No supplied assets found in your Compound position.\n\n"; } markdownOutput += `### Total Supply Value: $${totalSupplyValue.toFixed(2)}\n\n`; markdownOutput += "## Borrow Details\n\n"; const borrowDetails = await getBorrowDetails(wallet, cometAddress); if (borrowDetails.borrowAmount.gt(0)) { const token = borrowDetails.tokenSymbol; const price = borrowDetails.price; const borrowValue = borrowDetails.borrowAmount.mul(price); markdownOutput += `### ${token}\n`; markdownOutput += `- **Borrow Amount:** ${borrowDetails.borrowAmount.toFixed(6)}\n`; markdownOutput += `- **Price:** $${price.toFixed(2)}\n`; markdownOutput += `- **Borrow Value:** $${borrowValue.toFixed(2)}\n\n`; } else { markdownOutput += "No borrowed assets found in your Compound position.\n\n"; } markdownOutput += "## Overall Health\n\n"; const healthRatio = await (0, exports.getHealthRatio)(wallet, cometAddress); markdownOutput += `- **Health Ratio:** ${healthRatio.toFixed(2)}\n`; return markdownOutput; }; exports.getPortfolioDetailsMarkdown = getPortfolioDetailsMarkdown; /** * Fetch the latest price feed data. * * @param wallet - The wallet provider instance * @param priceFeedAddress - The address of the price feed contract * @returns A tuple containing the price and timestamp */ const getPriceFeedData = async (wallet, priceFeedAddress) => { const latestData = await wallet.readContract({ address: priceFeedAddress, abi: constants_1.PRICE_FEED_ABI, functionName: "latestRoundData", args: [], }); const answer = latestData[1].toString(); const updatedAt = Number(latestData[3]); return [answer, updatedAt]; }; /** * Retrieve borrow details: amount, base token symbol, and price. * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @returns An object containing borrow details */ const getBorrowDetails = async (wallet, cometAddress) => { const borrowAmountRaw = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "borrowBalanceOf", args: [(await wallet.getAddress())], }); const baseToken = await (0, exports.getBaseTokenAddress)(wallet, cometAddress); const baseDecimals = await (0, exports.getTokenDecimals)(wallet, baseToken); const baseTokenSymbol = await (0, exports.getTokenSymbol)(wallet, baseToken); const basePriceFeed = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "baseTokenPriceFeed", args: [], }); const [basePriceRaw] = await getPriceFeedData(wallet, basePriceFeed); const humanBorrowAmount = new decimal_js_1.Decimal((0, viem_1.formatUnits)(borrowAmountRaw, baseDecimals)); const price = new decimal_js_1.Decimal(basePriceRaw).div(new decimal_js_1.Decimal(10).pow(8)); return { tokenSymbol: baseTokenSymbol, borrowAmount: humanBorrowAmount, price }; }; /** * Retrieve supply details across all collateral assets. * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @returns An array of supply details for each asset */ const getSupplyDetails = async (wallet, cometAddress) => { const numAssets = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "numAssets", args: [], }); const supplyDetails = []; for (let i = 0; i < numAssets; i++) { const assetInfo = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "getAssetInfo", args: [i], }); const assetAddress = assetInfo.asset; const collateralBalance = await (0, exports.getCollateralBalance)(wallet, cometAddress, assetAddress); if (collateralBalance > 0n) { const tokenSymbol = await (0, exports.getTokenSymbol)(wallet, assetAddress); const decimals = await (0, exports.getTokenDecimals)(wallet, assetAddress); const [priceRaw] = await getPriceFeedData(wallet, assetInfo.priceFeed); const humanSupplyAmount = new decimal_js_1.Decimal((0, viem_1.formatUnits)(collateralBalance, decimals)); const price = new decimal_js_1.Decimal(priceRaw).div(new decimal_js_1.Decimal(10).pow(8)); const collateralFactor = new decimal_js_1.Decimal(assetInfo.borrowCollateralFactor.toString()).div(new decimal_js_1.Decimal(10).pow(18)); supplyDetails.push({ tokenSymbol, supplyAmount: humanSupplyAmount, price, collateralFactor, decimals, }); } } return supplyDetails; }; /** * Gets the Comet address for the current network. * * @param network - The network instance * @returns The Comet contract address */ const getCometAddress = (network) => { if (!network.networkId) { throw new Error("Network ID is required"); } if (network.networkId === "base-mainnet") { return constants_2.COMET_ADDRESSES["base-mainnet"]; } else if (network.networkId === "base-sepolia") { return constants_2.COMET_ADDRESSES["base-sepolia"]; } throw new Error(`Network ${network.networkId} not supported`); }; exports.getCometAddress = getCometAddress; /** * Gets the asset address for a given assetId on the current network. * * @param network - The network instance * @param assetId - The identifier of the asset * @returns The asset contract address */ const getAssetAddress = (network, assetId) => { if (!network.networkId) { throw new Error("Network ID is required"); } const normalizedAssetId = assetId.toLowerCase(); if (network.networkId === "base-mainnet") { const address = constants_2.ASSET_ADDRESSES["base-mainnet"][normalizedAssetId]; if (!address) { throw new Error(`Asset ${assetId} not supported on Base Mainnet`); } return address; } else if (network.networkId === "base-sepolia") { const address = constants_2.ASSET_ADDRESSES["base-sepolia"][normalizedAssetId]; if (!address) { throw new Error(`Asset ${assetId} not supported on Base Sepolia`); } return address; } throw new Error(`Network ${network.networkId} not supported`); }; exports.getAssetAddress = getAssetAddress; /** * Get the base token address for a Compound market * * @param wallet - The wallet provider instance * @param cometAddress - The address of the Comet contract * @returns The base token address */ const getBaseTokenAddress = async (wallet, cometAddress) => { const baseToken = await wallet.readContract({ address: cometAddress, abi: constants_1.COMET_ABI, functionName: "baseToken", args: [], }); return baseToken; }; exports.getBaseTokenAddress = getBaseTokenAddress;