@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
354 lines (353 loc) • 15.2 kB
JavaScript
"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;