From d26dea7ba6f69265ffc476c2e1382c73b7d7be40 Mon Sep 17 00:00:00 2001 From: cats2101 Date: Sun, 19 Apr 2026 00:13:22 +0000 Subject: [PATCH] refactor(sdk): replace `any` types in lend provider sources Part of #337. Replaces `any` with specific types in SDK source files (tests other than the directly-affected fixtures are untouched): - `lend/providers/morpho/api.ts`: type the GraphQL response with a `MorphoApiVault` interface and surface it from the fetch helper. - `lend/providers/morpho/sdk.ts`: type vault inputs as `AccrualVault` and allocations as `VaultMarketAllocation` from `@morpho-org/blue-sdk`; use `MorphoApiVault` for the GraphQL-sourced variant. - `lend/providers/aave/sdk.ts`: type `formattedReserve` as `FormatReserveUSDResponse` from `@aave/math-utils`. - `services/__mocks__/MockChainManager.ts` and `lend/__mocks__/MockLendProvider.ts`: replace `as any` with `as unknown as ` per the issue guidelines. Direct fixture call sites in the Morpho sdk tests are cast with `as unknown as AccrualVault` / `MorphoApiVault` to keep the partial test objects compatible. Remaining `any` warnings live in other test files and are left for follow-up PRs. pnpm typecheck, pnpm lint (no new warnings), and pnpm test all pass. --- .../lend/__mocks__/MockLendProvider.ts | 4 +- .../src/actions/lend/providers/aave/sdk.ts | 4 +- .../providers/morpho/__tests__/api.test.ts | 8 ++-- .../providers/morpho/__tests__/sdk.test.ts | 42 ++++++++++++----- .../src/actions/lend/providers/morpho/api.ts | 46 ++++++++++++++++++- .../src/actions/lend/providers/morpho/sdk.ts | 25 +++++----- .../services/__mocks__/MockChainManager.ts | 4 +- 7 files changed, 100 insertions(+), 33 deletions(-) diff --git a/packages/sdk/src/actions/lend/__mocks__/MockLendProvider.ts b/packages/sdk/src/actions/lend/__mocks__/MockLendProvider.ts index fa40d651c..86c346dc3 100644 --- a/packages/sdk/src/actions/lend/__mocks__/MockLendProvider.ts +++ b/packages/sdk/src/actions/lend/__mocks__/MockLendProvider.ts @@ -141,7 +141,9 @@ export class MockLendProvider extends LendProvider { * Helper method to simulate errors */ simulateError(method: keyof MockLendProvider, error: Error) { - const mockMethod = this[method] as MockedFunction + const mockMethod = this[method] as MockedFunction< + (...args: never[]) => unknown + > if (mockMethod && typeof mockMethod.mockRejectedValue === 'function') { mockMethod.mockRejectedValue(error) } diff --git a/packages/sdk/src/actions/lend/providers/aave/sdk.ts b/packages/sdk/src/actions/lend/providers/aave/sdk.ts index bc2823066..38efc51bf 100644 --- a/packages/sdk/src/actions/lend/providers/aave/sdk.ts +++ b/packages/sdk/src/actions/lend/providers/aave/sdk.ts @@ -1,5 +1,5 @@ import { UiPoolDataProvider } from '@aave/contract-helpers' -import { formatReserves } from '@aave/math-utils' +import { formatReserves, type FormatReserveUSDResponse } from '@aave/math-utils' import { providers } from 'ethers' import type { Address } from 'viem' @@ -64,7 +64,7 @@ function findMarketInAllowlist( * @returns APY breakdown with native APY and rewards */ export function calculateApyBreakdown(reserve: { - formattedReserve?: any + formattedReserve?: FormatReserveUSDResponse }): ApyBreakdown { // Get supply APY from formatted reserve data const supplyApy = reserve.formattedReserve?.supplyAPY diff --git a/packages/sdk/src/actions/lend/providers/morpho/__tests__/api.test.ts b/packages/sdk/src/actions/lend/providers/morpho/__tests__/api.test.ts index 1ec4c6f96..bee56d4c9 100644 --- a/packages/sdk/src/actions/lend/providers/morpho/__tests__/api.test.ts +++ b/packages/sdk/src/actions/lend/providers/morpho/__tests__/api.test.ts @@ -117,10 +117,10 @@ describe('Morpho API Integration', () => { expect(vaultData).toBeDefined() expect(vaultData).not.toBeNull() - expect(vaultData.address.toLowerCase()).toBe( + expect(vaultData!.address.toLowerCase()).toBe( GAUNTLET_USDC_VAULT.toLowerCase(), ) - expect(vaultData.state.rewards).toHaveLength(2) + expect(vaultData!.state!.rewards).toHaveLength(2) }) it('should return null when vault not found', async () => { @@ -308,10 +308,10 @@ describe('Morpho API Integration', () => { expect(vaultData).toBeDefined() expect(vaultData).not.toBeNull() - expect(vaultData.address.toLowerCase()).toBe( + expect(vaultData!.address.toLowerCase()).toBe( GAUNTLET_USDC_VAULT.toLowerCase(), ) - expect(vaultData.state).toBeDefined() + expect(vaultData!.state).toBeDefined() }, 30000) it('should fetch and calculate rewards breakdown', async () => { diff --git a/packages/sdk/src/actions/lend/providers/morpho/__tests__/sdk.test.ts b/packages/sdk/src/actions/lend/providers/morpho/__tests__/sdk.test.ts index c9948dcf6..0d45edb88 100644 --- a/packages/sdk/src/actions/lend/providers/morpho/__tests__/sdk.test.ts +++ b/packages/sdk/src/actions/lend/providers/morpho/__tests__/sdk.test.ts @@ -1,6 +1,8 @@ +import type { AccrualVault } from '@morpho-org/blue-sdk' import { mainnet } from 'viem/chains' import { describe, expect, it } from 'vitest' +import type { MorphoApiVault } from '@/actions/lend/providers/morpho/api.js' import { calculateBaseApy, calculateRewardsBreakdown, @@ -20,7 +22,7 @@ describe('Vault Utilities', () => { allocations: new Map(), } - const result = calculateBaseApy(vault) + const result = calculateBaseApy(vault as unknown as AccrualVault) expect(result).toBe(0) }) @@ -44,7 +46,7 @@ describe('Vault Utilities', () => { ]), } - const result = calculateBaseApy(vault) + const result = calculateBaseApy(vault as unknown as AccrualVault) // Expected calculation: // Weighted APY = (5% * 500K) / 1M = 2.5% before fees @@ -84,7 +86,7 @@ describe('Vault Utilities', () => { ]), } - const result = calculateBaseApy(vault) + const result = calculateBaseApy(vault as unknown as AccrualVault) // Expected calculation: // Weighted APY = (4% * 300K + 6% * 700K) / 1M = (1.2% + 4.2%) = 5.4% @@ -124,7 +126,7 @@ describe('Vault Utilities', () => { ]), } - const result = calculateBaseApy(vault) + const result = calculateBaseApy(vault as unknown as AccrualVault) // Should only count the allocation with shares // 3% * (1 - 0.1) = 2.7% @@ -149,7 +151,7 @@ describe('Vault Utilities', () => { ]), } - const result = calculateBaseApy(vault) + const result = calculateBaseApy(vault as unknown as AccrualVault) expect(result).toBe(0) }) }) @@ -163,7 +165,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) expect(result[USDC_ADDRESS]).toBe(0) expect(result[MORPHO_ADDRESS]).toBe(0) @@ -198,7 +203,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) expect(result[USDC_ADDRESS]).toBeCloseTo(0.03, 4) expect(result[MORPHO_ADDRESS]).toBeCloseTo(0.015, 4) @@ -251,7 +259,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) // Expected calculation: // USDC: 2% * (600k / 1M) = 1.2% @@ -298,7 +309,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) expect(result[USDC_ADDRESS]).toBeCloseTo(0.01, 4) // Vault-level expect(result[MORPHO_ADDRESS]).toBeCloseTo(0.015, 4) // Market-level (100% weight) @@ -323,7 +337,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) expect(result[USDC_ADDRESS]).toBe(0) expect(result[MORPHO_ADDRESS]).toBe(0) @@ -358,7 +375,10 @@ describe('Vault Utilities', () => { }, } - const result = calculateRewardsBreakdown(apiVault, CHAIN_ID) + const result = calculateRewardsBreakdown( + apiVault as unknown as MorphoApiVault, + CHAIN_ID, + ) // Should be zero because total supply is zero (weight = 0) expect(result[USDC_ADDRESS]).toBe(0) diff --git a/packages/sdk/src/actions/lend/providers/morpho/api.ts b/packages/sdk/src/actions/lend/providers/morpho/api.ts index f3a6db2e4..47b05bbc9 100644 --- a/packages/sdk/src/actions/lend/providers/morpho/api.ts +++ b/packages/sdk/src/actions/lend/providers/morpho/api.ts @@ -9,6 +9,48 @@ export interface RewardsBreakdown { totalRewards: number } +export interface MorphoRewardAsset { + address?: string + name?: string + symbol?: string + chain?: { id: number } +} + +export interface MorphoReward { + asset?: MorphoRewardAsset + amountPerSuppliedToken?: string + supplyApr?: number +} + +export interface MorphoMarketState { + rewards?: MorphoReward[] +} + +export interface MorphoAllocation { + market?: { + id?: string + uniqueKey?: string + state?: MorphoMarketState + } + supplyAssetsUsd?: number +} + +export interface MorphoVaultState { + rewards?: MorphoReward[] + allocation?: MorphoAllocation[] +} + +export interface MorphoApiVault { + address: string + id: string + state?: MorphoVaultState + chain?: { id: number } +} + +interface MorphoVaultApiResponse { + data?: { vaultByAddress?: MorphoApiVault | null } +} + /** * Fetch raw vault rewards data from Morpho GraphQL API * @param vaultAddress - Vault address @@ -17,7 +59,7 @@ export interface RewardsBreakdown { export async function fetchRewards( vaultAddress: Address, chainId: number, -): Promise { +): Promise { const vaultQuery = { query: ` query VaultByAddress($address: String!, $chainId: Int) { @@ -79,7 +121,7 @@ export async function fetchRewards( body: JSON.stringify(vaultQuery), }) - const vaultData = (await response.json()) as any + const vaultData = (await response.json()) as MorphoVaultApiResponse return vaultData.data?.vaultByAddress || null } catch (apiError) { // eslint-disable-next-line no-console diff --git a/packages/sdk/src/actions/lend/providers/morpho/sdk.ts b/packages/sdk/src/actions/lend/providers/morpho/sdk.ts index 3261be527..9b6c99ece 100644 --- a/packages/sdk/src/actions/lend/providers/morpho/sdk.ts +++ b/packages/sdk/src/actions/lend/providers/morpho/sdk.ts @@ -1,4 +1,9 @@ -import { type AccrualPosition, ChainId } from '@morpho-org/blue-sdk' +import { + type AccrualPosition, + type AccrualVault, + ChainId, + type VaultMarketAllocation, +} from '@morpho-org/blue-sdk' import { adaptiveCurveIrmAbi, blueAbi, @@ -9,6 +14,7 @@ import type { Address, PublicClient } from 'viem' import { fetchRewards, + type MorphoApiVault, type RewardsBreakdown, } from '@/actions/lend/providers/morpho/api.js' import { getMorphoContracts } from '@/actions/shared/morpho/contracts.js' @@ -71,7 +77,7 @@ function buildEmptyRewards( * @param vault - Vault data from Morpho SDK * @returns Base APY (before rewards, after fees) */ -export function calculateBaseApy(vault: any): number { +export function calculateBaseApy(vault: AccrualVault): number { try { if (vault.totalAssets === 0n) { return 0 @@ -81,7 +87,7 @@ export function calculateBaseApy(vault: any): number { const allocationsArray = Array.from(vault.allocations.values()) const totalWeightedApy = allocationsArray.reduce( - (total: bigint, allocation: any) => { + (total: bigint, allocation: VaultMarketAllocation) => { const position: AccrualPosition = allocation.position const market = position.market @@ -470,7 +476,7 @@ export async function findBestVaultForAsset( * @returns Complete APY breakdown */ export function calculateApyBreakdown( - vault: any, + vault: AccrualVault, rewardsBreakdown: RewardsBreakdown, ): ApyBreakdown { // 1. Calculate base APY from SDK data (before fees) @@ -517,7 +523,7 @@ function categorizeRewardAsset( * @returns Detailed rewards breakdown */ export function calculateRewardsBreakdown( - apiVault: any, + apiVault: MorphoApiVault, chainId: number, marketAsset?: Asset, ): RewardsBreakdown { @@ -553,12 +559,9 @@ export function calculateRewardsBreakdown( // Calculate market-level rewards (weighted by allocation) if (apiVault.state?.allocation && apiVault.state.allocation.length > 0) { - const totalSupplyUsd = apiVault.state.allocation.reduce( - (total: number, alloc: any) => { - return total + (alloc.supplyAssetsUsd || 0) - }, - 0, - ) + const totalSupplyUsd = apiVault.state.allocation.reduce((total, alloc) => { + return total + (alloc.supplyAssetsUsd || 0) + }, 0) for (const allocation of apiVault.state.allocation) { if ( diff --git a/packages/sdk/src/services/__mocks__/MockChainManager.ts b/packages/sdk/src/services/__mocks__/MockChainManager.ts index 13bfbb9bd..c2a7a4ae5 100644 --- a/packages/sdk/src/services/__mocks__/MockChainManager.ts +++ b/packages/sdk/src/services/__mocks__/MockChainManager.ts @@ -130,7 +130,7 @@ export class MockChainManager { getBalance: vi.fn().mockImplementation(() => { return Promise.resolve(this.config.defaultBalance) }), - } as any + } as unknown as PublicClient } private createBundlerClient(): BundlerClient { @@ -138,6 +138,6 @@ export class MockChainManager { sendUserOperation: vi.fn(), waitForUserOperationReceipt: vi.fn(), prepareUserOperation: vi.fn(), - } as any + } as unknown as BundlerClient } }