Skip to content
Open
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 @@ -141,7 +141,9 @@ export class MockLendProvider extends LendProvider<LendProviderConfig> {
* Helper method to simulate errors
*/
simulateError(method: keyof MockLendProvider, error: Error) {
const mockMethod = this[method] as MockedFunction<any>
const mockMethod = this[method] as MockedFunction<
(...args: never[]) => unknown
>
if (mockMethod && typeof mockMethod.mockRejectedValue === 'function') {
mockMethod.mockRejectedValue(error)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/actions/lend/providers/aave/sdk.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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 () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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)
})

Expand All @@ -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
Expand Down Expand Up @@ -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%
Expand Down Expand Up @@ -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%
Expand All @@ -149,7 +151,7 @@ describe('Vault Utilities', () => {
]),
}

const result = calculateBaseApy(vault)
const result = calculateBaseApy(vault as unknown as AccrualVault)
expect(result).toBe(0)
})
})
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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%
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
46 changes: 44 additions & 2 deletions packages/sdk/src/actions/lend/providers/morpho/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,7 +59,7 @@ export interface RewardsBreakdown {
export async function fetchRewards(
vaultAddress: Address,
chainId: number,
): Promise<any | null> {
): Promise<MorphoApiVault | null> {
const vaultQuery = {
query: `
query VaultByAddress($address: String!, $chainId: Int) {
Expand Down Expand Up @@ -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
Expand Down
25 changes: 14 additions & 11 deletions packages/sdk/src/actions/lend/providers/morpho/sdk.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -517,7 +523,7 @@ function categorizeRewardAsset(
* @returns Detailed rewards breakdown
*/
export function calculateRewardsBreakdown(
apiVault: any,
apiVault: MorphoApiVault,
chainId: number,
marketAsset?: Asset,
): RewardsBreakdown {
Expand Down Expand Up @@ -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 (
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk/src/services/__mocks__/MockChainManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,14 @@ export class MockChainManager {
getBalance: vi.fn().mockImplementation(() => {
return Promise.resolve(this.config.defaultBalance)
}),
} as any
} as unknown as PublicClient
}

private createBundlerClient(): BundlerClient {
return {
sendUserOperation: vi.fn(),
waitForUserOperationReceipt: vi.fn(),
prepareUserOperation: vi.fn(),
} as any
} as unknown as BundlerClient
}
}