Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
CompoundPortfolioSchema,
} from "./schemas";
import {
getBaseAssetBalance,
getCollateralBalance,
getHealthRatio,
getHealthRatioAfterBorrow,
Expand Down Expand Up @@ -165,22 +166,40 @@
const decimals = await getTokenDecimals(wallet, tokenAddress);
const amountAtomic = parseUnits(args.amount, decimals);

// Check that there is enough collateral supplied to withdraw
const collateralBalance = await getCollateralBalance(wallet, cometAddress, tokenAddress);
if (amountAtomic > collateralBalance) {
const humanBalance = 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 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)}`;
// Determine if this is the base asset (e.g. USDC) or a collateral asset
const baseTokenAddress = await getBaseTokenAddress(wallet, cometAddress);
const isBaseAsset =

Check failure on line 171 in typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts

View workflow job for this annotation

GitHub Actions / lint-typescript

Delete `⏎·······`
tokenAddress.toLowerCase() === baseTokenAddress.toLowerCase();

if (isBaseAsset) {
// Base asset: check Comet.balanceOf() (supply balance, earns APY)
const baseBalance = await getBaseAssetBalance(wallet, cometAddress);
if (amountAtomic > baseBalance) {
const humanBalance = formatUnits(baseBalance, decimals);
return `Error: Insufficient balance. Trying to withdraw ${args.amount}, but only have ${humanBalance} supplied`;
}
} else {
// Collateral asset: check collateralBalanceOf
const collateralBalance = await getCollateralBalance(

Check failure on line 183 in typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts

View workflow job for this annotation

GitHub Actions / lint-typescript

Replace `⏎··········wallet,⏎··········cometAddress,⏎··········tokenAddress,⏎········` with `wallet,·cometAddress,·tokenAddress`
wallet,
cometAddress,
tokenAddress,
);
if (amountAtomic > collateralBalance) {
const humanBalance = formatUnits(collateralBalance, decimals);
return `Error: Insufficient balance. Trying to withdraw ${args.amount}, but only have ${humanBalance} supplied`;
}

// Only check health ratio for collateral withdrawals (affects borrow capacity)
const projectedHealthRatio = await 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export const COMET_ABI = [
stateMutability: "view",
type: "function",
},
{
inputs: [{ internalType: "address", name: "account", type: "address" }],
name: "balanceOf",
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;

export const PRICE_FEED_ABI = [
Expand Down
57 changes: 54 additions & 3 deletions typescript/agentkit/src/action-providers/compound/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,28 @@ export const getCollateralBalance = async (
return balance;
};

/**
* Get base asset (e.g. USDC) supply balance via Comet.balanceOf().
* This is distinct from collateral — the base asset is what earns supply APY.
*
* @param wallet - The wallet provider instance
* @param cometAddress - The address of the Comet contract
* @returns The base asset supply balance as a bigint
*/
export const getBaseAssetBalance = async (
wallet: EvmWalletProvider,
cometAddress: Address,
): Promise<bigint> => {
const balance = await wallet.readContract({
address: cometAddress,
abi: COMET_ABI,
functionName: "balanceOf",
args: [(await wallet.getAddress()) as `0x${string}`],
});

return balance;
};

/**
* Get health ratio for an account
*
Expand Down Expand Up @@ -203,8 +225,37 @@ export const getPortfolioDetailsMarkdown = async (
cometAddress: Address,
): Promise<string> => {
let markdownOutput = "# Portfolio Details\n\n";
markdownOutput += "## Supply Details\n\n";
let totalSupplyValue = new Decimal(0);

// Base asset supply (e.g. USDC) — tracked via Comet.balanceOf(), earns APY
markdownOutput += "## Base Asset Supply\n\n";
const baseAssetBalance = await getBaseAssetBalance(wallet, cometAddress);
const baseToken = await getBaseTokenAddress(wallet, cometAddress);
const baseDecimals = await getTokenDecimals(wallet, baseToken);
const baseSymbol = await getTokenSymbol(wallet, baseToken);
const basePriceFeed = await wallet.readContract({
address: cometAddress,
abi: COMET_ABI,
functionName: "baseTokenPriceFeed",
args: [],
});
const [basePriceRaw] = await getPriceFeedData(wallet, basePriceFeed);
const basePrice = new Decimal(basePriceRaw).div(new Decimal(10).pow(8));
const baseSupplyAmount = new Decimal(formatUnits(baseAssetBalance, baseDecimals));

if (baseAssetBalance > BigInt(0)) {
const baseValue = baseSupplyAmount.mul(basePrice);
markdownOutput += `### ${baseSymbol} (Base Asset — earns supply APY)\n`;
markdownOutput += `- **Supply Amount:** ${baseSupplyAmount.toFixed(baseDecimals)}\n`;
markdownOutput += `- **Price:** $${basePrice.toFixed(2)}\n`;
markdownOutput += `- **Asset Value:** $${baseValue.toFixed(2)}\n\n`;
totalSupplyValue = totalSupplyValue.add(baseValue);
} else {
markdownOutput += "No base asset supplied.\n\n";
}

// Collateral assets — tracked via collateralBalanceOf(), used to back borrows
markdownOutput += "## Collateral Details\n\n";
const supplyDetails = await getSupplyDetails(wallet, cometAddress);

if (supplyDetails.length > 0) {
Expand All @@ -224,7 +275,7 @@ export const getPortfolioDetailsMarkdown = async (
totalSupplyValue = totalSupplyValue.add(assetValue);
}
} else {
markdownOutput += "No supplied assets found in your Compound position.\n\n";
markdownOutput += "No collateral assets supplied.\n\n";
}

markdownOutput += `### Total Supply Value: $${totalSupplyValue.toFixed(2)}\n\n`;
Expand Down Expand Up @@ -354,7 +405,7 @@ const getSupplyDetails = async (
const assetAddress = assetInfo.asset;
const collateralBalance = await getCollateralBalance(wallet, cometAddress, assetAddress);

if (collateralBalance > 0n) {
if (collateralBalance > BigInt(0)) {
const tokenSymbol = await getTokenSymbol(wallet, assetAddress);
const decimals = await getTokenDecimals(wallet, assetAddress);
const [priceRaw] = await getPriceFeedData(wallet, assetInfo.priceFeed);
Expand Down
Loading