diff --git a/typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts b/typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts index b13437323..507eb0532 100644 --- a/typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts +++ b/typescript/agentkit/src/action-providers/compound/compoundActionProvider.ts @@ -15,6 +15,7 @@ import { CompoundPortfolioSchema, } from "./schemas"; import { + getBaseAssetBalance, getCollateralBalance, getHealthRatio, getHealthRatioAfterBorrow, @@ -165,22 +166,40 @@ Important notes: 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 = + 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( + 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 diff --git a/typescript/agentkit/src/action-providers/compound/constants.ts b/typescript/agentkit/src/action-providers/compound/constants.ts index 9e93e85fa..5798aab67 100644 --- a/typescript/agentkit/src/action-providers/compound/constants.ts +++ b/typescript/agentkit/src/action-providers/compound/constants.ts @@ -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 = [ diff --git a/typescript/agentkit/src/action-providers/compound/utils.ts b/typescript/agentkit/src/action-providers/compound/utils.ts index fe7dcb6ea..f91e15053 100644 --- a/typescript/agentkit/src/action-providers/compound/utils.ts +++ b/typescript/agentkit/src/action-providers/compound/utils.ts @@ -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 => { + 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 * @@ -203,8 +225,37 @@ export const getPortfolioDetailsMarkdown = async ( cometAddress: Address, ): Promise => { 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) { @@ -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`; @@ -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);