From bffd0cd467a1acffecf4e8176451b33479b75fdf Mon Sep 17 00:00:00 2001 From: gmbronco <83549293+gmbronco@users.noreply.github.com> Date: Wed, 21 May 2025 20:02:54 +0200 Subject: [PATCH 01/37] extracting APRs module --- modules/aprs/README.md | 98 +++++ modules/aprs/STRUCTURE.md | 70 +++ modules/aprs/apr-manager.ts | 68 +++ modules/aprs/apr-repository.ts | 128 ++++++ modules/aprs/apr-service.ts | 69 +++ modules/aprs/config/arbitrum.ts | 5 + modules/aprs/config/avalanche.ts | 5 + modules/aprs/config/base.ts | 5 + modules/aprs/config/index.ts | 24 + modules/aprs/config/mainnet.ts | 411 ++++++++++++++++++ modules/aprs/config/optimism.ts | 5 + modules/aprs/config/polygon.ts | 5 + modules/aprs/examples/comparison.ts | 30 ++ modules/aprs/examples/integration.ts | 26 ++ .../aave-api-apr/aave-api-apr-handler.test.ts | 246 +++++++++++ .../aave-api-apr/aave-api-apr-handler.ts | 91 ++++ .../handlers/aave-api-apr/aave-chan-client.ts | 105 +++++ modules/aprs/handlers/aave-api-apr/index.ts | 1 + modules/aprs/handlers/create-handlers.ts | 26 ++ modules/aprs/handlers/index.ts | 10 + modules/aprs/handlers/swap-fee-apr/index.ts | 1 + .../swap-fee-apr/swap-fee-apr-handler.test.ts | 124 ++++++ .../swap-fee-apr/swap-fee-apr-handler.ts | 58 +++ modules/aprs/handlers/types.ts | 5 + modules/aprs/handlers/yb-tokens/index.ts | 2 + modules/aprs/handlers/yb-tokens/types.ts | 234 ++++++++++ .../yb-tokens/yb-tokens-apr-handler.ts | 146 +++++++ modules/aprs/index.ts | 1 + modules/aprs/types.ts | 43 ++ modules/pool/lib/pool-apr-updater.service.ts | 4 +- modules/pool/pool.service.ts | 4 +- 31 files changed, 2046 insertions(+), 4 deletions(-) create mode 100644 modules/aprs/README.md create mode 100644 modules/aprs/STRUCTURE.md create mode 100644 modules/aprs/apr-manager.ts create mode 100644 modules/aprs/apr-repository.ts create mode 100644 modules/aprs/apr-service.ts create mode 100644 modules/aprs/config/arbitrum.ts create mode 100644 modules/aprs/config/avalanche.ts create mode 100644 modules/aprs/config/base.ts create mode 100644 modules/aprs/config/index.ts create mode 100644 modules/aprs/config/mainnet.ts create mode 100644 modules/aprs/config/optimism.ts create mode 100644 modules/aprs/config/polygon.ts create mode 100644 modules/aprs/examples/comparison.ts create mode 100644 modules/aprs/examples/integration.ts create mode 100644 modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts create mode 100644 modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts create mode 100644 modules/aprs/handlers/aave-api-apr/aave-chan-client.ts create mode 100644 modules/aprs/handlers/aave-api-apr/index.ts create mode 100644 modules/aprs/handlers/create-handlers.ts create mode 100644 modules/aprs/handlers/index.ts create mode 100644 modules/aprs/handlers/swap-fee-apr/index.ts create mode 100644 modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts create mode 100644 modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts create mode 100644 modules/aprs/handlers/types.ts create mode 100644 modules/aprs/handlers/yb-tokens/index.ts create mode 100644 modules/aprs/handlers/yb-tokens/types.ts create mode 100644 modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts create mode 100644 modules/aprs/index.ts create mode 100644 modules/aprs/types.ts diff --git a/modules/aprs/README.md b/modules/aprs/README.md new file mode 100644 index 000000000..8479be088 --- /dev/null +++ b/modules/aprs/README.md @@ -0,0 +1,98 @@ +# APR Calculation Module + +This module provides a new implementation for pool APR calculations with improved separation of concerns and flexibility. + +## Key Features + +- Decoupling of database operations from APR calculations +- Ability to calculate APRs without writing to the database (for testing/debugging) +- Only writes to the database when APR values change +- Support for individual pool updates +- Clear separation of responsibilities with the Repository pattern +- Declarative configuration for chain-specific calculators + +## Architecture + +The module follows a layered architecture: + +1. **Repository Layer** - Handles data access (`PoolAprRepository`) +2. **Handlers Layer** - Contains pure calculation logic (`AprHandler` implementations) +3. **Configuration Layer** - Defines which calculators to use for each chain +4. **Manager Layer** - Coordinates calculators and persistence (`AprManager`) +5. **Service Layer** - Provides the public API (`AprService`) + +## Usage + +### Basic Usage + +```typescript +import { AprService } from '../modules/aprs'; +import { Chain } from '@prisma/client'; + +// Update APRs for all pools on a chain +const aprService = new AprService(); +await aprService.updateAprs('MAINNET' as Chain); + +// Update APR for a single pool +await aprService.updateAprForPool('MAINNET' as Chain, 'pool-id-here'); +``` + +### Debugging/Testing + +```typescript +// Calculate APRs without writing to database +const aprData = await aprService.calculateAprForPool('MAINNET' as Chain, 'pool-id-here'); +console.log('APR Items:', aprData); +``` + +### Reload All APRs + +```typescript +// Delete all existing APR items and recalculate +await aprService.reloadAllPoolAprs('MAINNET' as Chain); +``` + +## Testing + +There is a simple test script provided in `examples/integration.ts` that can be used to test the module: + +``` +bun run modules/examples/integration.ts MAINNET 0x1234... +``` + +## Development + +### Calculator Configuration + +The module uses a declarative approach to configure which handlers are used for each chain: + +1. **Configuration Definition**: Each chain has a object with handlers configuration +2. **Calculator Factories**: Factory functions create calculator instances with the right parameters + +This approach makes it easy to: + +- Add or remove calculators for specific chains +- Configure calculator parameters +- Understand at a glance which chains use which handlers + +### Adding New Handlers + +1. Create a new folder in the `handlers` directory +2. Implement the required methods +3. Add the handler to the `createHandlers` method in `handlers` folder + +```typescript +// Example calculator implementation +export class MyNewAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'MyNewAprHandler'; + } + + public async calculateAprForPools( + pools: PoolForAPRs[], + ): Promise[]> { + // Your calculation logic here + return []; + } +} +``` diff --git a/modules/aprs/STRUCTURE.md b/modules/aprs/STRUCTURE.md new file mode 100644 index 000000000..d72e46703 --- /dev/null +++ b/modules/aprs/STRUCTURE.md @@ -0,0 +1,70 @@ +# APR Module Structure + +This document outlines the structure and components of the new APR module. + +## Core Components + +### 1. Interfaces + +- **AprHandler**: Defines the contract for APR calculators + - `calculateAprForPools`: Calculates APR items for pools + - `getAprServiceName`: Returns the name of the calculator + +### 2. Repository + +- **AprRepository**: Handles all database operations + - Getting pools data for calculations + - Saving APR items + - Updating total APR values + - Deleting APR data + +### 3. Handlers + +- **SwapFeeAprHandler**: Calculates swap fee APR +- **BoostedPoolAprCalculator**: Calculates boosted pool APR + +### 4. Manager + +- **AprManager**: Coordinates calculators and persistence + - Orchestrates the calculation process + - Manages database operations through the repository + - Handles error reporting + +### 5. Configuration + +- **ChainConfig**: Defines which calculators to use for each chain +- **Configuration Approach**: Declarative configuration for easy maintenance + +### 6. Service + +- **AprService**: Main entry point for APR operations + - Uses configuration to create appropriate calculators for each chain + - Provides public methods for updating APRs + - Supports calculation without persistence + +## Data Flow + +1. Client calls `AprService` +2. `AprService` gets chain-specific handlers configuration +3. Handler factory creates the appropriate handler instances +4. `AprManager` coordinates the calculation process: + - Fetches required data through the repository + - Executes calculations using handlers + - Aggregates results + - Persists results through the repository (if needed) +5. Results are returned to the client + +## Configuration + +- Chain-specific configurations for handlers is defined in `config/` +- Easy to update which handlers are used for which chains + +## Key Features + +- Separation of concerns (calculation vs. persistence) +- Support for individual pool updates +- Calculation without persistence for testing +- Only writing to the database when values change +- Clear error handling and reporting +- Declarative configuration of chain-specific calculators +- Factory pattern for calculator instantiation diff --git a/modules/aprs/apr-manager.ts b/modules/aprs/apr-manager.ts new file mode 100644 index 000000000..ca94dbe9f --- /dev/null +++ b/modules/aprs/apr-manager.ts @@ -0,0 +1,68 @@ +import { Chain, PrismaPoolAprItem } from '@prisma/client'; +import { AprHandler } from './types'; +import { AprRepository } from './apr-repository'; +import _ from 'lodash'; + +export class AprManager { + constructor( + private readonly aprRepository: AprRepository, + private readonly aprHandlers: AprHandler[], + ) {} + + /** + * Calculate APRs without writing to the database + */ + async calculateAprs( + chain: Chain, + poolIds?: string[], + ): Promise[]> { + const pools = await this.aprRepository.getPoolsForAprCalculation(chain, poolIds); + + if (pools.length === 0) { + return []; + } + + // Get all APR items from all calculators + const allAprItems: Omit[] = []; + const failedCalculators: string[] = []; + + for (const calculator of this.aprHandlers) { + try { + const items = await calculator.calculateAprForPools(pools); + allAprItems.push(...items); + } catch (e) { + console.error(`Error during APR calculation in ${calculator.getAprServiceName()}:`, e); + failedCalculators.push(calculator.getAprServiceName()); + } + } + + if (failedCalculators.length > 0) { + console.warn(`The following APR calculators failed: ${failedCalculators.join(', ')}`); + } + + return allAprItems; + } + + /** + * Calculate and persist APRs + */ + async updateAprs(chain: Chain, poolIds?: string[]): Promise { + const aprItems = await this.calculateAprs(chain, poolIds); + const changedPoolIds = await this.aprRepository.savePoolAprItems(aprItems); + + if (changedPoolIds.length > 0) { + await this.aprRepository.updatePoolTotalApr(chain, changedPoolIds); + } + + return changedPoolIds; + } + + /** + * Reload all APRs for a chain (deletes existing data first) + * @returns Update statistics + */ + async reloadAllPoolAprs(chain: Chain): Promise { + await this.aprRepository.deleteAllPoolAprItems(chain); + return this.updateAprs(chain); + } +} diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts new file mode 100644 index 000000000..aacf8a982 --- /dev/null +++ b/modules/aprs/apr-repository.ts @@ -0,0 +1,128 @@ +import { Prisma, Chain, PrismaPoolAprItem } from '@prisma/client'; +import { prisma } from '../../prisma/prisma-client'; +import { prismaBulkExecuteOperations } from '../../prisma/prisma-util'; +import _ from 'lodash'; + +const aprsInclude = Prisma.validator()({ + include: { + dynamicData: true, + tokens: { include: { token: true, nestedPool: true } }, + staking: { include: { gauge: { include: { rewards: true } }, reliquary: true } }, + }, +}); + +export type PoolAPRData = Prisma.PrismaPoolGetPayload; + +export class AprRepository { + /** + * Get pools with data needed for APR calculations + */ + async getPoolsForAprCalculation(chain: Chain, poolIds?: string[]): Promise { + return prisma.prismaPool.findMany({ + include: { + dynamicData: true, + tokens: { include: { token: true, nestedPool: true } }, + staking: { include: { gauge: { include: { rewards: true } }, reliquary: true } }, + }, + where: { + chain, + ...(poolIds?.length ? { id: { in: poolIds } } : {}), + }, + }); + } + + /** + * Save APR items to the database + * Only updates when the APR value has changed + * @returns changed poolIDs + */ + async savePoolAprItems(aprItems: Omit[]): Promise { + if (aprItems.length === 0) return []; + + // Get unique chains and pool IDs from the items + const chains = [...new Set(aprItems.map((item) => item.chain))]; + const poolIds = [...new Set(aprItems.map((item) => item.poolId))]; + + // Fetch existing APR items + const existingItems = await prisma.prismaPoolAprItem.findMany({ + where: { + chain: { in: chains }, + poolId: { in: poolIds }, + id: { in: aprItems.map((item) => item.id) }, + }, + }); + + // Create a lookup map for quick access + const existingItemsMap = new Map(existingItems.map((item) => [`${item.id}-${item.chain}`, item])); + + // Only create operations for items that don't exist or have changed + const changedPoolIds = new Set(); + const operations = aprItems + .filter((item) => { + const existingItem = existingItemsMap.get(`${item.id}-${item.chain}`); + const changed = !existingItem || existingItem.apr !== item.apr; + if (changed) { + changedPoolIds.add(item.poolId); + } + return changed; + }) + .map((item) => + prisma.prismaPoolAprItem.upsert({ + where: { id_chain: { id: item.id, chain: item.chain } }, + create: item, + update: { apr: item.apr }, + }), + ); + + if (operations.length > 0) { + await prismaBulkExecuteOperations(operations); + console.log(`Updated ${operations.length} APR items`); + } + + return [...changedPoolIds]; + } + + /** + * Update total APR values in pool dynamic data + * Only updates when the APR value has changed + * @returns Number of pools that were actually updated + */ + async updatePoolTotalApr(chain: Chain, poolIds: string[]): Promise { + if (poolIds.length === 0) return true; + + await prisma.$executeRaw` + UPDATE "PrismaPoolDynamicData" AS dyn + SET apr = COALESCE(sub.total_apr, 0) + FROM ( + SELECT + "poolId", + "chain", + SUM("apr") AS total_apr + FROM "PrismaPoolAprItem" + WHERE "type" NOT IN ( + 'SURPLUS', + 'SURPLUS_30D', + 'SURPLUS_7D', + 'SWAP_FEE_30D', + 'SWAP_FEE_7D', + 'DYNAMIC_SWAP_FEE_24H' + ) + GROUP BY "poolId", "chain" + ) AS sub + WHERE dyn."poolId" = sub."poolId" + AND dyn."chain" = sub."chain" + AND dyn.chain = ${chain} + AND dyn."poolId" = ANY(${poolIds}); + `; + + return true; + } + + /** + * Delete all APR items for a chain + */ + async deleteAllPoolAprItems(chain: Chain): Promise { + await prisma.prismaPoolAprRange.deleteMany({ where: { chain } }); + await prisma.prismaPoolAprItem.deleteMany({ where: { chain } }); + } +} diff --git a/modules/aprs/apr-service.ts b/modules/aprs/apr-service.ts new file mode 100644 index 000000000..2dfbbee32 --- /dev/null +++ b/modules/aprs/apr-service.ts @@ -0,0 +1,69 @@ +import { Chain, PrismaPoolAprItem } from '@prisma/client'; +import { AprHandler } from './types'; +import { AprRepository } from './apr-repository'; +import { AprManager } from './apr-manager'; +import { createHandlers } from './handlers'; + +export class AprService { + private readonly aprRepository: AprRepository; + + constructor() { + this.aprRepository = new AprRepository(); + } + + /** + * Create appropriate handlers for a specific chain + */ + private createHandlersForChain(chain: Chain): AprHandler[] { + return createHandlers(chain); + } + + /** + * Get manager for a specific chain + */ + private getManagerForChain(chain: Chain): AprManager { + const handlers = this.createHandlersForChain(chain); + return new AprManager(this.aprRepository, handlers); + } + + /** + * Update APRs for all pools in a chain + * @returns Updated pool IDs + */ + async updateAprs(chain: Chain): Promise { + const manager = this.getManagerForChain(chain); + return manager.updateAprs(chain); + } + + /** + * Remove all existing APR data and recalculate for a chain + * @returns Updated pool IDs + */ + async reloadAprs(chain: Chain): Promise { + const manager = this.getManagerForChain(chain); + return manager.reloadAllPoolAprs(chain); + } + + /** + * Update APR for a specific pool + * @returns Updated pool ID + */ + async updateAprForPool(chain: Chain, poolId: string): Promise { + const manager = this.getManagerForChain(chain); + return manager.updateAprs(chain, [poolId]); + } + + /** + * Calculate APR for a specific pool without writing to the database + * Useful for testing and debugging + */ + async calculateAprForPool( + chain: Chain, + poolId: string, + ): Promise[]> { + const manager = this.getManagerForChain(chain); + const aprItems = await manager.calculateAprs(chain, [poolId]); + + return aprItems; + } +} diff --git a/modules/aprs/config/arbitrum.ts b/modules/aprs/config/arbitrum.ts new file mode 100644 index 000000000..a4a7b527f --- /dev/null +++ b/modules/aprs/config/arbitrum.ts @@ -0,0 +1,5 @@ +export default { + aaveApiConfig: { + chainId: 42161 + } +}; \ No newline at end of file diff --git a/modules/aprs/config/avalanche.ts b/modules/aprs/config/avalanche.ts new file mode 100644 index 000000000..75cd17360 --- /dev/null +++ b/modules/aprs/config/avalanche.ts @@ -0,0 +1,5 @@ +export default { + aaveApiConfig: { + chainId: 43114 + } +}; \ No newline at end of file diff --git a/modules/aprs/config/base.ts b/modules/aprs/config/base.ts new file mode 100644 index 000000000..c382fea06 --- /dev/null +++ b/modules/aprs/config/base.ts @@ -0,0 +1,5 @@ +export default { + aaveApiConfig: { + chainId: 8453 + } +}; \ No newline at end of file diff --git a/modules/aprs/config/index.ts b/modules/aprs/config/index.ts new file mode 100644 index 000000000..f88c808da --- /dev/null +++ b/modules/aprs/config/index.ts @@ -0,0 +1,24 @@ +import { Chain } from '@prisma/client'; +import mainnet from './mainnet'; +import arbitrum from './arbitrum'; +import avalanche from './avalanche'; +import base from './base'; +import optimism from './optimism'; +import polygon from './polygon'; +import { AaveApiConfig, YbAprConfig } from '../handlers/types'; + +export default >{ + [Chain.MAINNET]: mainnet, + [Chain.ARBITRUM]: arbitrum, + [Chain.AVALANCHE]: avalanche, + [Chain.BASE]: base, + [Chain.FANTOM]: {}, + [Chain.FRAXTAL]: {}, + [Chain.GNOSIS]: {}, + [Chain.MODE]: {}, + [Chain.OPTIMISM]: optimism, + [Chain.POLYGON]: polygon, + [Chain.SEPOLIA]: {}, + [Chain.SONIC]: {}, + [Chain.ZKEVM]: {}, +}; diff --git a/modules/aprs/config/mainnet.ts b/modules/aprs/config/mainnet.ts new file mode 100644 index 000000000..dfd78cdfc --- /dev/null +++ b/modules/aprs/config/mainnet.ts @@ -0,0 +1,411 @@ +import { env } from '../../../apps/env'; + +const underlyingTokens = { + USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', + DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', + wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + crvUSD: '0xf939e0a03fb07f59a73314e73794be0e57ac1b4e', + LUSD: '0x5f98805a4e8be255a32880fdec7f6728c6568ba0', + USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3', +}; + +export default { + aaveApiConfig: { + chainId: 1, + }, + ybAprConfig: { + usdl: true, + morpho: true, + aave: { + v2: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/8wR23o1zkS4gpLqLNU4kG3JHYVucqGyopL5utGxP2q1N`, + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', + wrappedTokens: { + waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', + wrappedTokens: { + waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', + wrappedTokens: { + waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', + }, + }, + }, + }, + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Cd2gEDVeqnjBn1hSeqFMitw8Q1iiyV9FYUZkLNRcL87g`, + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', + wrappedTokens: { + waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', + stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', + stataV2USDC: '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', + }, + }, + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', + wrappedTokens: { + waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', + stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', + stataEthUSDT2: '0x862c57d48becb45583aeba3f489696d22466ca1b', + stataV2USDT: '0x7bc3485026ac48b6cf9baf0a377477fff5703af8', + }, + }, + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', + wrappedTokens: { + waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + stataEthDAI: '0xeb708639e8e518b86a916db3685f90216b1c1c67', + }, + }, + wETH: { + underlyingAssetAddress: underlyingTokens.wETH, + aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', + wrappedTokens: { + waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', + stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', + stataV2WETH: '0x0bfc9d54fc184518a81162f8fb99c2eaca081202', + }, + }, + crvUSD: { + underlyingAssetAddress: underlyingTokens.crvUSD, + aTokenAddress: '0xb82fa9f31612989525992fcfbb09ab22eff5c85a', + wrappedTokens: { + stataEthcrvUSD: '0x848107491e029afde0ac543779c7790382f15929', + }, + }, + LUSD: { + underlyingAssetAddress: underlyingTokens.LUSD, + aTokenAddress: '0x3fe6a295459fae07df8a0cecc36f37160fe86aa9', + wrappedTokens: { + stataEthLUSD: '0xdbf5e36569798d1e39ee9d7b1c61a7409a74f23a', + }, + }, + USDe: { + underlyingAssetAddress: underlyingTokens.USDe, + aTokenAddress: '0x4f5923fc5fd4a93352581b38b7cd26943012decf', + wrappedTokens: { + stataEthUSDe: '0x5f9d59db355b4a60501544637b00e94082ca575b', + }, + }, + pyUSD: { + underlyingAssetAddress: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', + aTokenAddress: '0x0c0d01abf3e6adfca0989ebba9d6e85dd58eab1e', + wrappedTokens: { + waEthPYUSD: '0xb51edddd8c47856d81c8681ea71404cec93e92c6', + }, + }, + }, + }, + lido: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/5vxMbXRhG1oQr55MWC5j6qg78waWujx1wjeuEWDA6j3`, + tokens: { + LidoWETH: { + underlyingAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + aTokenAddress: '0xfa1fdbbd71b0aa16162d76914d69cd8cb3ef92da', + wrappedTokens: { + waEthLido: '0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9', + }, + }, + LidoWSTETH: { + underlyingAssetAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + aTokenAddress: '0xc035a7cf15375ce2706766804551791ad035e0c2', + wrappedTokens: { + waEthLidowstETH: '0x775f661b0bd1739349b9a2a3ef60be277c5d2d29', + }, + }, + LidoGHO: { + underlyingAssetAddress: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', + aTokenAddress: '0x18efe565a5373f430e2f809b97de30335b3ad96a', + wrappedTokens: { + waEthLidoGHO: '0xc71ea051a5f82c67adcf634c36ffe6334793d24c', + }, + }, + }, + }, + }, + bloom: { + tokens: { + tbyFeb1924: { + address: '0xc4cafefbc3dfea629c589728d648cb6111db3136', + feedAddress: '0xde1f5f2d69339171d679fb84e4562febb71f36e6', + }, + }, + }, + defillama: [ + { + defillamaPoolId: '5a9c2073-2190-4002-9654-8c245d1e8534', + tokenAddress: '0x6dc3ce9c57b20131347fdc9089d740daf6eb34c5', + }, + { + defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', + tokenAddress: '0xf073bac22dab7faf4a3dd6c6189a70d54110525c', + }, + ], + gearbox: { + sourceUrl: 'https://charts-server.fly.dev/api/pools', + tokens: { + dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, + dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, + }, + }, + idle: { + sourceUrl: 'https://api.idle.finance/junior-rates/', + authorizationHeader: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', + tokens: { + idleDAI: { + address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', + wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', + }, + idleUSDC: { + address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', + wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', + }, + idleUSDT: { + address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', + wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', + }, + }, + }, + maker: { + sdai: '0x83f20f44975d03b1b09e64809b757c47f942beea', + }, + tranchess: { + sourceUrl: 'https://tranchess.com/eth/api/v3/funds', + tokens: { + qETH: { + address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', + underlyingAssetName: 'WETH', + }, + }, + }, + stakewise: { + url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', + token: '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38', + }, + etherfi: '0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee', + maple: { + url: 'https://api.maple.finance/v2/graphql', + token: '0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b', + }, + yieldnest: { + url: 'https://gateway.yieldnest.finance/api/v1/graphql', + token: '0x09db87a538bd693e9d08544577d5ccfaa6373a48', + }, + teth: { + address: '0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8', + }, + fluid: { + url: 'https://api.fluid.instad.app/v2/lending/1/tokens', + }, + defaultHandlers: { + cUSDO: { + tokenAddress: '0xad55aebc9b8c03fc43cd9f62260391c13c23e7c0', + sourceUrl: 'https://prod-gw.openeden.com/sys/apy', + path: 'apy', + scale: 100, + }, + slpETHApr: { + tokenAddress: '0x3976d71e7ddfbab9bd120ec281b7d35fa0f28528', + sourceUrl: 'https://api-data.loopfi.xyz/api/getData', + path: 'loop.slpETHApr', + scale: 1, + }, + yUSD: { + tokenAddress: '0x1ce7d9942ff78c328a4181b9f3826fee6d845a97', + sourceUrl: 'https://ctrl.yield.fi/t/apy', + path: 'apy', + }, + uniETH: { + tokenAddress: '0xf1376bcef0f78459c0ed0ba5ddce976f1ddf51f4', + sourceUrl: 'https://app.bedrock.technology/unieth/api/v1/e2ls/apy', + path: 'data.apy', + scale: 10000, + }, + vETH: { + tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', + sourceUrl: 'https://dapi.bifrost.io/api/site', + path: 'vETH.totalApy', + }, + stETH: { + tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + amphrETH: { + tokenAddress: '0x5fd13359ba15a84b76f7f87568309040176167cd', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + rstETH: { + tokenAddress: '0x7a4effd87c2f3c55ca251080b1343b605f327e3a', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + Re7LRT: { + tokenAddress: '0x84631c0d0081fde56deb72f6de77abbbf6a9f93a', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + steakLRT: { + tokenAddress: '0xbeef69ac7870777598a04b2bd4771c71212e6abc', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + pufETH: { + tokenAddress: '0xd9a442856c234a39a81a089c06451ebaa4306a72', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + wstETH: { + tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + inwstETHs: { + tokenAddress: '0x8e0789d39db454dbe9f4a77acef6dc7c69f6d552', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + cbETH: { + tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + }, + sfrxETH: { + tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + }, + StaFirETH: { + tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', + sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', + path: 'data.stakeApr', + }, + rETH: { + tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + }, + wjAURA: { + tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', + sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', + path: 'wjauraApy', + }, + ETHx: { + tokenAddress: '0xa35b1b31ce002fbf2058d22f30f95d405200a15b', + sourceUrl: 'https://universe.staderlabs.com/eth/apy', + path: 'value', + }, + usdm: { + tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', + sourceUrl: 'https://apy.prod.mountainprotocol.com', + path: 'value', + scale: 1, + }, + ankrETH: { + tokenAddress: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "eth"}.apy', + }, + ezETH: { + tokenAddress: '0xbf5495efe5db9ce00f80364c8b423567e58d2110', + sourceUrl: 'https://app.renzoprotocol.com/api/apr', + path: 'apr', + }, + rsETH: { + tokenAddress: '0xa1290d69c65a6fe4df752f95823fae25cb99e5a7', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + }, + hgETH: { + tokenAddress: '0xc824a08db624942c5e5f330d56530cd1598859fd', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/gainApy', + path: 'hgETH', + }, + sDOLA: { + tokenAddress: '0xb45ad160634c528cc3d2926d9807104fa3157305', + sourceUrl: 'https://www.inverse.finance/api/dola-staking', + path: 'apr', + }, + rswETH: { + tokenAddress: '0xfae103dc9cf190ed75350761e95403b7b8afa6c0', + sourceUrl: 'https://v3-lrt.svc.swellnetwork.io/api/tokens/rsweth/apr', + }, + sUSDE: { + tokenAddress: '0x9d39a5de30e57443bff2a8307a4256c8797a3497', + sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', + path: 'stakingYield.value', + }, + saETH: { + tokenAddress: '0xf1617882a71467534d14eee865922de1395c9e89', + sourceUrl: 'https://api.aspidanet.com/page_data/?chainId=1', + path: 'apr', + }, + cdcETH: { + tokenAddress: '0xfe18ae03741a5b84e39c295ac9c856ed7991c38e', + sourceUrl: 'https://api.crypto.com/pos/v1/public/get-staking-instruments', + path: 'result.data.{instrument_name == "ETH.staked"}.est_rewards', + headers: { + 'Content-Type': 'application/json', + }, + body: { + params: { + country_code: 'POL', + }, + }, + scale: 1, + }, + agETH: { + tokenAddress: '0xe1b4d34e8754600962cd944b535180bd758e6c2e', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + }, + dvstETH: { + tokenAddress: '0x5e362eb2c0706bd1d134689ec75176018385430b', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + }, + sdeUSD: { + tokenAddress: '0x5c5b196abe0d54485975d1ec29617d42d9198326', + sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', + path: 'deusd_apy', + }, + sUSDX: { + tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', + sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', + path: 'result.susdxApy', + scale: 1, + }, + slpUSD: { + tokenAddress: '0xbfb53910c935e837c74e6c4ef584557352d20fde', + sourceUrl: 'https://api-data.loopfi.xyz/api/getData', + path: 'lpUSDLoop.slpUSDApr', + scale: 1, + }, + wUSDN: { + tokenAddress: '0x99999999999999cc837c997b882957dafdcb1af9', + sourceUrl: 'https://usdn.api.smardex.io/v1/wusdn/apr', + scale: 1, + }, + }, + }, +}; diff --git a/modules/aprs/config/optimism.ts b/modules/aprs/config/optimism.ts new file mode 100644 index 000000000..7dc450db5 --- /dev/null +++ b/modules/aprs/config/optimism.ts @@ -0,0 +1,5 @@ +export default { + aaveApiConfig: { + chainId: 10 + } +}; \ No newline at end of file diff --git a/modules/aprs/config/polygon.ts b/modules/aprs/config/polygon.ts new file mode 100644 index 000000000..a8b4c5c2b --- /dev/null +++ b/modules/aprs/config/polygon.ts @@ -0,0 +1,5 @@ +export default { + aaveApiConfig: { + chainId: 137 + } +}; \ No newline at end of file diff --git a/modules/aprs/examples/comparison.ts b/modules/aprs/examples/comparison.ts new file mode 100644 index 000000000..cd1dbdbb1 --- /dev/null +++ b/modules/aprs/examples/comparison.ts @@ -0,0 +1,30 @@ +import { AprService } from '../'; +import { poolService } from '../../pool/pool.service'; +import { Chain } from '@prisma/client'; +import { prisma } from '../../../prisma/prisma-client'; + +async function comparisonExample(chain: Chain = 'MAINNET', poolId = '0x85b2b559bc2d21104c4defdd6efca8a20343361d') { + // New implementation + const newAprService = new AprService(); + + console.log('Calculating APR with new implementation (no DB writes)...'); + + const items = await newAprService.calculateAprForPool(chain, poolId); + + console.log('\nAPR Items from new implementation:'); + items.forEach((item) => { + console.log(`- ${item.title}: ${item.apr}`); + }); + + // Old implementation + await poolService.updatePoolAprs(chain, [poolId]); + const oldItems = await prisma.prismaPoolAprItem.findMany({ where: { poolId } }); + + oldItems.forEach((item) => { + console.log(`- ${item.title}: ${item.apr}`); + }); +} + +// Run the example +// Uncomment to run: +comparisonExample().catch(console.error); diff --git a/modules/aprs/examples/integration.ts b/modules/aprs/examples/integration.ts new file mode 100644 index 000000000..20ca1d4c7 --- /dev/null +++ b/modules/aprs/examples/integration.ts @@ -0,0 +1,26 @@ +import { AprService } from '../'; +import { Chain } from '@prisma/client'; + +/** + * This example demonstrates how to integrate the new APR module with the existing code. + * It shows how to: + * - Run the new implementation for testing/debugging + * - Gradually migrate specific APR update calls + * - Compare results between old and new implementations + */ +async function integrationExample(chain: Chain = 'MAINNET', poolId = '0x85b2b559bc2d21104c4defdd6efca8a20343361d') { + const service = new AprService(); + + if (poolId) { + const items = await service.calculateAprForPool(chain, poolId); + + console.log('\nAPR Items:'); + items.forEach((item) => { + console.log(`- ${item.title}: ${item.apr}`); + }); + } +} + +// Run the example +// Uncomment to run: +integrationExample(process.argv[2] as Chain, process.argv[3]).catch(console.error); diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts new file mode 100644 index 000000000..915dc4700 --- /dev/null +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts @@ -0,0 +1,246 @@ +import { expect, test, describe, beforeEach, vi } from 'vitest'; +import { Chain } from '@prisma/client'; +import { AaveApiAprHandler } from './aave-api-apr-handler'; +import { AaveChanClientInterface } from './aave-chan-client'; +import type { PoolAPRData } from '../../types'; +import { AaveApiConfig } from '../types'; + +describe('AaveApiAprHandler', () => { + // Mock implementation of the client + const mockFetchIncentives = vi.fn(); + const mockFetchPrimeIncentives = vi.fn(); + + const mockClient: AaveChanClientInterface = { + fetchIncentives: mockFetchIncentives, + fetchPrimeIncentives: mockFetchPrimeIncentives, + }; + + let handler: AaveApiAprHandler; + let mockPool: PoolAPRData; + const mockConfig: AaveApiConfig = { chainId: 1 }; // Mainnet + + beforeEach(() => { + // Reset mocks + vi.clearAllMocks(); + + // Create handler with mocked client + handler = new AaveApiAprHandler(mockConfig, mockClient); + + // Create a mock pool with tokens for testing + mockPool = { + id: 'test-pool-id', + chain: Chain.MAINNET, + type: 'WEIGHTED', + tokens: [ + { + address: '0xtoken1', // Regular token + symbol: 'TKN1', + balance: '1000000000000000000', + balanceUSD: 5000, // $5k + }, + { + address: '0xincentivizedtoken', // Token that will be incentivized (lowercase to match handler logic) + symbol: 'ITKN', + balance: '2000000000000000000', + balanceUSD: 5000, // $5k + }, + ], + } as unknown as PoolAPRData; + + // Mock incentives response + mockFetchIncentives.mockResolvedValue({ + ITKN: { + tokenInfo: { + symbol: 'ITKN', + address: '0xIncentivizedTokenOriginal', + book: { + STATA_TOKEN: '0xIncentivizedToken', // Will be lowercased by the handler + }, + supplyApr: 0.02, // 2% + }, + supplyIncentives: [ + { + apr: 5, // 5% (will be divided by 100) + rewardToken: { + symbol: 'RWD', + address: '0xRewardToken', + }, + }, + ], + }, + }); + + // Mock prime incentives response + mockFetchPrimeIncentives.mockResolvedValue({ + LIDO: { + tokenInfo: { + symbol: 'LIDO', + address: '0xLidoOriginal', + book: { + STATA_TOKEN: '0xIncentivizedToken', // Will be lowercased by the handler + }, + }, + supplyIncentives: [ + { + apr: 3, // 3% (will be divided by 100) + rewardToken: { + symbol: 'PRIME', + address: '0xPrimeToken', + }, + }, + ], + }, + }); + }); + + test('should return correct service name', () => { + expect(handler.getAprServiceName()).toBe('AaveApiAprHandler'); + }); + + test('should fetch and calculate APR for incentivized tokens', async () => { + const aprItems = await handler.calculateAprForPools([mockPool]); + + // Verify client methods were called + expect(mockFetchIncentives).toHaveBeenCalledWith(1); + expect(mockFetchPrimeIncentives).toHaveBeenCalled(); + + // Should have two items (one for regular and one for prime) + expect(aprItems.length).toBe(2); + + // First APR item (regular incentive) + expect(aprItems[0].id).toBe('test-pool-id-0xincentivizedtoken-0xRewardToken'); + expect(aprItems[0].poolId).toBe('test-pool-id'); + expect(aprItems[0].chain).toBe(Chain.MAINNET); + expect(aprItems[0].title).toBe('RWD APR'); + expect(aprItems[0].type).toBe('MERKL'); + expect(aprItems[0].rewardTokenAddress).toBe('0xRewardToken'); + expect(aprItems[0].rewardTokenSymbol).toBe('RWD'); + + // Calculate expected APR: (apr / 100) * tokenShareOfPoolTvl + // (5 / 100) * (5000 / 10000) = 0.05 * 0.5 = 0.025 or 2.5% + expect(aprItems[0].apr).toBeCloseTo(0.025, 5); + + // Second APR item (prime incentive) + expect(aprItems[1].id).toBe('test-pool-id-0xincentivizedtoken-0xPrimeToken'); + expect(aprItems[1].title).toBe('PRIME APR'); + expect(aprItems[1].rewardTokenSymbol).toBe('PRIME'); + + // Calculate expected APR: (apr / 100) * tokenShareOfPoolTvl + // (3 / 100) * (5000 / 10000) = 0.03 * 0.5 = 0.015 or 1.5% + expect(aprItems[1].apr).toBeCloseTo(0.015, 5); + }); + + test('should handle a pool with no incentivized tokens', async () => { + // Change the token address so it doesn't match the incentivized one + mockPool.tokens[1].address = '0xNonIncentivizedToken'; + + const aprItems = await handler.calculateAprForPools([mockPool]); + + // Client methods should still be called + expect(mockFetchIncentives).toHaveBeenCalled(); + expect(mockFetchPrimeIncentives).toHaveBeenCalled(); + + // No APR items should be returned + expect(aprItems.length).toBe(0); + }); + + test('should handle multiple pools', async () => { + const secondPool = JSON.parse(JSON.stringify(mockPool)) as PoolAPRData; + secondPool.id = 'test-pool-id-2'; + + const aprItems = await handler.calculateAprForPools([mockPool, secondPool]); + + // Should have four items (2 pools × 2 incentives each) + expect(aprItems.length).toBe(4); + expect(aprItems[0].poolId).toBe('test-pool-id'); + expect(aprItems[1].poolId).toBe('test-pool-id-2'); + expect(aprItems[2].poolId).toBe('test-pool-id'); + expect(aprItems[3].poolId).toBe('test-pool-id-2'); + }); + + test('should handle empty incentives', async () => { + // Mock empty incentives response + mockFetchIncentives.mockResolvedValue({}); + mockFetchPrimeIncentives.mockResolvedValue({}); + + const aprItems = await handler.calculateAprForPools([mockPool]); + + // No APR items should be returned + expect(aprItems.length).toBe(0); + }); + + test('should handle incentives with no supplyIncentives', async () => { + // Mock response with no supplyIncentives + mockFetchIncentives.mockResolvedValue({ + ITKN: { + tokenInfo: { + symbol: 'ITKN', + address: '0xIncentivizedTokenOriginal', + book: { + STATA_TOKEN: '0xIncentivizedToken', + }, + }, + supplyIncentives: [], + }, + }); + mockFetchPrimeIncentives.mockResolvedValue({}); + + const aprItems = await handler.calculateAprForPools([mockPool]); + + // No APR items should be returned + expect(aprItems.length).toBe(0); + }); + + test('should handle non-mainnet chains', async () => { + // Create handler for Arbitrum + const arbitrumConfig: AaveApiConfig = { chainId: 42161 }; + const arbitrumHandler = new AaveApiAprHandler(arbitrumConfig, mockClient); + + // Mock pool for Arbitrum + const arbitrumPool = { + ...mockPool, + chain: Chain.ARBITRUM, + } as unknown as PoolAPRData; + + // Mock response for Arbitrum + mockFetchIncentives.mockResolvedValue({ + ITKN: { + tokenInfo: { + symbol: 'ITKN', + address: '0xIncentivizedTokenOriginal', + book: { + STATA_TOKEN: '0xIncentivizedToken', + }, + }, + supplyIncentives: [ + { + apr: 8, // 8% (will be divided by 100) + rewardToken: { + symbol: 'ARB', + address: '0xArbToken', + }, + }, + ], + }, + }); + + // Prime incentives should NOT be called for non-mainnet chains + mockFetchPrimeIncentives.mockResolvedValue({}); + + const aprItems = await arbitrumHandler.calculateAprForPools([arbitrumPool]); + + // Verify client methods were called correctly + expect(mockFetchIncentives).toHaveBeenCalledWith(42161); + // Prime incentives should not be called for Arbitrum + expect(mockFetchPrimeIncentives).not.toHaveBeenCalled(); + + // Should have one item + expect(aprItems.length).toBe(1); + expect(aprItems[0].chain).toBe(Chain.ARBITRUM); + expect(aprItems[0].rewardTokenSymbol).toBe('ARB'); + + // Calculate expected APR: (apr / 100) * tokenShareOfPoolTvl + // (8 / 100) * (5000 / 10000) = 0.08 * 0.5 = 0.04 or 4% + expect(aprItems[0].apr).toBeCloseTo(0.04, 5); + }); +}); diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts new file mode 100644 index 000000000..e5c38a370 --- /dev/null +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts @@ -0,0 +1,91 @@ +import _ from 'lodash'; +import { Chain, PrismaPoolAprItem } from '@prisma/client'; +import { AprHandler, PoolAPRData } from '../../types'; +import { AaveApiConfig } from '../types'; +import { AaveChanClientInterface, AaveChanResponse, AaveChanClient } from './aave-chan-client'; + +/** + * Implementation of AprHandler for Aave API + */ +export class AaveApiAprHandler implements AprHandler { + private client: AaveChanClientInterface; + + constructor( + private readonly config: AaveApiConfig, + injectedClient?: AaveChanClientInterface, + ) { + // Create a default client if not present + this.client = injectedClient || new AaveChanClient(this.config); + } + + public getAprServiceName(): string { + return 'AaveApiAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const aprItems: Omit[] = []; + + // Fetch incentives for this chain + const aaveIncentives = await this.client.fetchIncentives(this.config.chainId); + const incentiveItems = await this.processIncentives(pools, aaveIncentives); + aprItems.push(...incentiveItems); + + // For mainnet, also fetch prime instance items + if (this.config.chainId === 1) { + const primeIncentives = await this.client.fetchPrimeIncentives(); + const primeItems = await this.processIncentives(pools, primeIncentives); + aprItems.push(...primeItems); + } + + return aprItems; + } + + private async processIncentives( + pools: PoolAPRData[], + incentives: AaveChanResponse, + ): Promise[]> { + const aprItems: Omit[] = []; + + for (const incentiveTokenName in incentives) { + if ( + incentives[incentiveTokenName].tokenInfo.book?.STATA_TOKEN && + incentives[incentiveTokenName].supplyIncentives.length > 0 + ) { + const incentivizedToken = incentives[incentiveTokenName].tokenInfo.book.STATA_TOKEN.toLowerCase() + .toString() + .toLowerCase(); + + const supplyIncentivesForToken = incentives[incentiveTokenName].supplyIncentives; + + const poolsWithIncentivizedToken = pools.filter((pool) => + pool.tokens.find((token) => token.address === incentivizedToken), + ); + + for (const pool of poolsWithIncentivizedToken) { + const tvl = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); + const tokenTvl = pool.tokens.find((token) => token.address === incentivizedToken)?.balanceUSD || 0; + + const tokenShareOfPoolTvl = tokenTvl === 0 || tvl === 0 ? 0 : tokenTvl / tvl; + + for (const incentive of supplyIncentivesForToken) { + aprItems.push({ + id: `${pool.id}-${incentivizedToken}-${incentive.rewardToken.address}`, + chain: pool.chain as Chain, + poolId: pool.id, + title: `${incentive.rewardToken.symbol} APR`, + apr: (incentive.apr / 100) * tokenShareOfPoolTvl, + type: 'MERKL', + rewardTokenAddress: incentive.rewardToken.address, + rewardTokenSymbol: incentive.rewardToken.symbol, + group: null, + }); + } + } + } + } + + return aprItems; + } +} diff --git a/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts new file mode 100644 index 000000000..94361deee --- /dev/null +++ b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts @@ -0,0 +1,105 @@ +import { AaveApiConfig } from '../types'; + +/** + * Represents data for a token in Aave reserves + */ +export type ReserveToken = { + symbol: string; + address: string; + book?: { + STATA_TOKEN?: string; + }; + supplyApr?: number; +}; + +/** + * Represents incentive information for a token + */ +export type IncentiveInfo = { + apr: number; + rewardToken: { + symbol: string; + address: string; + }; +}; + +/** + * Represents incentives data for a specific token + */ +export type Incentives = { + tokenInfo: ReserveToken; + supplyIncentives: IncentiveInfo[]; +}; + +/** + * Represents the shape of Aave incentives API response + */ +export type AaveChanResponse = { + [tokenName: string]: Incentives; +}; + +/** + * Interface for Aave incentives client + */ +export interface AaveChanClientInterface { + /** + * Fetch incentives for a specific chain + * @param chainId Chain ID to fetch incentives for + * @returns Promise with the incentives data + */ + fetchIncentives(chainId: number): Promise; + + /** + * Fetch prime incentives (Lido on Mainnet) + * Only available on Mainnet (chainId = 1) + * @returns Promise with the prime incentives data + */ + fetchPrimeIncentives(): Promise; +} + +/** + * Implementation of Aave incentives client + */ +export class AaveChanClient implements AaveChanClientInterface { + private readonly baseUrl = 'https://apps.aavechan.com/api/aave-all-incentives?chainId='; + + constructor(private readonly config: AaveApiConfig) {} + + /** + * Fetch incentives for a specific chain + * @param chainId Chain ID to fetch incentives for + * @returns Promise with the incentives data + */ + async fetchIncentives(chainId: number): Promise { + try { + const response = (await fetch(`${this.baseUrl}${chainId}`).then((response) => + response.json(), + )) as AaveChanResponse; + return response; + } catch (error) { + console.error(`Error fetching Aave incentives for chain ${chainId}:`, error); + return {}; + } + } + + /** + * Fetch prime incentives (Lido on Mainnet) + * Only available on Mainnet (chainId = 1) + * @returns Promise with the prime incentives data + */ + async fetchPrimeIncentives(): Promise { + if (this.config.chainId !== 1) { + return {}; + } + + try { + const response = (await fetch(`${this.baseUrl}1&instance=prime`).then((response) => + response.json(), + )) as AaveChanResponse; + return response; + } catch (error) { + console.error('Error fetching Aave prime incentives:', error); + return {}; + } + } +} diff --git a/modules/aprs/handlers/aave-api-apr/index.ts b/modules/aprs/handlers/aave-api-apr/index.ts new file mode 100644 index 000000000..2ad1120fb --- /dev/null +++ b/modules/aprs/handlers/aave-api-apr/index.ts @@ -0,0 +1 @@ +export * from './aave-api-apr-handler'; diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts new file mode 100644 index 000000000..0eab21255 --- /dev/null +++ b/modules/aprs/handlers/create-handlers.ts @@ -0,0 +1,26 @@ +import { Chain } from '@prisma/client'; +import { AprHandler } from '../types'; +import * as handlers from '.'; +import chainConfigs from '../config'; +import { AaveApiConfig } from './types'; + +/** + * Creates handler instances for a specific chain + */ +export function createHandlers(chain: Chain): AprHandler[] { + const handlerList: AprHandler[] = []; + + // Default handlers for all of the chains + handlerList.push(new handlers.SwapFeeAprHandler()); + + if (chainConfigs[chain].ybAprConfig) { + handlerList.push(new handlers.YbTokensAprHandler(chainConfigs[chain].ybAprConfig, chain)); + } + + // Add Aave API handler if configured for this chain + if (chainConfigs[chain].aaveApiConfig) { + handlerList.push(new handlers.AaveApiAprHandler(chainConfigs[chain].aaveApiConfig as AaveApiConfig)); + } + + return handlerList; +} diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts new file mode 100644 index 000000000..77a883d10 --- /dev/null +++ b/modules/aprs/handlers/index.ts @@ -0,0 +1,10 @@ +// Export all handlers implementations +export { SwapFeeAprHandler } from './swap-fee-apr'; +export { YbTokensAprHandler } from './yb-tokens'; +export { AaveApiAprHandler } from './aave-api-apr'; + +// Add more handler exports as they are implemented +// Example: +// export { GaugeAprHandler } from './gauge-apr-handler'; + +export { createHandlers } from './create-handlers'; diff --git a/modules/aprs/handlers/swap-fee-apr/index.ts b/modules/aprs/handlers/swap-fee-apr/index.ts new file mode 100644 index 000000000..1acb3d543 --- /dev/null +++ b/modules/aprs/handlers/swap-fee-apr/index.ts @@ -0,0 +1 @@ +export * from './swap-fee-apr-handler'; diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts new file mode 100644 index 000000000..56ea6945b --- /dev/null +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts @@ -0,0 +1,124 @@ +import { expect, test, describe, beforeEach } from 'vitest'; +import { Chain, PrismaPoolAprType, PrismaPoolType } from '@prisma/client'; +import { SwapFeeAprHandler } from './swap-fee-apr-handler'; +import { PoolAPRData } from '../types'; + +describe('SwapFeeAprCalculator', () => { + let calculator: SwapFeeAprHandler; + let mockPool: PoolAPRData; + + beforeEach(() => { + calculator = new SwapFeeAprHandler(); + + // Create a mock pool with dynamic data for testing + mockPool = { + id: 'test-pool-id', + chain: Chain.MAINNET, + type: 'WEIGHTED', + dynamicData: { + totalLiquidity: 1000000, // $1M liquidity + fees24h: 1000, // $1k fees per 24h + protocolSwapFee: '0.2', // 20% protocol fee + protocolYieldFee: '0.1', // 10% yield fee + aggregateSwapFee: '0.0', // No aggregate fee + isInRecoveryMode: false, + }, + } as unknown as PoolAPRData; + }); + + test('should return correct calculator name', () => { + expect(calculator.getAprServiceName()).toBe('SwapFeeAprCalculator'); + }); + + test('should calculate correct APR with protocol fee', async () => { + const aprItems = await calculator.calculateAprForPools([mockPool]); + + expect(aprItems.length).toBe(1); + expect(aprItems[0].id).toBe('test-pool-id-SWAP_FEE_24H'); + expect(aprItems[0].type).toBe(PrismaPoolAprType.SWAP_FEE_24H); + expect(aprItems[0].poolId).toBe('test-pool-id'); + expect(aprItems[0].title).toBe('Swap fees APR (24h)'); + + // Calculate expected APR: (fees24h * 365 / totalLiquidity) * (1 - protocolFee) + // (1000 * 365 / 1000000) * (1 - 0.2) = 0.365 * 0.8 = 0.292 or 29.2% + expect(aprItems[0].apr).toBeCloseTo(0.292, 3); + }); + + test('should handle zero liquidity', async () => { + mockPool.dynamicData!.totalLiquidity = 0; + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + expect(aprItems.length).toBe(1); + expect(aprItems[0].apr).toBe(0); + }); + + test('should handle recovery mode', async () => { + mockPool.dynamicData!.isInRecoveryMode = true; + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + // In recovery mode, protocol fee should be 0 + // (1000 * 365 / 1000000) * (1 - 0) = 0.365 or 36.5% + expect(aprItems[0].apr).toBeCloseTo(0.365, 3); + }); + + test('should handle GYROE pool type', async () => { + mockPool.type = PrismaPoolType.GYROE; + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + // For GYROE, protocolFee = protocolYieldFee = 0.1 + // (1000 * 365 / 1000000) * (1 - 0.1) = 0.365 * 0.9 = 0.3285 or 32.85% + expect(aprItems[0].apr).toBeCloseTo(0.3285, 3); + }); + + test('should handle LIQUIDITY_BOOTSTRAPPING pool type', async () => { + mockPool.type = PrismaPoolType.LIQUIDITY_BOOTSTRAPPING; + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + // For LIQUIDITY_BOOTSTRAPPING, protocol fee should be 0 + // (1000 * 365 / 1000000) * (1 - 0) = 0.365 or 36.5% + expect(aprItems[0].apr).toBeCloseTo(0.365, 3); + }); + + test('should handle V3 protocol version', async () => { + mockPool.protocolVersion = 3; + mockPool.dynamicData!.aggregateSwapFee = '0.3'; // 30% aggregate fee + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + // For V3, protocolFee = aggregateSwapFee = 0.3 + // (1000 * 365 / 1000000) * (1 - 0.3) = 0.365 * 0.7 = 0.2555 or 25.55% + expect(aprItems[0].apr).toBeCloseTo(0.2555, 3); + }); + + test('should cap extremely large APR values', async () => { + mockPool.dynamicData!.fees24h = 1e28; // Extremely large fees + + const aprItems = await calculator.calculateAprForPools([mockPool]); + + // Should cap the value to 0 when it exceeds MAX_DB_INT + expect(aprItems[0].apr).toBe(0); + }); + + test('should calculate APR for multiple pools', async () => { + const secondPool = JSON.parse(JSON.stringify(mockPool)) as PoolAPRData; + secondPool.id = 'test-pool-id-2'; + secondPool.dynamicData!.poolId = 'test-pool-id-2'; + secondPool.dynamicData!.fees24h = 2000; // $2k fees per 24h + + const aprItems = await calculator.calculateAprForPools([mockPool, secondPool]); + + expect(aprItems.length).toBe(2); + expect(aprItems[0].poolId).toBe('test-pool-id'); + expect(aprItems[1].poolId).toBe('test-pool-id-2'); + + // First pool: (1000 * 365 / 1000000) * (1 - 0.2) = 0.365 * 0.8 = 0.292 or 29.2% + expect(aprItems[0].apr).toBeCloseTo(0.292, 3); + + // Second pool: (2000 * 365 / 1000000) * (1 - 0.2) = 0.73 * 0.8 = 0.584 or 58.4% + expect(aprItems[1].apr).toBeCloseTo(0.584, 3); + }); +}); diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts new file mode 100644 index 000000000..386c7fcc8 --- /dev/null +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts @@ -0,0 +1,58 @@ +import { AprHandler, PoolAPRData } from '../../types'; +import { PrismaPoolAprItem } from '@prisma/client'; + +const MAX_DB_INT = 9223372036854775807; + +export class SwapFeeAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'SwapFeeAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const aprItems: Omit[] = []; + + for (const pool of pools) { + if (pool.dynamicData) { + const apr = + pool.dynamicData.totalLiquidity > 0 + ? (pool.dynamicData.fees24h * 365) / pool.dynamicData.totalLiquidity + : 0; + + let protocolFee = parseFloat(pool.dynamicData.protocolSwapFee); + if (pool.type === 'GYROE') { + protocolFee = parseFloat(pool.dynamicData.protocolYieldFee || '0'); + } + + if (pool.protocolVersion === 3) { + protocolFee = parseFloat(pool.dynamicData.aggregateSwapFee); + } + + if (pool.dynamicData.isInRecoveryMode || pool.type === 'LIQUIDITY_BOOTSTRAPPING') { + protocolFee = 0; + } + + let userApr = apr * (1 - protocolFee); + + if (userApr > MAX_DB_INT) { + userApr = 0; + } + + aprItems.push({ + id: `${pool.id}-swap-apr-24h`, + chain: pool.chain, + poolId: pool.id, + title: 'Swap fees APR (24h)', + apr: userApr, + type: 'SWAP_FEE_24H', + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }); + } + } + + return aprItems; + } +} diff --git a/modules/aprs/handlers/types.ts b/modules/aprs/handlers/types.ts new file mode 100644 index 000000000..2a1256deb --- /dev/null +++ b/modules/aprs/handlers/types.ts @@ -0,0 +1,5 @@ +export type { YbAprConfig } from './yb-tokens/types'; + +export type AaveApiConfig = { + chainId: number; +}; diff --git a/modules/aprs/handlers/yb-tokens/index.ts b/modules/aprs/handlers/yb-tokens/index.ts new file mode 100644 index 000000000..ffb65f875 --- /dev/null +++ b/modules/aprs/handlers/yb-tokens/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './yb-tokens-apr-handler'; diff --git a/modules/aprs/handlers/yb-tokens/types.ts b/modules/aprs/handlers/yb-tokens/types.ts new file mode 100644 index 000000000..a96324f01 --- /dev/null +++ b/modules/aprs/handlers/yb-tokens/types.ts @@ -0,0 +1,234 @@ +import { Chain } from '@prisma/client'; + +export interface AprHandlerConstructor { + new (config?: any): AprHandler; +} + +export interface AprHandler { + group?: string; + getAprs(chain?: Chain): Promise<{ + [tokenAddress: string]: { + /** Defined as float, eg: 0.01 is 1% */ + apr: number; + isIbYield: boolean; + group?: string; + }; + }>; +} + +export type TokenApr = { + apr: number; + address: string; + isIbYield: boolean; + group?: string; +}; + +export interface YbAprConfig { + avalon?: AvalonAprConfig; + bloom?: BloomAprConfig; + beefy?: BeefyAprConfig; + sftmx?: SftmxAprConfig; + sts?: { + token: string; + }; + silo?: SiloAprConfig; + euler?: EulerAprConfig; + gearbox?: GearBoxAprConfig; + idle?: IdleAprConfig; + maker?: MakerAprConfig; + ovix?: OvixAprConfig; + reaper?: ReaperAprConfig; + tetu?: TetuAprConfig; + tranchess?: TranchessAprConfig; + yearn?: YearnAprConfig; + stakewise?: { + url: string; + token: string; + }; + fluid?: { + url: string; + }; + maple?: { + url: string; + token: string; + }; + yieldnest?: { + url: string; + token: string; + }; + dforce?: { + token: string; + }; + etherfi?: string; + sveth?: boolean; + defillama?: { + defillamaPoolId: string; + tokenAddress: string; + }[]; + defaultHandlers?: DefaultHandlerAprConfig; + fixedAprHandler?: FixedAprConfig; +} + +export interface AvalonAprConfig { + [market: string]: { + subgraphUrl: string; + tokens: { + [underlyingAssetName: string]: { + underlyingAssetAddress: string; + aTokenAddress: string; + wrappedTokens: { + [wrappedTokenName: string]: string; + }; + isIbYield?: boolean; + }; + }; + }; +} + +export interface BeefyAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + // To get the vaultId, get the vault address from the token contract(token.vault()), + // and search for the vault address in the link: https://api.beefy.finance/vaults + vaultId: string; + isIbYield?: boolean; + }; + }; +} + +export interface BloomAprConfig { + tokens: { + [tokenName: string]: { + address: string; + feedAddress: string; + isIbYield?: boolean; + }; + }; +} + +export interface SftmxAprConfig { + tokens: { + [underlyingAssetName: string]: { + address: string; + ftmStakingAddress: string; + }; + }; +} + +export interface EulerAprConfig { + vaultsJsonUrl: string; + lensContractAddress: string; +} + +export interface GearBoxAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isIbYield?: boolean; + }; + }; +} + +export interface IdleAprConfig { + sourceUrl: string; + authorizationHeader: string; + tokens: { + [tokenName: string]: { + address: string; + wrapped4626Address: string; + isIbYield?: boolean; + }; + }; +} + +export interface MakerAprConfig { + sdai: string; +} + +export interface OvixAprConfig { + tokens: { + [tokenName: string]: { + yieldAddress: string; + wrappedAddress: string; + isIbYield?: boolean; + }; + }; +} + +export interface ReaperAprConfig { + subgraphSource?: { + subgraphUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isSftmX?: boolean; + isWstETH?: boolean; + isIbYield?: boolean; + }; + }; + }; + onchainSource?: { + averageAPRAcrossLastNHarvests: number; + tokens: { + [tokenName: string]: { + address: string; + isSftmX?: boolean; + isWstETH?: boolean; + isIbYield?: boolean; + }; + }; + }; +} + +export interface TetuAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isIbYield?: boolean; + }; + }; +} + +export interface TranchessAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + underlyingAssetName: string; + isIbYield?: boolean; + }; + }; +} + +export interface YearnAprConfig { + sourceUrl: string; + isIbYield?: boolean; +} + +export interface DefaultHandlerAprConfig { + [tokenName: string]: { + sourceUrl: string; + tokenAddress: string; + path?: string; + scale?: number; + group?: string; + isIbYield?: boolean; + }; +} + +export interface FixedAprConfig { + [tokenName: string]: { + address: string; + apr: number; + group?: string; + isIbYield?: boolean; + }; +} + +export interface SiloAprConfig { + markets: string[]; +} diff --git a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts new file mode 100644 index 000000000..ea81b98ce --- /dev/null +++ b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts @@ -0,0 +1,146 @@ +import { Chain, PrismaPoolAprItem, PrismaPoolAprItemGroup, PrismaPoolAprType } from '@prisma/client'; +import { YbAprHandlers } from '../../../pool/lib/apr-data-sources/yb-apr-handlers'; +import { TokenApr, YbAprConfig } from './types'; +import { PoolAPRData, AprHandler } from '../../types'; + +/** + * Helper utility functions for pool operations + */ +function collectsYieldFee(pool: PoolAPRData): boolean { + return pool.type === 'COMPOSABLE_STABLE' || pool.type === 'META_STABLE' || pool.type === 'PHANTOM_STABLE'; +} + +function tokenCollectsYieldFee(token: any): boolean { + return token.token.underlyingTokenAddress !== null && token.token.underlyingTokenAddress !== undefined; +} + +/** + * Calculator for yield-bearing tokens APR + * This calculates the APR for various yield-bearing tokens in pools + */ +export class YbTokensAprHandler implements AprHandler { + private ybTokensAprHandlers: YbAprHandlers; + + constructor( + private aprConfig: YbAprConfig, + chain: Chain, + ) { + this.ybTokensAprHandlers = new YbAprHandlers(this.aprConfig, chain); + } + + public getAprServiceName(): string { + return 'YbTokensAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const aprItems: Omit[] = []; + + // Fetch APRs for all yield-bearing tokens + const aprs = await this.fetchYieldTokensApr(); + const aprKeysLowercase = Array.from(aprs.keys()).map((key) => key.toLowerCase()); + const aprKeysLowercaseSet = new Set(aprKeysLowercase); + + // Filter pools that contain yield-bearing tokens + const poolsWithYbTokens = pools.filter((pool) => { + const addresses = new Set( + pool.tokens + .flatMap((token) => [ + token.token.underlyingTokenAddress?.toLowerCase(), + token.address.toLowerCase(), + ]) + .filter((address): address is string => address !== null && address !== undefined), + ); + + for (const address of addresses) { + if (aprKeysLowercaseSet.has(address)) { + return true; + } + } + return false; + }); + + // Process each pool with yield-bearing tokens + for (const pool of poolsWithYbTokens) { + if (!pool.dynamicData) { + continue; + } + + const totalLiquidity = pool.dynamicData?.totalLiquidity; + if (!totalLiquidity) { + continue; + } + + // Calculate APR for each token in the pool + const tokenAprs = pool.tokens.map((token) => { + const tokenApr = aprs.get(token.address); + + // Wrapper + underlying case, apply underlying token APR on top of the lending protocol market APR + const underlyingApr = aprs.get(token.token.underlyingTokenAddress?.toLowerCase() || ''); + + let apr = tokenApr?.apr || 0; + if (underlyingApr) { + apr = (1 + apr) * (1 + underlyingApr.apr) - 1; + } + + return { + ...token, + apr, + group: tokenApr?.group, + share: token.balanceUSD / totalLiquidity, + }; + }); + + // Create APR items for each token with a non-zero APR + for (const token of tokenAprs) { + if (!token.apr || !token.share) { + continue; + } + + let userApr = token.apr * token.share; + + // Apply yield fee if applicable + let fee = 0; + if (collectsYieldFee(pool) && tokenCollectsYieldFee(token) && pool.dynamicData) { + fee = + pool.type === 'META_STABLE' + ? parseFloat(pool.dynamicData.protocolSwapFee || '0') + : pool.protocolVersion === 3 + ? parseFloat(pool.dynamicData.aggregateYieldFee || '0.1') + : parseFloat(pool.dynamicData.protocolYieldFee || '0'); + + userApr = userApr * (1 - fee); + } + + const yieldType: PrismaPoolAprType = 'IB_YIELD'; + const itemId = `${token.poolId}-${token.address}-yield-apr`; + + aprItems.push({ + id: itemId, + chain: pool.chain, + poolId: pool.id, + title: `${token.token.symbol} APR`, + apr: userApr, + group: token.group as PrismaPoolAprItemGroup, + type: yieldType, + rewardTokenAddress: token.address, + rewardTokenSymbol: token.token.symbol, + }); + } + } + + return aprItems; + } + + private async fetchYieldTokensApr(): Promise> { + const data = await this.ybTokensAprHandlers.fetchAprsFromAllHandlers(); + return new Map( + data + .filter((tokenApr) => { + return !isNaN(tokenApr.apr); + }) + .map((apr) => [apr.address, apr]), + ); + } +} diff --git a/modules/aprs/index.ts b/modules/aprs/index.ts new file mode 100644 index 000000000..c5716ab6e --- /dev/null +++ b/modules/aprs/index.ts @@ -0,0 +1 @@ +export { AprService } from './apr-service'; diff --git a/modules/aprs/types.ts b/modules/aprs/types.ts new file mode 100644 index 000000000..0ea4c51b4 --- /dev/null +++ b/modules/aprs/types.ts @@ -0,0 +1,43 @@ +import { PrismaPoolAprItem } from '@prisma/client'; +import type { PoolAPRData } from './apr-repository'; +export type { PoolAPRData }; + +export interface AprHandler { + /** + * Calculate APR for the given pools + * @param pools Array of pools to calculate APR for + * @returns Array of APR items ready to be saved (without createdAt/updatedAt) + */ + calculateAprForPools(pools: PoolAPRData[]): Promise[]>; + + /** + * Get the name of this calculator + */ + getAprServiceName(): string; +} + +/** + * Types of APR calculators available in the system + */ +export type AprCalculatorType = + | 'swapFeeApr' + | 'boostedPoolApr' + | 'dynamicSwapFeeApr' + | 'gaugeApr' + | 'veBalProtocolApr' + | 'veBalVotingApr' + | 'morphoRewardsApr' + | 'ybTokensApr' + | 'aaveApiApr' + | 'masterchefFarmApr' + | 'reliquaryFarmApr' + | 'beetswarsGaugeVotingApr' + | 'quantAmmApr'; + +/** + * Configuration for APR calculators + */ +export interface AprCalculatorConfig { + type: AprCalculatorType; + params?: Record; +} diff --git a/modules/pool/lib/pool-apr-updater.service.ts b/modules/pool/lib/pool-apr-updater.service.ts index 178f6505a..822fb2387 100644 --- a/modules/pool/lib/pool-apr-updater.service.ts +++ b/modules/pool/lib/pool-apr-updater.service.ts @@ -13,10 +13,10 @@ export class PoolAprUpdaterService { return networkContext.config.poolAprServices; } - async updatePoolAprs(chain: Chain) { + async updatePoolAprs(chain: Chain, poolIds?: string[]) { const pools = await prisma.prismaPool.findMany({ ...poolsIncludeForAprs, - where: { chain: chain }, + where: { chain: chain, ...(poolIds?.length ? { id: { in: poolIds } } : {}) }, }); await this.updateAprsForPools(pools); diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index d8836ae9c..1022f7bf4 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -203,8 +203,8 @@ export class PoolService { } } - public async updatePoolAprs(chain: Chain) { - await this.poolAprUpdaterService.updatePoolAprs(chain); + public async updatePoolAprs(chain: Chain, poolIds?: string[]) { + await this.poolAprUpdaterService.updatePoolAprs(chain, poolIds); await syncIncentivizedCategory(); } From 5f390b6711814ef1040a354c1994c60eb4f153e0 Mon Sep 17 00:00:00 2001 From: franz Date: Fri, 13 Jun 2025 14:15:11 +0300 Subject: [PATCH 02/37] fix yield fee collection bool --- .../yb-tokens/yb-tokens-apr-handler.ts | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts index ea81b98ce..deaea8f33 100644 --- a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts @@ -2,17 +2,7 @@ import { Chain, PrismaPoolAprItem, PrismaPoolAprItemGroup, PrismaPoolAprType } f import { YbAprHandlers } from '../../../pool/lib/apr-data-sources/yb-apr-handlers'; import { TokenApr, YbAprConfig } from './types'; import { PoolAPRData, AprHandler } from '../../types'; - -/** - * Helper utility functions for pool operations - */ -function collectsYieldFee(pool: PoolAPRData): boolean { - return pool.type === 'COMPOSABLE_STABLE' || pool.type === 'META_STABLE' || pool.type === 'PHANTOM_STABLE'; -} - -function tokenCollectsYieldFee(token: any): boolean { - return token.token.underlyingTokenAddress !== null && token.token.underlyingTokenAddress !== undefined; -} +import { collectsYieldFee, tokenCollectsYieldFee } from '../../../pool/lib/pool-utils'; /** * Calculator for yield-bearing tokens APR @@ -21,10 +11,7 @@ function tokenCollectsYieldFee(token: any): boolean { export class YbTokensAprHandler implements AprHandler { private ybTokensAprHandlers: YbAprHandlers; - constructor( - private aprConfig: YbAprConfig, - chain: Chain, - ) { + constructor(private aprConfig: YbAprConfig, chain: Chain) { this.ybTokensAprHandlers = new YbAprHandlers(this.aprConfig, chain); } @@ -107,8 +94,8 @@ export class YbTokensAprHandler implements AprHandler { pool.type === 'META_STABLE' ? parseFloat(pool.dynamicData.protocolSwapFee || '0') : pool.protocolVersion === 3 - ? parseFloat(pool.dynamicData.aggregateYieldFee || '0.1') - : parseFloat(pool.dynamicData.protocolYieldFee || '0'); + ? parseFloat(pool.dynamicData.aggregateYieldFee || '0.1') + : parseFloat(pool.dynamicData.protocolYieldFee || '0'); userApr = userApr * (1 - fee); } From 064fdb0625a1d526876f4267187c6e4573128dd4 Mon Sep 17 00:00:00 2001 From: franz Date: Fri, 13 Jun 2025 18:28:36 +0300 Subject: [PATCH 03/37] move config --- config/arbitrum.ts | 404 +++++---- config/avalanche.ts | 172 ++-- config/base.ts | 192 ++--- config/fantom.ts | 11 +- config/fraxtal.ts | 44 +- config/gnosis.ts | 104 +-- config/mainnet.ts | 790 +++++++++--------- config/mode.ts | 22 +- config/optimism.ts | 265 +++--- config/polygon.ts | 216 ++--- config/sepolia.ts | 2 +- config/sonic.ts | 223 ++--- config/zkevm.ts | 72 +- modules/aprs/apr-manager.ts | 7 +- modules/aprs/apr-repository.ts | 14 +- modules/aprs/config/arbitrum.ts | 5 - modules/aprs/config/avalanche.ts | 5 - modules/aprs/config/base.ts | 5 - modules/aprs/config/index.ts | 24 - modules/aprs/config/mainnet.ts | 411 --------- modules/aprs/config/optimism.ts | 5 - modules/aprs/config/polygon.ts | 5 - modules/aprs/examples/comparison.ts | 2 +- .../aave-api-apr/aave-api-apr-handler.test.ts | 6 +- .../aave-api-apr/aave-api-apr-handler.ts | 7 +- .../handlers/aave-api-apr/aave-chan-client.ts | 4 +- modules/aprs/handlers/create-handlers.ts | 14 +- .../swap-fee-apr/swap-fee-apr-handler.test.ts | 2 +- modules/aprs/handlers/types.ts | 2 +- modules/aprs/types.ts | 26 - modules/network/apr-config-types.ts | 17 + modules/network/arbitrum.ts | 6 +- modules/network/avalanche.ts | 6 +- modules/network/base.ts | 6 +- modules/network/fraxtal.ts | 6 +- modules/network/gnosis.ts | 6 +- modules/network/mainnet.ts | 6 +- modules/network/mode.ts | 6 +- modules/network/network-config-types.ts | 4 +- modules/network/optimism.ts | 6 +- modules/network/polygon.ts | 6 +- modules/network/sepolia.ts | 6 +- modules/network/sonic.ts | 6 +- modules/network/zkevm.ts | 6 +- .../nested-pool-apr.service.ts | 2 +- modules/pool/pool.service.ts | 4 +- .../aave-price-handler.service.ts | 2 +- 47 files changed, 1316 insertions(+), 1846 deletions(-) delete mode 100644 modules/aprs/config/arbitrum.ts delete mode 100644 modules/aprs/config/avalanche.ts delete mode 100644 modules/aprs/config/base.ts delete mode 100644 modules/aprs/config/index.ts delete mode 100644 modules/aprs/config/mainnet.ts delete mode 100644 modules/aprs/config/optimism.ts delete mode 100644 modules/aprs/config/polygon.ts diff --git a/config/arbitrum.ts b/config/arbitrum.ts index 793086271..6ddbd39f5 100644 --- a/config/arbitrum.ts +++ b/config/arbitrum.ts @@ -72,230 +72,216 @@ export default { multicall: '0x80c7dd17b01855a6d2347444a0fcc36136a314de', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, - ybAprConfig: { - usdl: true, - teth: { - address: '0xd09acb80c1e8f2291862c4978a008791c9167003', - }, - aave: { - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/DLuE98kEb5pQNXAcKFQGQgfSQ57Xdou4jnVbAEqMfy3B`, - tokens: { - USDC: { - underlyingAssetAddress: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', - aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', - wrappedTokens: { - waUSDC: '0xe719aef17468c7e10c0c205be62c990754dff7e5', - stataArbUSDC: '0x3a301e7917689b8e8a19498b8a28fc912583490c', + aprHandlers: { + ybAprHandler: { + usdl: true, + teth: { + address: '0xd09acb80c1e8f2291862c4978a008791c9167003', + }, + aave: { + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/DLuE98kEb5pQNXAcKFQGQgfSQ57Xdou4jnVbAEqMfy3B`, + tokens: { + USDC: { + underlyingAssetAddress: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8', + aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', + wrappedTokens: { + waUSDC: '0xe719aef17468c7e10c0c205be62c990754dff7e5', + stataArbUSDC: '0x3a301e7917689b8e8a19498b8a28fc912583490c', + }, }, - }, - USDCn: { - underlyingAssetAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', - aTokenAddress: '0x724dc807b04555b71ed48a6896b6f41593b8c637', - wrappedTokens: { - stataArbUSDCn: '0xbde67e089886ec0e615d6f054bc6f746189a3d56', - stataArbUSDCn2: '0x7cfadfd5645b50be87d546f42699d863648251ad', - waArbUSDCn: '0x7f6501d3b98ee91f9b9535e4b0ac710fb0f9e0bc', + USDCn: { + underlyingAssetAddress: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', + aTokenAddress: '0x724dc807b04555b71ed48a6896b6f41593b8c637', + wrappedTokens: { + stataArbUSDCn: '0xbde67e089886ec0e615d6f054bc6f746189a3d56', + stataArbUSDCn2: '0x7cfadfd5645b50be87d546f42699d863648251ad', + waArbUSDCn: '0x7f6501d3b98ee91f9b9535e4b0ac710fb0f9e0bc', + }, }, - }, - USDT: { - underlyingAssetAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', - aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', - wrappedTokens: { - waUSDT: '0x3c7680dfe7f732ca0279c39ff30fe2eafdae49db', - stataArbUSDT: '0x8b5541b773dd781852940490b0c3dc1a8cdb6a87', - stataArbUSDT2: '0xb165a74407fe1e519d6bcbdec1ed3202b35a4140', + USDT: { + underlyingAssetAddress: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9', + aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', + wrappedTokens: { + waUSDT: '0x3c7680dfe7f732ca0279c39ff30fe2eafdae49db', + stataArbUSDT: '0x8b5541b773dd781852940490b0c3dc1a8cdb6a87', + stataArbUSDT2: '0xb165a74407fe1e519d6bcbdec1ed3202b35a4140', + }, }, - }, - DAI: { - underlyingAssetAddress: '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1', - aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', - wrappedTokens: { - waDAI: '0x345a864ac644c82c2d649491c905c71f240700b2', - stataArbDAI: '0x426e8778bf7f54b0e4fc703dcca6f26a4e5b71de', + DAI: { + underlyingAssetAddress: '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1', + aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', + wrappedTokens: { + waDAI: '0x345a864ac644c82c2d649491c905c71f240700b2', + stataArbDAI: '0x426e8778bf7f54b0e4fc703dcca6f26a4e5b71de', + }, }, - }, - wETH: { - underlyingAssetAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', - aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', - wrappedTokens: { - waWETH: '0x18c100415988bef4354effad1188d1c22041b046', - stataArbWETH: '0x18468b6eba332285c6d9bb03fe7fb52e108c4596', + wETH: { + underlyingAssetAddress: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1', + aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', + wrappedTokens: { + waWETH: '0x18c100415988bef4354effad1188d1c22041b046', + stataArbWETH: '0x18468b6eba332285c6d9bb03fe7fb52e108c4596', + }, }, - }, - FRAX: { - underlyingAssetAddress: '0x17fc002b466eec40dae837fc4be5c67993ddbd6f', - aTokenAddress: '0x38d693ce1df5aadf7bc62595a37d667ad57922e5', - wrappedTokens: { - stataArbFRAX: '0x89aec2023f89e26dbb7eaa7a98fe3996f9d112a8', + FRAX: { + underlyingAssetAddress: '0x17fc002b466eec40dae837fc4be5c67993ddbd6f', + aTokenAddress: '0x38d693ce1df5aadf7bc62595a37d667ad57922e5', + wrappedTokens: { + stataArbFRAX: '0x89aec2023f89e26dbb7eaa7a98fe3996f9d112a8', + }, }, - }, - GHO: { - underlyingAssetAddress: '0x7dff72693f6a4149b17e7c6314655f6a9f7c8b33', - aTokenAddress: '0xebe517846d0f36eced99c735cbf6131e1feb775d', - wrappedTokens: { - stataArbGHO: '0xd9fba68d89178e3538e708939332c79efc540179', + GHO: { + underlyingAssetAddress: '0x7dff72693f6a4149b17e7c6314655f6a9f7c8b33', + aTokenAddress: '0xebe517846d0f36eced99c735cbf6131e1feb775d', + wrappedTokens: { + stataArbGHO: '0xd9fba68d89178e3538e708939332c79efc540179', + }, }, - }, - EZETH: { - underlyingAssetAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', - aTokenAddress: '0xea1132120ddcdda2f119e99fa7a27a0d036f7ac9', - wrappedTokens: { - stataArbEZETH: '0x4ff50c17df0d1b788d021acd85039810a1aa68a1', + EZETH: { + underlyingAssetAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', + aTokenAddress: '0xea1132120ddcdda2f119e99fa7a27a0d036f7ac9', + wrappedTokens: { + stataArbEZETH: '0x4ff50c17df0d1b788d021acd85039810a1aa68a1', + }, }, - }, - WSTETH: { - underlyingAssetAddress: '0x5979d7b546e38e414f7e9822514be443a4800529', - aTokenAddress: '0x513c7e3a9c69ca3e22550ef58ac1c0088e918fff', - wrappedTokens: { - stataArbWSTETH: '0xe98fc055c99decd8da0c111b090885d5d15c774e', + WSTETH: { + underlyingAssetAddress: '0x5979d7b546e38e414f7e9822514be443a4800529', + aTokenAddress: '0x513c7e3a9c69ca3e22550ef58ac1c0088e918fff', + wrappedTokens: { + stataArbWSTETH: '0xe98fc055c99decd8da0c111b090885d5d15c774e', + }, }, }, }, }, - }, - defillama: [ - { - defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', - tokenAddress: '0x5a7a183b6b44dc4ec2e3d2ef43f98c5152b1d76d', - }, - ], - reaper: { - onchainSource: { - averageAPRAcrossLastNHarvests: 5, - tokens: { - rfGrainDAI: { - address: '0x12f256109e744081f633a827be80e06d97ff7447', - }, - rfGrainUSDT: { - address: '0x0179bac7493a92ac812730a4c64a0b41b7ea0ecf', - }, - rfGrainUSDC: { - address: '0xaeacf641a0342330ec681b57c0a6af0b71d5cbff', - }, + defillama: [ + { + defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', + tokenAddress: '0x5a7a183b6b44dc4ec2e3d2ef43f98c5152b1d76d', }, + ], + stakewise: { + url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', + token: '0xf7d4e7273e5015c96728a6b02f31c505ee184603', }, - }, - stakewise: { - url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', - token: '0xf7d4e7273e5015c96728a6b02f31c505ee184603', - }, - etherfi: '0x35751007a407ca6feffe80b3cb397736d2cf4dbe', - dforce: { - token: '0xbc404429558292ee2d769e57d57d6e74bbd2792d', - }, - defaultHandlers: { - sUSDai: { - tokenAddress: '0x0b2b2b2076d95dda7817e785989fe353fe955ef9', - sourceUrl: 'https://api-platform-analytics.metastreet.xyz/v2/usdai/dashboard/apy', - path: '', - scale: 1e18, - }, - apxETH: { - tokenAddress: '0xcf6c2bb97a8978321c9e207afe8a2037fa9be45c', - sourceUrl: 'https://dinero.xyz/api/apr', - path: 'apxEth', - scale: 100, - }, - yUSD: { - tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', - sourceUrl: 'https://ctrl.yield.fi/t/apy', - path: 'apy', - isIbYield: true, - }, - yUSD2: { - tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', - sourceUrl: 'https://api.yield.fi/t/7Dapy', - path: '7d-apy[0].weighted_apy_7d_avg', - isIbYield: true, - }, - usdm: { - tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', - sourceUrl: 'https://apy.prod.mountainprotocol.com', - path: 'value', - isIbYield: true, - scale: 1, - }, - wstETH: { - tokenAddress: '0x5979d7b546e38e414f7e9822514be443a4800529', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, - }, - cbETH: { - tokenAddress: '0x1debd73e752beaf79865fd6446b0c970eae7732f', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - }, - sfrxETH: { - tokenAddress: '0x95ab45875cffdba1e5f451b950bc2e42c0053f39', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, + etherfi: '0x35751007a407ca6feffe80b3cb397736d2cf4dbe', + dforce: { + token: '0xbc404429558292ee2d769e57d57d6e74bbd2792d', }, - sFRAX: { - tokenAddress: '0xe3b3fe7bca19ca77ad877a5bebab186becfad906', - sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', - path: 'items.0.sfraxApr', - isIbYield: true, - }, - ankrETH: { - tokenAddress: '0xe05a08226c49b636acf99c40da8dc6af83ce5bb3', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "eth"}.apy', - isIbYield: true, - }, - plsRDNT: { - tokenAddress: '0x6dbf2155b0636cb3fd5359fccefb8a2c02b6cb51', - sourceUrl: 'https://www.plutusdao.io/api/getPlsRdntInfo', - path: 'apr', - scale: 1, - isIbYield: true, - }, - ezETH: { - tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', - sourceUrl: 'https://app.renzoprotocol.com/api/apr', - path: 'apr', - isIbYield: true, - }, - sUSDE: { - tokenAddress: '0x211cc4dd073734da055fbf44a2b4667d5e5fe5d2', - sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', - path: 'stakingYield.value', - isIbYield: true, - }, - jitoSOL: { - tokenAddress: '0x83e1d2310ade410676b1733d16e89f91822fd5c3', - sourceUrl: 'https://kobe.mainnet.jito.network/api/v1/stake_pool_stats', - path: 'apy.0.data', - scale: 1, - isIbYield: true, - }, - ETHx: { - tokenAddress: '0xed65c5085a18fa160af0313e60dcc7905e944dc7', - sourceUrl: 'https://universe.staderlabs.com/eth/apy', - path: 'value', - isIbYield: true, - }, - gUSDC: { - tokenAddress: '0xd3443ee1e91af28e5fb858fbd0d72a63ba8046e0', - sourceUrl: 'https://backend-arbitrum.gains.trade/apr', - path: 'collateralRewards.{symbol == "USDC"}.vaultApr', - isIbYield: true, - }, - sUSDX: { - tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', - sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', - path: 'result.susdxApy', - scale: 1, - isIbYield: true, + defaultHandlers: { + sUSDai: { + tokenAddress: '0x0b2b2b2076d95dda7817e785989fe353fe955ef9', + sourceUrl: 'https://api-platform-analytics.metastreet.xyz/v2/usdai/dashboard/apy', + path: '', + scale: 1e18, + }, + apxETH: { + tokenAddress: '0xcf6c2bb97a8978321c9e207afe8a2037fa9be45c', + sourceUrl: 'https://dinero.xyz/api/apr', + path: 'apxEth', + scale: 100, + }, + yUSD: { + tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', + sourceUrl: 'https://ctrl.yield.fi/t/apy', + path: 'apy', + isIbYield: true, + }, + yUSD2: { + tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', + sourceUrl: 'https://api.yield.fi/t/7Dapy', + path: '7d-apy[0].weighted_apy_7d_avg', + isIbYield: true, + }, + usdm: { + tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', + sourceUrl: 'https://apy.prod.mountainprotocol.com', + path: 'value', + isIbYield: true, + scale: 1, + }, + wstETH: { + tokenAddress: '0x5979d7b546e38e414f7e9822514be443a4800529', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xec70dcb4a1efa46b8f2d97c310c9c4790ba5ffa8', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, + cbETH: { + tokenAddress: '0x1debd73e752beaf79865fd6446b0c970eae7732f', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + }, + sfrxETH: { + tokenAddress: '0x95ab45875cffdba1e5f451b950bc2e42c0053f39', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + sFRAX: { + tokenAddress: '0xe3b3fe7bca19ca77ad877a5bebab186becfad906', + sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', + path: 'items.0.sfraxApr', + isIbYield: true, + }, + ankrETH: { + tokenAddress: '0xe05a08226c49b636acf99c40da8dc6af83ce5bb3', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "eth"}.apy', + isIbYield: true, + }, + plsRDNT: { + tokenAddress: '0x6dbf2155b0636cb3fd5359fccefb8a2c02b6cb51', + sourceUrl: 'https://www.plutusdao.io/api/getPlsRdntInfo', + path: 'apr', + scale: 1, + isIbYield: true, + }, + ezETH: { + tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', + sourceUrl: 'https://app.renzoprotocol.com/api/apr', + path: 'apr', + isIbYield: true, + }, + sUSDE: { + tokenAddress: '0x211cc4dd073734da055fbf44a2b4667d5e5fe5d2', + sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', + path: 'stakingYield.value', + isIbYield: true, + }, + jitoSOL: { + tokenAddress: '0x83e1d2310ade410676b1733d16e89f91822fd5c3', + sourceUrl: 'https://kobe.mainnet.jito.network/api/v1/stake_pool_stats', + path: 'apy.0.data', + scale: 1, + isIbYield: true, + }, + ETHx: { + tokenAddress: '0xed65c5085a18fa160af0313e60dcc7905e944dc7', + sourceUrl: 'https://universe.staderlabs.com/eth/apy', + path: 'value', + isIbYield: true, + }, + gUSDC: { + tokenAddress: '0xd3443ee1e91af28e5fb858fbd0d72a63ba8046e0', + sourceUrl: 'https://backend-arbitrum.gains.trade/apr', + path: 'collateralRewards.{symbol == "USDC"}.vaultApr', + isIbYield: true, + }, + sUSDX: { + tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', + sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', + path: 'result.susdxApy', + scale: 1, + isIbYield: true, + }, }, }, }, diff --git a/config/avalanche.ts b/config/avalanche.ts index 52f238b0a..9de8d0ef2 100644 --- a/config/avalanche.ts +++ b/config/avalanche.ts @@ -72,101 +72,103 @@ export default { multicall: '0xca11bde05977b3631167028862be2a173976ca11', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 2, - ybAprConfig: { - aave: { - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/2h9woxy8RTjHu1HJsCEnmzpPHFArU33avmUh4f71JpVn`, - tokens: { - USDC: { - underlyingAssetAddress: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e', - aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', - wrappedTokens: { - stataAvaUSDC: '0xe7839ea8ea8543c7f5d9c9d7269c661904729fe7', + aprHandlers: { + ybAprHandler: { + aave: { + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/2h9woxy8RTjHu1HJsCEnmzpPHFArU33avmUh4f71JpVn`, + tokens: { + USDC: { + underlyingAssetAddress: '0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e', + aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', + wrappedTokens: { + stataAvaUSDC: '0xe7839ea8ea8543c7f5d9c9d7269c661904729fe7', + }, }, - }, - USDT: { - underlyingAssetAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7', - aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', - wrappedTokens: { - stataAvaUSDT: '0x759a2e28d4c3ad394d3125d5ab75a6a5d6782fd9', + USDT: { + underlyingAssetAddress: '0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7', + aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', + wrappedTokens: { + stataAvaUSDT: '0x759a2e28d4c3ad394d3125d5ab75a6a5d6782fd9', + }, }, - }, - DAI: { - underlyingAssetAddress: '0xd586e7f844cea2f87f50152665bcbc2c279d8d70', - aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', - wrappedTokens: { - stataAvaDAI: '0x234c4b76f749dfffd9c18ea7cc0972206b42d019', + DAI: { + underlyingAssetAddress: '0xd586e7f844cea2f87f50152665bcbc2c279d8d70', + aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', + wrappedTokens: { + stataAvaDAI: '0x234c4b76f749dfffd9c18ea7cc0972206b42d019', + }, }, - }, - wETH: { - underlyingAssetAddress: '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab', - aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', - wrappedTokens: { - stataAvaWETH: '0x41bafe0091d55378ed921af3784622923651fdd8', + wETH: { + underlyingAssetAddress: '0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab', + aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', + wrappedTokens: { + stataAvaWETH: '0x41bafe0091d55378ed921af3784622923651fdd8', + }, }, - }, - wAVAX: { - underlyingAssetAddress: '0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7', - aTokenAddress: '0x6d80113e533a2c0fe82eabd35f1875dcea89ea97', - wrappedTokens: { - stataAvaWAVAX: '0xa291ae608d8854cdbf9838e28e9badcf10181669', - stataAvaWAVAX2: '0x6a02c7a974f1f13a67980c80f774ec1d2ed8f98d', + wAVAX: { + underlyingAssetAddress: '0xb31f66aa3c1e785363f0875a1b74e27b85fd66c7', + aTokenAddress: '0x6d80113e533a2c0fe82eabd35f1875dcea89ea97', + wrappedTokens: { + stataAvaWAVAX: '0xa291ae608d8854cdbf9838e28e9badcf10181669', + stataAvaWAVAX2: '0x6a02c7a974f1f13a67980c80f774ec1d2ed8f98d', + }, }, - }, - wBTC: { - underlyingAssetAddress: '0x50b7545627a5162f82a992c33b87adc75187b218', - aTokenAddress: '0x078f358208685046a11c85e8ad32895ded33a249', - wrappedTokens: { - stataAvaWBTC: '0xb516f74eb030cebd5f616b1a33f88e1213b93c2c', + wBTC: { + underlyingAssetAddress: '0x50b7545627a5162f82a992c33b87adc75187b218', + aTokenAddress: '0x078f358208685046a11c85e8ad32895ded33a249', + wrappedTokens: { + stataAvaWBTC: '0xb516f74eb030cebd5f616b1a33f88e1213b93c2c', + }, }, }, }, }, - }, - euler: { - vaultsJsonUrl: - 'https://raw.githubusercontent.com/euler-xyz/euler-labels/refs/heads/master/43114/vaults.json', - lensContractAddress: '0xc820c24905c210aefe21dae40723ec28d62c1544', - }, - defaultHandlers: { - sAVAX: { - tokenAddress: '0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be', - sourceUrl: 'https://api.benqi.fi/liquidstaking/apr', - path: 'apr', - scale: 1, - }, - yyAVAX: { - tokenAddress: '0xf7d9281e8e363584973f946201b82ba72c965d27', - sourceUrl: 'https://staging-api.yieldyak.com/yyavax', - path: 'yyAVAX.apr', - }, - ggAVAX: { - tokenAddress: '0xa25eaf2906fa1a3a13edac9b9657108af7b703e3', - sourceUrl: 'https://api.gogopool.com/metrics', - path: 'ggavax_apy', - // Updated from https://ceres.gogopool.com/ which used below calculation and scale -8.3333 - // According to solarcurve, the AVAX Monthly Interest must be multiplied by -12 to represent the APR in normal scale, for example, if the monthly interest is -0,15, the APR would be -0,15 * -12 = 1,8%. - // @solarcurve: We estimate by multiplying that value by -12 since its the exchange rate of AVAX -> ggAVAX, which will always return less ggAVAX than AVAX - // How this -12 became -8,333? It's because the scale parameter is used to divide the number, and the final apr percentage is in decimal format (1,8% = 0,018), so if: - // M * -12 = A (M is monthly rate and A is APR) => (M/x) = (A/100) => (A / -12x) = (A / 100) [replacing M by A/-12] => x = 100/-12 = -8,33333 + euler: { + vaultsJsonUrl: + 'https://raw.githubusercontent.com/euler-xyz/euler-labels/refs/heads/master/43114/vaults.json', + lensContractAddress: '0xc820c24905c210aefe21dae40723ec28d62c1544', }, - ankrAVAX: { - tokenAddress: '0xc3344870d52688874b06d844e0c36cc39fc727f6', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "avax"}.apy', - isIbYield: true, - }, - sdeUSD: { - tokenAddress: '0x68088c91446c7bea49ea7dbd3b96ce62b272dc96', - sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', - path: 'deusd_apy', - isIbYield: true, - }, - savUSD: { - tokenAddress: '0x06d47f3fb376649c3a9dafe069b3d6e35572219e', - sourceUrl: 'https://app.avantprotocol.com/api/savusdApy', - path: 'savusdApy', - isIbYield: true, + defaultHandlers: { + sAVAX: { + tokenAddress: '0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be', + sourceUrl: 'https://api.benqi.fi/liquidstaking/apr', + path: 'apr', + scale: 1, + }, + yyAVAX: { + tokenAddress: '0xf7d9281e8e363584973f946201b82ba72c965d27', + sourceUrl: 'https://staging-api.yieldyak.com/yyavax', + path: 'yyAVAX.apr', + }, + ggAVAX: { + tokenAddress: '0xa25eaf2906fa1a3a13edac9b9657108af7b703e3', + sourceUrl: 'https://api.gogopool.com/metrics', + path: 'ggavax_apy', + // Updated from https://ceres.gogopool.com/ which used below calculation and scale -8.3333 + // According to solarcurve, the AVAX Monthly Interest must be multiplied by -12 to represent the APR in normal scale, for example, if the monthly interest is -0,15, the APR would be -0,15 * -12 = 1,8%. + // @solarcurve: We estimate by multiplying that value by -12 since its the exchange rate of AVAX -> ggAVAX, which will always return less ggAVAX than AVAX + // How this -12 became -8,333? It's because the scale parameter is used to divide the number, and the final apr percentage is in decimal format (1,8% = 0,018), so if: + // M * -12 = A (M is monthly rate and A is APR) => (M/x) = (A/100) => (A / -12x) = (A / 100) [replacing M by A/-12] => x = 100/-12 = -8,33333 + }, + ankrAVAX: { + tokenAddress: '0xc3344870d52688874b06d844e0c36cc39fc727f6', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "avax"}.apy', + isIbYield: true, + }, + sdeUSD: { + tokenAddress: '0x68088c91446c7bea49ea7dbd3b96ce62b272dc96', + sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', + path: 'deusd_apy', + isIbYield: true, + }, + savUSD: { + tokenAddress: '0x06d47f3fb376649c3a9dafe069b3d6e35572219e', + sourceUrl: 'https://app.avantprotocol.com/api/savusdApy', + path: 'savusdApy', + isIbYield: true, + }, }, }, }, diff --git a/config/base.ts b/config/base.ts index 7b0729756..4da944895 100644 --- a/config/base.ts +++ b/config/base.ts @@ -74,107 +74,109 @@ export default { defaultYieldFeePercentage: '0.1', }, }, - ybAprConfig: { - fluid: { - url: 'https://api.fluid.instad.app/v2/lending/8453/tokens', - }, - extra: { - url: 'https://extra-static.s3.amazonaws.com/data/xlend/pools/apr.json', - }, - susds: { - oracle: '0x65d946e533748a998b1f0e430803e39a6388f7a1', - token: '0x5875eee11cf8398102fdad704c9e96607675467a', - }, - morpho: { - tokens: {}, - }, - defaultHandlers: { - yoETH: { - tokenAddress: '0x3a43aec53490cb9fa922847385d82fe25d0e9de7', - sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0x3A43AEC53490CB9Fa922847385D82fe25d0E9De7', - path: 'data.stats.yield.7d', - isIbYield: true, - }, - yoUSD: { - tokenAddress: '0x0000000f2eb9f69274678c76222b35eec7588a65', - sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0x0000000f2eB9f69274678c76222B35eEc7588a65', - path: 'data.stats.yield.7d', - isIbYield: true, - }, - yoBTC: { - tokenAddress: '0xbcbc8cb4d1e8ed048a6276a5e94a3e952660bcbc', - sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0xbCbc8cb4D1e8ED048a6276a5E94A3e952660BcbC', - path: 'data.stats.yield.7d', - isIbYield: true, + aprHandlers: { + ybAprHandler: { + fluid: { + url: 'https://api.fluid.instad.app/v2/lending/8453/tokens', }, - ezETH: { - tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', - sourceUrl: 'https://app.renzoprotocol.com/api/apr', - path: 'apr', - isIbYield: true, + extra: { + url: 'https://extra-static.s3.amazonaws.com/data/xlend/pools/apr.json', }, - sUSDz: { - tokenAddress: '0xe31ee12bdfdd0573d634124611e85338e2cbf0cf', - sourceUrl: 'https://rwa-api.anzen.finance/metrics/susdz_stats', - path: 'apy', + susds: { + oracle: '0x65d946e533748a998b1f0e430803e39a6388f7a1', + token: '0x5875eee11cf8398102fdad704c9e96607675467a', }, - 'sp-ysUSDC': { - tokenAddress: '0xffe8b2295cef70290819a8193834cc7900bcef5f', - sourceUrl: 'https://www.superform.xyz/api/proxy/stats/vault/supervault/vL7k-5ZgYCoFgi6kz2jIJ/', - path: 'apy', - isIbYield: true, + morpho: { + tokens: {}, }, - ysUSDC: { - tokenAddress: '0xe9f2a5f9f3c846f29066d7fb3564f8e6b6b2d65b', - sourceUrl: 'https://www.superform.xyz/api/proxy/stats/vault/supervault/zLVQbgScIbXJuSz-NNsK-/', - path: 'apy', - isIbYield: true, - }, - yUSD: { - tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', - sourceUrl: 'https://ctrl.yield.fi/t/apy', - path: 'apy', - isIbYield: true, - }, - yUSD2: { - tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', - sourceUrl: 'https://api.yield.fi/t/7Dapy', - path: '7d-apy[0].weighted_apy_7d_avg', - isIbYield: true, - }, - cbETH: { - tokenAddress: '0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - isIbYield: true, - }, - wstETH: { - tokenAddress: '0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, + defaultHandlers: { + yoETH: { + tokenAddress: '0x3a43aec53490cb9fa922847385d82fe25d0e9de7', + sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0x3A43AEC53490CB9Fa922847385D82fe25d0E9De7', + path: 'data.stats.yield.7d', + isIbYield: true, + }, + yoUSD: { + tokenAddress: '0x0000000f2eb9f69274678c76222b35eec7588a65', + sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0x0000000f2eB9f69274678c76222B35eEc7588a65', + path: 'data.stats.yield.7d', + isIbYield: true, + }, + yoBTC: { + tokenAddress: '0xbcbc8cb4d1e8ed048a6276a5e94a3e952660bcbc', + sourceUrl: 'https://api.yo.xyz/api/v1/vault/base/0xbCbc8cb4D1e8ED048a6276a5E94A3e952660BcbC', + path: 'data.stats.yield.7d', + isIbYield: true, + }, + ezETH: { + tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', + sourceUrl: 'https://app.renzoprotocol.com/api/apr', + path: 'apr', + isIbYield: true, + }, + sUSDz: { + tokenAddress: '0xe31ee12bdfdd0573d634124611e85338e2cbf0cf', + sourceUrl: 'https://rwa-api.anzen.finance/metrics/susdz_stats', + path: 'apy', + }, + 'sp-ysUSDC': { + tokenAddress: '0xffe8b2295cef70290819a8193834cc7900bcef5f', + sourceUrl: 'https://www.superform.xyz/api/proxy/stats/vault/supervault/vL7k-5ZgYCoFgi6kz2jIJ/', + path: 'apy', + isIbYield: true, + }, + ysUSDC: { + tokenAddress: '0xe9f2a5f9f3c846f29066d7fb3564f8e6b6b2d65b', + sourceUrl: 'https://www.superform.xyz/api/proxy/stats/vault/supervault/zLVQbgScIbXJuSz-NNsK-/', + path: 'apy', + isIbYield: true, + }, + yUSD: { + tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', + sourceUrl: 'https://ctrl.yield.fi/t/apy', + path: 'apy', + isIbYield: true, + }, + yUSD2: { + tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', + sourceUrl: 'https://api.yield.fi/t/7Dapy', + path: '7d-apy[0].weighted_apy_7d_avg', + isIbYield: true, + }, + cbETH: { + tokenAddress: '0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + isIbYield: true, + }, + wstETH: { + tokenAddress: '0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, }, - rETH: { - tokenAddress: '0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, + maker: { + sdai: '0x99ac4484e8a1dbd6a185380b3a811913ac884d87', }, - }, - maker: { - sdai: '0x99ac4484e8a1dbd6a185380b3a811913ac884d87', - }, - etherfi: '0x04c0599ae5a44757c0af6f9ec3b93da8976c150a', - aave: { - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/GQFbb95cE6d8mV989mL5figjaGaKCQB3xqYrr1bRyXqF`, - tokens: { - USDC: { - underlyingAssetAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', - aTokenAddress: '0x4e65fe4dba92790696d040ac24aa414708f5c0ab', - wrappedTokens: { - stataBasUSDC: '0x4ea71a20e655794051d1ee8b6e4a3269b13ccacc', + etherfi: '0x04c0599ae5a44757c0af6f9ec3b93da8976c150a', + aave: { + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/GQFbb95cE6d8mV989mL5figjaGaKCQB3xqYrr1bRyXqF`, + tokens: { + USDC: { + underlyingAssetAddress: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', + aTokenAddress: '0x4e65fe4dba92790696d040ac24aa414708f5c0ab', + wrappedTokens: { + stataBasUSDC: '0x4ea71a20e655794051d1ee8b6e4a3269b13ccacc', + }, }, }, }, diff --git a/config/fantom.ts b/config/fantom.ts index 7e234ca2d..e68b6d2a9 100644 --- a/config/fantom.ts +++ b/config/fantom.ts @@ -116,16 +116,7 @@ export default { ], }, avgBlockSpeed: 1, - ybAprConfig: { - sftmx: { - tokens: { - sftmx: { - address: '0xd7028092c830b5c8fce061af2e593413ebbc1fc1', - ftmStakingAddress: '0xb458bfc855ab504a8a327720fcef98886065529b', - }, - }, - }, - }, + aprHandlers: {}, datastudio: { main: { user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', diff --git a/config/fraxtal.ts b/config/fraxtal.ts index 99deea939..951fed058 100644 --- a/config/fraxtal.ts +++ b/config/fraxtal.ts @@ -60,28 +60,30 @@ export default { defaultYieldFeePercentage: '0.5', }, }, - ybAprConfig: { - maker: { - sdai: '0x09eadcbaa812a4c076c3a6cde765dc4a22e0d775', - }, - defaultHandlers: { - sfrxETH: { - tokenAddress: '0xfc00000000000000000000000000000000000005', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - sFRAX: { - tokenAddress: '0xfc00000000000000000000000000000000000008', - sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', - path: 'items.0.sfraxApr', - isIbYield: true, + aprHandlers: { + ybAprHandler: { + maker: { + sdai: '0x09eadcbaa812a4c076c3a6cde765dc4a22e0d775', }, - sUSDe: { - tokenAddress: '0x211cc4dd073734da055fbf44a2b4667d5e5fe5d2', - sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', - path: 'stakingYield.value', - isIbYield: true, + defaultHandlers: { + sfrxETH: { + tokenAddress: '0xfc00000000000000000000000000000000000005', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + sFRAX: { + tokenAddress: '0xfc00000000000000000000000000000000000008', + sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', + path: 'items.0.sfraxApr', + isIbYield: true, + }, + sUSDe: { + tokenAddress: '0x211cc4dd073734da055fbf44a2b4667d5e5fe5d2', + sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', + path: 'stakingYield.value', + isIbYield: true, + }, }, }, }, diff --git a/config/gnosis.ts b/config/gnosis.ts index 97eced716..a685f4933 100644 --- a/config/gnosis.ts +++ b/config/gnosis.ts @@ -71,63 +71,65 @@ export default { multicall: '0xbb6fab6b627947dae0a75808250d8b2652952cb5', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, - ybAprConfig: { - stakewise: { - url: 'https://gnosis-graph.stakewise.io/subgraphs/name/stakewise/stakewise', - token: '0xf490c80aae5f2616d3e3bda2483e30c4cb21d1a0', - }, - defaultHandlers: { - wstETH: { - tokenAddress: '0x6c76971f98945ae98dd7d4dfca8711ebea946ea6', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, + aprHandlers: { + ybAprHandler: { + stakewise: { + url: 'https://gnosis-graph.stakewise.io/subgraphs/name/stakewise/stakewise', + token: '0xf490c80aae5f2616d3e3bda2483e30c4cb21d1a0', }, - rETH: { - tokenAddress: '0xc791240d1f2def5938e2031364ff4ed887133c3d', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, + defaultHandlers: { + wstETH: { + tokenAddress: '0x6c76971f98945ae98dd7d4dfca8711ebea946ea6', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xc791240d1f2def5938e2031364ff4ed887133c3d', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, }, - }, - aave: { - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/HtcDaL8L8iZ2KQNNS44EBVmLruzxuNAz1RkBYdui1QUT`, - tokens: { - USDC: { - underlyingAssetAddress: '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83', - aTokenAddress: '0xc6b7aca6de8a6044e0e32d0c841a89244a10d284', - wrappedTokens: { - stataGnoUSDC: '0x270ba1f35d8b87510d24f693fccc0da02e6e4eeb', + aave: { + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/HtcDaL8L8iZ2KQNNS44EBVmLruzxuNAz1RkBYdui1QUT`, + tokens: { + USDC: { + underlyingAssetAddress: '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83', + aTokenAddress: '0xc6b7aca6de8a6044e0e32d0c841a89244a10d284', + wrappedTokens: { + stataGnoUSDC: '0x270ba1f35d8b87510d24f693fccc0da02e6e4eeb', + }, }, - }, - USDCn: { - underlyingAssetAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', - aTokenAddress: '0xc0333cb85b59a788d8c7cae5e1fd6e229a3e5a65', - wrappedTokens: { - stataGnoUSDCe: '0xf0e7ec247b918311afa054e0aedb99d74c31b809', - waGnoUSDCe: '0x51350d88c1bd32cc6a79368c9fb70373fb71f375', + USDCn: { + underlyingAssetAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', + aTokenAddress: '0xc0333cb85b59a788d8c7cae5e1fd6e229a3e5a65', + wrappedTokens: { + stataGnoUSDCe: '0xf0e7ec247b918311afa054e0aedb99d74c31b809', + waGnoUSDCe: '0x51350d88c1bd32cc6a79368c9fb70373fb71f375', + }, }, - }, - WETH: { - underlyingAssetAddress: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', - aTokenAddress: '0xa818f1b57c201e092c4a2017a91815034326efd1', - wrappedTokens: { - waGnoWETH: '0x57f664882f762fa37903fc864e2b633d384b411a', + WETH: { + underlyingAssetAddress: '0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1', + aTokenAddress: '0xa818f1b57c201e092c4a2017a91815034326efd1', + wrappedTokens: { + waGnoWETH: '0x57f664882f762fa37903fc864e2b633d384b411a', + }, }, - }, - GNO: { - underlyingAssetAddress: '0x9c58bacc331c9aa871afd802db6379a98e80cedb', - aTokenAddress: '0xa1fa064a85266e2ca82dee5c5ccec84df445760e', - wrappedTokens: { - waGnoGNO: '0x7c16f0185a26db0ae7a9377f23bc18ea7ce5d644', + GNO: { + underlyingAssetAddress: '0x9c58bacc331c9aa871afd802db6379a98e80cedb', + aTokenAddress: '0xa1fa064a85266e2ca82dee5c5ccec84df445760e', + wrappedTokens: { + waGnoGNO: '0x7c16f0185a26db0ae7a9377f23bc18ea7ce5d644', + }, }, - }, - wstETH: { - underlyingAssetAddress: '0x6c76971f98945ae98dd7d4dfca8711ebea946ea6', - aTokenAddress: '0x23e4e76d01b2002be436ce8d6044b0aa2f68b68a', - wrappedTokens: { - waGnowstETH: '0x773cda0cade2a3d86e6d4e30699d40bb95174ff2', + wstETH: { + underlyingAssetAddress: '0x6c76971f98945ae98dd7d4dfca8711ebea946ea6', + aTokenAddress: '0x23e4e76d01b2002be436ce8d6044b0aa2f68b68a', + wrappedTokens: { + waGnowstETH: '0x773cda0cade2a3d86e6d4e30699d40bb95174ff2', + }, }, }, }, diff --git a/config/mainnet.ts b/config/mainnet.ts index a9e27e01f..3535ac5fc 100644 --- a/config/mainnet.ts +++ b/config/mainnet.ts @@ -115,436 +115,438 @@ export default { ], }, }, - ybAprConfig: { - usdl: true, - morpho: { - tokens: {}, - }, - aave: { - v2: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/8wR23o1zkS4gpLqLNU4kG3JHYVucqGyopL5utGxP2q1N`, - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', - wrappedTokens: { - waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + aprHandlers: { + ybAprHandler: { + usdl: true, + morpho: { + tokens: {}, + }, + aave: { + v2: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/8wR23o1zkS4gpLqLNU4kG3JHYVucqGyopL5utGxP2q1N`, + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', + wrappedTokens: { + waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', + }, }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', - wrappedTokens: { - waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', + wrappedTokens: { + waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', + }, }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', - wrappedTokens: { - waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', + wrappedTokens: { + waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', + }, }, }, }, - }, - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Cd2gEDVeqnjBn1hSeqFMitw8Q1iiyV9FYUZkLNRcL87g`, - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', - wrappedTokens: { - waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', - stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', - stataV2USDC: '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Cd2gEDVeqnjBn1hSeqFMitw8Q1iiyV9FYUZkLNRcL87g`, + tokens: { + USDC: { + underlyingAssetAddress: underlyingTokens.USDC, + aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', + wrappedTokens: { + waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', + stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', + stataV2USDC: '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', + }, }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', - wrappedTokens: { - waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', - stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', - stataEthUSDT2: '0x862c57d48becb45583aeba3f489696d22466ca1b', - stataV2USDT: '0x7bc3485026ac48b6cf9baf0a377477fff5703af8', + USDT: { + underlyingAssetAddress: underlyingTokens.USDT, + aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', + wrappedTokens: { + waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', + stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', + stataEthUSDT2: '0x862c57d48becb45583aeba3f489696d22466ca1b', + stataV2USDT: '0x7bc3485026ac48b6cf9baf0a377477fff5703af8', + }, }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', - wrappedTokens: { - waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - stataEthDAI: '0xeb708639e8e518b86a916db3685f90216b1c1c67', + DAI: { + underlyingAssetAddress: underlyingTokens.DAI, + aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', + wrappedTokens: { + waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', + stataEthDAI: '0xeb708639e8e518b86a916db3685f90216b1c1c67', + }, }, - }, - wETH: { - underlyingAssetAddress: underlyingTokens.wETH, - aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', - wrappedTokens: { - waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', - stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', - stataV2WETH: '0x0bfc9d54fc184518a81162f8fb99c2eaca081202', + wETH: { + underlyingAssetAddress: underlyingTokens.wETH, + aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', + wrappedTokens: { + waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', + stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', + stataV2WETH: '0x0bfc9d54fc184518a81162f8fb99c2eaca081202', + }, }, - }, - crvUSD: { - underlyingAssetAddress: underlyingTokens.crvUSD, - aTokenAddress: '0xb82fa9f31612989525992fcfbb09ab22eff5c85a', - wrappedTokens: { - stataEthcrvUSD: '0x848107491e029afde0ac543779c7790382f15929', + crvUSD: { + underlyingAssetAddress: underlyingTokens.crvUSD, + aTokenAddress: '0xb82fa9f31612989525992fcfbb09ab22eff5c85a', + wrappedTokens: { + stataEthcrvUSD: '0x848107491e029afde0ac543779c7790382f15929', + }, }, - }, - LUSD: { - underlyingAssetAddress: underlyingTokens.LUSD, - aTokenAddress: '0x3fe6a295459fae07df8a0cecc36f37160fe86aa9', - wrappedTokens: { - stataEthLUSD: '0xdbf5e36569798d1e39ee9d7b1c61a7409a74f23a', + LUSD: { + underlyingAssetAddress: underlyingTokens.LUSD, + aTokenAddress: '0x3fe6a295459fae07df8a0cecc36f37160fe86aa9', + wrappedTokens: { + stataEthLUSD: '0xdbf5e36569798d1e39ee9d7b1c61a7409a74f23a', + }, }, - }, - USDe: { - underlyingAssetAddress: underlyingTokens.USDe, - aTokenAddress: '0x4f5923fc5fd4a93352581b38b7cd26943012decf', - wrappedTokens: { - stataEthUSDe: '0x5f9d59db355b4a60501544637b00e94082ca575b', + USDe: { + underlyingAssetAddress: underlyingTokens.USDe, + aTokenAddress: '0x4f5923fc5fd4a93352581b38b7cd26943012decf', + wrappedTokens: { + stataEthUSDe: '0x5f9d59db355b4a60501544637b00e94082ca575b', + }, }, - }, - pyUSD: { - underlyingAssetAddress: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', - aTokenAddress: '0x0c0d01abf3e6adfca0989ebba9d6e85dd58eab1e', - wrappedTokens: { - waEthPYUSD: '0xb51edddd8c47856d81c8681ea71404cec93e92c6', + pyUSD: { + underlyingAssetAddress: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', + aTokenAddress: '0x0c0d01abf3e6adfca0989ebba9d6e85dd58eab1e', + wrappedTokens: { + waEthPYUSD: '0xb51edddd8c47856d81c8681ea71404cec93e92c6', + }, }, }, }, - }, - lido: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/5vxMbXRhG1oQr55MWC5j6qg78waWujx1wjeuEWDA6j3`, - tokens: { - LidoWETH: { - underlyingAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - aTokenAddress: '0xfa1fdbbd71b0aa16162d76914d69cd8cb3ef92da', - wrappedTokens: { - waEthLido: '0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9', + lido: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/5vxMbXRhG1oQr55MWC5j6qg78waWujx1wjeuEWDA6j3`, + tokens: { + LidoWETH: { + underlyingAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', + aTokenAddress: '0xfa1fdbbd71b0aa16162d76914d69cd8cb3ef92da', + wrappedTokens: { + waEthLido: '0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9', + }, }, - }, - LidoWSTETH: { - underlyingAssetAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - aTokenAddress: '0xc035a7cf15375ce2706766804551791ad035e0c2', - wrappedTokens: { - waEthLidowstETH: '0x775f661b0bd1739349b9a2a3ef60be277c5d2d29', + LidoWSTETH: { + underlyingAssetAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + aTokenAddress: '0xc035a7cf15375ce2706766804551791ad035e0c2', + wrappedTokens: { + waEthLidowstETH: '0x775f661b0bd1739349b9a2a3ef60be277c5d2d29', + }, }, - }, - LidoGHO: { - underlyingAssetAddress: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', - aTokenAddress: '0x18efe565a5373f430e2f809b97de30335b3ad96a', - wrappedTokens: { - waEthLidoGHO: '0xc71ea051a5f82c67adcf634c36ffe6334793d24c', + LidoGHO: { + underlyingAssetAddress: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', + aTokenAddress: '0x18efe565a5373f430e2f809b97de30335b3ad96a', + wrappedTokens: { + waEthLidoGHO: '0xc71ea051a5f82c67adcf634c36ffe6334793d24c', + }, }, }, }, }, - }, - bloom: { - tokens: { - tbyFeb1924: { - address: '0xc4cafefbc3dfea629c589728d648cb6111db3136', - feedAddress: '0xde1f5f2d69339171d679fb84e4562febb71f36e6', + bloom: { + tokens: { + tbyFeb1924: { + address: '0xc4cafefbc3dfea629c589728d648cb6111db3136', + feedAddress: '0xde1f5f2d69339171d679fb84e4562febb71f36e6', + }, }, }, - }, - defillama: [ - { - defillamaPoolId: '5a9c2073-2190-4002-9654-8c245d1e8534', - tokenAddress: '0x6dc3ce9c57b20131347fdc9089d740daf6eb34c5', - }, - { - defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', - tokenAddress: '0xf073bac22dab7faf4a3dd6c6189a70d54110525c', - }, - ], - gearbox: { - sourceUrl: 'https://charts-server.fly.dev/api/pools', - tokens: { - dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, - dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, - }, - }, - idle: { - sourceUrl: 'https://api.idle.finance/junior-rates/', - authorizationHeader: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', - tokens: { - idleDAI: { - address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', - wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', - }, - idleUSDC: { - address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', - wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', - }, - idleUSDT: { - address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', - wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', + defillama: [ + { + defillamaPoolId: '5a9c2073-2190-4002-9654-8c245d1e8534', + tokenAddress: '0x6dc3ce9c57b20131347fdc9089d740daf6eb34c5', }, - }, - }, - maker: { - sdai: '0x83f20f44975d03b1b09e64809b757c47f942beea', - }, - tranchess: { - sourceUrl: 'https://tranchess.com/eth/api/v3/funds', - tokens: { - qETH: { - address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', - underlyingAssetName: 'WETH', + { + defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', + tokenAddress: '0xf073bac22dab7faf4a3dd6c6189a70d54110525c', + }, + ], + gearbox: { + sourceUrl: 'https://charts-server.fly.dev/api/pools', + tokens: { + dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, + dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, }, }, - }, - stakewise: { - url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', - token: '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38', - }, - etherfi: '0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee', - maple: { - url: 'https://api.maple.finance/v2/graphql', - token: '0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b', - }, - yieldnest: { - url: 'https://gateway.yieldnest.finance/api/v1/graphql', - token: '0x09db87a538bd693e9d08544577d5ccfaa6373a48', - }, - teth: { - address: '0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8', - }, - fluid: { - url: 'https://api.fluid.instad.app/v2/lending/1/tokens', - }, - defaultHandlers: { - siUSD: { - tokenAddress: '0xdbdc1ef57537e34680b898e1febd3d68c7389bcb', - sourceUrl: 'https://api.infinifi.xyz/api/protocol/data', - path: 'data.stats.siusd.lastWeekAPY', - scale: 100, - }, - cUSDO: { - tokenAddress: '0xad55aebc9b8c03fc43cd9f62260391c13c23e7c0', - sourceUrl: 'https://prod-gw.openeden.com/sys/apy', - path: 'apy', - scale: 100, - }, - slpETHApr: { - tokenAddress: '0x3976d71e7ddfbab9bd120ec281b7d35fa0f28528', - sourceUrl: 'https://api-data.loopfi.xyz/api/getData', - path: 'loop.slpETHApr', - scale: 1, - }, - yUSD: { - tokenAddress: '0x1ce7d9942ff78c328a4181b9f3826fee6d845a97', - sourceUrl: 'https://ctrl.yield.fi/t/apy', - path: 'apy', - isIbYield: true, - }, - uniETH: { - tokenAddress: '0xf1376bcef0f78459c0ed0ba5ddce976f1ddf51f4', - sourceUrl: 'https://app.bedrock.technology/unieth/api/v1/e2ls/apy', - path: 'data.apy', - scale: 10000, - }, - vETH: { - tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', - sourceUrl: 'https://dapi.bifrost.io/api/site', - path: 'vETH.totalApy', - }, - stETH: { - tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - amphrETH: { - tokenAddress: '0x5fd13359ba15a84b76f7f87568309040176167cd', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - rstETH: { - tokenAddress: '0x7a4effd87c2f3c55ca251080b1343b605f327e3a', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - Re7LRT: { - tokenAddress: '0x84631c0d0081fde56deb72f6de77abbbf6a9f93a', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - steakLRT: { - tokenAddress: '0xbeef69ac7870777598a04b2bd4771c71212e6abc', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - pufETH: { - tokenAddress: '0xd9a442856c234a39a81a089c06451ebaa4306a72', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - wstETH: { - tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - inwstETHs: { - tokenAddress: '0x8e0789d39db454dbe9f4a77acef6dc7c69f6d552', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - cbETH: { - tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - isIbYield: true, - }, - sfrxETH: { - tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - StaFirETH: { - tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', - sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', - path: 'data.stakeApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, - }, - wjAURA: { - tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', - sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', - path: 'wjauraApy', - isIbYield: true, - }, - ETHx: { - tokenAddress: '0xa35b1b31ce002fbf2058d22f30f95d405200a15b', - sourceUrl: 'https://universe.staderlabs.com/eth/apy', - path: 'value', - isIbYield: true, - }, - usdm: { - tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', - sourceUrl: 'https://apy.prod.mountainprotocol.com', - path: 'value', - isIbYield: true, - scale: 1, - }, - ankrETH: { - tokenAddress: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "eth"}.apy', - isIbYield: true, - }, - ezETH: { - tokenAddress: '0xbf5495efe5db9ce00f80364c8b423567e58d2110', - sourceUrl: 'https://app.renzoprotocol.com/api/apr', - path: 'apr', - isIbYield: true, - }, - rsETH: { - tokenAddress: '0xa1290d69c65a6fe4df752f95823fae25cb99e5a7', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - isIbYield: true, - }, - hgETH: { - tokenAddress: '0xc824a08db624942c5e5f330d56530cd1598859fd', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/gainApy', - path: 'hgETH', - isIbYield: true, - }, - sDOLA: { - tokenAddress: '0xb45ad160634c528cc3d2926d9807104fa3157305', - sourceUrl: 'https://www.inverse.finance/api/dola-staking', - path: 'apr', - isIbYield: true, - }, - rswETH: { - tokenAddress: '0xfae103dc9cf190ed75350761e95403b7b8afa6c0', - sourceUrl: 'https://v3-lrt.svc.swellnetwork.io/api/tokens/rsweth/apr', - isIbYield: true, - }, - sUSDE: { - tokenAddress: '0x9d39a5de30e57443bff2a8307a4256c8797a3497', - sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', - path: 'stakingYield.value', - isIbYield: true, + idle: { + sourceUrl: 'https://api.idle.finance/junior-rates/', + authorizationHeader: + 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', + tokens: { + idleDAI: { + address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', + wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', + }, + idleUSDC: { + address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', + wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', + }, + idleUSDT: { + address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', + wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', + }, + }, }, - saETH: { - tokenAddress: '0xf1617882a71467534d14eee865922de1395c9e89', - sourceUrl: 'https://api.aspidanet.com/page_data/?chainId=1', - path: 'apr', - isIbYield: true, + maker: { + sdai: '0x83f20f44975d03b1b09e64809b757c47f942beea', }, - cdcETH: { - tokenAddress: '0xfe18ae03741a5b84e39c295ac9c856ed7991c38e', - sourceUrl: 'https://api.crypto.com/pos/v1/public/get-staking-instruments', - path: 'result.data.{instrument_name == "ETH.staked"}.est_rewards', - isIbYield: true, - headers: { - 'Content-Type': 'application/json', - }, - body: { - params: { - country_code: 'POL', + tranchess: { + sourceUrl: 'https://tranchess.com/eth/api/v3/funds', + tokens: { + qETH: { + address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', + underlyingAssetName: 'WETH', }, }, - scale: 1, }, - agETH: { - tokenAddress: '0xe1b4d34e8754600962cd944b535180bd758e6c2e', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - isIbYield: true, + stakewise: { + url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', + token: '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38', }, - dvstETH: { - tokenAddress: '0x5e362eb2c0706bd1d134689ec75176018385430b', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, + etherfi: '0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee', + maple: { + url: 'https://api.maple.finance/v2/graphql', + token: '0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b', }, - sdeUSD: { - tokenAddress: '0x5c5b196abe0d54485975d1ec29617d42d9198326', - sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', - path: 'deusd_apy', - isIbYield: true, + yieldnest: { + url: 'https://gateway.yieldnest.finance/api/v1/graphql', + token: '0x09db87a538bd693e9d08544577d5ccfaa6373a48', }, - sUSDX: { - tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', - sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', - path: 'result.susdxApy', - scale: 1, - isIbYield: true, + teth: { + address: '0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8', }, - slpUSD: { - tokenAddress: '0xbfb53910c935e837c74e6c4ef584557352d20fde', - sourceUrl: 'https://api-data.loopfi.xyz/api/getData', - path: 'lpUSDLoop.slpUSDApr', - scale: 1, - isIbYield: true, + fluid: { + url: 'https://api.fluid.instad.app/v2/lending/1/tokens', }, - wUSDN: { - tokenAddress: '0x99999999999999cc837c997b882957dafdcb1af9', - sourceUrl: 'https://usdn.api.smardex.io/v1/wusdn/apr', - scale: 1, - isIbYield: true, + defaultHandlers: { + siUSD: { + tokenAddress: '0xdbdc1ef57537e34680b898e1febd3d68c7389bcb', + sourceUrl: 'https://api.infinifi.xyz/api/protocol/data', + path: 'data.stats.siusd.lastWeekAPY', + scale: 100, + }, + cUSDO: { + tokenAddress: '0xad55aebc9b8c03fc43cd9f62260391c13c23e7c0', + sourceUrl: 'https://prod-gw.openeden.com/sys/apy', + path: 'apy', + scale: 100, + }, + slpETHApr: { + tokenAddress: '0x3976d71e7ddfbab9bd120ec281b7d35fa0f28528', + sourceUrl: 'https://api-data.loopfi.xyz/api/getData', + path: 'loop.slpETHApr', + scale: 1, + }, + yUSD: { + tokenAddress: '0x1ce7d9942ff78c328a4181b9f3826fee6d845a97', + sourceUrl: 'https://ctrl.yield.fi/t/apy', + path: 'apy', + isIbYield: true, + }, + uniETH: { + tokenAddress: '0xf1376bcef0f78459c0ed0ba5ddce976f1ddf51f4', + sourceUrl: 'https://app.bedrock.technology/unieth/api/v1/e2ls/apy', + path: 'data.apy', + scale: 10000, + }, + vETH: { + tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', + sourceUrl: 'https://dapi.bifrost.io/api/site', + path: 'vETH.totalApy', + }, + stETH: { + tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + amphrETH: { + tokenAddress: '0x5fd13359ba15a84b76f7f87568309040176167cd', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rstETH: { + tokenAddress: '0x7a4effd87c2f3c55ca251080b1343b605f327e3a', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + Re7LRT: { + tokenAddress: '0x84631c0d0081fde56deb72f6de77abbbf6a9f93a', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + steakLRT: { + tokenAddress: '0xbeef69ac7870777598a04b2bd4771c71212e6abc', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + pufETH: { + tokenAddress: '0xd9a442856c234a39a81a089c06451ebaa4306a72', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + wstETH: { + tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + inwstETHs: { + tokenAddress: '0x8e0789d39db454dbe9f4a77acef6dc7c69f6d552', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + cbETH: { + tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', + sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', + path: 'apy', + scale: 1, + isIbYield: true, + }, + sfrxETH: { + tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + StaFirETH: { + tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', + sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', + path: 'data.stakeApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, + wjAURA: { + tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', + sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', + path: 'wjauraApy', + isIbYield: true, + }, + ETHx: { + tokenAddress: '0xa35b1b31ce002fbf2058d22f30f95d405200a15b', + sourceUrl: 'https://universe.staderlabs.com/eth/apy', + path: 'value', + isIbYield: true, + }, + usdm: { + tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', + sourceUrl: 'https://apy.prod.mountainprotocol.com', + path: 'value', + isIbYield: true, + scale: 1, + }, + ankrETH: { + tokenAddress: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "eth"}.apy', + isIbYield: true, + }, + ezETH: { + tokenAddress: '0xbf5495efe5db9ce00f80364c8b423567e58d2110', + sourceUrl: 'https://app.renzoprotocol.com/api/apr', + path: 'apr', + isIbYield: true, + }, + rsETH: { + tokenAddress: '0xa1290d69c65a6fe4df752f95823fae25cb99e5a7', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + isIbYield: true, + }, + hgETH: { + tokenAddress: '0xc824a08db624942c5e5f330d56530cd1598859fd', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/gainApy', + path: 'hgETH', + isIbYield: true, + }, + sDOLA: { + tokenAddress: '0xb45ad160634c528cc3d2926d9807104fa3157305', + sourceUrl: 'https://www.inverse.finance/api/dola-staking', + path: 'apr', + isIbYield: true, + }, + rswETH: { + tokenAddress: '0xfae103dc9cf190ed75350761e95403b7b8afa6c0', + sourceUrl: 'https://v3-lrt.svc.swellnetwork.io/api/tokens/rsweth/apr', + isIbYield: true, + }, + sUSDE: { + tokenAddress: '0x9d39a5de30e57443bff2a8307a4256c8797a3497', + sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', + path: 'stakingYield.value', + isIbYield: true, + }, + saETH: { + tokenAddress: '0xf1617882a71467534d14eee865922de1395c9e89', + sourceUrl: 'https://api.aspidanet.com/page_data/?chainId=1', + path: 'apr', + isIbYield: true, + }, + cdcETH: { + tokenAddress: '0xfe18ae03741a5b84e39c295ac9c856ed7991c38e', + sourceUrl: 'https://api.crypto.com/pos/v1/public/get-staking-instruments', + path: 'result.data.{instrument_name == "ETH.staked"}.est_rewards', + isIbYield: true, + headers: { + 'Content-Type': 'application/json', + }, + body: { + params: { + country_code: 'POL', + }, + }, + scale: 1, + }, + agETH: { + tokenAddress: '0xe1b4d34e8754600962cd944b535180bd758e6c2e', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + isIbYield: true, + }, + dvstETH: { + tokenAddress: '0x5e362eb2c0706bd1d134689ec75176018385430b', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + sdeUSD: { + tokenAddress: '0x5c5b196abe0d54485975d1ec29617d42d9198326', + sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', + path: 'deusd_apy', + isIbYield: true, + }, + sUSDX: { + tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', + sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', + path: 'result.susdxApy', + scale: 1, + isIbYield: true, + }, + slpUSD: { + tokenAddress: '0xbfb53910c935e837c74e6c4ef584557352d20fde', + sourceUrl: 'https://api-data.loopfi.xyz/api/getData', + path: 'lpUSDLoop.slpUSDApr', + scale: 1, + isIbYield: true, + }, + wUSDN: { + tokenAddress: '0x99999999999999cc837c997b882957dafdcb1af9', + sourceUrl: 'https://usdn.api.smardex.io/v1/wusdn/apr', + scale: 1, + isIbYield: true, + }, }, }, }, diff --git a/config/mode.ts b/config/mode.ts index 6ce4070b0..a2551e5e4 100644 --- a/config/mode.ts +++ b/config/mode.ts @@ -58,16 +58,18 @@ export default { defaultYieldFeePercentage: '0.5', }, }, - ybAprConfig: { - maker: { - sdai: '0x3f51c6c5927b88cdec4b61e2787f9bd0f5249138', - }, - defaultHandlers: { - ezETH: { - tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', - sourceUrl: 'https://app.renzoprotocol.com/api/apr', - path: 'apr', - isIbYield: true, + aprHandlers: { + ybAprHandler: { + maker: { + sdai: '0x3f51c6c5927b88cdec4b61e2787f9bd0f5249138', + }, + defaultHandlers: { + ezETH: { + tokenAddress: '0x2416092f143378750bb29b79ed961ab195cceea5', + sourceUrl: 'https://app.renzoprotocol.com/api/apr', + path: 'apr', + isIbYield: true, + }, }, }, }, diff --git a/config/optimism.ts b/config/optimism.ts index e77369192..db878c16b 100644 --- a/config/optimism.ts +++ b/config/optimism.ts @@ -80,178 +80,123 @@ export default { excludedFarmIds: [], }, avgBlockSpeed: 1, - ybAprConfig: { - aave: { - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/DSfLz8oQBUeU5atALgUFQKMTSYV9mZAVYp4noLSXAfvb`, - tokens: { - USDCe: { - underlyingAssetAddress: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', - aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', - wrappedTokens: { - stataOptUSDC: '0x9f281eb58fd98ad98ede0fc4c553ad4d73e7ca2c', + aprHandlers: { + ybAprHandler: { + aave: { + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/DSfLz8oQBUeU5atALgUFQKMTSYV9mZAVYp4noLSXAfvb`, + tokens: { + USDCe: { + underlyingAssetAddress: '0x7f5c764cbc14f9669b88837ca1490cca17c31607', + aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', + wrappedTokens: { + stataOptUSDC: '0x9f281eb58fd98ad98ede0fc4c553ad4d73e7ca2c', + }, }, - }, - USDCn: { - underlyingAssetAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85', - aTokenAddress: '0x38d693ce1df5aadf7bc62595a37d667ad57922e5', - wrappedTokens: { - stataOptUSDCn: '0x4dd03dfd36548c840b563745e3fbec320f37ba7e', + USDCn: { + underlyingAssetAddress: '0x0b2c639c533813f4aa9d7837caf62653d097ff85', + aTokenAddress: '0x38d693ce1df5aadf7bc62595a37d667ad57922e5', + wrappedTokens: { + stataOptUSDCn: '0x4dd03dfd36548c840b563745e3fbec320f37ba7e', + }, }, - }, - USDT: { - underlyingAssetAddress: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', - aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', - wrappedTokens: { - stataOptUSDT: '0x035c93db04e5aaea54e6cd0261c492a3e0638b37', + USDT: { + underlyingAssetAddress: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58', + aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', + wrappedTokens: { + stataOptUSDT: '0x035c93db04e5aaea54e6cd0261c492a3e0638b37', + }, }, }, }, }, - }, - beefy: { - sourceUrl: 'https://api.beefy.finance/apy/', - tokens: { - wmooExactlySupplyUSDC: { - address: '0xe5e9168b45a90c1e5730da6184cc5901c6e4353f', - vaultId: 'exactly-supply-usdc', - }, - wmooExactlySupplyETH: { - address: '0x44b1cea4f597f493e2fd0833a9c04dfb1e479ef0', - vaultId: 'exactly-supply-eth', - }, - // To get the vaultId, get the vault address from the token contract(token.vault()), - // and search for the vault address in the link: https://api.beefy.finance/vaults - }, - }, - defillama: [ - { - defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', - tokenAddress: '0x5a7a183b6b44dc4ec2e3d2ef43f98c5152b1d76d', - }, - ], - reaper: { - subgraphSource: { - subgraphUrl: 'https://api.thegraph.com/subgraphs/name/byte-masons/multi-strategy-vaults-optimism', + beefy: { + sourceUrl: 'https://api.beefy.finance/apy/', tokens: { - rfUSDT: { - address: '0x51868bb8b71fb423b87129908fa039b880c8612d', - }, - rfWETH: { - address: '0x1bad45e92dce078cf68c2141cd34f54a02c92806', - }, - rfOP: { - address: '0xcecd29559a84e4d4f6467b36bbd4b9c3e6b89771', + wmooExactlySupplyUSDC: { + address: '0xe5e9168b45a90c1e5730da6184cc5901c6e4353f', + vaultId: 'exactly-supply-usdc', }, - rfwstETH: { - address: '0xb19f4d65882f6c103c332f0bc012354548e9ce0e', - isWstETH: true, - }, - rfWBTC: { - address: '0xf6533b6fcb3f42d2fc91da7c379858ae6ebc7448', - }, - rfDAI: { - address: '0xc0f5da4fb484ce6d8a6832819299f7cd0d15726e', - }, - rfUSDC: { - address: '0x508734b52ba7e04ba068a2d4f67720ac1f63df47', + wmooExactlySupplyETH: { + address: '0x44b1cea4f597f493e2fd0833a9c04dfb1e479ef0', + vaultId: 'exactly-supply-eth', }, + // To get the vaultId, get the vault address from the token contract(token.vault()), + // and search for the vault address in the link: https://api.beefy.finance/vaults }, }, - onchainSource: { - averageAPRAcrossLastNHarvests: 2, - tokens: { - rfsoUSDC: { - address: '0x875456b73cbc58aa1be98dfe3b0459e0c0bf7b0e', - }, - rfsoUSDT: { - address: '0x1e1bf73db9b278a95c9fe9205759956edea8b6ae', - }, - rfsoDAI: { - address: '0x19ca00d242e96a30a1cad12f08c375caa989628f', - }, - rfsoWBTC: { - address: '0x73e51b0368ef8bd0070b12dd992c54aa53bcb5f4', - }, - rfsoWSTETH: { - address: '0x3573de618ae4a740fb24215d93f4483436fbb2b6', - }, + defillama: [ + { + defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', + tokenAddress: '0x5a7a183b6b44dc4ec2e3d2ef43f98c5152b1d76d', + }, + ], + maker: { + sdai: '0x2218a117083f5b482b0bb821d27056ba9c04b1d3', + }, + etherfi: '0x5a7facb970d094b6c7ff1df0ea68d99e6e73cbff', + defaultHandlers: { + yUSD: { + tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', + sourceUrl: 'https://ctrl.yield.fi/t/apy', + path: 'apy', + isIbYield: true, + }, + wstEth: { + tokenAddress: '0x1f32b1c2345538c0c6f582fcb022739c4a194ebb', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0x9bcef72be871e61ed4fbbc7630889bee758eb81d', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, + sfrxETH: { + tokenAddress: '0x484c2d6e3cdd945a8b2df735e079178c1036578c', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + sfrxETHOft: { + tokenAddress: '0x3ec3849c33291a9ef4c5db86de593eb4a37fde45', + sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', + path: 'sfrxethApr', + isIbYield: true, + }, + sFRAX: { + tokenAddress: '0x5bff88ca1442c2496f7e475e9e7786383bc070c0', + sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', + path: 'items.0.sfraxApr', + isIbYield: true, + }, + sfrxUSD: { + tokenAddress: '0x2dd1b4d4548accea497050619965f91f78b3b532', + sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', + path: 'items.0.sfraxApr', + isIbYield: true, + }, + ankrETH: { + tokenAddress: '0xe05a08226c49b636acf99c40da8dc6af83ce5bb3', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "eth"}.apy', + isIbYield: true, + }, + wrsETH: { + tokenAddress: '0x87eee96d50fb761ad85b1c982d28a042169d61b1', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + isIbYield: true, + }, + wusdm: { + tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', + sourceUrl: 'https://apy.prod.mountainprotocol.com', + path: 'value', + isIbYield: true, + scale: 1, }, - }, - }, - maker: { - sdai: '0x2218a117083f5b482b0bb821d27056ba9c04b1d3', - }, - etherfi: '0x5a7facb970d094b6c7ff1df0ea68d99e6e73cbff', - defaultHandlers: { - yUSD: { - tokenAddress: '0x895e15020c3f52ddd4d8e9514eb83c39f53b1579', - sourceUrl: 'https://ctrl.yield.fi/t/apy', - path: 'apy', - isIbYield: true, - }, - wstEth: { - tokenAddress: '0x1f32b1c2345538c0c6f582fcb022739c4a194ebb', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0x9bcef72be871e61ed4fbbc7630889bee758eb81d', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, - }, - sfrxETH: { - tokenAddress: '0x484c2d6e3cdd945a8b2df735e079178c1036578c', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - sfrxETHOft: { - tokenAddress: '0x3ec3849c33291a9ef4c5db86de593eb4a37fde45', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - isIbYield: true, - }, - sFRAX: { - tokenAddress: '0x5bff88ca1442c2496f7e475e9e7786383bc070c0', - sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', - path: 'items.0.sfraxApr', - isIbYield: true, - }, - sfrxUSD: { - tokenAddress: '0x2dd1b4d4548accea497050619965f91f78b3b532', - sourceUrl: 'https://api.frax.finance/v2/frax/sfrax/summary/history?range=1d', - path: 'items.0.sfraxApr', - isIbYield: true, - }, - stERN: { - tokenAddress: '0x3ee6107d9c93955acbb3f39871d32b02f82b78ab', - sourceUrl: - 'https://2ch9hbg8hh.execute-api.us-east-1.amazonaws.com/dev/api/vault/0x3eE6107d9C93955acBb3f39871D32B02F82B78AB:0xa', - path: 'data.yields.apy', - scale: 1, - isIbYield: true, - }, - ankrETH: { - tokenAddress: '0xe05a08226c49b636acf99c40da8dc6af83ce5bb3', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "eth"}.apy', - isIbYield: true, - }, - wrsETH: { - tokenAddress: '0x87eee96d50fb761ad85b1c982d28a042169d61b1', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - isIbYield: true, - }, - wusdm: { - tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', - sourceUrl: 'https://apy.prod.mountainprotocol.com', - path: 'value', - isIbYield: true, - scale: 1, }, }, }, diff --git a/config/polygon.ts b/config/polygon.ts index ab5e1a638..de9f1ee7d 100644 --- a/config/polygon.ts +++ b/config/polygon.ts @@ -65,128 +65,130 @@ export default { multicall: '0x275617327c958bd06b5d6b871e7f491d76113dd8', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, - ybAprConfig: { - aave: { - v2: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/H1Et77RZh3XEf27vkAmJyzgCME2RSFLtDS2f4PPW6CGp`, - tokens: { - USDC: { - underlyingAssetAddress: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - aTokenAddress: '0x1a13f4ca1d028320a707d99520abfefca3998b7f', - wrappedTokens: { - waUSDC: '0x221836a597948dce8f3568e044ff123108acc42a', + aprHandlers: { + ybAprHandler: { + aave: { + v2: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/H1Et77RZh3XEf27vkAmJyzgCME2RSFLtDS2f4PPW6CGp`, + tokens: { + USDC: { + underlyingAssetAddress: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', + aTokenAddress: '0x1a13f4ca1d028320a707d99520abfefca3998b7f', + wrappedTokens: { + waUSDC: '0x221836a597948dce8f3568e044ff123108acc42a', + }, }, - }, - USDT: { - underlyingAssetAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - aTokenAddress: '0x60d55f02a771d515e077c9c2403a1ef324885cec', - wrappedTokens: { - waUSDT: '0x19c60a251e525fa88cd6f3768416a8024e98fc19', + USDT: { + underlyingAssetAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + aTokenAddress: '0x60d55f02a771d515e077c9c2403a1ef324885cec', + wrappedTokens: { + waUSDT: '0x19c60a251e525fa88cd6f3768416a8024e98fc19', + }, }, - }, - DAI: { - underlyingAssetAddress: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - aTokenAddress: '0x27f8d03b3a2196956ed754badc28d73be8830a6e', - wrappedTokens: { - waDAI: '0xee029120c72b0607344f35b17cdd90025e647b00', + DAI: { + underlyingAssetAddress: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', + aTokenAddress: '0x27f8d03b3a2196956ed754badc28d73be8830a6e', + wrappedTokens: { + waDAI: '0xee029120c72b0607344f35b17cdd90025e647b00', + }, }, }, }, - }, - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Co2URyXjnxaw8WqxKyVHdirq9Ahhm5vcTs4dMedAq211`, - tokens: { - USDCn: { - underlyingAssetAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', - aTokenAddress: '0xa4d94019934d8333ef880abffbf2fdd611c762bd', - wrappedTokens: { - stataPolUSDCn: '0x2dca80061632f3f87c9ca28364d1d0c30cd79a19', + v3: { + subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Co2URyXjnxaw8WqxKyVHdirq9Ahhm5vcTs4dMedAq211`, + tokens: { + USDCn: { + underlyingAssetAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359', + aTokenAddress: '0xa4d94019934d8333ef880abffbf2fdd611c762bd', + wrappedTokens: { + stataPolUSDCn: '0x2dca80061632f3f87c9ca28364d1d0c30cd79a19', + }, }, - }, - USDC: { - underlyingAssetAddress: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', - wrappedTokens: { - waUSDC: '0xac69e38ed4298490906a3f8d84aefe883f3e86b5', - stataPolUSDC: '0xc04296aa4534f5a3bab2d948705bc89317b2f1ed', + USDC: { + underlyingAssetAddress: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', + aTokenAddress: '0x625e7708f30ca75bfd92586e17077590c60eb4cd', + wrappedTokens: { + waUSDC: '0xac69e38ed4298490906a3f8d84aefe883f3e86b5', + stataPolUSDC: '0xc04296aa4534f5a3bab2d948705bc89317b2f1ed', + }, }, - }, - USDT: { - underlyingAssetAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', - wrappedTokens: { - stataPolUSDT: '0x31f5ac91804a4c0b54c0243789df5208993235a1', - stataPolUSDT2: '0x87a1fdc4c726c459f597282be639a045062c0e46', + USDT: { + underlyingAssetAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + aTokenAddress: '0x6ab707aca953edaefbc4fd23ba73294241490620', + wrappedTokens: { + stataPolUSDT: '0x31f5ac91804a4c0b54c0243789df5208993235a1', + stataPolUSDT2: '0x87a1fdc4c726c459f597282be639a045062c0e46', + }, }, - }, - DAI: { - underlyingAssetAddress: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', - wrappedTokens: { - waDAI: '0xdb6df721a6e7fdb97363079b01f107860ac156f9', - stataPolDAI: '0xfcf5d4b313e06bb3628eb4fe73320e94039dc4b7', + DAI: { + underlyingAssetAddress: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', + aTokenAddress: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee', + wrappedTokens: { + waDAI: '0xdb6df721a6e7fdb97363079b01f107860ac156f9', + stataPolDAI: '0xfcf5d4b313e06bb3628eb4fe73320e94039dc4b7', + }, }, - }, - wETH: { - underlyingAssetAddress: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', - aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', - wrappedTokens: { - waWETH: '0xa5bbf0f46b9dc8a43147862ba35c8134eb45f1f5', - stataPolWETH: '0xd08b78b11df105d2861568959fca28e30c91cf68', + wETH: { + underlyingAssetAddress: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619', + aTokenAddress: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8', + wrappedTokens: { + waWETH: '0xa5bbf0f46b9dc8a43147862ba35c8134eb45f1f5', + stataPolWETH: '0xd08b78b11df105d2861568959fca28e30c91cf68', + }, }, - }, - wMATIC: { - underlyingAssetAddress: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', - aTokenAddress: '0x6d80113e533a2c0fe82eabd35f1875dcea89ea97', - wrappedTokens: { - waWMATIC: '0x0d6135b2cfbae3b1c58368a93b855fa54fa5aae1', - stataPolWMATIC: '0x6f3913333f2d4b7b01d17bedbce1e4c758b94465', + wMATIC: { + underlyingAssetAddress: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', + aTokenAddress: '0x6d80113e533a2c0fe82eabd35f1875dcea89ea97', + wrappedTokens: { + waWMATIC: '0x0d6135b2cfbae3b1c58368a93b855fa54fa5aae1', + stataPolWMATIC: '0x6f3913333f2d4b7b01d17bedbce1e4c758b94465', + }, }, }, }, }, - }, - tetu: { - sourceUrl: 'https://api.tetu.io/api/v1/reader/compoundAPRs?network=MATIC', - tokens: { - tUSDC: { address: '0x113f3d54c31ebc71510fd664c8303b34fbc2b355' }, - tUSDT: { address: '0x236975da9f0761e9cf3c2b0f705d705e22829886' }, - tDAI: { address: '0xace2ac58e1e5a7bfe274916c4d82914d490ed4a5' }, - tetuStQI: { address: '0x4cd44ced63d9a6fef595f6ad3f7ced13fceac768' }, - }, - }, - defaultHandlers: { - wstETH: { - tokenAddress: '0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - stMATIC: { - tokenAddress: '0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4', - sourceUrl: 'https://polygon.lido.fi/api/stats', - path: 'apr', - isIbYield: true, - }, - MATICX: { - tokenAddress: '0xfa68fb4628dff1028cfec22b4162fccd0d45efb6', - sourceUrl: 'https://universe.staderlabs.com/polygon/apy', - path: 'value', - isIbYield: true, - }, - wbETH: { - tokenAddress: '0xa2e3356610840701bdf5611a53974510ae27e2e1', - sourceUrl: - 'https://www.binance.com/bapi/earn/v1/public/pos/cftoken/project/rewardRateList?projectId=BETH', - path: 'data.0.rewardRate', - isIbYield: true, + tetu: { + sourceUrl: 'https://api.tetu.io/api/v1/reader/compoundAPRs?network=MATIC', + tokens: { + tUSDC: { address: '0x113f3d54c31ebc71510fd664c8303b34fbc2b355' }, + tUSDT: { address: '0x236975da9f0761e9cf3c2b0f705d705e22829886' }, + tDAI: { address: '0xace2ac58e1e5a7bfe274916c4d82914d490ed4a5' }, + tetuStQI: { address: '0x4cd44ced63d9a6fef595f6ad3f7ced13fceac768' }, + }, }, - truMATIC: { - tokenAddress: '0xf33687811f3ad0cd6b48dd4b39f9f977bd7165a2', - sourceUrl: 'https://api.trufin.io/staker/apy?staker=MATIC', - path: 'apy', - scale: 100, - isIbYield: true, + defaultHandlers: { + wstETH: { + tokenAddress: '0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + stMATIC: { + tokenAddress: '0x3a58a54c066fdc0f2d55fc9c89f0415c92ebf3c4', + sourceUrl: 'https://polygon.lido.fi/api/stats', + path: 'apr', + isIbYield: true, + }, + MATICX: { + tokenAddress: '0xfa68fb4628dff1028cfec22b4162fccd0d45efb6', + sourceUrl: 'https://universe.staderlabs.com/polygon/apy', + path: 'value', + isIbYield: true, + }, + wbETH: { + tokenAddress: '0xa2e3356610840701bdf5611a53974510ae27e2e1', + sourceUrl: + 'https://www.binance.com/bapi/earn/v1/public/pos/cftoken/project/rewardRateList?projectId=BETH', + path: 'data.0.rewardRate', + isIbYield: true, + }, + truMATIC: { + tokenAddress: '0xf33687811f3ad0cd6b48dd4b39f9f977bd7165a2', + sourceUrl: 'https://api.trufin.io/staker/apy?staker=MATIC', + path: 'apy', + scale: 100, + isIbYield: true, + }, }, }, }, diff --git a/config/sepolia.ts b/config/sepolia.ts index 3f50adaaa..c22df0a32 100644 --- a/config/sepolia.ts +++ b/config/sepolia.ts @@ -79,7 +79,7 @@ export default { multicall: '0x25eef291876194aefad0d60dff89e268b90754bb', multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, - ybAprConfig: {}, + aprHandlers: {}, datastudio: { main: { user: 'datafeed-service@datastudio-366113.iam.gserviceaccount.com', diff --git a/config/sonic.ts b/config/sonic.ts index 445f77768..b51d6b26c 100644 --- a/config/sonic.ts +++ b/config/sonic.ts @@ -75,124 +75,127 @@ export default { excludedFarmIds: [], }, avgBlockSpeed: 1, - ybAprConfig: { - sts: { - token: '0xe5da20f15420ad15de0fa650600afc998bbe3955', - }, - euler: { - vaultsJsonUrl: 'https://raw.githubusercontent.com/euler-xyz/euler-labels/refs/heads/master/146/vaults.json', - lensContractAddress: '0xc3a705ea6e339a53a7d301d3c5d7e6f499a9366a', - }, - beefy: { - sourceUrl: 'https://api.beefy.finance/apy/', - tokens: { - 'silov2-usdc': { - address: '0x7870ddfd5aca4e977b2287e9a212bcbe8fc4135a', - vaultId: 'silov2-sonic-usdce-ws', - isIbYield: true, - }, - 'beefy-besonic': { - address: '0x871a101dcf22fe4fe37be7b654098c801cba1c88', - vaultId: 'beefy-besonic', - isIbYield: true, - }, + aprHandlers: { + ybAprHandler: { + sts: { + token: '0xe5da20f15420ad15de0fa650600afc998bbe3955', }, - }, - silo: { - markets: [ - '0x87178fe8698c7eda8aa207083c3d66aea569ab98', //solvbtc market 13 - '0x52fc9e0a68b6a4c9b57b9d1d99fb71449a99dcd8', // solvbtc.bbn market 13 - '0x016c306e103fbf48ec24810d078c65ad13c5f11b', // wS market 25 - '0x219656f33c58488d09d518badf50aa8cdcaca2aa', // wETH market 26 - '0x5954ce6671d97d24b782920ddcdbb4b1e63ab2de', // usdc market 23 - '0x6c49b18333a1135e9a376560c07e6d1fd0350eaf', // Ws market 28 - '0xda14a41dbda731f03a94cb722191639dd22b35b2', // frxUSD market 37 - '0x0a94e18bdbccd048198806d7ff28a1b1d2590724', // scbtc market 32 - '0x42ce2234fd5a26bf161477a996961c4d01f466a3', // usdc 33 - '0xe6605932e4a686534d19005bb9db0fba1f101272', // scusdc 46 - '0x08c320a84a59c6f533e0dca655cf497594bca1f9', // weth 35 - '0x24c74b30d1a4261608e84bf5a618693032681dac', // sceth 47 - '0x11ba70c0ebab7946ac84f0e6d79162b0cbb2693f', // usdc 36 - ], - }, - avalon: { - solv: { - subgraphUrl: `https://api.studio.thegraph.com/query/102993/avalon-defi-lending-v3/version/latest`, + euler: { + vaultsJsonUrl: + 'https://raw.githubusercontent.com/euler-xyz/euler-labels/refs/heads/master/146/vaults.json', + lensContractAddress: '0xc3a705ea6e339a53a7d301d3c5d7e6f499a9366a', + }, + beefy: { + sourceUrl: 'https://api.beefy.finance/apy/', tokens: { - SOLVBTC: { - underlyingAssetAddress: '0x541fd749419ca806a8bc7da8ac23d346f2df8b77', - aTokenAddress: '0x6c56ddccb3726faa089a5e9e29b712525cf916d7', - wrappedTokens: { - waSOLVBTC: '0xd31e89ffb929b38ba60d1c7dbeb68c7712eaab0a', - }, + 'silov2-usdc': { + address: '0x7870ddfd5aca4e977b2287e9a212bcbe8fc4135a', + vaultId: 'silov2-sonic-usdce-ws', + isIbYield: true, }, - SOLVBTCBBN: { - underlyingAssetAddress: '0xcc0966d8418d412c599a6421b760a847eb169a8c', - aTokenAddress: '0xe3a97c4cc6725b96fb133c636d2e88cc3d6cfdbe', - wrappedTokens: { - waSOLVBTCBBN: '0xa28d4dbcc90c849e3249d642f356d85296a12954', - }, + 'beefy-besonic': { + address: '0x871a101dcf22fe4fe37be7b654098c801cba1c88', + vaultId: 'beefy-besonic', + isIbYield: true, }, }, }, - }, - defaultHandlers: { - wOS: { - tokenAddress: '0x9f0df7799f6fdad409300080cff680f5a23df4b1', - sourceUrl: 'https://api.originprotocol.com/api/v2/os/apr/trailing/7?146', - path: 'apr', - isIbYield: true, - }, - wanS: { - tokenAddress: '0xfa85fe5a8f5560e9039c04f2b0a90de1415abd70', - sourceUrl: 'https://be.angles.fi/api/v2/angles/apr/trailing/7', - path: 'apy', - isIbYield: true, - }, - wstkscUSD: { - tokenAddress: '0x9fb76f7ce5fceaa2c42887ff441d46095e494206', - sourceUrl: 'https://usd-locks-api.rings.money/wrapper/apy', - path: 'apy', - scale: 1, - isIbYield: true, - }, - wstkscETH: { - tokenAddress: '0xe8a41c62bb4d5863c6eadc96792cfe90a1f37c47', - sourceUrl: 'https://eth-locks-api.rings.money/wrapper/apy', - path: 'apy', - scale: 1, - isIbYield: true, + silo: { + markets: [ + '0x87178fe8698c7eda8aa207083c3d66aea569ab98', //solvbtc market 13 + '0x52fc9e0a68b6a4c9b57b9d1d99fb71449a99dcd8', // solvbtc.bbn market 13 + '0x016c306e103fbf48ec24810d078c65ad13c5f11b', // wS market 25 + '0x219656f33c58488d09d518badf50aa8cdcaca2aa', // wETH market 26 + '0x5954ce6671d97d24b782920ddcdbb4b1e63ab2de', // usdc market 23 + '0x6c49b18333a1135e9a376560c07e6d1fd0350eaf', // Ws market 28 + '0xda14a41dbda731f03a94cb722191639dd22b35b2', // frxUSD market 37 + '0x0a94e18bdbccd048198806d7ff28a1b1d2590724', // scbtc market 32 + '0x42ce2234fd5a26bf161477a996961c4d01f466a3', // usdc 33 + '0xe6605932e4a686534d19005bb9db0fba1f101272', // scusdc 46 + '0x08c320a84a59c6f533e0dca655cf497594bca1f9', // weth 35 + '0x24c74b30d1a4261608e84bf5a618693032681dac', // sceth 47 + '0x11ba70c0ebab7946ac84f0e6d79162b0cbb2693f', // usdc 36 + ], }, - varlamoreUSDC: { - tokenAddress: '0xf6f87073cf8929c206a77b0694619dc776f89885', - sourceUrl: - 'https://v2.silo.finance/api/detailed-vault/sonic-0xf6f87073cf8929c206a77b0694619dc776f89885', - path: 'supplyApr', - scale: 1000000000000000000, - isIbYield: true, - }, - varlamorewS: { - tokenAddress: '0xded4ac8645619334186f28b8798e07ca354cfa0e', - sourceUrl: - 'https://v2.silo.finance/api/detailed-vault/sonic-0xded4ac8645619334186f28b8798e07ca354cfa0e', - path: 'supplyApr', - scale: 1000000000000000000, - isIbYield: true, - }, - varlamorescUSD: { - tokenAddress: '0xb6a23cb29e512df41876b28d7a848bd831f9c5ba', - sourceUrl: - 'https://v2.silo.finance/api/detailed-vault/sonic-0xb6a23cb29e512df41876b28d7a848bd831f9c5ba', - path: 'supplyApr', - scale: 1000000000000000000, - isIbYield: true, + avalon: { + solv: { + subgraphUrl: `https://api.studio.thegraph.com/query/102993/avalon-defi-lending-v3/version/latest`, + tokens: { + SOLVBTC: { + underlyingAssetAddress: '0x541fd749419ca806a8bc7da8ac23d346f2df8b77', + aTokenAddress: '0x6c56ddccb3726faa089a5e9e29b712525cf916d7', + wrappedTokens: { + waSOLVBTC: '0xd31e89ffb929b38ba60d1c7dbeb68c7712eaab0a', + }, + }, + SOLVBTCBBN: { + underlyingAssetAddress: '0xcc0966d8418d412c599a6421b760a847eb169a8c', + aTokenAddress: '0xe3a97c4cc6725b96fb133c636d2e88cc3d6cfdbe', + wrappedTokens: { + waSOLVBTCBBN: '0xa28d4dbcc90c849e3249d642f356d85296a12954', + }, + }, + }, + }, }, - xUSD: { - tokenAddress: '0x6202b9f02e30e5e1c62cc01e4305450e5d83b926', - sourceUrl: 'https://api-v2.streamprotocol.money/vaults/xUSD/apy', - path: 'apy', - scale: 100, - isIbYield: true, + defaultHandlers: { + wOS: { + tokenAddress: '0x9f0df7799f6fdad409300080cff680f5a23df4b1', + sourceUrl: 'https://api.originprotocol.com/api/v2/os/apr/trailing/7?146', + path: 'apr', + isIbYield: true, + }, + wanS: { + tokenAddress: '0xfa85fe5a8f5560e9039c04f2b0a90de1415abd70', + sourceUrl: 'https://be.angles.fi/api/v2/angles/apr/trailing/7', + path: 'apy', + isIbYield: true, + }, + wstkscUSD: { + tokenAddress: '0x9fb76f7ce5fceaa2c42887ff441d46095e494206', + sourceUrl: 'https://usd-locks-api.rings.money/wrapper/apy', + path: 'apy', + scale: 1, + isIbYield: true, + }, + wstkscETH: { + tokenAddress: '0xe8a41c62bb4d5863c6eadc96792cfe90a1f37c47', + sourceUrl: 'https://eth-locks-api.rings.money/wrapper/apy', + path: 'apy', + scale: 1, + isIbYield: true, + }, + varlamoreUSDC: { + tokenAddress: '0xf6f87073cf8929c206a77b0694619dc776f89885', + sourceUrl: + 'https://v2.silo.finance/api/detailed-vault/sonic-0xf6f87073cf8929c206a77b0694619dc776f89885', + path: 'supplyApr', + scale: 1000000000000000000, + isIbYield: true, + }, + varlamorewS: { + tokenAddress: '0xded4ac8645619334186f28b8798e07ca354cfa0e', + sourceUrl: + 'https://v2.silo.finance/api/detailed-vault/sonic-0xded4ac8645619334186f28b8798e07ca354cfa0e', + path: 'supplyApr', + scale: 1000000000000000000, + isIbYield: true, + }, + varlamorescUSD: { + tokenAddress: '0xb6a23cb29e512df41876b28d7a848bd831f9c5ba', + sourceUrl: + 'https://v2.silo.finance/api/detailed-vault/sonic-0xb6a23cb29e512df41876b28d7a848bd831f9c5ba', + path: 'supplyApr', + scale: 1000000000000000000, + isIbYield: true, + }, + xUSD: { + tokenAddress: '0x6202b9f02e30e5e1c62cc01e4305450e5d83b926', + sourceUrl: 'https://api-v2.streamprotocol.money/vaults/xUSD/apy', + path: 'apy', + scale: 100, + isIbYield: true, + }, }, }, }, diff --git a/config/zkevm.ts b/config/zkevm.ts index 26ad059fd..db2c438e8 100644 --- a/config/zkevm.ts +++ b/config/zkevm.ts @@ -66,43 +66,45 @@ export default { excludedFarmIds: [], }, avgBlockSpeed: 1, - ybAprConfig: { - ovix: { - tokens: { - USDT: { - yieldAddress: '0xad41c77d99e282267c1492cdefe528d7d5044253', - wrappedAddress: '0x550d3bb1f77f97e4debb45d4f817d7b9f9a1affb', + aprHandlers: { + ybAprHandler: { + ovix: { + tokens: { + USDT: { + yieldAddress: '0xad41c77d99e282267c1492cdefe528d7d5044253', + wrappedAddress: '0x550d3bb1f77f97e4debb45d4f817d7b9f9a1affb', + }, + USDC: { + yieldAddress: '0x68d9baa40394da2e2c1ca05d30bf33f52823ee7b', + wrappedAddress: '0x3a6789fc7c05a83cfdff5d2f9428ad9868b4ff85', + }, }, - USDC: { - yieldAddress: '0x68d9baa40394da2e2c1ca05d30bf33f52823ee7b', - wrappedAddress: '0x3a6789fc7c05a83cfdff5d2f9428ad9868b4ff85', - }, - }, - }, - defaultHandlers: { - wstETH: { - tokenAddress: '0x5d8cff95d7a57c0bf50b30b43c7cc0d52825d4a9', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - isIbYield: true, - }, - rETH: { - tokenAddress: '0xb23c20efce6e24acca0cef9b7b7aa196b84ec942', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - isIbYield: true, - }, - ankrETH: { - tokenAddress: '0x12d8ce035c5de3ce39b1fdd4c1d5a745eaba3b8c', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "eth"}.apy', - isIbYield: true, }, - rsETH: { - tokenAddress: '0x8c7d118b5c47a5bcbd47cc51789558b98dad17c5', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - isIbYield: true, + defaultHandlers: { + wstETH: { + tokenAddress: '0x5d8cff95d7a57c0bf50b30b43c7cc0d52825d4a9', + sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', + path: 'data.smaApr', + isIbYield: true, + }, + rETH: { + tokenAddress: '0xb23c20efce6e24acca0cef9b7b7aa196b84ec942', + sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', + path: 'yearlyAPR', + isIbYield: true, + }, + ankrETH: { + tokenAddress: '0x12d8ce035c5de3ce39b1fdd4c1d5a745eaba3b8c', + sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', + path: 'services.{serviceName == "eth"}.apy', + isIbYield: true, + }, + rsETH: { + tokenAddress: '0x8c7d118b5c47a5bcbd47cc51789558b98dad17c5', + sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', + path: 'value', + isIbYield: true, + }, }, }, }, diff --git a/modules/aprs/apr-manager.ts b/modules/aprs/apr-manager.ts index ca94dbe9f..dda89f15e 100644 --- a/modules/aprs/apr-manager.ts +++ b/modules/aprs/apr-manager.ts @@ -4,10 +4,7 @@ import { AprRepository } from './apr-repository'; import _ from 'lodash'; export class AprManager { - constructor( - private readonly aprRepository: AprRepository, - private readonly aprHandlers: AprHandler[], - ) {} + constructor(private readonly aprRepository: AprRepository, private readonly aprHandlers: AprHandler[]) {} /** * Calculate APRs without writing to the database @@ -48,7 +45,7 @@ export class AprManager { */ async updateAprs(chain: Chain, poolIds?: string[]): Promise { const aprItems = await this.calculateAprs(chain, poolIds); - const changedPoolIds = await this.aprRepository.savePoolAprItems(aprItems); + const changedPoolIds = await this.aprRepository.savePoolAprItems(chain, aprItems); if (changedPoolIds.length > 0) { await this.aprRepository.updatePoolTotalApr(chain, changedPoolIds); diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index aacf8a982..50ed872ac 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -36,30 +36,32 @@ export class AprRepository { * Only updates when the APR value has changed * @returns changed poolIDs */ - async savePoolAprItems(aprItems: Omit[]): Promise { + async savePoolAprItems( + chain: Chain, + aprItems: Omit[], + ): Promise { if (aprItems.length === 0) return []; - // Get unique chains and pool IDs from the items - const chains = [...new Set(aprItems.map((item) => item.chain))]; + // Get unique pool IDs from the items const poolIds = [...new Set(aprItems.map((item) => item.poolId))]; // Fetch existing APR items const existingItems = await prisma.prismaPoolAprItem.findMany({ where: { - chain: { in: chains }, + chain: chain, poolId: { in: poolIds }, id: { in: aprItems.map((item) => item.id) }, }, }); // Create a lookup map for quick access - const existingItemsMap = new Map(existingItems.map((item) => [`${item.id}-${item.chain}`, item])); + const existingItemsMap = new Map(existingItems.map((item) => [item.id, item])); // Only create operations for items that don't exist or have changed const changedPoolIds = new Set(); const operations = aprItems .filter((item) => { - const existingItem = existingItemsMap.get(`${item.id}-${item.chain}`); + const existingItem = existingItemsMap.get(item.id); const changed = !existingItem || existingItem.apr !== item.apr; if (changed) { changedPoolIds.add(item.poolId); diff --git a/modules/aprs/config/arbitrum.ts b/modules/aprs/config/arbitrum.ts deleted file mode 100644 index a4a7b527f..000000000 --- a/modules/aprs/config/arbitrum.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - aaveApiConfig: { - chainId: 42161 - } -}; \ No newline at end of file diff --git a/modules/aprs/config/avalanche.ts b/modules/aprs/config/avalanche.ts deleted file mode 100644 index 75cd17360..000000000 --- a/modules/aprs/config/avalanche.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - aaveApiConfig: { - chainId: 43114 - } -}; \ No newline at end of file diff --git a/modules/aprs/config/base.ts b/modules/aprs/config/base.ts deleted file mode 100644 index c382fea06..000000000 --- a/modules/aprs/config/base.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - aaveApiConfig: { - chainId: 8453 - } -}; \ No newline at end of file diff --git a/modules/aprs/config/index.ts b/modules/aprs/config/index.ts deleted file mode 100644 index f88c808da..000000000 --- a/modules/aprs/config/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Chain } from '@prisma/client'; -import mainnet from './mainnet'; -import arbitrum from './arbitrum'; -import avalanche from './avalanche'; -import base from './base'; -import optimism from './optimism'; -import polygon from './polygon'; -import { AaveApiConfig, YbAprConfig } from '../handlers/types'; - -export default >{ - [Chain.MAINNET]: mainnet, - [Chain.ARBITRUM]: arbitrum, - [Chain.AVALANCHE]: avalanche, - [Chain.BASE]: base, - [Chain.FANTOM]: {}, - [Chain.FRAXTAL]: {}, - [Chain.GNOSIS]: {}, - [Chain.MODE]: {}, - [Chain.OPTIMISM]: optimism, - [Chain.POLYGON]: polygon, - [Chain.SEPOLIA]: {}, - [Chain.SONIC]: {}, - [Chain.ZKEVM]: {}, -}; diff --git a/modules/aprs/config/mainnet.ts b/modules/aprs/config/mainnet.ts deleted file mode 100644 index dfd78cdfc..000000000 --- a/modules/aprs/config/mainnet.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { env } from '../../../apps/env'; - -const underlyingTokens = { - USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7', - DAI: '0x6b175474e89094c44da98b954eedeac495271d0f', - wETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - crvUSD: '0xf939e0a03fb07f59a73314e73794be0e57ac1b4e', - LUSD: '0x5f98805a4e8be255a32880fdec7f6728c6568ba0', - USDe: '0x4c9edd5852cd905f086c759e8383e09bff1e68b3', -}; - -export default { - aaveApiConfig: { - chainId: 1, - }, - ybAprConfig: { - usdl: true, - morpho: true, - aave: { - v2: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/8wR23o1zkS4gpLqLNU4kG3JHYVucqGyopL5utGxP2q1N`, - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0xbcca60bb61934080951369a648fb03df4f96263c', - wrappedTokens: { - waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811', - wrappedTokens: { - waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x028171bca77440897b824ca71d1c56cac55b68a3', - wrappedTokens: { - waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6', - }, - }, - }, - }, - v3: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/Cd2gEDVeqnjBn1hSeqFMitw8Q1iiyV9FYUZkLNRcL87g`, - tokens: { - USDC: { - underlyingAssetAddress: underlyingTokens.USDC, - aTokenAddress: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', - wrappedTokens: { - waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c', - stataEthUSDC: '0x02c2d189b45ce213a40097b62d311cf0dd16ec92', - stataV2USDC: '0xd4fa2d31b7968e448877f69a96de69f5de8cd23e', - }, - }, - USDT: { - underlyingAssetAddress: underlyingTokens.USDT, - aTokenAddress: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a', - wrappedTokens: { - waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455', - stataEthUSDT: '0x65799b9fd4206cdaa4a1db79254fcbc2fd2ffee6', - stataEthUSDT2: '0x862c57d48becb45583aeba3f489696d22466ca1b', - stataV2USDT: '0x7bc3485026ac48b6cf9baf0a377477fff5703af8', - }, - }, - DAI: { - underlyingAssetAddress: underlyingTokens.DAI, - aTokenAddress: '0x018008bfb33d285247a21d44e50697654f754e63', - wrappedTokens: { - waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b', - stataEthDAI: '0xeb708639e8e518b86a916db3685f90216b1c1c67', - }, - }, - wETH: { - underlyingAssetAddress: underlyingTokens.wETH, - aTokenAddress: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8', - wrappedTokens: { - waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6', - stataEthWETH: '0x03928473f25bb2da6bc880b07ecbadc636822264', - stataV2WETH: '0x0bfc9d54fc184518a81162f8fb99c2eaca081202', - }, - }, - crvUSD: { - underlyingAssetAddress: underlyingTokens.crvUSD, - aTokenAddress: '0xb82fa9f31612989525992fcfbb09ab22eff5c85a', - wrappedTokens: { - stataEthcrvUSD: '0x848107491e029afde0ac543779c7790382f15929', - }, - }, - LUSD: { - underlyingAssetAddress: underlyingTokens.LUSD, - aTokenAddress: '0x3fe6a295459fae07df8a0cecc36f37160fe86aa9', - wrappedTokens: { - stataEthLUSD: '0xdbf5e36569798d1e39ee9d7b1c61a7409a74f23a', - }, - }, - USDe: { - underlyingAssetAddress: underlyingTokens.USDe, - aTokenAddress: '0x4f5923fc5fd4a93352581b38b7cd26943012decf', - wrappedTokens: { - stataEthUSDe: '0x5f9d59db355b4a60501544637b00e94082ca575b', - }, - }, - pyUSD: { - underlyingAssetAddress: '0x6c3ea9036406852006290770bedfcaba0e23a0e8', - aTokenAddress: '0x0c0d01abf3e6adfca0989ebba9d6e85dd58eab1e', - wrappedTokens: { - waEthPYUSD: '0xb51edddd8c47856d81c8681ea71404cec93e92c6', - }, - }, - }, - }, - lido: { - subgraphUrl: `https://gateway-arbitrum.network.thegraph.com/api/${env.THEGRAPH_API_KEY_BALANCER}/subgraphs/id/5vxMbXRhG1oQr55MWC5j6qg78waWujx1wjeuEWDA6j3`, - tokens: { - LidoWETH: { - underlyingAssetAddress: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2', - aTokenAddress: '0xfa1fdbbd71b0aa16162d76914d69cd8cb3ef92da', - wrappedTokens: { - waEthLido: '0x0fe906e030a44ef24ca8c7dc7b7c53a6c4f00ce9', - }, - }, - LidoWSTETH: { - underlyingAssetAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - aTokenAddress: '0xc035a7cf15375ce2706766804551791ad035e0c2', - wrappedTokens: { - waEthLidowstETH: '0x775f661b0bd1739349b9a2a3ef60be277c5d2d29', - }, - }, - LidoGHO: { - underlyingAssetAddress: '0x40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f', - aTokenAddress: '0x18efe565a5373f430e2f809b97de30335b3ad96a', - wrappedTokens: { - waEthLidoGHO: '0xc71ea051a5f82c67adcf634c36ffe6334793d24c', - }, - }, - }, - }, - }, - bloom: { - tokens: { - tbyFeb1924: { - address: '0xc4cafefbc3dfea629c589728d648cb6111db3136', - feedAddress: '0xde1f5f2d69339171d679fb84e4562febb71f36e6', - }, - }, - }, - defillama: [ - { - defillamaPoolId: '5a9c2073-2190-4002-9654-8c245d1e8534', - tokenAddress: '0x6dc3ce9c57b20131347fdc9089d740daf6eb34c5', - }, - { - defillamaPoolId: '46f3828a-cbf6-419e-8399-a83b905bf556', - tokenAddress: '0xf073bac22dab7faf4a3dd6c6189a70d54110525c', - }, - ], - gearbox: { - sourceUrl: 'https://charts-server.fly.dev/api/pools', - tokens: { - dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, - dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, - }, - }, - idle: { - sourceUrl: 'https://api.idle.finance/junior-rates/', - authorizationHeader: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', - tokens: { - idleDAI: { - address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', - wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', - }, - idleUSDC: { - address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', - wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', - }, - idleUSDT: { - address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', - wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', - }, - }, - }, - maker: { - sdai: '0x83f20f44975d03b1b09e64809b757c47f942beea', - }, - tranchess: { - sourceUrl: 'https://tranchess.com/eth/api/v3/funds', - tokens: { - qETH: { - address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', - underlyingAssetName: 'WETH', - }, - }, - }, - stakewise: { - url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', - token: '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38', - }, - etherfi: '0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee', - maple: { - url: 'https://api.maple.finance/v2/graphql', - token: '0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b', - }, - yieldnest: { - url: 'https://gateway.yieldnest.finance/api/v1/graphql', - token: '0x09db87a538bd693e9d08544577d5ccfaa6373a48', - }, - teth: { - address: '0xd11c452fc99cf405034ee446803b6f6c1f6d5ed8', - }, - fluid: { - url: 'https://api.fluid.instad.app/v2/lending/1/tokens', - }, - defaultHandlers: { - cUSDO: { - tokenAddress: '0xad55aebc9b8c03fc43cd9f62260391c13c23e7c0', - sourceUrl: 'https://prod-gw.openeden.com/sys/apy', - path: 'apy', - scale: 100, - }, - slpETHApr: { - tokenAddress: '0x3976d71e7ddfbab9bd120ec281b7d35fa0f28528', - sourceUrl: 'https://api-data.loopfi.xyz/api/getData', - path: 'loop.slpETHApr', - scale: 1, - }, - yUSD: { - tokenAddress: '0x1ce7d9942ff78c328a4181b9f3826fee6d845a97', - sourceUrl: 'https://ctrl.yield.fi/t/apy', - path: 'apy', - }, - uniETH: { - tokenAddress: '0xf1376bcef0f78459c0ed0ba5ddce976f1ddf51f4', - sourceUrl: 'https://app.bedrock.technology/unieth/api/v1/e2ls/apy', - path: 'data.apy', - scale: 10000, - }, - vETH: { - tokenAddress: '0x4bc3263eb5bb2ef7ad9ab6fb68be80e43b43801f', - sourceUrl: 'https://dapi.bifrost.io/api/site', - path: 'vETH.totalApy', - }, - stETH: { - tokenAddress: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - amphrETH: { - tokenAddress: '0x5fd13359ba15a84b76f7f87568309040176167cd', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - rstETH: { - tokenAddress: '0x7a4effd87c2f3c55ca251080b1343b605f327e3a', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - Re7LRT: { - tokenAddress: '0x84631c0d0081fde56deb72f6de77abbbf6a9f93a', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - steakLRT: { - tokenAddress: '0xbeef69ac7870777598a04b2bd4771c71212e6abc', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - pufETH: { - tokenAddress: '0xd9a442856c234a39a81a089c06451ebaa4306a72', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - wstETH: { - tokenAddress: '0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - inwstETHs: { - tokenAddress: '0x8e0789d39db454dbe9f4a77acef6dc7c69f6d552', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - cbETH: { - tokenAddress: '0xbe9895146f7af43049ca1c1ae358b0541ea49704', - sourceUrl: 'https://api.exchange.coinbase.com/wrapped-assets/CBETH/', - path: 'apy', - scale: 1, - }, - sfrxETH: { - tokenAddress: '0xac3e018457b222d93114458476f3e3416abbe38f', - sourceUrl: 'https://api.frax.finance/v2/frxeth/summary/latest', - path: 'sfrxethApr', - }, - StaFirETH: { - tokenAddress: '0x9559aaa82d9649c7a7b220e7c461d2e74c9a3593', - sourceUrl: 'https://drop-api.stafi.io/reth/v1/poolData', - path: 'data.stakeApr', - }, - rETH: { - tokenAddress: '0xae78736cd615f374d3085123a210448e74fc6393', - sourceUrl: 'https://api.rocketpool.net/mainnet/reth/apr', - path: 'yearlyAPR', - }, - wjAURA: { - tokenAddress: '0x198d7387fa97a73f05b8578cdeff8f2a1f34cd1f', - sourceUrl: 'https://data.jonesdao.io/api/v1/jones/apy-wjaura', - path: 'wjauraApy', - }, - ETHx: { - tokenAddress: '0xa35b1b31ce002fbf2058d22f30f95d405200a15b', - sourceUrl: 'https://universe.staderlabs.com/eth/apy', - path: 'value', - }, - usdm: { - tokenAddress: '0x57f5e098cad7a3d1eed53991d4d66c45c9af7812', - sourceUrl: 'https://apy.prod.mountainprotocol.com', - path: 'value', - scale: 1, - }, - ankrETH: { - tokenAddress: '0xe95a203b1a91a908f9b9ce46459d101078c2c3cb', - sourceUrl: 'https://api.staking.ankr.com/v1alpha/metrics', - path: 'services.{serviceName == "eth"}.apy', - }, - ezETH: { - tokenAddress: '0xbf5495efe5db9ce00f80364c8b423567e58d2110', - sourceUrl: 'https://app.renzoprotocol.com/api/apr', - path: 'apr', - }, - rsETH: { - tokenAddress: '0xa1290d69c65a6fe4df752f95823fae25cb99e5a7', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - }, - hgETH: { - tokenAddress: '0xc824a08db624942c5e5f330d56530cd1598859fd', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/gainApy', - path: 'hgETH', - }, - sDOLA: { - tokenAddress: '0xb45ad160634c528cc3d2926d9807104fa3157305', - sourceUrl: 'https://www.inverse.finance/api/dola-staking', - path: 'apr', - }, - rswETH: { - tokenAddress: '0xfae103dc9cf190ed75350761e95403b7b8afa6c0', - sourceUrl: 'https://v3-lrt.svc.swellnetwork.io/api/tokens/rsweth/apr', - }, - sUSDE: { - tokenAddress: '0x9d39a5de30e57443bff2a8307a4256c8797a3497', - sourceUrl: 'https://ethena.fi/api/yields/protocol-and-staking-yield', - path: 'stakingYield.value', - }, - saETH: { - tokenAddress: '0xf1617882a71467534d14eee865922de1395c9e89', - sourceUrl: 'https://api.aspidanet.com/page_data/?chainId=1', - path: 'apr', - }, - cdcETH: { - tokenAddress: '0xfe18ae03741a5b84e39c295ac9c856ed7991c38e', - sourceUrl: 'https://api.crypto.com/pos/v1/public/get-staking-instruments', - path: 'result.data.{instrument_name == "ETH.staked"}.est_rewards', - headers: { - 'Content-Type': 'application/json', - }, - body: { - params: { - country_code: 'POL', - }, - }, - scale: 1, - }, - agETH: { - tokenAddress: '0xe1b4d34e8754600962cd944b535180bd758e6c2e', - sourceUrl: 'https://universe.kelpdao.xyz/rseth/apy', - path: 'value', - }, - dvstETH: { - tokenAddress: '0x5e362eb2c0706bd1d134689ec75176018385430b', - sourceUrl: 'https://eth-api.lido.fi/v1/protocol/steth/apr/sma', - path: 'data.smaApr', - }, - sdeUSD: { - tokenAddress: '0x5c5b196abe0d54485975d1ec29617d42d9198326', - sourceUrl: 'https://api-deusd-prod-public.elixir.xyz/public/deusd_apy', - path: 'deusd_apy', - }, - sUSDX: { - tokenAddress: '0x7788a3538c5fc7f9c7c8a74eac4c898fc8d87d92', - sourceUrl: 'https://app.usdx.money/v1/base/apyInfo', - path: 'result.susdxApy', - scale: 1, - }, - slpUSD: { - tokenAddress: '0xbfb53910c935e837c74e6c4ef584557352d20fde', - sourceUrl: 'https://api-data.loopfi.xyz/api/getData', - path: 'lpUSDLoop.slpUSDApr', - scale: 1, - }, - wUSDN: { - tokenAddress: '0x99999999999999cc837c997b882957dafdcb1af9', - sourceUrl: 'https://usdn.api.smardex.io/v1/wusdn/apr', - scale: 1, - }, - }, - }, -}; diff --git a/modules/aprs/config/optimism.ts b/modules/aprs/config/optimism.ts deleted file mode 100644 index 7dc450db5..000000000 --- a/modules/aprs/config/optimism.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - aaveApiConfig: { - chainId: 10 - } -}; \ No newline at end of file diff --git a/modules/aprs/config/polygon.ts b/modules/aprs/config/polygon.ts deleted file mode 100644 index a8b4c5c2b..000000000 --- a/modules/aprs/config/polygon.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default { - aaveApiConfig: { - chainId: 137 - } -}; \ No newline at end of file diff --git a/modules/aprs/examples/comparison.ts b/modules/aprs/examples/comparison.ts index cd1dbdbb1..9a01baaae 100644 --- a/modules/aprs/examples/comparison.ts +++ b/modules/aprs/examples/comparison.ts @@ -17,7 +17,7 @@ async function comparisonExample(chain: Chain = 'MAINNET', poolId = '0x85b2b559b }); // Old implementation - await poolService.updatePoolAprs(chain, [poolId]); + await poolService.updatePoolAprs(chain); const oldItems = await prisma.prismaPoolAprItem.findMany({ where: { poolId } }); oldItems.forEach((item) => { diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts index 915dc4700..44c97e3d9 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts @@ -3,7 +3,7 @@ import { Chain } from '@prisma/client'; import { AaveApiAprHandler } from './aave-api-apr-handler'; import { AaveChanClientInterface } from './aave-chan-client'; import type { PoolAPRData } from '../../types'; -import { AaveApiConfig } from '../types'; +import { AaveRewardsAprConfig } from '../types'; describe('AaveApiAprHandler', () => { // Mock implementation of the client @@ -17,7 +17,7 @@ describe('AaveApiAprHandler', () => { let handler: AaveApiAprHandler; let mockPool: PoolAPRData; - const mockConfig: AaveApiConfig = { chainId: 1 }; // Mainnet + const mockConfig: AaveRewardsAprConfig = { chainId: 1 }; // Mainnet beforeEach(() => { // Reset mocks @@ -193,7 +193,7 @@ describe('AaveApiAprHandler', () => { test('should handle non-mainnet chains', async () => { // Create handler for Arbitrum - const arbitrumConfig: AaveApiConfig = { chainId: 42161 }; + const arbitrumConfig: AaveRewardsAprConfig = { chainId: 42161 }; const arbitrumHandler = new AaveApiAprHandler(arbitrumConfig, mockClient); // Mock pool for Arbitrum diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts index e5c38a370..f6468c69a 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts @@ -1,7 +1,7 @@ import _ from 'lodash'; import { Chain, PrismaPoolAprItem } from '@prisma/client'; import { AprHandler, PoolAPRData } from '../../types'; -import { AaveApiConfig } from '../types'; +import { AaveRewardsAprConfig } from '../types'; import { AaveChanClientInterface, AaveChanResponse, AaveChanClient } from './aave-chan-client'; /** @@ -10,10 +10,7 @@ import { AaveChanClientInterface, AaveChanResponse, AaveChanClient } from './aav export class AaveApiAprHandler implements AprHandler { private client: AaveChanClientInterface; - constructor( - private readonly config: AaveApiConfig, - injectedClient?: AaveChanClientInterface, - ) { + constructor(private readonly config: AaveRewardsAprConfig, injectedClient?: AaveChanClientInterface) { // Create a default client if not present this.client = injectedClient || new AaveChanClient(this.config); } diff --git a/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts index 94361deee..579feccdf 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts @@ -1,4 +1,4 @@ -import { AaveApiConfig } from '../types'; +import { AaveRewardsAprConfig } from '../types'; /** * Represents data for a token in Aave reserves @@ -63,7 +63,7 @@ export interface AaveChanClientInterface { export class AaveChanClient implements AaveChanClientInterface { private readonly baseUrl = 'https://apps.aavechan.com/api/aave-all-incentives?chainId='; - constructor(private readonly config: AaveApiConfig) {} + constructor(private readonly config: AaveRewardsAprConfig) {} /** * Fetch incentives for a specific chain diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 0eab21255..a105d4e70 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -1,8 +1,8 @@ import { Chain } from '@prisma/client'; import { AprHandler } from '../types'; import * as handlers from '.'; -import chainConfigs from '../config'; -import { AaveApiConfig } from './types'; +import { AaveRewardsAprConfig } from './types'; +import config from '../../../config'; /** * Creates handler instances for a specific chain @@ -13,13 +13,15 @@ export function createHandlers(chain: Chain): AprHandler[] { // Default handlers for all of the chains handlerList.push(new handlers.SwapFeeAprHandler()); - if (chainConfigs[chain].ybAprConfig) { - handlerList.push(new handlers.YbTokensAprHandler(chainConfigs[chain].ybAprConfig, chain)); + if (config[chain].aprHandlers.ybAprHandler) { + handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); } // Add Aave API handler if configured for this chain - if (chainConfigs[chain].aaveApiConfig) { - handlerList.push(new handlers.AaveApiAprHandler(chainConfigs[chain].aaveApiConfig as AaveApiConfig)); + if (config[chain].aprHandlers.aaveRewardsAprHandler) { + handlerList.push( + new handlers.AaveApiAprHandler(config[chain].aprHandlers.aaveRewardsAprHandler as AaveRewardsAprConfig), + ); } return handlerList; diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts index 56ea6945b..b10b86ecb 100644 --- a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts @@ -1,7 +1,7 @@ import { expect, test, describe, beforeEach } from 'vitest'; import { Chain, PrismaPoolAprType, PrismaPoolType } from '@prisma/client'; import { SwapFeeAprHandler } from './swap-fee-apr-handler'; -import { PoolAPRData } from '../types'; +import { PoolAPRData } from '../../apr-repository'; describe('SwapFeeAprCalculator', () => { let calculator: SwapFeeAprHandler; diff --git a/modules/aprs/handlers/types.ts b/modules/aprs/handlers/types.ts index 2a1256deb..d929ff4d3 100644 --- a/modules/aprs/handlers/types.ts +++ b/modules/aprs/handlers/types.ts @@ -1,5 +1,5 @@ export type { YbAprConfig } from './yb-tokens/types'; -export type AaveApiConfig = { +export type AaveRewardsAprConfig = { chainId: number; }; diff --git a/modules/aprs/types.ts b/modules/aprs/types.ts index 0ea4c51b4..11557fde8 100644 --- a/modules/aprs/types.ts +++ b/modules/aprs/types.ts @@ -15,29 +15,3 @@ export interface AprHandler { */ getAprServiceName(): string; } - -/** - * Types of APR calculators available in the system - */ -export type AprCalculatorType = - | 'swapFeeApr' - | 'boostedPoolApr' - | 'dynamicSwapFeeApr' - | 'gaugeApr' - | 'veBalProtocolApr' - | 'veBalVotingApr' - | 'morphoRewardsApr' - | 'ybTokensApr' - | 'aaveApiApr' - | 'masterchefFarmApr' - | 'reliquaryFarmApr' - | 'beetswarsGaugeVotingApr' - | 'quantAmmApr'; - -/** - * Configuration for APR calculators - */ -export interface AprCalculatorConfig { - type: AprCalculatorType; - params?: Record; -} diff --git a/modules/network/apr-config-types.ts b/modules/network/apr-config-types.ts index f6c605401..b55c96144 100644 --- a/modules/network/apr-config-types.ts +++ b/modules/network/apr-config-types.ts @@ -1,3 +1,20 @@ +import { AaveRewardsAprConfig } from '../aprs/handlers/types'; + +export interface AprHandlerConfigs { + // nestedPoolAprHandler: boolean; + // swapFeeAprHandler: boolean; + // dynamicSwapFeeAprHandler: boolean; + // gaugeAprHandler: boolean; + ybAprHandler?: YbAprConfig; + aaveRewardsAprHandler?: AaveRewardsAprConfig; + // reliquaryAprHandler?: ReliquaryAprConfig; + // beetswarsAprHandler?: BeetswarsAprConfig; + // veBalProtocolAprHandler?: VeBalProtocolAprConfig; + // veBalVotingAprHandler?: VeBalVotingAprConfig; + // morphoRewardAprHandler?: MorphоRewardAprConfig; + // quantAmmAprHandler?: QuantAmmAprConfig; +} + export interface YbAprConfig { aave?: AaveAprConfig; avalon?: AvalonAprConfig; diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 8cebab3fc..2470c40b6 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -19,8 +19,8 @@ export const arbitrumNetworkConfig: NetworkConfig = { data: arbitrumNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: arbitrumNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(arbitrumNetworkData.ybAprConfig, arbitrumNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(arbitrumNetworkData.aprHandlers.ybAprHandler!, arbitrumNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index 15dbcd81f..179b748ab 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -1,6 +1,6 @@ import { BigNumber, ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -18,8 +18,8 @@ export const avalancheNetworkConfig: NetworkConfig = { data: avalancheNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: avalancheNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(avalancheNetworkData.ybAprConfig, avalancheNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(avalancheNetworkData.aprHandlers.ybAprHandler!, avalancheNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), new AaveApiAprService(), diff --git a/modules/network/base.ts b/modules/network/base.ts index 32df4f811..b00d102bf 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -3,7 +3,7 @@ import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-type import { SwapFeeAprService, GaugeAprService, - BoostedPoolAprService, + NestedPoolAprService, YbTokensAprService, MorphoRewardsAprService, DynamicSwapFeeFromEventsAprService, @@ -23,8 +23,8 @@ export const baseNetworkConfig: NetworkConfig = { data: baseNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: baseNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(baseNetworkData.ybAprConfig, baseNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(baseNetworkData.aprHandlers.ybAprHandler!, baseNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/fraxtal.ts b/modules/network/fraxtal.ts index c85c37cb6..b6094af6a 100644 --- a/modules/network/fraxtal.ts +++ b/modules/network/fraxtal.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -17,8 +17,8 @@ export const fraxtalNetworkConfig: NetworkConfig = { data: fraxtalNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: fraxtalNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(fraxtalNetworkData.ybAprConfig, fraxtalNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(fraxtalNetworkData.aprHandlers.ybAprHandler!, fraxtalNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), ], diff --git a/modules/network/gnosis.ts b/modules/network/gnosis.ts index 6f6bca26f..b5290999e 100644 --- a/modules/network/gnosis.ts +++ b/modules/network/gnosis.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -18,8 +18,8 @@ export const gnosisNetworkConfig: NetworkConfig = { data: gnosisNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: gnosisNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(gnosisNetworkData.ybAprConfig, gnosisNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(gnosisNetworkData.aprHandlers.ybAprHandler!, gnosisNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 94e552a65..259c69928 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; import { - BoostedPoolAprService, + NestedPoolAprService, SwapFeeAprService, GaugeAprService, YbTokensAprService, @@ -26,8 +26,8 @@ export const mainnetNetworkConfig: NetworkConfig = { data, provider: new ethers.providers.JsonRpcProvider({ url: data.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(data.ybAprConfig, data.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(data.aprHandlers.ybAprHandler!, data.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/mode.ts b/modules/network/mode.ts index 9c30a8ad6..e6be89e07 100644 --- a/modules/network/mode.ts +++ b/modules/network/mode.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -16,8 +16,8 @@ export const modeNetworkConfig: NetworkConfig = { data: modeNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: modeNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(modeNetworkData.ybAprConfig, modeNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(modeNetworkData.aprHandlers.ybAprHandler!, modeNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), ], diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index 97dfee20b..a73bdb94b 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -3,7 +3,7 @@ import type { PoolAprService } from '../pool/pool-types'; import type { UserStakedBalanceService } from '../user/user-types'; import type { BaseProvider } from '@ethersproject/providers'; import type { GqlChain, GqlHookType } from '../../apps/api/gql/generated-schema'; -import type { YbAprConfig } from './apr-config-types'; +import type { AprHandlerConfigs, YbAprConfig } from './apr-config-types'; import type { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import { SftmxSubgraphService } from '../sources/subgraphs/sftmx-subgraph/sftmx.service'; @@ -126,7 +126,7 @@ export interface NetworkData { address: string; excludedFarmIds: string[]; }; - ybAprConfig: YbAprConfig; + aprHandlers: AprHandlerConfigs; reliquary?: { address: string; excludedFarmIds: string[]; diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index d0bc0f4e1..abec61522 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -17,8 +17,8 @@ export const optimismNetworkConfig: NetworkConfig = { data: optimismNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: optimismNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(optimismNetworkData.ybAprConfig, optimismNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(optimismNetworkData.aprHandlers.ybAprHandler!, optimismNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), ], diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 5c5e24e23..2da8fedb4 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -17,8 +17,8 @@ export const polygonNetworkConfig: NetworkConfig = { data: polygonNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: polygonNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(polygonNetworkData.ybAprConfig, polygonNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(polygonNetworkData.aprHandlers.ybAprHandler!, polygonNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), ], diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index 6ae9966dd..2f545122b 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService, DynamicSwapFeeFromEventsAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -17,8 +17,8 @@ export const sepoliaNetworkConfig: NetworkConfig = { data: sepoliaNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: sepoliaNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(sepoliaNetworkData.ybAprConfig, sepoliaNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(sepoliaNetworkData.aprHandlers.ybAprHandler!, sepoliaNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/sonic.ts b/modules/network/sonic.ts index 7a4d12e84..020f26a80 100644 --- a/modules/network/sonic.ts +++ b/modules/network/sonic.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -19,8 +19,8 @@ export const sonicNetworkConfig: NetworkConfig = { data: sonicNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: sonicNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(sonicNetworkData.ybAprConfig, sonicNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(sonicNetworkData.aprHandlers.ybAprHandler!, sonicNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new DynamicSwapFeeFromEventsAprService(), new GaugeAprService(), diff --git a/modules/network/zkevm.ts b/modules/network/zkevm.ts index 1e6a6ecd2..1fc5858d4 100644 --- a/modules/network/zkevm.ts +++ b/modules/network/zkevm.ts @@ -1,6 +1,6 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { BoostedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; +import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; @@ -17,8 +17,8 @@ export const zkevmNetworkConfig: NetworkConfig = { data: zkevmNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: zkevmNetworkData.rpcUrl, timeout: 60000 }), poolAprServices: [ - new YbTokensAprService(zkevmNetworkData.ybAprConfig, zkevmNetworkData.chain.prismaId), - new BoostedPoolAprService(), + new YbTokensAprService(zkevmNetworkData.aprHandlers.ybAprHandler!, zkevmNetworkData.chain.prismaId), + new NestedPoolAprService(), new SwapFeeAprService(), new GaugeAprService(), ], diff --git a/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts b/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts index 703a3e66e..129956852 100644 --- a/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts +++ b/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts @@ -3,7 +3,7 @@ import { PoolForAPRs } from '../../../../prisma/prisma-types'; import { prisma } from '../../../../prisma/prisma-client'; import { collectsYieldFee } from '../pool-utils'; -export class BoostedPoolAprService implements PoolAprService { +export class NestedPoolAprService implements PoolAprService { public getAprServiceName(): string { return 'BoostedPoolAprService'; } diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index aed544f7d..c14bc0dfb 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -160,8 +160,8 @@ export class PoolService { } } - public async updatePoolAprs(chain: Chain, poolIds?: string[]) { - await this.poolAprUpdaterService.updatePoolAprs(chain, poolIds); + public async updatePoolAprs(chain: Chain) { + await this.poolAprUpdaterService.updatePoolAprs(chain); await syncIncentivizedCategory(); } diff --git a/modules/token/lib/token-price-handlers/aave-price-handler.service.ts b/modules/token/lib/token-price-handlers/aave-price-handler.service.ts index 83464aebf..25909c299 100644 --- a/modules/token/lib/token-price-handlers/aave-price-handler.service.ts +++ b/modules/token/lib/token-price-handlers/aave-price-handler.service.ts @@ -14,7 +14,7 @@ export class AavePriceHandlerService implements TokenPriceHandler { public readonly id = 'AavePriceHandlerService'; aaveTokens = Object.keys(config).flatMap((chain) => { const chainConfig = config[chain as keyof typeof config]; - const v3 = chainConfig.ybAprConfig.aave?.v3?.tokens; + const v3 = chainConfig.aprHandlers.ybAprHandler?.aave?.v3?.tokens; if (!v3) return []; return Object.values(v3).flatMap(({ aTokenAddress, underlyingAssetAddress, wrappedTokens }) => Object.values(wrappedTokens).map((wrappedToken) => ({ From 9886e23dbe96cd0cec155cb7dca98674bf6ee0a4 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 08:41:15 +0300 Subject: [PATCH 04/37] add dynamic swapfee apr handler --- modules/aprs/handlers/create-handlers.ts | 1 + .../dynamic-swap-fee-apr-handler.ts | 98 +++++++++++++++++++ .../handlers/dynamic-swap-fee-apr/index.ts | 1 + modules/aprs/handlers/index.ts | 1 + 4 files changed, 101 insertions(+) create mode 100644 modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts create mode 100644 modules/aprs/handlers/dynamic-swap-fee-apr/index.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index a105d4e70..06672bbd4 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -12,6 +12,7 @@ export function createHandlers(chain: Chain): AprHandler[] { // Default handlers for all of the chains handlerList.push(new handlers.SwapFeeAprHandler()); + handlerList.push(new handlers.DynamicSwapFeeAprHandler()); if (config[chain].aprHandlers.ybAprHandler) { handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); diff --git a/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts b/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts new file mode 100644 index 000000000..b057fb6a5 --- /dev/null +++ b/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts @@ -0,0 +1,98 @@ +import { prisma } from '../../../../prisma/prisma-client'; +import { Chain, PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { daysAgo } from '../../../common/time'; +import { AprHandler, PoolAPRData } from '../../types'; + +type PoolSwapFeeData = { + poolId: string; + chain: Chain; + fees_24h: number; +}; + +const query = (chain: Chain, timestamp: number) => ` + SELECT + "poolId", + chain, + SUM((payload->'dynamicFee'->>'valueUSD')::numeric) AS fees_24h + FROM + "PartitionedPoolEvent" + WHERE + "blockTimestamp" >= ${timestamp} + AND chain = '${chain}' + AND type = 'SWAP' + GROUP BY + 1, 2 +`; + +const MAX_DB_INT = 9223372036854775807; + +export class DynamicSwapFeeAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'DynamicSwapFeeAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const chain = pools[0].chain; + const yesterday = daysAgo(1); + + const [dynamicData, existingAprItems] = await Promise.all([ + prisma.prismaPoolDynamicData.findMany({ + where: { chain, poolId: { in: pools.map((pool) => pool.id) } }, + }), + prisma.prismaPoolAprItem + .findMany({ select: { id: true, apr: true }, where: { chain, type: 'DYNAMIC_SWAP_FEE_24H' } }) + .then((records) => Object.fromEntries(records.map((item) => [item.id, item.apr]))), + ]); + + // Fetch the swap fees for the last 30 days + const swapFeeData = await prisma.$queryRawUnsafe(query(chain, yesterday)); + + // Map the swap fee data to the pool id + const swapFeeDataMap = swapFeeData.reduce((acc, data) => { + acc[data.poolId] = data; + return acc; + }, {} as Record); + + // const aprItems: Omit[] = []; + + const aprItems: Omit[] = ([] = dynamicData + .map((pool) => { + let apr_24h = 0; + let protocolFee = parseFloat(pool.aggregateSwapFee); + + if (pool.isInRecoveryMode) { + protocolFee = 0; + } + + if (pool.totalLiquidity > 0 && swapFeeDataMap[pool.poolId]) { + apr_24h = ((swapFeeDataMap[pool.poolId].fees_24h * 365) / pool.totalLiquidity) * (1 - protocolFee); + } + if (apr_24h > MAX_DB_INT) { + apr_24h = 0; + } + + const id = `${pool.poolId}-dynamic-swap-apr-24h`; + + if (Math.abs(existingAprItems[id] || 0 - apr_24h) > 0.0001) { + return { + id, + chain, + poolId: pool.poolId, + title: 'Dynamic swap fees APR', + apr: apr_24h, + type: PrismaPoolAprType.DYNAMIC_SWAP_FEE_24H, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }; + } else { + return null; + } + }) + .filter((pool): pool is NonNullable => !!pool)); + + return aprItems; + } +} diff --git a/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts b/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts new file mode 100644 index 000000000..5bc3f169a --- /dev/null +++ b/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts @@ -0,0 +1 @@ +export * from './dynamic-swap-fee-apr-handler'; diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 77a883d10..cdebf4acc 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -2,6 +2,7 @@ export { SwapFeeAprHandler } from './swap-fee-apr'; export { YbTokensAprHandler } from './yb-tokens'; export { AaveApiAprHandler } from './aave-api-apr'; +export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; // Add more handler exports as they are implemented // Example: From a2c85ff04869fa27b9238d228c65bb040eefce23 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 09:30:07 +0300 Subject: [PATCH 05/37] add mabeets apr --- config/sonic.ts | 3 + modules/aprs/apr-repository.ts | 4 +- modules/aprs/handlers/create-handlers.ts | 7 +- modules/aprs/handlers/index.ts | 1 + .../beetswars-gauge-voting-apr-handler.ts | 61 +++++++++ modules/aprs/handlers/mabeets-apr/index.ts | 2 + .../mabeets-apr/mabeets-apr-handler.ts | 126 ++++++++++++++++++ .../migration.sql | 2 + .../migration.sql | 10 ++ 9 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts create mode 100644 modules/aprs/handlers/mabeets-apr/index.ts create mode 100644 modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts create mode 100644 prisma/migrations/20250616060552_add_staking_boost_apr_type/migration.sql create mode 100644 prisma/migrations/20250616061555_add_native_emissions_apr_type/migration.sql diff --git a/config/sonic.ts b/config/sonic.ts index b51d6b26c..82a1ceb06 100644 --- a/config/sonic.ts +++ b/config/sonic.ts @@ -76,6 +76,9 @@ export default { }, avgBlockSpeed: 1, aprHandlers: { + maBeetsAprHandler: { + beetsAddress: '0x2d0e0814e62d80056181f5cd932274405966e4f0', + }, ybAprHandler: { sts: { token: '0xe5da20f15420ad15de0fa650600afc998bbe3955', diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index 50ed872ac..f79156350 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -7,7 +7,7 @@ const aprsInclude = Prisma.validator()({ include: { dynamicData: true, tokens: { include: { token: true, nestedPool: true } }, - staking: { include: { gauge: { include: { rewards: true } }, reliquary: true } }, + staking: { include: { gauge: { include: { rewards: true } }, reliquary: { include: { levels: true } } } }, }, }); @@ -54,6 +54,8 @@ export class AprRepository { }, }); + //TODO also remove items that are not in aprItems anymore + // Create a lookup map for quick access const existingItemsMap = new Map(existingItems.map((item) => [item.id, item])); diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 06672bbd4..093a16039 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -1,8 +1,8 @@ import { Chain } from '@prisma/client'; import { AprHandler } from '../types'; import * as handlers from '.'; -import { AaveRewardsAprConfig } from './types'; import config from '../../../config'; +import { AaveRewardsAprConfig } from './types'; /** * Creates handler instances for a specific chain @@ -18,6 +18,11 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); } + if (config[chain].aprHandlers.maBeetsAprHandler) { + handlerList.push(new handlers.MaBeetsAprHandler(config[chain].aprHandlers.maBeetsAprHandler.beetsAddress)); + handlerList.push(new handlers.BeetswarsGaugeVotingAprHandler()); + } + // Add Aave API handler if configured for this chain if (config[chain].aprHandlers.aaveRewardsAprHandler) { handlerList.push( diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index cdebf4acc..4a4f21542 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -3,6 +3,7 @@ export { SwapFeeAprHandler } from './swap-fee-apr'; export { YbTokensAprHandler } from './yb-tokens'; export { AaveApiAprHandler } from './aave-api-apr'; export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; +export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr'; // Add more handler exports as they are implemented // Example: diff --git a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts new file mode 100644 index 000000000..fd61d1798 --- /dev/null +++ b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts @@ -0,0 +1,61 @@ +import axios from 'axios'; +import { networkContext } from '../../../network/network-context.service'; +import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { AprHandler, PoolAPRData } from '../../types'; + +export class BeetswarsGaugeVotingAprHandler implements AprHandler { + private readonly FRESH_BEETS_POOL_ID = '0x10ac2f9dae6539e77e372adb14b1bf8fbd16b3e8000200000000000000000005'; + + public getAprServiceName(): string { + return 'BeetswarsGaugeVotingAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const aprItems: Omit[] = []; + + for (const pool of pools) { + if (pool.id !== this.FRESH_BEETS_POOL_ID) { + continue; + } + + const response = await axios.get('https://www.beetswars.live/api/trpc/chart.chartdata'); + + const raw: number[] = response.data.result.data.json.chartdata.votingApr; + + // Filter out non-numbers and infinity values + const votingAprs = raw.filter((apr) => apr && isFinite(apr)); + + const minApr = 0; + const maxApr = votingAprs[votingAprs.length - 1] / 100; + + const itemId = `${this.FRESH_BEETS_POOL_ID}-voting-apr`; + + aprItems.push({ + id: itemId, + chain: networkContext.chain, + poolId: this.FRESH_BEETS_POOL_ID, + title: 'Voting APR*', + apr: minApr, + type: PrismaPoolAprType.VOTING, + group: null, + rewardTokenAddress: null, + rewardTokenSymbol: null, + }); + + aprItems.push({ + id: `${itemId}-boost`, + chain: networkContext.chain, + poolId: this.FRESH_BEETS_POOL_ID, + title: 'Voting APR Boost', + apr: maxApr, + type: PrismaPoolAprType.STAKING_BOOST, + group: null, + rewardTokenAddress: null, + rewardTokenSymbol: null, + }); + } + return aprItems; + } +} diff --git a/modules/aprs/handlers/mabeets-apr/index.ts b/modules/aprs/handlers/mabeets-apr/index.ts new file mode 100644 index 000000000..1605d9027 --- /dev/null +++ b/modules/aprs/handlers/mabeets-apr/index.ts @@ -0,0 +1,2 @@ +export * from './beetswars-gauge-voting-apr-handler'; +export * from './mabeets-apr-handler'; diff --git a/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts new file mode 100644 index 000000000..81c02c1ba --- /dev/null +++ b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts @@ -0,0 +1,126 @@ +import { addressesMatch } from '../../../web3/addresses'; +import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { prisma } from '../../../../prisma/prisma-client'; +import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; +import { secondsPerYear } from '../../../common/time'; +import { tokenService } from '../../../token/token.service'; +import { ReliquarySubgraphService } from '../../../subgraphs/reliquary-subgraph/reliquary.service'; +import { AprHandler, PoolAPRData } from '../../types'; +import config from '../../../../config'; + +export class MaBeetsAprHandler implements AprHandler { + constructor(private readonly beetsAddress: string) {} + + public getAprServiceName(): string { + return 'MaBeetsAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const chain = pools[0].chain; + if (!config[chain].subgraphs.reliquary) { + throw new Error(`Reliquary subgraph not configured for chain ${chain}`); + } + + const reliquarySubgraphService = new ReliquarySubgraphService(config[chain].subgraphs.reliquary); + const allSubgraphFarms = await reliquarySubgraphService.getAllFarms({}); + + const excludedFarmIds = config[chain].reliquary?.excludedFarmIds || []; + + const filteredFarms = allSubgraphFarms.filter((farm) => !excludedFarmIds.includes(farm.pid.toString())); + + const tokenPrices = await tokenService.getTokenPrices(chain); + const operations: any[] = []; + + const aprItems: Omit[] = []; + + for (const pool of pools) { + const subgraphFarm = filteredFarms.find((farm) => addressesMatch(pool.address, farm.poolTokenAddress)); + let farm; + for (const stake of pool.staking) { + farm = stake.reliquary; + } + + if (!subgraphFarm || !pool.dynamicData || !farm || subgraphFarm.totalBalance === '0') { + continue; + } + + const totalShares = parseFloat(pool.dynamicData.totalShares); + const totalLiquidity = pool.dynamicData?.totalLiquidity || 0; + const pricePerShare = totalLiquidity / totalShares; + + const beetsPrice = tokenService.getPriceForToken(tokenPrices, this.beetsAddress, chain); + const farmBeetsPerYear = parseFloat(farm.beetsPerSecond) * secondsPerYear; + const beetsValuePerYear = beetsPrice * farmBeetsPerYear; + + const totalWeightedSupply = subgraphFarm.levels.reduce( + (total, level) => total + level.allocationPoints * parseFloat(level.balance), + 0, + ); + + /* + on the pool overview & detail page, we only show min & max apr values, but on the + reliquary page we want to show apr values for each level, so we search for the min / max + apr values and add the as apr items and also update the apr for each level of the farm + */ + let minApr = 0; + let maxApr = 0; + + for (let farmLevel of subgraphFarm.levels) { + const levelSupply = parseFloat(farmLevel.balance); + const aprShare = (farmLevel.allocationPoints * levelSupply) / totalWeightedSupply; + const apr = levelSupply !== 0 ? (beetsValuePerYear * aprShare) / (levelSupply * pricePerShare) : 0; + + if (minApr === 0 && maxApr === 0) { + minApr = apr; + maxApr = apr; + } else if (apr !== 0 && apr < minApr) { + minApr = apr; + } else if (apr > maxApr) { + maxApr = apr; + } + operations.push( + prisma.prismaPoolStakingReliquaryFarmLevel.update({ + where: { + id_chain: { + id: `${subgraphFarm.pid}-${farmLevel.level}`, + chain: chain, + }, + }, + data: { + apr: apr || 0, + }, + }), + ); + } + + aprItems.push({ + id: `${pool.id}-beets-apr`, + chain: chain, + poolId: pool.id, + title: 'BEETS reward APR', + apr: minApr, + type: PrismaPoolAprType.MABEETS_EMISSIONS, + group: null, + rewardTokenAddress: this.beetsAddress, + rewardTokenSymbol: 'BEETS', + }); + + aprItems.push({ + id: `${pool.id}-beets-apr-boost`, + chain: chain, + poolId: pool.id, + title: 'BEETS reward APR', + apr: maxApr, + type: PrismaPoolAprType.STAKING_BOOST, + group: null, + rewardTokenAddress: this.beetsAddress, + rewardTokenSymbol: 'BEETS', + }); + } + + await prismaBulkExecuteOperations(operations); + return aprItems; + } +} diff --git a/prisma/migrations/20250616060552_add_staking_boost_apr_type/migration.sql b/prisma/migrations/20250616060552_add_staking_boost_apr_type/migration.sql new file mode 100644 index 000000000..525b91aac --- /dev/null +++ b/prisma/migrations/20250616060552_add_staking_boost_apr_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "PrismaPoolAprType" ADD VALUE 'STAKING_BOOST'; diff --git a/prisma/migrations/20250616061555_add_native_emissions_apr_type/migration.sql b/prisma/migrations/20250616061555_add_native_emissions_apr_type/migration.sql new file mode 100644 index 000000000..66bd715d6 --- /dev/null +++ b/prisma/migrations/20250616061555_add_native_emissions_apr_type/migration.sql @@ -0,0 +1,10 @@ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "PrismaPoolAprType" ADD VALUE 'MABEETS_EMISSIONS'; +ALTER TYPE "PrismaPoolAprType" ADD VALUE 'VEBAL_EMISSIONS'; From 7b98d4e8587ac40b239ed47d9fccde14958d4927 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 09:30:14 +0300 Subject: [PATCH 06/37] move types --- .../aave-api-apr/aave-api-apr-handler.ts | 2 +- .../dynamic-swap-fee-apr-handler.ts | 2 - modules/aprs/handlers/types.ts | 248 +++++++++++++++++- modules/network/apr-config-types.ts | 17 -- modules/network/network-config-types.ts | 2 +- prisma/schema/pool.prisma | 3 + 6 files changed, 252 insertions(+), 22 deletions(-) diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts index f6468c69a..689a853f4 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts @@ -1,8 +1,8 @@ import _ from 'lodash'; import { Chain, PrismaPoolAprItem } from '@prisma/client'; import { AprHandler, PoolAPRData } from '../../types'; -import { AaveRewardsAprConfig } from '../types'; import { AaveChanClientInterface, AaveChanResponse, AaveChanClient } from './aave-chan-client'; +import { AaveRewardsAprConfig } from '../types'; /** * Implementation of AprHandler for Aave API diff --git a/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts b/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts index b057fb6a5..79a4231b2 100644 --- a/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts +++ b/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts @@ -55,8 +55,6 @@ export class DynamicSwapFeeAprHandler implements AprHandler { return acc; }, {} as Record); - // const aprItems: Omit[] = []; - const aprItems: Omit[] = ([] = dynamicData .map((pool) => { let apr_24h = 0; diff --git a/modules/aprs/handlers/types.ts b/modules/aprs/handlers/types.ts index d929ff4d3..a22ba0160 100644 --- a/modules/aprs/handlers/types.ts +++ b/modules/aprs/handlers/types.ts @@ -1,5 +1,251 @@ -export type { YbAprConfig } from './yb-tokens/types'; +export interface AprHandlerConfigs { + // nestedPoolAprHandler: boolean; + // swapFeeAprHandler: boolean; + // dynamicSwapFeeAprHandler: boolean; + // gaugeAprHandler: boolean; + ybAprHandler?: YbAprConfig; + aaveRewardsAprHandler?: AaveRewardsAprConfig; + maBeetsAprHandler?: MaBeetsAprConfig; + // veBalProtocolAprHandler?: VeBalProtocolAprConfig; + // veBalVotingAprHandler?: VeBalVotingAprConfig; + // morphoRewardAprHandler?: MorphоRewardAprConfig; + // quantAmmAprHandler?: QuantAmmAprConfig; +} export type AaveRewardsAprConfig = { chainId: number; }; + +export interface MaBeetsAprConfig { + beetsAddress: string; +} + +export interface YbAprConfig { + aave?: AaveAprConfig; + avalon?: AvalonAprConfig; + bloom?: BloomAprConfig; + beefy?: BeefyAprConfig; + sftmx?: SftmxAprConfig; + sts?: { + token: string; + }; + silo?: SiloAprConfig; + euler?: EulerAprConfig; + gearbox?: GearBoxAprConfig; + idle?: IdleAprConfig; + maker?: MakerAprConfig; + ovix?: OvixAprConfig; + reaper?: ReaperAprConfig; + tetu?: TetuAprConfig; + tranchess?: TranchessAprConfig; + yearn?: YearnAprConfig; + stakewise?: { + url: string; + token: string; + }; + extra?: { + url: string; + }; + fluid?: { + url: string; + }; + maple?: { + url: string; + token: string; + }; + yieldnest?: { + url: string; + token: string; + }; + dforce?: { + token: string; + }; + etherfi?: string; + sveth?: boolean; + defillama?: { + defillamaPoolId: string; + tokenAddress: string; + }[]; + defaultHandlers?: DefaultHandlerAprConfig; + fixedAprHandler?: FixedAprConfig; +} + +export interface AaveAprConfig { + [version: string]: { + subgraphUrl: string; + tokens: { + [underlyingAssetName: string]: { + underlyingAssetAddress: string; + aTokenAddress: string; + wrappedTokens: { + [wrappedTokenName: string]: string; + }; + isIbYield?: boolean; + }; + }; + }; +} + +export interface AvalonAprConfig { + [market: string]: { + subgraphUrl: string; + tokens: { + [underlyingAssetName: string]: { + underlyingAssetAddress: string; + aTokenAddress: string; + wrappedTokens: { + [wrappedTokenName: string]: string; + }; + isIbYield?: boolean; + }; + }; + }; +} + +export interface BeefyAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + // To get the vaultId, get the vault address from the token contract(token.vault()), + // and search for the vault address in the link: https://api.beefy.finance/vaults + vaultId: string; + isIbYield?: boolean; + }; + }; +} + +export interface BloomAprConfig { + tokens: { + [tokenName: string]: { + address: string; + feedAddress: string; + isIbYield?: boolean; + }; + }; +} + +export interface SftmxAprConfig { + tokens: { + [underlyingAssetName: string]: { + address: string; + ftmStakingAddress: string; + }; + }; +} + +export interface EulerAprConfig { + vaultsJsonUrl: string; + lensContractAddress: string; +} + +export interface GearBoxAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isIbYield?: boolean; + }; + }; +} + +export interface IdleAprConfig { + sourceUrl: string; + authorizationHeader: string; + tokens: { + [tokenName: string]: { + address: string; + wrapped4626Address: string; + isIbYield?: boolean; + }; + }; +} + +export interface MakerAprConfig { + sdai: string; +} + +export interface OvixAprConfig { + tokens: { + [tokenName: string]: { + yieldAddress: string; + wrappedAddress: string; + isIbYield?: boolean; + }; + }; +} + +export interface ReaperAprConfig { + subgraphSource?: { + subgraphUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isSftmX?: boolean; + isWstETH?: boolean; + isIbYield?: boolean; + }; + }; + }; + onchainSource?: { + averageAPRAcrossLastNHarvests: number; + tokens: { + [tokenName: string]: { + address: string; + isSftmX?: boolean; + isWstETH?: boolean; + isIbYield?: boolean; + }; + }; + }; +} + +export interface TetuAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + isIbYield?: boolean; + }; + }; +} + +export interface TranchessAprConfig { + sourceUrl: string; + tokens: { + [tokenName: string]: { + address: string; + underlyingAssetName: string; + isIbYield?: boolean; + }; + }; +} + +export interface YearnAprConfig { + sourceUrl: string; + isIbYield?: boolean; +} + +export interface DefaultHandlerAprConfig { + [tokenName: string]: { + sourceUrl: string; + tokenAddress: string; + path?: string; + scale?: number; + group?: string; + isIbYield?: boolean; + }; +} + +export interface FixedAprConfig { + [tokenName: string]: { + address: string; + apr: number; + group?: string; + isIbYield?: boolean; + }; +} + +export interface SiloAprConfig { + markets: string[]; +} diff --git a/modules/network/apr-config-types.ts b/modules/network/apr-config-types.ts index b55c96144..f6c605401 100644 --- a/modules/network/apr-config-types.ts +++ b/modules/network/apr-config-types.ts @@ -1,20 +1,3 @@ -import { AaveRewardsAprConfig } from '../aprs/handlers/types'; - -export interface AprHandlerConfigs { - // nestedPoolAprHandler: boolean; - // swapFeeAprHandler: boolean; - // dynamicSwapFeeAprHandler: boolean; - // gaugeAprHandler: boolean; - ybAprHandler?: YbAprConfig; - aaveRewardsAprHandler?: AaveRewardsAprConfig; - // reliquaryAprHandler?: ReliquaryAprConfig; - // beetswarsAprHandler?: BeetswarsAprConfig; - // veBalProtocolAprHandler?: VeBalProtocolAprConfig; - // veBalVotingAprHandler?: VeBalVotingAprConfig; - // morphoRewardAprHandler?: MorphоRewardAprConfig; - // quantAmmAprHandler?: QuantAmmAprConfig; -} - export interface YbAprConfig { aave?: AaveAprConfig; avalon?: AvalonAprConfig; diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index a73bdb94b..b0812356e 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -3,9 +3,9 @@ import type { PoolAprService } from '../pool/pool-types'; import type { UserStakedBalanceService } from '../user/user-types'; import type { BaseProvider } from '@ethersproject/providers'; import type { GqlChain, GqlHookType } from '../../apps/api/gql/generated-schema'; -import type { AprHandlerConfigs, YbAprConfig } from './apr-config-types'; import type { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import { SftmxSubgraphService } from '../sources/subgraphs/sftmx-subgraph/sftmx.service'; +import { AprHandlerConfigs } from '../aprs/handlers/types'; export interface NetworkConfig { data: NetworkData; diff --git a/prisma/schema/pool.prisma b/prisma/schema/pool.prisma index e8e78e265..03336282c 100644 --- a/prisma/schema/pool.prisma +++ b/prisma/schema/pool.prisma @@ -205,6 +205,9 @@ enum PrismaPoolAprType { SURPLUS_24H SURPLUS_7D SURPLUS_30D + STAKING_BOOST + MABEETS_EMISSIONS + VEBAL_EMISSIONS } enum PrismaPoolAprItemGroup { From 072ce6509200a1751023c59e22f1906cb084ce3d Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 10:08:46 +0300 Subject: [PATCH 07/37] add morpho rewards handler --- config/base.ts | 1 + config/mainnet.ts | 1 + modules/aprs/handlers/create-handlers.ts | 4 + modules/aprs/handlers/index.ts | 1 + .../aprs/handlers/morpho-apr-handler/index.ts | 1 + .../morpho-apr-handler/morpho-api-client.ts | 90 +++++++++++++++++++ .../morpho-rewards-apr-handler.ts | 52 +++++++++++ modules/aprs/handlers/types.ts | 2 +- 8 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 modules/aprs/handlers/morpho-apr-handler/index.ts create mode 100644 modules/aprs/handlers/morpho-apr-handler/morpho-api-client.ts create mode 100644 modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts diff --git a/config/base.ts b/config/base.ts index 4da944895..08a03a7ee 100644 --- a/config/base.ts +++ b/config/base.ts @@ -75,6 +75,7 @@ export default { }, }, aprHandlers: { + morphoRewardsAprHandler: true, ybAprHandler: { fluid: { url: 'https://api.fluid.instad.app/v2/lending/8453/tokens', diff --git a/config/mainnet.ts b/config/mainnet.ts index 3535ac5fc..a36c19878 100644 --- a/config/mainnet.ts +++ b/config/mainnet.ts @@ -116,6 +116,7 @@ export default { }, }, aprHandlers: { + morphoRewardsAprHandler: true, ybAprHandler: { usdl: true, morpho: { diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 093a16039..fcb63919e 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -23,6 +23,10 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.BeetswarsGaugeVotingAprHandler()); } + if (config[chain].aprHandlers.morphoRewardsAprHandler) { + handlerList.push(new handlers.MorphoRewardsAprHandler()); + } + // Add Aave API handler if configured for this chain if (config[chain].aprHandlers.aaveRewardsAprHandler) { handlerList.push( diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 4a4f21542..2321b614f 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -4,6 +4,7 @@ export { YbTokensAprHandler } from './yb-tokens'; export { AaveApiAprHandler } from './aave-api-apr'; export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr'; +export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr-handler'; // Add more handler exports as they are implemented // Example: diff --git a/modules/aprs/handlers/morpho-apr-handler/index.ts b/modules/aprs/handlers/morpho-apr-handler/index.ts new file mode 100644 index 000000000..643439c22 --- /dev/null +++ b/modules/aprs/handlers/morpho-apr-handler/index.ts @@ -0,0 +1 @@ +export * from './morpho-rewards-apr-handler'; diff --git a/modules/aprs/handlers/morpho-apr-handler/morpho-api-client.ts b/modules/aprs/handlers/morpho-apr-handler/morpho-api-client.ts new file mode 100644 index 000000000..69fa3d713 --- /dev/null +++ b/modules/aprs/handlers/morpho-apr-handler/morpho-api-client.ts @@ -0,0 +1,90 @@ +import request, { gql } from 'graphql-request'; + +const url = 'https://blue-api.morpho.org/graphql'; +const query = gql` + { + vaults(first: 1000, where: { netApy_gte: 0.00001 }) { + items { + address + asset { + address + yield { + apr + } + } + chain { + network + } + state { + fee + dailyApy + dailyNetApy + } + } + } + } +`; + +/* +Morpho APIs results are as follows: +- dailyApy: Vault APY excluding rewards, before deducting the performance fee. Also NOT including the net APY of the underlying asset. +- dailyNetApy: Vault APY including rewards and underlying yield, after deducting the performance fee. + + +We only want to get the APY for rewards as we account for underlying yield separately inside the YB APR service. +We therefore deduct the fee from the apy and subtract the asset yield apr from it. +*/ + +type Vault = { + address: string; + chain: { + network: string; + }; + asset: { + address: string; + yield?: { + apr: number; + }; + }; + state: { + fee: number; + dailyApy: number; + dailyNetApy: number; + }; +}; + +type BlueApiResponse = { + vaults: { + items: Vault[]; + }; +}; + +const mapMorphoNetworkToChain = { + ethereum: 'MAINNET', + base: 'BASE', +}; + +export const morphoApiClient = { + morphoApr: async () => { + const { + vaults: { items }, + } = await request(url, query); + + // Map apy to vault addresses + return Object.fromEntries( + items.map((vault: Vault) => [ + vault.address.toLowerCase(), + { + dailyApy: vault.state.dailyApy, + dailyNetApy: vault.state.dailyNetApy, + fee: vault.state.fee, + rewardApy: + vault.state.dailyNetApy - + vault.state.dailyApy * (1 - vault.state.fee) - + (vault.asset.yield?.apr || 0), + chain: mapMorphoNetworkToChain[vault.chain.network as keyof typeof mapMorphoNetworkToChain], + }, + ]), + ); + }, +}; diff --git a/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts b/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts new file mode 100644 index 000000000..29616195c --- /dev/null +++ b/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts @@ -0,0 +1,52 @@ +import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { morphoApiClient } from './morpho-api-client'; +import { AprHandler, PoolAPRData } from '../../types'; +// IDs can be converted to hashes for DB perf optimization +// import murmurhash from 'murmurhash'; + +export class MorphoRewardsAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'MorphoRewardsAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + // Get Morpho aprs + const morphoApr = await morphoApiClient.morphoApr(); + + // Find all pools with Morpho vault tokens + const morphoVaultAddresses = Object.keys(morphoApr); + const poolsWithMorphoTokens = pools.filter((pool) => { + return pool.tokens.find((token) => morphoVaultAddresses.includes(token.address)); + }); + + // For each of them get reward token APRs + const aprItems = poolsWithMorphoTokens.flatMap((pool) => { + const tokens = pool.tokens.filter((token) => morphoVaultAddresses.includes(token.address)); + const tvl = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); + + const vaultRewards = tokens.flatMap((token) => { + const vaultApr = morphoApr[token.address]; + const weight = token.balanceUSD / tvl || 0; + + return { + // id: murmurhash.v3(`${pool.id}-${token.address}-${rewardToken.address}`).toString(36), + id: `${pool.id}-morphovault-rewards`, + chain: pool.chain, + poolId: pool.id, + title: 'MORPHO VAULT APR', + apr: vaultApr.rewardApy * weight, + type: PrismaPoolAprType.MERKL, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }; + }); + + return vaultRewards; + }); + + return aprItems; + } +} diff --git a/modules/aprs/handlers/types.ts b/modules/aprs/handlers/types.ts index a22ba0160..f0a2a6d4d 100644 --- a/modules/aprs/handlers/types.ts +++ b/modules/aprs/handlers/types.ts @@ -8,7 +8,7 @@ export interface AprHandlerConfigs { maBeetsAprHandler?: MaBeetsAprConfig; // veBalProtocolAprHandler?: VeBalProtocolAprConfig; // veBalVotingAprHandler?: VeBalVotingAprConfig; - // morphoRewardAprHandler?: MorphоRewardAprConfig; + morphoRewardsAprHandler?: boolean; // quantAmmAprHandler?: QuantAmmAprConfig; } From c606276e51374738bd351e59dde19c81167408de Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 10:13:01 +0300 Subject: [PATCH 08/37] add nested pool apr service --- modules/aprs/handlers/create-handlers.ts | 1 + modules/aprs/handlers/index.ts | 1 + .../handlers/nested-pool-apr-handler/index.ts | 1 + .../nested-pool-apr.service.ts | 81 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 modules/aprs/handlers/nested-pool-apr-handler/index.ts create mode 100644 modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index fcb63919e..cb5737abf 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -13,6 +13,7 @@ export function createHandlers(chain: Chain): AprHandler[] { // Default handlers for all of the chains handlerList.push(new handlers.SwapFeeAprHandler()); handlerList.push(new handlers.DynamicSwapFeeAprHandler()); + handlerList.push(new handlers.NestedPoolAprHandler()); if (config[chain].aprHandlers.ybAprHandler) { handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 2321b614f..6697db9ea 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -5,6 +5,7 @@ export { AaveApiAprHandler } from './aave-api-apr'; export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr'; export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr-handler'; +export { NestedPoolAprHandler } from './nested-pool-apr-handler'; // Add more handler exports as they are implemented // Example: diff --git a/modules/aprs/handlers/nested-pool-apr-handler/index.ts b/modules/aprs/handlers/nested-pool-apr-handler/index.ts new file mode 100644 index 000000000..33293bade --- /dev/null +++ b/modules/aprs/handlers/nested-pool-apr-handler/index.ts @@ -0,0 +1 @@ +export * from './nested-pool-apr.service'; diff --git a/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts b/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts new file mode 100644 index 000000000..5d7c22fb7 --- /dev/null +++ b/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts @@ -0,0 +1,81 @@ +import { prisma } from '../../../../prisma/prisma-client'; +import { collectsYieldFee } from '../../../pool/lib/pool-utils'; +import { AprHandler, PoolAPRData } from '../../types'; +import { PrismaPoolAprItem } from '@prisma/client'; + +export class NestedPoolAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'NestedPoolAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const aprItems: Omit[] = []; + + for (const pool of pools) { + const protocolYieldFeePercentage = parseFloat(pool.dynamicData?.protocolYieldFee || '0'); + const tokens = pool.tokens.filter((token) => { + // exclude the phantom bpt pool token itself + if (token.address === pool.address) { + return false; + } + }); + + const poolIds = tokens.map((token) => token.nestedPool?.id || ''); + // swap fee and IB yield is also earned on the parent pool + const aprItems = await prisma.prismaPoolAprItem.findMany({ + where: { + poolId: { in: poolIds }, + type: { in: ['IB_YIELD', 'SWAP_FEE_24H'] }, + chain: pool.chain, + }, + }); + + for (const token of tokens) { + const tokenAprItems = aprItems.filter((item) => item.poolId === token.nestedPoolId); + + if ( + !pool.dynamicData || + !token.nestedPool || + !token.nestedPool.type || + token.balanceUSD === 0 || + pool.dynamicData.totalLiquidity === 0 + ) { + continue; + } + + for (const aprItem of tokenAprItems) { + const itemId = `${pool.id}-${aprItem.id}`; + //scale the apr as a % of total liquidity + + const apr = aprItem.apr * (token.balanceUSD / pool.dynamicData.totalLiquidity); + let userApr = apr; + + if ( + collectsYieldFee(pool) && + // nested tokens/bpts that dont have a rate provider, we don't take any fees + token.priceRate !== '1.0' + ) { + userApr = userApr * (1 - protocolYieldFeePercentage); + } + + const title = aprItem.type === 'SWAP_FEE_24H' ? `${token.token.symbol} APR` : aprItem.title; + + aprItems.push({ + id: itemId, + chain: pool.chain, + poolId: pool.id, + apr: userApr, + title: title, + type: aprItem.type, + group: aprItem.group, + rewardTokenAddress: aprItem.rewardTokenAddress, + rewardTokenSymbol: aprItem.rewardTokenSymbol, + }); + } + } + } + return aprItems; + } +} From 8866cc29d202c969931c31482047f3af8aadc072 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 10:17:31 +0300 Subject: [PATCH 09/37] add quant amm apr handler --- modules/aprs/handlers/create-handlers.ts | 1 + modules/aprs/handlers/index.ts | 1 + modules/aprs/handlers/quant-amm-apr/index.ts | 1 + .../quant-amm-apr/quant-amm-apr-handler.ts | 141 ++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 modules/aprs/handlers/quant-amm-apr/index.ts create mode 100644 modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index cb5737abf..b91c88f55 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -14,6 +14,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.SwapFeeAprHandler()); handlerList.push(new handlers.DynamicSwapFeeAprHandler()); handlerList.push(new handlers.NestedPoolAprHandler()); + handlerList.push(new handlers.QuantAmmAprHandler()); if (config[chain].aprHandlers.ybAprHandler) { handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 6697db9ea..5f82b3895 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -6,6 +6,7 @@ export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr'; export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr-handler'; export { NestedPoolAprHandler } from './nested-pool-apr-handler'; +export { QuantAmmAprHandler } from './quant-amm-apr'; // Add more handler exports as they are implemented // Example: diff --git a/modules/aprs/handlers/quant-amm-apr/index.ts b/modules/aprs/handlers/quant-amm-apr/index.ts new file mode 100644 index 000000000..e46fcff7c --- /dev/null +++ b/modules/aprs/handlers/quant-amm-apr/index.ts @@ -0,0 +1 @@ +export * from './quant-amm-apr-handler'; diff --git a/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts b/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts new file mode 100644 index 000000000..d59e095cd --- /dev/null +++ b/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts @@ -0,0 +1,141 @@ +import _ from 'lodash'; +import { prisma } from '../../../../prisma/prisma-client'; +import moment from 'moment'; +import { PrismaPoolAprItem, PrismaTokenPrice } from '@prisma/client'; +import { AprHandler, PoolAPRData } from '../../types'; + +export class QuantAmmAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'QuantAmmAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const quantAmmPools = pools.filter((pool) => pool.type === 'QUANT_AMM_WEIGHTED'); + + const aprItems: Omit[] = []; + + if (quantAmmPools.length === 0) { + return aprItems; + } + const chain = quantAmmPools[0].chain; + + const poolAddresses = pools.map((pool) => pool.address.toLowerCase()); + + const tokensToPrice = pools + .map((pool) => { + return pool.tokens.map((token) => token.address.toLowerCase()); + }) + .flat(); + + const uniqueTokensToPrice = _.uniq([...tokensToPrice, ...poolAddresses]); + + const midnightOneMonthAgo = moment().utc().startOf('day').subtract(30, 'days').unix(); + + const prices = await prisma.prismaTokenPrice.findMany({ + where: { + tokenAddress: { in: uniqueTokensToPrice }, + chain: chain, + timestamp: { gte: midnightOneMonthAgo }, + }, + orderBy: { timestamp: 'asc' }, + }); + + const currentPrices = await prisma.prismaTokenCurrentPrice.findMany({ + where: { + tokenAddress: { in: uniqueTokensToPrice }, + chain: chain, + }, + }); + + const pricesByToken = _.groupBy(prices, 'tokenAddress'); + const pricesByTimestamp = _.groupBy(prices, 'timestamp'); + + for (const pool of pools) { + const poolPrices = pricesByToken[pool.address.toLowerCase()]; + + if (!poolPrices || poolPrices.length === 0 || !pool.dynamicData?.totalLiquidity) { + continue; + } + + const poolTokenAddresses = pool.tokens.map((token) => token.address.toLowerCase()); + + // find oldest timestamp that has all prices + let startTokenPrices: PrismaTokenPrice[] = []; + let oldestIndexForAllPrices = 0; + for (oldestIndexForAllPrices = 0; oldestIndexForAllPrices < poolPrices.length; oldestIndexForAllPrices++) { + const poolPrice = poolPrices[oldestIndexForAllPrices]; + const foundPrices = pricesByTimestamp[poolPrice.timestamp].filter( + (price) => + price.tokenAddress !== pool.address.toLowerCase() && + poolTokenAddresses.includes(price.tokenAddress), + ); + if (foundPrices.length === poolTokenAddresses.length) { + startTokenPrices = foundPrices; + break; + } + } + + if (startTokenPrices.length === 0) { + console.error(`Quant AMM APR: No start prices found for pool ${pool.id} on chain ${chain}.`); + continue; + } + + const oldestEntryPoolPrice = poolPrices[oldestIndexForAllPrices]; + + const startLpPrice = oldestEntryPoolPrice; + + const endTokenPrices = currentPrices.filter( + (price) => + price.tokenAddress !== pool.address.toLowerCase() && + poolTokenAddresses.includes(price.tokenAddress), + ); + + if (endTokenPrices.length === 0) { + console.error(`Quant AMM APR: No end prices found for pool ${pool.id} on chain ${chain}.`); + } + + if (startTokenPrices.length !== endTokenPrices.length) { + console.error( + `Quant AMM APR: Mismatched price data for pool ${pool.id} on chain ${chain}. Start prices: ${startTokenPrices.length}, End prices: ${endTokenPrices.length}`, + ); + continue; + } + + const endLpPrice = currentPrices.filter((price) => price.tokenAddress === pool.address.toLowerCase())[0]; + + if (!endLpPrice) { + console.error(`Quant AMM APR: No end LP price found for pool ${pool.id} on chain ${chain}.`); + } + + const weight = 1 / pool.tokens.length; + + const sortedStartTokenPrices = _.sortBy(startTokenPrices, (price) => price.tokenAddress); + const sortedEndTokenPrices = _.sortBy(endTokenPrices, (price) => price.tokenAddress); + + const priceRatios = sortedEndTokenPrices.map((end, i) => end.price / sortedStartTokenPrices[i].price); + + const endWeightedValue = + startLpPrice.price * priceRatios.reduce((acc, ratio) => acc * Math.pow(ratio, weight), 1); + + const relativeReturn = endLpPrice.price / endWeightedValue - 1; + + const totalYearlyReturn = relativeReturn * 12; + + aprItems.push({ + id: `${pool.id}-quant-amm-apr`, + chain: chain, + poolId: pool.id, + apr: totalYearlyReturn, + title: 'Quant AMM APR', + type: 'QUANT_AMM_UPLIFT', + group: null, + rewardTokenAddress: null, + rewardTokenSymbol: null, + }); + } + + return aprItems; + } +} From 4a29981a17fe910d6b3fd2b2e37b0750458cc326 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 10:31:31 +0300 Subject: [PATCH 10/37] liquidity gauge apr handler --- modules/aprs/handlers/create-handlers.ts | 1 + modules/aprs/handlers/index.ts | 5 +- .../handlers/liquidity-gauge-apr/index.ts | 1 + .../liquidity-gauge-apr-handler.ts | 132 ++++++++++++++++++ 4 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 modules/aprs/handlers/liquidity-gauge-apr/index.ts create mode 100644 modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index b91c88f55..57e224aa6 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -15,6 +15,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.DynamicSwapFeeAprHandler()); handlerList.push(new handlers.NestedPoolAprHandler()); handlerList.push(new handlers.QuantAmmAprHandler()); + handlerList.push(new handlers.LiquidityGaugeAprHandler()); if (config[chain].aprHandlers.ybAprHandler) { handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 5f82b3895..03ba2cd3f 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -7,9 +7,6 @@ export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr-handler'; export { NestedPoolAprHandler } from './nested-pool-apr-handler'; export { QuantAmmAprHandler } from './quant-amm-apr'; - -// Add more handler exports as they are implemented -// Example: -// export { GaugeAprHandler } from './gauge-apr-handler'; +export { LiquidityGaugeAprHandler } from './liquidity-gauge-apr'; export { createHandlers } from './create-handlers'; diff --git a/modules/aprs/handlers/liquidity-gauge-apr/index.ts b/modules/aprs/handlers/liquidity-gauge-apr/index.ts new file mode 100644 index 000000000..59473fc15 --- /dev/null +++ b/modules/aprs/handlers/liquidity-gauge-apr/index.ts @@ -0,0 +1 @@ +export * from './liquidity-gauge-apr-handler'; diff --git a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts new file mode 100644 index 000000000..b586c68aa --- /dev/null +++ b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts @@ -0,0 +1,132 @@ +/** + * This service calculates the APR for a pool based on the gauge rewards + * + * Definitions: + * The “working supply” of the gauge - the effective total LP token amount after all deposits have been boosted. + * "Working balance" is 40% of a user balance in a gauge - used only for BAL rewards on v2 gauges on child gauges or on mainnet + */ +import { secondsPerYear } from '../../../common/time'; +import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType } from '@prisma/client'; +import { prisma } from '../../../../prisma/prisma-client'; +import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; +import { tokenService } from '../../../token/token.service'; +import { AprHandler, PoolAPRData } from '../../types'; + +export class LiquidityGaugeAprHandler implements AprHandler { + private readonly MAX_VEBAL_BOOST = 2.5; + + constructor() {} + + public getAprServiceName(): string { + return 'LiquidityGaugeAprHandler'; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const chain = pools[0].chain; + + // Get the data + const tokenPrices = await tokenService.getTokenPrices(chain); + + const aprItems: Omit[] = []; + + for (const pool of pools) { + const gauge = pool.staking.find((s) => s.type === 'GAUGE')?.gauge; + + if (!gauge || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { + continue; + } + + // Get token rewards per year with data needed for the DB + const rewards = await Promise.allSettled( + gauge.rewards.map(async ({ id, tokenAddress, rewardPerSecond, isVeBalemissions }) => { + const price = tokenService.getPriceForToken(tokenPrices, tokenAddress, pool.chain); + if (!price) { + return Promise.reject(`Price not found for ${tokenAddress}`); + } + + let definition; + try { + definition = await prisma.prismaToken.findUniqueOrThrow({ + where: { address_chain: { address: tokenAddress, chain: pool.chain } }, + }); + } catch (e) { + //we don't have the reward token added as a token, only happens for testing tokens + return Promise.reject('Definition not found'); + } + + return { + id: id, + address: tokenAddress, + symbol: definition.symbol, + rewardPerYear: parseFloat(rewardPerSecond) * secondsPerYear * price, + isVeBalemissions: isVeBalemissions, + }; + }), + ); + + // Calculate APRs + const totalShares = parseFloat(pool.dynamicData.totalShares); + const gaugeTotalShares = parseFloat(gauge.totalSupply); + const bptPrice = pool.dynamicData.totalLiquidity / totalShares; + const gaugeTvl = gaugeTotalShares * bptPrice; + const workingSupply = parseFloat(gauge.workingSupply); + + for (const reward of rewards) { + if (reward.status === 'rejected') { + console.error( + `Error: Failed to get reward data for ${gauge.id} on chain ${pool.chain}: ${reward.reason}`, + ); + continue; + } + + const { address, symbol, rewardPerYear, isVeBalemissions } = reward.value; + + const itemData: PrismaPoolAprItem = { + id: `${reward.value.id}-${symbol}-apr`, + chain: pool.chain, + poolId: pool.id, + title: `${symbol} reward APR`, + group: null, + apr: 0, + rewardTokenAddress: address, + rewardTokenSymbol: symbol, + type: isVeBalemissions ? PrismaPoolAprType.VEBAL_EMISSIONS : PrismaPoolAprType.THIRD_PARTY_REWARD, + }; + + // veBAL rewards have a min and max, we create two items for them + if (isVeBalemissions && (pool.chain === 'MAINNET' || gauge.version === 2)) { + let minApr = 0; + if (gaugeTvl > 0) { + if (workingSupply > 0 && gaugeTotalShares > 0) { + minApr = (((gaugeTotalShares * 0.4) / workingSupply) * rewardPerYear) / gaugeTvl; + } else { + minApr = rewardPerYear / gaugeTvl; + } + } + + itemData.apr = minApr; + aprItems.push(itemData); + + aprItems.push({ + id: `${itemData.id}-boost`, + chain: pool.chain, + poolId: pool.id, + title: `${symbol} reward APR`, + group: null, + apr: minApr * this.MAX_VEBAL_BOOST, + rewardTokenAddress: address, + rewardTokenSymbol: symbol, + type: PrismaPoolAprType.STAKING_BOOST, + }); + } else { + itemData.apr = gaugeTvl > 0 ? rewardPerYear / gaugeTvl : 0; + + aprItems.push(itemData); + } + } + } + return aprItems; + } +} From 1e2257daf6df47b3eb75adcd2ad6ca4d7e25d242 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 10:33:44 +0300 Subject: [PATCH 11/37] rename calculator to handler --- modules/aprs/README.md | 10 +++++----- modules/aprs/apr-manager.ts | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/aprs/README.md b/modules/aprs/README.md index 8479be088..84bfabc12 100644 --- a/modules/aprs/README.md +++ b/modules/aprs/README.md @@ -17,8 +17,8 @@ The module follows a layered architecture: 1. **Repository Layer** - Handles data access (`PoolAprRepository`) 2. **Handlers Layer** - Contains pure calculation logic (`AprHandler` implementations) -3. **Configuration Layer** - Defines which calculators to use for each chain -4. **Manager Layer** - Coordinates calculators and persistence (`AprManager`) +3. **Configuration Layer** - Defines which handlers to use for each chain +4. **Manager Layer** - Coordinates handlers and persistence (`AprManager`) 5. **Service Layer** - Provides the public API (`AprService`) ## Usage @@ -67,12 +67,12 @@ bun run modules/examples/integration.ts MAINNET 0x1234... The module uses a declarative approach to configure which handlers are used for each chain: 1. **Configuration Definition**: Each chain has a object with handlers configuration -2. **Calculator Factories**: Factory functions create calculator instances with the right parameters +2. **Handler Factories**: Factory functions create handlers instances with the right parameters This approach makes it easy to: -- Add or remove calculators for specific chains -- Configure calculator parameters +- Add or remove handlers for specific chains +- Configure handlers parameters - Understand at a glance which chains use which handlers ### Adding New Handlers diff --git a/modules/aprs/apr-manager.ts b/modules/aprs/apr-manager.ts index dda89f15e..636ce97d8 100644 --- a/modules/aprs/apr-manager.ts +++ b/modules/aprs/apr-manager.ts @@ -19,22 +19,22 @@ export class AprManager { return []; } - // Get all APR items from all calculators + // Get all APR items from all handlers const allAprItems: Omit[] = []; - const failedCalculators: string[] = []; + const failedHandlers: string[] = []; - for (const calculator of this.aprHandlers) { + for (const handler of this.aprHandlers) { try { - const items = await calculator.calculateAprForPools(pools); + const items = await handler.calculateAprForPools(pools); allAprItems.push(...items); } catch (e) { - console.error(`Error during APR calculation in ${calculator.getAprServiceName()}:`, e); - failedCalculators.push(calculator.getAprServiceName()); + console.error(`Error during APR calculation in ${handler.getAprServiceName()}:`, e); + failedHandlers.push(handler.getAprServiceName()); } } - if (failedCalculators.length > 0) { - console.warn(`The following APR calculators failed: ${failedCalculators.join(', ')}`); + if (failedHandlers.length > 0) { + console.warn(`The following APR handlers failed: ${failedHandlers.join(', ')}`); } return allAprItems; From f8f62147c09e7f9e9168a8276236a7ecb7c1ed4c Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 11:03:05 +0300 Subject: [PATCH 12/37] add vebal apr handlers --- modules/aprs/handlers/create-handlers.ts | 10 +- modules/aprs/handlers/index.ts | 1 + .../swap-fee-apr/swap-fee-apr-handler.test.ts | 2 +- modules/aprs/handlers/vebal-apr/index.ts | 2 + .../vebal-apr/vebal-protocol-apr-handler.ts | 144 +++++++++++++++++ .../vebal-apr/vebal-voting-apr-handler.ts | 145 ++++++++++++++++++ 6 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 modules/aprs/handlers/vebal-apr/index.ts create mode 100644 modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts create mode 100644 modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 57e224aa6..27eb1a348 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -17,8 +17,10 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.QuantAmmAprHandler()); handlerList.push(new handlers.LiquidityGaugeAprHandler()); - if (config[chain].aprHandlers.ybAprHandler) { - handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); + // Mainnet specific handlers + if (chain === Chain.MAINNET) { + handlerList.push(new handlers.VeBalProtocolAprHandler()); + handlerList.push(new handlers.VeBalVotingAprHandler()); } if (config[chain].aprHandlers.maBeetsAprHandler) { @@ -26,6 +28,10 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.BeetswarsGaugeVotingAprHandler()); } + if (config[chain].aprHandlers.ybAprHandler) { + handlerList.push(new handlers.YbTokensAprHandler(config[chain].aprHandlers.ybAprHandler, chain)); + } + if (config[chain].aprHandlers.morphoRewardsAprHandler) { handlerList.push(new handlers.MorphoRewardsAprHandler()); } diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 03ba2cd3f..3285f9b30 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -8,5 +8,6 @@ export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr export { NestedPoolAprHandler } from './nested-pool-apr-handler'; export { QuantAmmAprHandler } from './quant-amm-apr'; export { LiquidityGaugeAprHandler } from './liquidity-gauge-apr'; +export { VeBalProtocolAprHandler, VeBalVotingAprHandler } from './vebal-apr'; export { createHandlers } from './create-handlers'; diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts index b10b86ecb..366220abd 100644 --- a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.test.ts @@ -1,7 +1,7 @@ import { expect, test, describe, beforeEach } from 'vitest'; import { Chain, PrismaPoolAprType, PrismaPoolType } from '@prisma/client'; import { SwapFeeAprHandler } from './swap-fee-apr-handler'; -import { PoolAPRData } from '../../apr-repository'; +import { PoolAPRData } from '../../types'; describe('SwapFeeAprCalculator', () => { let calculator: SwapFeeAprHandler; diff --git a/modules/aprs/handlers/vebal-apr/index.ts b/modules/aprs/handlers/vebal-apr/index.ts new file mode 100644 index 000000000..cb8ce1692 --- /dev/null +++ b/modules/aprs/handlers/vebal-apr/index.ts @@ -0,0 +1,2 @@ +export * from './vebal-protocol-apr-handler'; +export * from './vebal-voting-apr-handler'; diff --git a/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts b/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts new file mode 100644 index 000000000..719770b4b --- /dev/null +++ b/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts @@ -0,0 +1,144 @@ +import { prisma } from '../../../../prisma/prisma-client'; +import { multicallViem } from '../../../web3/multicaller-viem'; +import { mainnet } from 'viem/chains'; +import { createPublicClient, formatUnits, http, parseAbi } from 'viem'; +import { AprHandler, PoolAPRData } from '../../types'; +import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import config from '../../../../config'; + +const feeDistributorAbi = parseAbi([ + 'function getTokensDistributedInWeek(address token, uint timestamp) view returns (uint)', + 'function claimTokens(address user, address[] tokens) returns (uint256[])', + 'function claimToken(address user, address token) returns (uint256)', +]); + +const veBalAbi = parseAbi(['function totalSupply() view returns (uint)']); + +const feeDistributorAddress = '0xd3cf852898b21fc233251427c2dc93d3d604f3bb'; +const balAddress = '0xba100000625a3754423978a60c9317c58a424e3d'; +const vebalPoolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; +const vebalPoolAddress = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56'; +const vebalAddress = '0xc128a9954e6c874ea3d62ce62b468ba073093f25'; +const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; +const aprItemId = `${vebalPoolId}-protocol-apr`; +const chain = 'MAINNET'; + +const getPreviousWeek = (fromJSTimestamp: number): number => { + const weeksToGoBack = 1; + const midnight = new Date(Math.floor(fromJSTimestamp)); + midnight.setUTCHours(0); + midnight.setUTCMinutes(0); + midnight.setUTCSeconds(0); + midnight.setUTCMilliseconds(0); + + let daysSinceThursday = midnight.getUTCDay() - 4; + if (daysSinceThursday < 0) daysSinceThursday += 7; + + daysSinceThursday = daysSinceThursday + weeksToGoBack * 7; + + return Math.floor(midnight.getTime() / 1000) - daysSinceThursday * 86400; +}; + +const fetchRevenue = async (timestamp: number, rpcUrl: string) => { + const previousWeek = getPreviousWeek(timestamp); + + const viemClient = createPublicClient({ + chain: mainnet, + transport: http(rpcUrl), + }); + + const results = await multicallViem(viemClient, [ + { + path: 'balAmount', + address: feeDistributorAddress, + abi: feeDistributorAbi, + functionName: 'getTokensDistributedInWeek', + args: [balAddress, previousWeek], + }, + { + path: 'usdcAmount', + address: feeDistributorAddress, + abi: feeDistributorAbi, + functionName: 'getTokensDistributedInWeek', + args: [usdcAddress, previousWeek], + }, + { + path: 'veBalSupply', + address: vebalAddress, + abi: veBalAbi, + functionName: 'totalSupply', + }, + ]); + + const data = { + balAmount: parseFloat(formatUnits(results.balAmount, 18)), + usdcAmount: parseFloat(formatUnits(results.usdcAmount, 6)), + veBalSupply: parseFloat(formatUnits(results.veBalSupply, 18)), + usdcPrice: parseFloat('1.0'), + balAddress: balAddress, + }; + + return data; +}; + +export class VeBalProtocolAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'VeBalProtocolAprHandler'; + } + + async getApr(): Promise { + const revenue = await fetchRevenue(Date.now(), config['MAINNET'].rpcUrl); + + // Prices + const balPrice = await prisma.prismaTokenCurrentPrice.findFirst({ + where: { tokenAddress: balAddress, chain: 'MAINNET' }, + select: { price: true }, + }); + + const usdcPrice = await prisma.prismaTokenCurrentPrice.findFirst({ + where: { tokenAddress: usdcAddress, chain: 'MAINNET' }, + select: { price: true }, + }); + + const bptPrice = await prisma.prismaTokenCurrentPrice.findFirst({ + where: { tokenAddress: vebalPoolAddress, chain: 'MAINNET' }, + select: { price: true }, + }); + + if (!balPrice || !usdcPrice || !bptPrice) { + return 0; + } + + const lastWeekBalRevenue = revenue.balAmount * balPrice.price; + const lastWeekUsdcRevenue = revenue.usdcAmount * usdcPrice.price; + + const dailyRevenue = (lastWeekBalRevenue + lastWeekUsdcRevenue) / 7; + const apr = (365 * dailyRevenue) / (bptPrice.price * revenue.veBalSupply); + + return apr; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + if (!pools.map((pool) => pool.id).includes(vebalPoolId)) { + return []; + } + + const apr = await this.getApr(); + + return [ + { + id: aprItemId, + chain: chain, + poolId: vebalPoolId, + apr, + title: 'Protocol APR', + type: PrismaPoolAprType.LOCKING, + rewardTokenAddress: balAddress, + rewardTokenSymbol: 'BAL', + group: null, + }, + ]; + } +} diff --git a/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts b/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts new file mode 100644 index 000000000..59f21f57c --- /dev/null +++ b/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts @@ -0,0 +1,145 @@ +import { Chain, PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { prisma } from '../../../../prisma/prisma-client'; +import { AprHandler, PoolAPRData } from '../../types'; + +const HIDDEN_HAND_API_URL = 'https://api.hiddenhand.finance/proposal/balancer'; +const veBalPoolAddress = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56'; + +const veBalPoolId = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; +const aprItemId = `${veBalPoolId}-voting-apr`; +const chain = 'MAINNET'; + +type HiddenHandResponse = { + error: boolean; + data: { + poolId: string; + proposal: string; + proposalHash: string; + title: string; + proposalDeadline: number; + totalValue: number; + maxTotalValue: number; + voteCount: number; + valuePerVote: number; + maxValuePerVote: number; + bribes: { + token: string; + symbol: string; + decimals: number; + value: number; + maxValue: number; + amount: number; + maxTokensPerVote: number; + briber: string; + periodIndex: number; + chainId: number; + }[]; + }[]; +}; + +const fetchHiddenHandRound = async (timestamp?: number) => { + const response = await fetch(`${HIDDEN_HAND_API_URL}/${timestamp || ''}`); + const data = (await response.json()) as HiddenHandResponse; + if (data.error) { + throw new Error('Failed to fetch voting APR'); + } + + // Get sum of all incentivized votes and total value + const total = data.data.reduce((acc, proposal) => acc + proposal.totalValue, 0); + const votes = data.data + .filter((proposal) => proposal.totalValue > 0) + .reduce((acc, proposal) => acc + proposal.voteCount, 0); + + return { total, votes, timestamp: data.data[0].proposalDeadline }; +}; + +export const getHiddenHandAPR = async (timestamp: number) => { + const round = await fetchHiddenHandRound(timestamp); + + // Debugging purposes + console.log('Hiddenhand round', timestamp, round.timestamp, round.total, round.votes); + + timestamp = round.timestamp; + + const avgValuePerVote = round.total / round.votes; + + let veBalPrice; + // When the timestamp is older than 24 hours, we can fetch the historical price + if (timestamp < Math.ceil(+Date.now() / 1000) - 86400) { + veBalPrice = await prisma.prismaTokenPrice.findFirst({ + where: { + tokenAddress: veBalPoolAddress, + chain: Chain.MAINNET, + timestamp, + }, + }); + } + // Otherwise we fetch the current price + else { + veBalPrice = await prisma.prismaTokenCurrentPrice.findFirst({ + where: { + tokenAddress: veBalPoolAddress, + chain: Chain.MAINNET, + }, + }); + } + + if (!veBalPrice) { + throw new Error('Failed to fetch veBAL price'); + } + + const apr = (avgValuePerVote * 52) / veBalPrice.price; + + return apr; +}; + +export class VeBalVotingAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'VeBalVotingAprHandler'; + } + + async getApr(): Promise { + // Get APRs for last 3 weeks, if available + const timestamp = (await fetchHiddenHandRound()).timestamp; + + const aprs = await Promise.allSettled([ + getHiddenHandAPR(timestamp - 1 * 604800), + getHiddenHandAPR(timestamp - 2 * 604800), + getHiddenHandAPR(timestamp - 3 * 604800), + ]); + + // Average successfully fetched APRs + const avg = aprs + .filter((apr): apr is PromiseFulfilledResult => apr.status === 'fulfilled') + .map((apr) => apr.value); + + if (avg.length === 0) { + throw new Error('Failed to fetch APRs'); + } + + return avg.reduce((acc, val) => acc + val, 0) / avg.length; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + if (!pools.map((pool) => pool.id).includes(veBalPoolId)) { + return []; + } + const apr = await this.getApr(); + + return [ + { + id: aprItemId, + chain, + poolId: veBalPoolId, + apr, + title: 'Voting APR', + type: PrismaPoolAprType.VOTING, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }, + ]; + } +} From 816bffb31d38c01bff91785894405c8a2fc2d4da Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 11:20:32 +0300 Subject: [PATCH 13/37] remove config from aave rewards and enable it on chains --- config/arbitrum.ts | 1 + config/avalanche.ts | 2 ++ config/base.ts | 1 + config/gnosis.ts | 1 + config/mainnet.ts | 1 + .../handlers/aave-api-apr/aave-api-apr-handler.test.ts | 9 ++++----- .../aprs/handlers/aave-api-apr/aave-api-apr-handler.ts | 9 ++++----- modules/aprs/handlers/aave-api-apr/aave-chan-client.ts | 10 ++++------ modules/aprs/handlers/create-handlers.ts | 6 ++---- modules/aprs/handlers/types.ts | 6 +----- 10 files changed, 21 insertions(+), 25 deletions(-) diff --git a/config/arbitrum.ts b/config/arbitrum.ts index 6ddbd39f5..14d689007 100644 --- a/config/arbitrum.ts +++ b/config/arbitrum.ts @@ -73,6 +73,7 @@ export default { multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, aprHandlers: { + aaveRewardsAprHandler: true, ybAprHandler: { usdl: true, teth: { diff --git a/config/avalanche.ts b/config/avalanche.ts index 9de8d0ef2..53ed743c1 100644 --- a/config/avalanche.ts +++ b/config/avalanche.ts @@ -1,3 +1,4 @@ +import { tr } from '@faker-js/faker'; import { env } from '../apps/env'; import { DeploymentEnv, NetworkData } from '../modules/network/network-config-types'; @@ -73,6 +74,7 @@ export default { multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 2, aprHandlers: { + aaveRewardsAprHandler: true, ybAprHandler: { aave: { v3: { diff --git a/config/base.ts b/config/base.ts index 08a03a7ee..8b54268aa 100644 --- a/config/base.ts +++ b/config/base.ts @@ -76,6 +76,7 @@ export default { }, aprHandlers: { morphoRewardsAprHandler: true, + aaveRewardsAprHandler: true, ybAprHandler: { fluid: { url: 'https://api.fluid.instad.app/v2/lending/8453/tokens', diff --git a/config/gnosis.ts b/config/gnosis.ts index a685f4933..c309a5dd6 100644 --- a/config/gnosis.ts +++ b/config/gnosis.ts @@ -72,6 +72,7 @@ export default { multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 1, aprHandlers: { + aaveRewardsAprHandler: true, ybAprHandler: { stakewise: { url: 'https://gnosis-graph.stakewise.io/subgraphs/name/stakewise/stakewise', diff --git a/config/mainnet.ts b/config/mainnet.ts index a36c19878..ae6b46baf 100644 --- a/config/mainnet.ts +++ b/config/mainnet.ts @@ -117,6 +117,7 @@ export default { }, aprHandlers: { morphoRewardsAprHandler: true, + aaveRewardsAprHandler: true, ybAprHandler: { usdl: true, morpho: { diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts index 44c97e3d9..a6988bb52 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.test.ts @@ -3,7 +3,6 @@ import { Chain } from '@prisma/client'; import { AaveApiAprHandler } from './aave-api-apr-handler'; import { AaveChanClientInterface } from './aave-chan-client'; import type { PoolAPRData } from '../../types'; -import { AaveRewardsAprConfig } from '../types'; describe('AaveApiAprHandler', () => { // Mock implementation of the client @@ -17,14 +16,14 @@ describe('AaveApiAprHandler', () => { let handler: AaveApiAprHandler; let mockPool: PoolAPRData; - const mockConfig: AaveRewardsAprConfig = { chainId: 1 }; // Mainnet + const mockChainId = '1'; // Mainnet beforeEach(() => { // Reset mocks vi.clearAllMocks(); // Create handler with mocked client - handler = new AaveApiAprHandler(mockConfig, mockClient); + handler = new AaveApiAprHandler(mockChainId, mockClient); // Create a mock pool with tokens for testing mockPool = { @@ -193,8 +192,8 @@ describe('AaveApiAprHandler', () => { test('should handle non-mainnet chains', async () => { // Create handler for Arbitrum - const arbitrumConfig: AaveRewardsAprConfig = { chainId: 42161 }; - const arbitrumHandler = new AaveApiAprHandler(arbitrumConfig, mockClient); + const arbitrumChainId = '42161'; + const arbitrumHandler = new AaveApiAprHandler(arbitrumChainId, mockClient); // Mock pool for Arbitrum const arbitrumPool = { diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts index 689a853f4..65334ae5c 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts @@ -2,7 +2,6 @@ import _ from 'lodash'; import { Chain, PrismaPoolAprItem } from '@prisma/client'; import { AprHandler, PoolAPRData } from '../../types'; import { AaveChanClientInterface, AaveChanResponse, AaveChanClient } from './aave-chan-client'; -import { AaveRewardsAprConfig } from '../types'; /** * Implementation of AprHandler for Aave API @@ -10,9 +9,9 @@ import { AaveRewardsAprConfig } from '../types'; export class AaveApiAprHandler implements AprHandler { private client: AaveChanClientInterface; - constructor(private readonly config: AaveRewardsAprConfig, injectedClient?: AaveChanClientInterface) { + constructor(private readonly chainId: string, injectedClient?: AaveChanClientInterface) { // Create a default client if not present - this.client = injectedClient || new AaveChanClient(this.config); + this.client = injectedClient || new AaveChanClient(chainId); } public getAprServiceName(): string { @@ -25,12 +24,12 @@ export class AaveApiAprHandler implements AprHandler { const aprItems: Omit[] = []; // Fetch incentives for this chain - const aaveIncentives = await this.client.fetchIncentives(this.config.chainId); + const aaveIncentives = await this.client.fetchIncentives(this.chainId); const incentiveItems = await this.processIncentives(pools, aaveIncentives); aprItems.push(...incentiveItems); // For mainnet, also fetch prime instance items - if (this.config.chainId === 1) { + if (this.chainId === `1`) { const primeIncentives = await this.client.fetchPrimeIncentives(); const primeItems = await this.processIncentives(pools, primeIncentives); aprItems.push(...primeItems); diff --git a/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts index 579feccdf..af22d286e 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-chan-client.ts @@ -1,5 +1,3 @@ -import { AaveRewardsAprConfig } from '../types'; - /** * Represents data for a token in Aave reserves */ @@ -47,7 +45,7 @@ export interface AaveChanClientInterface { * @param chainId Chain ID to fetch incentives for * @returns Promise with the incentives data */ - fetchIncentives(chainId: number): Promise; + fetchIncentives(chainId: string): Promise; /** * Fetch prime incentives (Lido on Mainnet) @@ -63,14 +61,14 @@ export interface AaveChanClientInterface { export class AaveChanClient implements AaveChanClientInterface { private readonly baseUrl = 'https://apps.aavechan.com/api/aave-all-incentives?chainId='; - constructor(private readonly config: AaveRewardsAprConfig) {} + constructor(private readonly chainId: string) {} /** * Fetch incentives for a specific chain * @param chainId Chain ID to fetch incentives for * @returns Promise with the incentives data */ - async fetchIncentives(chainId: number): Promise { + async fetchIncentives(chainId: string): Promise { try { const response = (await fetch(`${this.baseUrl}${chainId}`).then((response) => response.json(), @@ -88,7 +86,7 @@ export class AaveChanClient implements AaveChanClientInterface { * @returns Promise with the prime incentives data */ async fetchPrimeIncentives(): Promise { - if (this.config.chainId !== 1) { + if (this.chainId !== `1`) { return {}; } diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 27eb1a348..b53a3fa6f 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -2,7 +2,7 @@ import { Chain } from '@prisma/client'; import { AprHandler } from '../types'; import * as handlers from '.'; import config from '../../../config'; -import { AaveRewardsAprConfig } from './types'; +import { chainToChainId } from '../../network/chain-id-to-chain'; /** * Creates handler instances for a specific chain @@ -38,9 +38,7 @@ export function createHandlers(chain: Chain): AprHandler[] { // Add Aave API handler if configured for this chain if (config[chain].aprHandlers.aaveRewardsAprHandler) { - handlerList.push( - new handlers.AaveApiAprHandler(config[chain].aprHandlers.aaveRewardsAprHandler as AaveRewardsAprConfig), - ); + handlerList.push(new handlers.AaveApiAprHandler(chainToChainId[chain])); } return handlerList; diff --git a/modules/aprs/handlers/types.ts b/modules/aprs/handlers/types.ts index f0a2a6d4d..09c73b684 100644 --- a/modules/aprs/handlers/types.ts +++ b/modules/aprs/handlers/types.ts @@ -4,7 +4,7 @@ export interface AprHandlerConfigs { // dynamicSwapFeeAprHandler: boolean; // gaugeAprHandler: boolean; ybAprHandler?: YbAprConfig; - aaveRewardsAprHandler?: AaveRewardsAprConfig; + aaveRewardsAprHandler?: boolean; maBeetsAprHandler?: MaBeetsAprConfig; // veBalProtocolAprHandler?: VeBalProtocolAprConfig; // veBalVotingAprHandler?: VeBalVotingAprConfig; @@ -12,10 +12,6 @@ export interface AprHandlerConfigs { // quantAmmAprHandler?: QuantAmmAprConfig; } -export type AaveRewardsAprConfig = { - chainId: number; -}; - export interface MaBeetsAprConfig { beetsAddress: string; } From f1912b498466259cb198eb44ec0cc6d36f48b058 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 11:25:01 +0300 Subject: [PATCH 14/37] remove items that dont exist anymore --- modules/aprs/apr-repository.ts | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index f79156350..09b257d6e 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -38,30 +38,40 @@ export class AprRepository { */ async savePoolAprItems( chain: Chain, - aprItems: Omit[], + newAprItems: Omit[], ): Promise { - if (aprItems.length === 0) return []; + if (newAprItems.length === 0) return []; // Get unique pool IDs from the items - const poolIds = [...new Set(aprItems.map((item) => item.poolId))]; + const poolIds = [...new Set(newAprItems.map((item) => item.poolId))]; - // Fetch existing APR items + // Fetch all existing APR items const existingItems = await prisma.prismaPoolAprItem.findMany({ where: { chain: chain, - poolId: { in: poolIds }, - id: { in: aprItems.map((item) => item.id) }, + id: { in: newAprItems.map((item) => item.id) }, }, }); - //TODO also remove items that are not in aprItems anymore + // Remove items that are not in newAprItems anymore + const itemsToRemove = existingItems.filter( + (existingItem) => !newAprItems.find((newAprItem) => newAprItem.id === existingItem.id), + ); + if (itemsToRemove.length > 0) { + await prisma.prismaPoolAprItem.deleteMany({ + where: { + id: { in: itemsToRemove.map((item) => item.id) }, + chain: chain, + }, + }); + } // Create a lookup map for quick access const existingItemsMap = new Map(existingItems.map((item) => [item.id, item])); // Only create operations for items that don't exist or have changed const changedPoolIds = new Set(); - const operations = aprItems + const operations = newAprItems .filter((item) => { const existingItem = existingItemsMap.get(item.id); const changed = !existingItem || existingItem.apr !== item.apr; From 5250be7d6040cf211eff4a1ecb97b5b8624dc65b Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 11:36:53 +0300 Subject: [PATCH 15/37] add merkl --- modules/aprs/handlers/create-handlers.ts | 1 + modules/aprs/handlers/index.ts | 1 + modules/aprs/handlers/merkl-apr/index.ts | 1 + .../handlers/merkl-apr/merkl-apr-handler.ts | 235 ++++++++++++++++++ 4 files changed, 238 insertions(+) create mode 100644 modules/aprs/handlers/merkl-apr/index.ts create mode 100644 modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index b53a3fa6f..06ab1c24b 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -16,6 +16,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.NestedPoolAprHandler()); handlerList.push(new handlers.QuantAmmAprHandler()); handlerList.push(new handlers.LiquidityGaugeAprHandler()); + handlerList.push(new handlers.MerklAprHandler()); // Mainnet specific handlers if (chain === Chain.MAINNET) { diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index 3285f9b30..a17954cb6 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -9,5 +9,6 @@ export { NestedPoolAprHandler } from './nested-pool-apr-handler'; export { QuantAmmAprHandler } from './quant-amm-apr'; export { LiquidityGaugeAprHandler } from './liquidity-gauge-apr'; export { VeBalProtocolAprHandler, VeBalVotingAprHandler } from './vebal-apr'; +export { MerklAprHandler } from './merkl-apr'; export { createHandlers } from './create-handlers'; diff --git a/modules/aprs/handlers/merkl-apr/index.ts b/modules/aprs/handlers/merkl-apr/index.ts new file mode 100644 index 000000000..87123dd49 --- /dev/null +++ b/modules/aprs/handlers/merkl-apr/index.ts @@ -0,0 +1 @@ +export * from './merkl-apr-handler'; diff --git a/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts new file mode 100644 index 000000000..4ec02819f --- /dev/null +++ b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts @@ -0,0 +1,235 @@ +import { $Enums, PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; +import { AprHandler, PoolAPRData } from '../../types'; +import { chainIdToChain, chainToChainId } from '../../../network/chain-id-to-chain'; + +const opportunityUrl = + 'https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&mainProtocolId=balancer'; + +interface MerklOpportunity { + chainId: number; + identifier: string; + apr: number; + tvl: number; + campaigns: { + startTimestamp: number; + endTimestamp: number; + params: { + whitelist: string[]; + forwarders: { + token: string; + sender: string; + }[]; + }; + }[]; +} + +export class MerklAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'MerklAprHandler'; + } + + private async fetchMerklOpportunities() { + const response = await fetch(opportunityUrl); + const data = (await response.json()) as MerklOpportunity[]; + + // remove opportunities with whitelist + const opportunities = data.filter( + (opportunity) => + opportunity.tvl > 0 && + opportunity.campaigns.every((campaign) => campaign.params.whitelist.length === 0), + ); + + return opportunities; + } + + private async fetchForwardedMerklOpportunities(chainId: string) { + const response = await fetch( + `https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&items=2000&chainId=${chainId}`, + ); + const data = (await response.json()) as MerklOpportunity[]; + + // remove opportunities with whitelist, only add where fowarder is vault v3 + const opportunities = data.filter( + (opportunity) => + opportunity.campaigns.every( + (campaign) => campaign.params.whitelist && campaign.params.whitelist.length === 0, + ) && + opportunity.campaigns.some((campaign) => + campaign.params.forwarders.some( + (forwarder) => forwarder.sender.toLowerCase() === '0xba1333333333a1ba1108e8412f11850a5c319ba9', + ), + ), + ); + return opportunities; + } + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + const chain = pools[0].chain; + const chainId = chainToChainId[chain]; + + const opportunities = await this.fetchMerklOpportunities(); + const forwardedOpportunities = await this.fetchForwardedMerklOpportunities(chainId); + + const poolAddressesFromForwardedOpportunities = forwardedOpportunities + .map((opportunity) => + opportunity.campaigns.map((campaign) => + campaign.params.forwarders.map((forwarder) => { + if (forwarder.sender.toLowerCase() !== '0xba1333333333a1ba1108e8412f11850a5c319ba9') { + return null; + } + return forwarder.token.toLowerCase(); + }), + ), + ) + .flat(2) + .filter((item) => item !== null) as string[]; + + const allAffectedPoolAddresses = [ + ...opportunities.map((campaign) => campaign.identifier.toLowerCase()), + ...poolAddressesFromForwardedOpportunities, + ]; + + const affectedPools = pools.filter((pool) => allAffectedPoolAddresses.includes(pool.address.toLowerCase())); + + const aprsFromOpportunities = this.mapOpportunitiesToAprs(opportunities, affectedPools); + const aprsFromForwardedOpportunities = this.mapForwardedOpportunitiesToAprs( + forwardedOpportunities, + affectedPools, + ); + + const data = aprsFromOpportunities; + + for (const forwardedOpportunity of aprsFromForwardedOpportunities) { + const existingApr = data.find( + (apr) => apr.poolId === forwardedOpportunity.poolId && apr.chain === forwardedOpportunity.chain, + ); + if (existingApr) { + existingApr.apr += forwardedOpportunity.apr; + } else { + data.push(forwardedOpportunity); + } + } + + return data.map((apr) => ({ + id: apr.id, + type: apr.type, + title: apr.title, + chain: apr.chain, + poolId: apr.poolId, + apr: apr.apr, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + })); + } + + private mapForwardedOpportunitiesToAprs( + opportunities: MerklOpportunity[], + affectedPools: PoolAPRData[], + ): { + id: string; + type: PrismaPoolAprType; + title: string; + chain: $Enums.Chain; + poolId: string; + apr: number; + }[] { + const aprs: { + id: string; + type: PrismaPoolAprType; + title: string; + chain: $Enums.Chain; + poolId: string; + apr: number; + }[] = []; + + opportunities.forEach((opportunity) => { + opportunity.campaigns.forEach((campaign) => { + if (campaign.startTimestamp < Date.now() / 1000 && campaign.endTimestamp > Date.now() / 1000) { + campaign.params.forwarders.forEach((forwarder) => { + if (forwarder.sender.toLowerCase() !== '0xba1333333333a1ba1108e8412f11850a5c319ba9') { + return; + } + + const pool = affectedPools.find( + (pool) => + pool.address === forwarder.token.toLowerCase() && + pool.chain === chainIdToChain[opportunity.chainId], + ); + + if (!pool) { + return; + } + + const tokenBalanceUsd = + pool.tokens.find((token) => token.address === opportunity.identifier.toLowerCase()) + ?.balanceUSD || 0; + const totalLiquidity = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); + const poolApr = opportunity.apr * (tokenBalanceUsd / totalLiquidity) || 0; + + if (poolApr === 0) { + return; + } + + aprs.push({ + id: `${pool.id}-merkl-forwarded-${opportunity.identifier}`, + type: PrismaPoolAprType.MERKL, + title: `Merkl Forwarded Rewards`, + chain: chainIdToChain[opportunity.chainId], + poolId: pool.id, + apr: poolApr / 100, + }); + }); + } + }); + }); + + return aprs; + } + + private mapOpportunitiesToAprs( + opportunities: MerklOpportunity[], + affectedPools: PoolAPRData[], + ): { + id: string; + type: PrismaPoolAprType; + title: string; + chain: $Enums.Chain; + poolId: string; + apr: number; + }[] { + const aprs: { + id: string; + type: PrismaPoolAprType; + title: string; + chain: $Enums.Chain; + poolId: string; + apr: number; + }[] = []; + + for (const opportunity of opportunities) { + const poolId = affectedPools.find( + (pool) => + pool.address === opportunity.identifier.toLowerCase() && + pool.chain === chainIdToChain[opportunity.chainId], + )?.id; + + if (!poolId) { + continue; + } + + aprs.push({ + id: `${poolId}-merkl`, + type: PrismaPoolAprType.MERKL, + title: `Merkl Rewards`, + chain: chainIdToChain[opportunity.chainId], + poolId: poolId, + apr: opportunity.apr / 100, + }); + } + + return aprs.filter((item) => item !== null); + } +} From af79da9c815949ce1aa9882cfd38e06215b4c3c3 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 11:58:12 +0300 Subject: [PATCH 16/37] add but dont enable 7d30d swap fee apr handler --- modules/aprs/handlers/swap-fee-apr/index.ts | 1 + .../swap-fee-apr-7d-30d-handler.ts | 167 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts diff --git a/modules/aprs/handlers/swap-fee-apr/index.ts b/modules/aprs/handlers/swap-fee-apr/index.ts index 1acb3d543..21aafd127 100644 --- a/modules/aprs/handlers/swap-fee-apr/index.ts +++ b/modules/aprs/handlers/swap-fee-apr/index.ts @@ -1 +1,2 @@ export * from './swap-fee-apr-handler'; +export * from './swap-fee-apr-7d-30d-handler'; diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts new file mode 100644 index 000000000..89d8afd48 --- /dev/null +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts @@ -0,0 +1,167 @@ +import { PoolAprService } from '../../pool-types'; +import { PoolForAPRs } from '../../../../prisma/prisma-types'; +import { prisma } from '../../../../prisma/prisma-client'; +import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; +import { Chain, PrismaPoolAprItem, PrismaPoolAprType, PrismaPoolType } from '@prisma/client'; +import { daysAgo, roundToMidnight } from '../../../common/time'; +import _ from 'lodash'; +import { AprHandler, PoolAPRData } from '../../types'; + +type PoolSwapFeeData = { + poolId: string; + chain: Chain; + fees_30d: number; + fees_7d: number; +}; + +const fetchSwapFeeData = async (chain: Chain) => { + const [snapshots30d, snapshots7d] = await Promise.all([ + prisma.prismaPoolSnapshot.findMany({ + where: { + chain, + timestamp: roundToMidnight(daysAgo(30)), + }, + select: { + poolId: true, + totalSwapFee: true, + }, + }), + prisma.prismaPoolSnapshot.findMany({ + where: { + chain, + timestamp: roundToMidnight(daysAgo(7)), + }, + select: { + poolId: true, + totalSwapFee: true, + }, + }), + ]); + + const poolIds = _.uniq([ + ...snapshots30d.map((snapshot) => snapshot.poolId), + ...snapshots7d.map((snapshot) => snapshot.poolId), + ]); + + const swapFeeData: PoolSwapFeeData[] = poolIds.map((poolId) => { + const snapshot30d = snapshots30d.find((s) => s.poolId === poolId); + const snapshot7d = snapshots7d.find((s) => s.poolId === poolId); + + return { + poolId, + chain, + fees_30d: snapshot30d ? snapshot30d.totalSwapFee : 0, + fees_7d: snapshot7d ? snapshot7d.totalSwapFee : 0, + }; + }); + + return swapFeeData; +}; + +const MAX_DB_INT = 9223372036854775807; + +export class SwapFeeApr7d30dHandler implements AprHandler { + public getAprServiceName(): string { + return 'SwapFeeApr7d30dHandler'; + } + + // This service is used outside of main APRs look, only for 7,30 day swap fee aprs. + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + // It will receive one pool only, because data is refetched in the body later + const chain = pools[0].chain; + + // Get pool type map + const [typeMap, dynamicData, currentAprs] = await Promise.all([ + prisma.prismaPool.findMany({ select: { id: true, type: true }, where: { chain } }).then((records) => + records.reduce((acc, pool) => { + acc[pool.id] = pool.type; + return acc; + }, {} as Record), + ), + prisma.prismaPoolDynamicData.findMany({ where: { chain } }), + prisma.prismaPoolAprItem + .findMany({ + select: { id: true, apr: true }, + where: { chain, type: { in: ['SWAP_FEE_7D', 'SWAP_FEE_30D'] } }, + }) + .then((records) => Object.fromEntries(records.map((item) => [item.id, item.apr]))), + ]); + + // Fetch the swap fees for the last 30 days + const swapFeeData = await fetchSwapFeeData(chain); + + // Map the swap fee data to the pool id + const swapFeeDataMap = swapFeeData.reduce((acc, data) => { + acc[data.poolId] = data; + return acc; + }, {} as Record); + + const aprItems = dynamicData.flatMap((pool) => { + let apr_7d = 0; + let apr_30d = 0; + + if (pool.totalLiquidity > 0 && swapFeeDataMap[pool.poolId]) { + apr_7d = (swapFeeDataMap[pool.poolId].fees_7d * 365) / 7 / pool.totalLiquidity; + apr_30d = (swapFeeDataMap[pool.poolId].fees_30d * 365) / 30 / pool.totalLiquidity; + } + + let protocolFee = parseFloat(pool.protocolSwapFee); + + if (typeMap[pool.poolId] === 'GYROE') { + // Gyro has custom protocol fee structure + protocolFee = parseFloat(pool.protocolYieldFee || '0'); + } + if (pool.isInRecoveryMode || typeMap[pool.poolId] === 'LIQUIDITY_BOOTSTRAPPING') { + // pool does not collect any protocol fees + protocolFee = 0; + } + + apr_7d = apr_7d * (1 - protocolFee); + apr_30d = apr_30d * (1 - protocolFee); + + if (apr_7d > MAX_DB_INT) { + apr_7d = 0; + } + if (apr_30d > MAX_DB_INT) { + apr_30d = 0; + } + + return [ + ...(Math.abs((currentAprs[`${pool.poolId}-swap-apr-7d`] || 0) - apr_7d) > 0.0001 + ? [ + { + id: `${pool.poolId}-swap-apr-7d`, + chain, + poolId: pool.poolId, + title: 'Swap fees APR (7d)', + apr: apr_7d, + type: PrismaPoolAprType.SWAP_FEE_7D, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }, + ] + : []), + ...(Math.abs((currentAprs[`${pool.poolId}-swap-apr-30d`] || 0) - apr_30d) > 0.0001 + ? [ + { + id: `${pool.poolId}-swap-apr-30d`, + chain, + poolId: pool.poolId, + title: 'Swap fees APR (30d)', + apr: apr_30d, + type: PrismaPoolAprType.SWAP_FEE_30D, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + }, + ] + : []), + ]; + }); + + return aprItems; + } +} From aa4e447762ad28d7d617a4143791c3756c132484 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 12:00:39 +0300 Subject: [PATCH 17/37] fix imports --- .../aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts index 89d8afd48..c2d934f8b 100644 --- a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-7d-30d-handler.ts @@ -1,7 +1,4 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { Chain, PrismaPoolAprItem, PrismaPoolAprType, PrismaPoolType } from '@prisma/client'; import { daysAgo, roundToMidnight } from '../../../common/time'; import _ from 'lodash'; From 76fbbf8fb09336343e03a2d092a8f7a80d69050c Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 12:08:03 +0300 Subject: [PATCH 18/37] sync incentivized category after apr sync --- modules/actions/pool/sync-incentivized-category.ts | 4 +++- modules/aprs/apr-service.ts | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/modules/actions/pool/sync-incentivized-category.ts b/modules/actions/pool/sync-incentivized-category.ts index 64a197a3e..99f31e75e 100644 --- a/modules/actions/pool/sync-incentivized-category.ts +++ b/modules/actions/pool/sync-incentivized-category.ts @@ -1,10 +1,11 @@ import { Chain, Prisma } from '@prisma/client'; import { prisma } from '../../../prisma/prisma-client'; -export const syncIncentivizedCategory = async () => { +export const syncIncentivizedCategory = async (chain: Chain) => { const poolsWithReward = await prisma.prismaPoolAprItem.findMany({ select: { poolId: true }, where: { + chain, type: { in: ['NATIVE_REWARD', 'THIRD_PARTY_REWARD', 'MERKL', 'VOTING', 'LOCKING'], }, @@ -17,6 +18,7 @@ export const syncIncentivizedCategory = async () => { const incentivizedPoolIds = await prisma.prismaPool.findMany({ select: { id: true }, where: { + chain, categories: { has: 'INCENTIVIZED', }, diff --git a/modules/aprs/apr-service.ts b/modules/aprs/apr-service.ts index 2dfbbee32..3898a63f0 100644 --- a/modules/aprs/apr-service.ts +++ b/modules/aprs/apr-service.ts @@ -3,6 +3,7 @@ import { AprHandler } from './types'; import { AprRepository } from './apr-repository'; import { AprManager } from './apr-manager'; import { createHandlers } from './handlers'; +import { syncIncentivizedCategory } from '../actions/pool/sync-incentivized-category'; export class AprService { private readonly aprRepository: AprRepository; @@ -32,7 +33,9 @@ export class AprService { */ async updateAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - return manager.updateAprs(chain); + const changedIds = manager.updateAprs(chain); + await syncIncentivizedCategory(chain); + return changedIds; } /** @@ -41,7 +44,9 @@ export class AprService { */ async reloadAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - return manager.reloadAllPoolAprs(chain); + const changedIds = manager.reloadAllPoolAprs(chain); + await syncIncentivizedCategory(chain); + return changedIds; } /** From b17d40f6efb2d17d6a0bba67e45c40ceac01c342 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 12:26:12 +0300 Subject: [PATCH 19/37] replace old service with new one --- apps/api/gql/resolvers/pool.resolvers.ts | 3 +- apps/worker/job-handlers.ts | 10 +- modules/actions/aprs/merkl.ts | 229 ------------------ modules/aprs/apr-repository.ts | 4 +- modules/aprs/examples/comparison.ts | 30 --- modules/controllers/aprs-controller.ts | 25 -- modules/controllers/index.ts | 1 - modules/network/arbitrum.ts | 16 -- modules/network/avalanche.ts | 14 +- modules/network/base.ts | 20 -- modules/network/fantom.ts | 2 - modules/network/fraxtal.ts | 10 - modules/network/gnosis.ts | 13 - modules/network/mainnet.ts | 29 +-- modules/network/mode.ts | 10 - modules/network/network-config-types.ts | 1 - modules/network/optimism.ts | 10 - modules/network/polygon.ts | 10 - modules/network/sepolia.ts | 13 - modules/network/sonic.ts | 15 -- modules/network/zkevm.ts | 10 - .../apr-data-sources/aave-api-apr-handler.ts | 153 ------------ .../beetswars-gauge-voting-apr.ts | 62 ----- ...ynamic-swap-fee-apr-from-events.service.ts | 100 -------- modules/pool/lib/apr-data-sources/index.ts | 9 - .../lib/apr-data-sources/morpho-api-client.ts | 90 ------- .../morpho-rewards-apr.service.ts | 65 ----- .../nested-pool-apr.service.ts | 105 -------- .../apr-data-sources/quant-amm-apr-handler.ts | 163 ------------- .../reliquary-farm-apr.service.ts | 141 ----------- .../swap-fee-apr-from-events.service.ts | 157 ------------ .../swap-fee-apr-from-snapshots.service.ts | 172 ------------- .../apr-data-sources/swap-fee-apr.service.ts | 76 ------ .../ve-bal-gauge-apr.service.ts | 181 -------------- .../vebal-protocol-apr.service.ts | 138 ----------- .../vebal-voting-apr.service.ts | 142 ----------- .../apr-data-sources/yb-tokens-apr.service.ts | 139 ----------- modules/pool/lib/pool-apr-updater.service.ts | 112 --------- modules/pool/pool.service.ts | 13 - tasks/index.ts | 40 ++- 40 files changed, 28 insertions(+), 2505 deletions(-) delete mode 100644 modules/actions/aprs/merkl.ts delete mode 100644 modules/aprs/examples/comparison.ts delete mode 100644 modules/controllers/aprs-controller.ts delete mode 100644 modules/pool/lib/apr-data-sources/aave-api-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/beetswars-gauge-voting-apr.ts delete mode 100644 modules/pool/lib/apr-data-sources/dynamic-swap-fee-apr-from-events.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/index.ts delete mode 100644 modules/pool/lib/apr-data-sources/morpho-api-client.ts delete mode 100644 modules/pool/lib/apr-data-sources/morpho-rewards-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/quant-amm-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/reliquary-farm-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/swap-fee-apr-from-events.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/swap-fee-apr-from-snapshots.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/swap-fee-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/vebal-protocol-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/vebal-voting-apr.service.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts delete mode 100644 modules/pool/lib/pool-apr-updater.service.ts diff --git a/apps/api/gql/resolvers/pool.resolvers.ts b/apps/api/gql/resolvers/pool.resolvers.ts index 47584c9c5..b5ab2e545 100644 --- a/apps/api/gql/resolvers/pool.resolvers.ts +++ b/apps/api/gql/resolvers/pool.resolvers.ts @@ -16,6 +16,7 @@ import { GraphQLError } from 'graphql'; import { upsertLastSyncedBlock } from '../../../../modules/actions/last-synced-block'; import { PrismaLastBlockSyncedCategory } from '@prisma/client'; import graphqlFields from 'graphql-fields'; +import { AprService } from '../../../../modules/aprs'; const balancerResolvers: Resolvers = { Query: { @@ -120,7 +121,7 @@ const balancerResolvers: Resolvers = { poolReloadAllPoolAprs: async (parent, { chain }, context) => { isAdminRoute(context); - await poolService.reloadAllPoolAprs(chain); + await new AprService().reloadAprs(chain); return 'success'; }, diff --git a/apps/worker/job-handlers.ts b/apps/worker/job-handlers.ts index 447ce0a10..05986c082 100644 --- a/apps/worker/job-handlers.ts +++ b/apps/worker/job-handlers.ts @@ -19,7 +19,6 @@ import { SftmxController, CowAmmController, SnapshotsController, - AprsController, ContentController, PoolController, EventController, @@ -33,6 +32,7 @@ import { TokenController } from '../../modules/controllers/token-controller'; import { SubgraphMonitorController } from '../../modules/controllers/subgraph-monitor-controller'; import config from '../../config'; import { LBPController } from '../../modules/controllers/lbp-controller'; +import { AprService } from '../../modules/aprs'; const runningJobs: Set = new Set(); @@ -312,12 +312,6 @@ const setupJobHandlers = async (name: string, chainId: string, res: any, next: N ); break; // APRs - case 'sync-merkl': - await runIfNotAlreadyRunning(name, chainId, () => AprsController().syncMerkl(), res, next); - break; - case 'update-7-30-days-swap-apr': - // Disabling unused APRs - break; case 'update-surplus-aprs': await runIfNotAlreadyRunning(name, chainId, () => CowAmmController().updateSurplusAprs(), res, next); break; @@ -327,7 +321,7 @@ const setupJobHandlers = async (name: string, chainId: string, res: any, next: N chainId, () => { const chain = chainIdToChain[chainId]; - return poolService.updatePoolAprs(chain); + return new AprService().updateAprs(chain); }, res, next, diff --git a/modules/actions/aprs/merkl.ts b/modules/actions/aprs/merkl.ts deleted file mode 100644 index 74687cdb4..000000000 --- a/modules/actions/aprs/merkl.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { $Enums, PrismaPoolAprType } from '@prisma/client'; -import { prisma } from '../../../prisma/prisma-client'; -import { poolsIncludeForAprs, PoolForAPRs } from '../../../prisma/prisma-types'; -import { chainIdToChain } from '../../network/chain-id-to-chain'; -import { AllNetworkConfigs } from '../../network/network-config'; - -const opportunityUrl = - 'https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&mainProtocolId=balancer'; - -interface MerklOpportunity { - chainId: number; - identifier: string; - apr: number; - tvl: number; - campaigns: { - startTimestamp: number; - endTimestamp: number; - params: { - whitelist: string[]; - forwarders: { - token: string; - sender: string; - }[]; - }; - }[]; -} - -const fetchMerklOpportunities = async () => { - const response = await fetch(opportunityUrl); - const data = (await response.json()) as MerklOpportunity[]; - - // remove opportunities with whitelist - const opportunities = data.filter( - (opportunity) => - opportunity.tvl > 0 && opportunity.campaigns.every((campaign) => campaign.params.whitelist.length === 0), - ); - - return opportunities; -}; - -const fetchForwardedMerklOpportunities = async () => { - const allOpportunities: MerklOpportunity[] = []; - for (const chainId of Object.keys(AllNetworkConfigs)) { - const response = await fetch( - `https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&items=2000&chainId=${chainId}`, - ); - const data = (await response.json()) as MerklOpportunity[]; - - if (data.length > 0) { - allOpportunities.push(...data); - } - } - - // remove opportunities with whitelist, only add where fowarder is vault v3 - const opportunities = allOpportunities.filter( - (opportunity) => - opportunity.campaigns.every( - (campaign) => campaign.params.whitelist && campaign.params.whitelist.length === 0, - ) && - opportunity.campaigns.some((campaign) => - campaign.params.forwarders.some( - (forwarder) => forwarder.sender.toLowerCase() === '0xba1333333333a1ba1108e8412f11850a5c319ba9', - ), - ), - ); - return opportunities; -}; - -export const syncMerklRewards = async () => { - const opportunities = await fetchMerklOpportunities(); - const forwardedOpportunities = await fetchForwardedMerklOpportunities(); - - const poolIdsFromForwardedOpportunities = forwardedOpportunities - .map((opportunity) => - opportunity.campaigns.map((campaign) => - campaign.params.forwarders.map((forwarder) => { - if (forwarder.sender.toLowerCase() !== '0xba1333333333a1ba1108e8412f11850a5c319ba9') { - return null; - } - return forwarder.token.toLowerCase(); - }), - ), - ) - .flat(2) - .filter((item) => item !== null) as string[]; - - const allAffectedPoolAddresses = [ - ...opportunities.map((campaign) => campaign.identifier.toLowerCase()), - ...poolIdsFromForwardedOpportunities, - ]; - - const affectedPools = await prisma.prismaPool.findMany({ - where: { - address: { - in: allAffectedPoolAddresses, - }, - }, - include: { dynamicData: true, tokens: { include: { token: true } } }, - }); - - const aprsFromOpportunities = mapOpportunitiesToAprs(opportunities, affectedPools); - const aprsFromForwardedOpportunities = mapForwardedOpportunitiesToAprs(forwardedOpportunities, affectedPools); - - const data = aprsFromOpportunities; - - for (const forwardedOpportunity of aprsFromForwardedOpportunities) { - const existingApr = data.find( - (apr) => apr.poolId === forwardedOpportunity.poolId && apr.chain === forwardedOpportunity.chain, - ); - if (existingApr) { - existingApr.apr += forwardedOpportunity.apr; - } else { - data.push(forwardedOpportunity); - } - } - - await prisma.$transaction([ - prisma.prismaPoolAprItem.deleteMany({ where: { type: PrismaPoolAprType.MERKL } }), - prisma.prismaPoolAprItem.createMany({ data: data.filter((item) => item !== null) }), - ]); -}; - -function mapForwardedOpportunitiesToAprs( - opportunities: MerklOpportunity[], - affectedPools: PoolForAPRs[], -): { - id: string; - type: PrismaPoolAprType; - title: string; - chain: $Enums.Chain; - poolId: string; - apr: number; -}[] { - const aprs: { - id: string; - type: PrismaPoolAprType; - title: string; - chain: $Enums.Chain; - poolId: string; - apr: number; - }[] = []; - - opportunities.forEach((opportunity) => { - opportunity.campaigns.forEach((campaign) => { - if (campaign.startTimestamp < Date.now() / 1000 && campaign.endTimestamp > Date.now() / 1000) { - campaign.params.forwarders.forEach((forwarder) => { - if (forwarder.sender.toLowerCase() !== '0xba1333333333a1ba1108e8412f11850a5c319ba9') { - return; - } - - const pool = affectedPools.find( - (pool) => - pool.address === forwarder.token.toLowerCase() && - pool.chain === chainIdToChain[opportunity.chainId], - ); - - if (!pool) { - return; - } - - const tokenBalanceUsd = - pool.tokens.find((token) => token.address === opportunity.identifier.toLowerCase()) - ?.balanceUSD || 0; - const totalLiquidity = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); - const poolApr = opportunity.apr * (tokenBalanceUsd / totalLiquidity) || 0; - - if (poolApr === 0) { - return; - } - - aprs.push({ - id: `${pool.id}-merkl-forwarded-${opportunity.identifier}`, - type: PrismaPoolAprType.MERKL, - title: `Merkl Forwarded Rewards`, - chain: chainIdToChain[opportunity.chainId], - poolId: pool.id, - apr: poolApr / 100, - }); - }); - } - }); - }); - - return aprs; -} - -function mapOpportunitiesToAprs( - opportunities: MerklOpportunity[], - affectedPools: PoolForAPRs[], -): { - id: string; - type: PrismaPoolAprType; - title: string; - chain: $Enums.Chain; - poolId: string; - apr: number; -}[] { - const aprs: { - id: string; - type: PrismaPoolAprType; - title: string; - chain: $Enums.Chain; - poolId: string; - apr: number; - }[] = []; - - for (const opportunity of opportunities) { - const poolId = affectedPools.find( - (pool) => - pool.address === opportunity.identifier.toLowerCase() && - pool.chain === chainIdToChain[opportunity.chainId], - )?.id; - - if (!poolId) { - continue; - } - - aprs.push({ - id: `${poolId}-merkl`, - type: PrismaPoolAprType.MERKL, - title: `Merkl Rewards`, - chain: chainIdToChain[opportunity.chainId], - poolId: poolId, - apr: opportunity.apr / 100, - }); - } - - return aprs.filter((item) => item !== null); -} diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index 09b257d6e..803495e1e 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -22,7 +22,9 @@ export class AprRepository { include: { dynamicData: true, tokens: { include: { token: true, nestedPool: true } }, - staking: { include: { gauge: { include: { rewards: true } }, reliquary: true } }, + staking: { + include: { gauge: { include: { rewards: true } }, reliquary: { include: { levels: true } } }, + }, }, where: { chain, diff --git a/modules/aprs/examples/comparison.ts b/modules/aprs/examples/comparison.ts deleted file mode 100644 index 9a01baaae..000000000 --- a/modules/aprs/examples/comparison.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { AprService } from '../'; -import { poolService } from '../../pool/pool.service'; -import { Chain } from '@prisma/client'; -import { prisma } from '../../../prisma/prisma-client'; - -async function comparisonExample(chain: Chain = 'MAINNET', poolId = '0x85b2b559bc2d21104c4defdd6efca8a20343361d') { - // New implementation - const newAprService = new AprService(); - - console.log('Calculating APR with new implementation (no DB writes)...'); - - const items = await newAprService.calculateAprForPool(chain, poolId); - - console.log('\nAPR Items from new implementation:'); - items.forEach((item) => { - console.log(`- ${item.title}: ${item.apr}`); - }); - - // Old implementation - await poolService.updatePoolAprs(chain); - const oldItems = await prisma.prismaPoolAprItem.findMany({ where: { poolId } }); - - oldItems.forEach((item) => { - console.log(`- ${item.title}: ${item.apr}`); - }); -} - -// Run the example -// Uncomment to run: -comparisonExample().catch(console.error); diff --git a/modules/controllers/aprs-controller.ts b/modules/controllers/aprs-controller.ts deleted file mode 100644 index 28b2f9f77..000000000 --- a/modules/controllers/aprs-controller.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Chain } from '@prisma/client'; -import { syncMerklRewards } from '../actions/aprs/merkl'; -import { SwapFeeFromSnapshotsAprService } from '../pool/lib/apr-data-sources/swap-fee-apr-from-snapshots.service'; -import { prisma } from '../../prisma/prisma-client'; -import { poolsIncludeForAprs } from '../../prisma/prisma-types'; - -export function AprsController(tracer?: any) { - // Setup tracing - // ... - return { - async syncMerkl() { - return await syncMerklRewards(); - }, - async update7And30DaysSwapAprs(chain: Chain) { - const service = new SwapFeeFromSnapshotsAprService(); - const pools = await prisma.prismaPool.findMany({ - ...poolsIncludeForAprs, - where: { chain }, - take: 1, - }); - await service.updateAprForPools(pools); - return 'Done'; - }, - }; -} diff --git a/modules/controllers/index.ts b/modules/controllers/index.ts index 175fb77a8..6e7333802 100644 --- a/modules/controllers/index.ts +++ b/modules/controllers/index.ts @@ -5,7 +5,6 @@ export * from './pool-mutation-controller'; export * from './user-balances-controller'; export * from './cow-amm-controller'; export * from './event-query-controller'; -export * from './aprs-controller'; export * from './content-controller'; export * from './fx-pools-controller'; export * from './pool-controller'; diff --git a/modules/network/arbitrum.ts b/modules/network/arbitrum.ts index 2470c40b6..13e50bce7 100644 --- a/modules/network/arbitrum.ts +++ b/modules/network/arbitrum.ts @@ -1,32 +1,16 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { env } from '../../apps/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; -import { AaveApiAprService } from '../pool/lib/apr-data-sources/aave-api-apr-handler'; -import { QuantAmmAprService } from '../pool/lib/apr-data-sources/quant-amm-apr-handler'; - export const arbitrumNetworkData = config.ARBITRUM; export const arbitrumNetworkConfig: NetworkConfig = { data: arbitrumNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: arbitrumNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(arbitrumNetworkData.aprHandlers.ybAprHandler!, arbitrumNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new AaveApiAprService(), - new QuantAmmAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/avalanche.ts b/modules/network/avalanche.ts index 179b748ab..38da09260 100644 --- a/modules/network/avalanche.ts +++ b/modules/network/avalanche.ts @@ -1,29 +1,17 @@ -import { BigNumber, ethers } from 'ethers'; +import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; -import { AaveApiAprService } from '../pool/lib/apr-data-sources/aave-api-apr-handler'; const avalancheNetworkData: NetworkData = config.AVALANCHE; export const avalancheNetworkConfig: NetworkConfig = { data: avalancheNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: avalancheNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(avalancheNetworkData.aprHandlers.ybAprHandler!, avalancheNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - new AaveApiAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/base.ts b/modules/network/base.ts index b00d102bf..82b6948fe 100644 --- a/modules/network/base.ts +++ b/modules/network/base.ts @@ -1,37 +1,17 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { - SwapFeeAprService, - GaugeAprService, - NestedPoolAprService, - YbTokensAprService, - MorphoRewardsAprService, - DynamicSwapFeeFromEventsAprService, -} from '../pool/lib/apr-data-sources/'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; -import { AaveApiAprService } from '../pool/lib/apr-data-sources/aave-api-apr-handler'; -import { QuantAmmAprService } from '../pool/lib/apr-data-sources/quant-amm-apr-handler'; const baseNetworkData: NetworkData = config.BASE; export const baseNetworkConfig: NetworkConfig = { data: baseNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: baseNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(baseNetworkData.aprHandlers.ybAprHandler!, baseNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new MorphoRewardsAprService(), - new AaveApiAprService(), - new QuantAmmAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/fantom.ts b/modules/network/fantom.ts index fb1f0e0bd..631a3fc9d 100644 --- a/modules/network/fantom.ts +++ b/modules/network/fantom.ts @@ -1,6 +1,5 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; import { UserSyncMasterchefFarmBalanceService } from '../user/lib/user-sync-masterchef-farm-balance.service'; import { UserSyncReliquaryFarmBalanceService } from '../user/lib/user-sync-reliquary-farm-balance.service'; import { every } from '../../apps/scheduler/intervals'; @@ -13,7 +12,6 @@ const fantomNetworkData: NetworkData = config.FANTOM; export const fantomNetworkConfig: NetworkConfig = { data: fantomNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: fantomNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [new SwapFeeAprService()], userStakedBalanceServices: [ new UserSyncMasterchefFarmBalanceService( fantomNetworkData.fbeets!.address, diff --git a/modules/network/fraxtal.ts b/modules/network/fraxtal.ts index b6094af6a..16bf72ec8 100644 --- a/modules/network/fraxtal.ts +++ b/modules/network/fraxtal.ts @@ -1,12 +1,8 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; @@ -16,12 +12,6 @@ export const fraxtalNetworkData = config.FRAXTAL; export const fraxtalNetworkConfig: NetworkConfig = { data: fraxtalNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: fraxtalNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(fraxtalNetworkData.aprHandlers.ybAprHandler!, fraxtalNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/gnosis.ts b/modules/network/gnosis.ts index b5290999e..1e9903ea9 100644 --- a/modules/network/gnosis.ts +++ b/modules/network/gnosis.ts @@ -1,30 +1,17 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; -import { AaveApiAprService } from '../pool/lib/apr-data-sources/aave-api-apr-handler'; const gnosisNetworkData: NetworkData = config.GNOSIS; export const gnosisNetworkConfig: NetworkConfig = { data: gnosisNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: gnosisNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(gnosisNetworkData.aprHandlers.ybAprHandler!, gnosisNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new AaveApiAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/mainnet.ts b/modules/network/mainnet.ts index 259c69928..9821b27da 100644 --- a/modules/network/mainnet.ts +++ b/modules/network/mainnet.ts @@ -1,15 +1,5 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { - NestedPoolAprService, - SwapFeeAprService, - GaugeAprService, - YbTokensAprService, - VeBalProtocolAprService, - VeBalVotingAprService, - MorphoRewardsAprService, - DynamicSwapFeeFromEventsAprService, -} from '../pool/lib/apr-data-sources'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; @@ -17,26 +7,13 @@ import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; import { UserSyncVebalLockBalanceService } from '../user/lib/user-sync-vebal-lock-balance.service'; -import { AaveApiAprService } from '../pool/lib/apr-data-sources/aave-api-apr-handler'; -import { QuantAmmAprService } from '../pool/lib/apr-data-sources/quant-amm-apr-handler'; export const data: NetworkData = config.MAINNET; export const mainnetNetworkConfig: NetworkConfig = { data, provider: new ethers.providers.JsonRpcProvider({ url: data.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(data.aprHandlers.ybAprHandler!, data.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new VeBalProtocolAprService(data.rpcUrl), - new VeBalVotingAprService(), - new MorphoRewardsAprService(), - new AaveApiAprService(), - new QuantAmmAprService(), - ], + userStakedBalanceServices: [ new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService(), @@ -133,10 +110,6 @@ export const mainnetNetworkConfig: NetworkConfig = { alarmEvaluationPeriod: 1, alarmDatapointsToAlarm: 1, }, - { - name: 'sync-merkl', - interval: every(15, 'minutes'), - }, { name: 'sync-rate-provider-reviews', interval: every(15, 'minutes'), diff --git a/modules/network/mode.ts b/modules/network/mode.ts index e6be89e07..eb7494ec9 100644 --- a/modules/network/mode.ts +++ b/modules/network/mode.ts @@ -1,12 +1,8 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; @@ -15,12 +11,6 @@ export const modeNetworkData = config.MODE; export const modeNetworkConfig: NetworkConfig = { data: modeNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: modeNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(modeNetworkData.aprHandlers.ybAprHandler!, modeNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/network-config-types.ts b/modules/network/network-config-types.ts index b0812356e..c76844098 100644 --- a/modules/network/network-config-types.ts +++ b/modules/network/network-config-types.ts @@ -9,7 +9,6 @@ import { AprHandlerConfigs } from '../aprs/handlers/types'; export interface NetworkConfig { data: NetworkData; - poolAprServices: PoolAprService[]; userStakedBalanceServices: UserStakedBalanceService[]; provider: BaseProvider; workerJobs: WorkerJob[]; diff --git a/modules/network/optimism.ts b/modules/network/optimism.ts index abec61522..9269318f9 100644 --- a/modules/network/optimism.ts +++ b/modules/network/optimism.ts @@ -1,11 +1,7 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { env } from '../../apps/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; @@ -16,12 +12,6 @@ const optimismNetworkData: NetworkData = config.OPTIMISM; export const optimismNetworkConfig: NetworkConfig = { data: optimismNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: optimismNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(optimismNetworkData.aprHandlers.ybAprHandler!, optimismNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/polygon.ts b/modules/network/polygon.ts index 2da8fedb4..7a4255cce 100644 --- a/modules/network/polygon.ts +++ b/modules/network/polygon.ts @@ -1,11 +1,7 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { env } from '../../apps/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; @@ -16,12 +12,6 @@ const polygonNetworkData: NetworkData = config.POLYGON; export const polygonNetworkConfig: NetworkConfig = { data: polygonNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: polygonNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(polygonNetworkData.aprHandlers.ybAprHandler!, polygonNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/sepolia.ts b/modules/network/sepolia.ts index 2f545122b..95f56d5de 100644 --- a/modules/network/sepolia.ts +++ b/modules/network/sepolia.ts @@ -1,29 +1,16 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService, DynamicSwapFeeFromEventsAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { env } from '../../apps/env'; -import { QuantAmmAprService } from '../pool/lib/apr-data-sources/quant-amm-apr-handler'; export const sepoliaNetworkData = config.SEPOLIA; export const sepoliaNetworkConfig: NetworkConfig = { data: sepoliaNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: sepoliaNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(sepoliaNetworkData.aprHandlers.ybAprHandler!, sepoliaNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new QuantAmmAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/network/sonic.ts b/modules/network/sonic.ts index 020f26a80..84c758945 100644 --- a/modules/network/sonic.ts +++ b/modules/network/sonic.ts @@ -1,32 +1,17 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { DynamicSwapFeeFromEventsAprService, SwapFeeAprService } from '../pool/lib/apr-data-sources'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { env } from '../../apps/env'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; -import { ReliquaryFarmAprService } from '../pool/lib/apr-data-sources/reliquary-farm-apr.service'; import { UserSyncReliquaryFarmBalanceService } from '../user/lib/user-sync-reliquary-farm-balance.service'; -import { BeetswarsGaugeVotingAprService } from '../pool/lib/apr-data-sources/beetswars-gauge-voting-apr'; const sonicNetworkData: NetworkData = config.SONIC; export const sonicNetworkConfig: NetworkConfig = { data: sonicNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: sonicNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(sonicNetworkData.aprHandlers.ybAprHandler!, sonicNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new DynamicSwapFeeFromEventsAprService(), - new GaugeAprService(), - new ReliquaryFarmAprService(sonicNetworkData.beets!.address), - new BeetswarsGaugeVotingAprService(), - ], userStakedBalanceServices: [ new UserSyncGaugeBalanceService(), new UserSyncReliquaryFarmBalanceService(sonicNetworkData.reliquary!.address), diff --git a/modules/network/zkevm.ts b/modules/network/zkevm.ts index 1fc5858d4..832d60fec 100644 --- a/modules/network/zkevm.ts +++ b/modules/network/zkevm.ts @@ -1,12 +1,8 @@ import { ethers } from 'ethers'; import { DeploymentEnv, NetworkConfig, NetworkData } from './network-config-types'; -import { NestedPoolAprService } from '../pool/lib/apr-data-sources/nested-pool-apr.service'; -import { SwapFeeAprService } from '../pool/lib/apr-data-sources/'; -import { GaugeAprService } from '../pool/lib/apr-data-sources/ve-bal-gauge-apr.service'; import { UserSyncGaugeBalanceService } from '../user/lib/user-sync-gauge-balance.service'; import { every } from '../../apps/scheduler/intervals'; import { env } from '../../apps/env'; -import { YbTokensAprService } from '../pool/lib/apr-data-sources/yb-tokens-apr.service'; import { BalancerSubgraphService } from '../subgraphs/balancer-subgraph/balancer-subgraph.service'; import config from '../../config'; import { UserSyncAuraBalanceService } from '../user/lib/user-sync-aura-balance.service'; @@ -16,12 +12,6 @@ const zkevmNetworkData: NetworkData = config.ZKEVM; export const zkevmNetworkConfig: NetworkConfig = { data: zkevmNetworkData, provider: new ethers.providers.JsonRpcProvider({ url: zkevmNetworkData.rpcUrl, timeout: 60000 }), - poolAprServices: [ - new YbTokensAprService(zkevmNetworkData.aprHandlers.ybAprHandler!, zkevmNetworkData.chain.prismaId), - new NestedPoolAprService(), - new SwapFeeAprService(), - new GaugeAprService(), - ], userStakedBalanceServices: [new UserSyncGaugeBalanceService(), new UserSyncAuraBalanceService()], services: { balancerSubgraphService: new BalancerSubgraphService( diff --git a/modules/pool/lib/apr-data-sources/aave-api-apr-handler.ts b/modules/pool/lib/apr-data-sources/aave-api-apr-handler.ts deleted file mode 100644 index 6cdc64df7..000000000 --- a/modules/pool/lib/apr-data-sources/aave-api-apr-handler.ts +++ /dev/null @@ -1,153 +0,0 @@ -import _ from 'lodash'; -import { prisma } from '../../../../prisma/prisma-client'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { PoolAprService } from '../../pool-types'; -import { Chain } from '@prisma/client'; -import { chainToChainId } from '../../../network/chain-id-to-chain'; - -type Incentives = { - tokenInfo: ReserveToken; - supplyIncentives: IncentiveInfo[]; -}; - -type IncentiveInfo = { - apr: number; - rewardToken: Token; -}; - -type ReserveToken = Token & { - supplyApr?: number; -}; - -export type Token = { - symbol: string; - address: string; - book?: BookType; -}; - -export type BookType = { - STATA_TOKEN?: string; -}; - -type AaveIncentive = { - [key: string]: Incentives; -}; - -export class AaveApiAprService implements PoolAprService { - base = 'https://apps.aavechan.com/api/aave-all-incentives?chainId='; - - public getAprServiceName(): string { - return 'AaveApiAprServices'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const aprItems = await this.getAprItemsForSupplyIncentives(pools); - await prisma.$transaction( - aprItems.map((item) => - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: item.id, chain: item.chain } }, - update: { - apr: item.apr, - }, - create: item, - }), - ), - ); - } - - private async getAprItemsForSupplyIncentives(pools: PoolForAPRs[]): Promise< - { - id: string; - chain: Chain; - poolId: string; - title: string; - apr: number; - type: 'MERKL'; - rewardTokenAddress: string; - rewardTokenSymbol: string; - }[] - > { - const poolsByChain = _.groupBy(pools, 'chain'); - - const aprItems: { - id: string; - chain: Chain; - poolId: string; - title: string; - apr: number; - type: 'MERKL'; - rewardTokenAddress: string; - rewardTokenSymbol: string; - }[] = []; - - for (const chain in poolsByChain) { - const aprItemsForChain = await this.fetchAprForChain(chainToChainId[chain], poolsByChain[chain]); - aprItems.push(...aprItemsForChain); - if (chain === 'MAINNET') { - // also fetch lido prime instance items on mainnet - const aprItemsForChain = await this.fetchAprForChain(`1&instance=prime`, poolsByChain[chain]); - aprItems.push(...aprItemsForChain); - } - } - - return aprItems; - } - - private async fetchAprForChain(chainId: string, pools: PoolForAPRs[]) { - const aprItems: { - id: string; - chain: Chain; - poolId: string; - title: string; - apr: number; - type: 'MERKL'; - rewardTokenAddress: string; - rewardTokenSymbol: string; - }[] = []; - - const aaveIncentivesForChain = (await fetch(`${this.base}${chainId}`).then((res) => - res.json(), - )) as AaveIncentive; - - for (const incentiveTokenName in aaveIncentivesForChain) { - if ( - aaveIncentivesForChain[incentiveTokenName].tokenInfo.book?.STATA_TOKEN && - aaveIncentivesForChain[incentiveTokenName].supplyIncentives.length > 0 - ) { - const incentivizedToken = aaveIncentivesForChain[ - incentiveTokenName - ].tokenInfo.book.STATA_TOKEN.toLowerCase() - .toString() - .toLowerCase(); - const supplyIncentivesForToken = aaveIncentivesForChain[incentiveTokenName].supplyIncentives; - - const poolsWithIncentivizedTokenToken = pools.filter((pool) => - pool.tokens.find((token) => token.address === incentivizedToken), - ); - - for (const pool of poolsWithIncentivizedTokenToken) { - const tvl = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); - const tokenTvl = pool.tokens.find((token) => token.address === incentivizedToken)?.balanceUSD || 0; - - const tokenShareOfPoolTvl = tokenTvl === 0 || tvl === 0 ? 0 : tokenTvl / tvl; - - for (const incentive of supplyIncentivesForToken) { - aprItems.push({ - id: `${pool.id}-${incentivizedToken}-${incentive.rewardToken.address}`, - chain: pool.chain, - poolId: pool.id, - title: `${incentive.rewardToken.symbol} APR`, - apr: (incentive.apr / 100) * tokenShareOfPoolTvl, - type: 'MERKL', - rewardTokenAddress: incentive.rewardToken.address, - rewardTokenSymbol: incentive.rewardToken.symbol, - }); - } - } - } - } - return aprItems; - } -} - -type VaultApr = Record; diff --git a/modules/pool/lib/apr-data-sources/beetswars-gauge-voting-apr.ts b/modules/pool/lib/apr-data-sources/beetswars-gauge-voting-apr.ts deleted file mode 100644 index 3e58bffe2..000000000 --- a/modules/pool/lib/apr-data-sources/beetswars-gauge-voting-apr.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import axios from 'axios'; -import { prisma } from '../../../../prisma/prisma-client'; -import { networkContext } from '../../../network/network-context.service'; -import { PrismaPoolAprType } from '@prisma/client'; - -export class BeetswarsGaugeVotingAprService implements PoolAprService { - private readonly FRESH_BEETS_POOL_ID = '0x10ac2f9dae6539e77e372adb14b1bf8fbd16b3e8000200000000000000000005'; - - public getAprServiceName(): string { - return 'BeetswarsGaugeVotingAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - for (const pool of pools) { - if (pool.id !== this.FRESH_BEETS_POOL_ID) { - continue; - } - - const response = await axios.get('https://www.beetswars.live/api/trpc/chart.chartdata'); - - const raw: number[] = response.data.result.data.json.chartdata.votingApr; - - // Filter out non-numbers and infinity values - const votingAprs = raw.filter((apr) => apr && isFinite(apr)); - - const minApr = 0; - const maxApr = votingAprs[votingAprs.length - 1] / 100; - - const itemId = `${this.FRESH_BEETS_POOL_ID}-voting-apr`; - - await prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: itemId, chain: networkContext.chain } }, - update: { - range: { - update: { min: minApr, max: maxApr }, - }, - title: 'Voting APR*', - apr: maxApr, - type: PrismaPoolAprType.VOTING, - }, - create: { - id: itemId, - chain: networkContext.chain, - poolId: this.FRESH_BEETS_POOL_ID, - title: 'Voting APR*', - apr: maxApr, - range: { - create: { - id: `${itemId}-range`, - min: minApr, - max: maxApr, - }, - }, - type: PrismaPoolAprType.VOTING, - group: null, - }, - }); - } - } -} diff --git a/modules/pool/lib/apr-data-sources/dynamic-swap-fee-apr-from-events.service.ts b/modules/pool/lib/apr-data-sources/dynamic-swap-fee-apr-from-events.service.ts deleted file mode 100644 index 561085736..000000000 --- a/modules/pool/lib/apr-data-sources/dynamic-swap-fee-apr-from-events.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { Chain, PrismaPoolType } from '@prisma/client'; -import { daysAgo } from '../../../common/time'; - -type PoolSwapFeeData = { - poolId: string; - chain: Chain; - fees_24h: number; -}; - -const query = (chain: Chain, timestamp: number) => ` - SELECT - "poolId", - chain, - SUM((payload->'dynamicFee'->>'valueUSD')::numeric) AS fees_24h - FROM - "PartitionedPoolEvent" - WHERE - "blockTimestamp" >= ${timestamp} - AND chain = '${chain}' - AND type = 'SWAP' - GROUP BY - 1, 2 -`; - -const MAX_DB_INT = 9223372036854775807; - -export class DynamicSwapFeeFromEventsAprService implements PoolAprService { - public getAprServiceName(): string { - return 'DynamicSwapFeeAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const chain = pools[0].chain; - const yesterday = daysAgo(1); - - const [dynamicData, existingAprItems] = await Promise.all([ - prisma.prismaPoolDynamicData.findMany({ - where: { chain, poolId: { in: pools.map((pool) => pool.id) } }, - }), - prisma.prismaPoolAprItem - .findMany({ select: { id: true, apr: true }, where: { chain, type: 'DYNAMIC_SWAP_FEE_24H' } }) - .then((records) => Object.fromEntries(records.map((item) => [item.id, item.apr]))), - ]); - - // Fetch the swap fees for the last 30 days - const swapFeeData = await prisma.$queryRawUnsafe(query(chain, yesterday)); - - // Map the swap fee data to the pool id - const swapFeeDataMap = swapFeeData.reduce( - (acc, data) => { - acc[data.poolId] = data; - return acc; - }, - {} as Record, - ); - - const operations = dynamicData - .map((pool) => { - let apr_24h = 0; - let protocolFee = parseFloat(pool.aggregateSwapFee); - - if (pool.isInRecoveryMode) { - protocolFee = 0; - } - - if (pool.totalLiquidity > 0 && swapFeeDataMap[pool.poolId]) { - apr_24h = ((swapFeeDataMap[pool.poolId].fees_24h * 365) / pool.totalLiquidity) * (1 - protocolFee); - } - if (apr_24h > MAX_DB_INT) { - apr_24h = 0; - } - - const id = `${pool.poolId}-dynamic-swap-apr-24h`; - - if (Math.abs(existingAprItems[id] || 0 - apr_24h) > 0.0001) { - return prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id, chain } }, - create: { - id, - chain, - poolId: pool.poolId, - title: 'Dynamic swap fees APR', - apr: apr_24h, - type: 'DYNAMIC_SWAP_FEE_24H', - }, - update: { apr: apr_24h }, - }); - } else { - return null; - } - }) - .filter((pool): pool is NonNullable => !!pool); - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/lib/apr-data-sources/index.ts b/modules/pool/lib/apr-data-sources/index.ts deleted file mode 100644 index c2e330ee1..000000000 --- a/modules/pool/lib/apr-data-sources/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './nested-pool-apr.service'; -export * from './swap-fee-apr.service'; -export * from './swap-fee-apr-from-events.service'; -export * from './ve-bal-gauge-apr.service'; -export * from './yb-tokens-apr.service'; -export * from './vebal-protocol-apr.service'; -export * from './vebal-voting-apr.service'; -export * from './morpho-rewards-apr.service'; -export * from './dynamic-swap-fee-apr-from-events.service'; diff --git a/modules/pool/lib/apr-data-sources/morpho-api-client.ts b/modules/pool/lib/apr-data-sources/morpho-api-client.ts deleted file mode 100644 index 69fa3d713..000000000 --- a/modules/pool/lib/apr-data-sources/morpho-api-client.ts +++ /dev/null @@ -1,90 +0,0 @@ -import request, { gql } from 'graphql-request'; - -const url = 'https://blue-api.morpho.org/graphql'; -const query = gql` - { - vaults(first: 1000, where: { netApy_gte: 0.00001 }) { - items { - address - asset { - address - yield { - apr - } - } - chain { - network - } - state { - fee - dailyApy - dailyNetApy - } - } - } - } -`; - -/* -Morpho APIs results are as follows: -- dailyApy: Vault APY excluding rewards, before deducting the performance fee. Also NOT including the net APY of the underlying asset. -- dailyNetApy: Vault APY including rewards and underlying yield, after deducting the performance fee. - - -We only want to get the APY for rewards as we account for underlying yield separately inside the YB APR service. -We therefore deduct the fee from the apy and subtract the asset yield apr from it. -*/ - -type Vault = { - address: string; - chain: { - network: string; - }; - asset: { - address: string; - yield?: { - apr: number; - }; - }; - state: { - fee: number; - dailyApy: number; - dailyNetApy: number; - }; -}; - -type BlueApiResponse = { - vaults: { - items: Vault[]; - }; -}; - -const mapMorphoNetworkToChain = { - ethereum: 'MAINNET', - base: 'BASE', -}; - -export const morphoApiClient = { - morphoApr: async () => { - const { - vaults: { items }, - } = await request(url, query); - - // Map apy to vault addresses - return Object.fromEntries( - items.map((vault: Vault) => [ - vault.address.toLowerCase(), - { - dailyApy: vault.state.dailyApy, - dailyNetApy: vault.state.dailyNetApy, - fee: vault.state.fee, - rewardApy: - vault.state.dailyNetApy - - vault.state.dailyApy * (1 - vault.state.fee) - - (vault.asset.yield?.apr || 0), - chain: mapMorphoNetworkToChain[vault.chain.network as keyof typeof mapMorphoNetworkToChain], - }, - ]), - ); - }, -}; diff --git a/modules/pool/lib/apr-data-sources/morpho-rewards-apr.service.ts b/modules/pool/lib/apr-data-sources/morpho-rewards-apr.service.ts deleted file mode 100644 index 56cb06be8..000000000 --- a/modules/pool/lib/apr-data-sources/morpho-rewards-apr.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { PrismaPoolAprType } from '@prisma/client'; -import { morphoApiClient } from './morpho-api-client'; -// IDs can be converted to hashes for DB perf optimization -// import murmurhash from 'murmurhash'; - -export class MorphoRewardsAprService implements PoolAprService { - public getAprServiceName(): string { - return 'MorphoRewardsAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const aprItems = await this.getAprItems(pools); - - await prisma.$transaction( - aprItems.map((item) => - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: item.id, chain: item.chain } }, - update: { - apr: item.apr, - }, - create: item, - }), - ), - ); - } - - private async getAprItems(pools: PoolForAPRs[]) { - // Get Morpho aprs - const morphoApr = await morphoApiClient.morphoApr(); - - // Find all pools with Morpho vault tokens - const morphoVaultAddresses = Object.keys(morphoApr); - const poolsWithMorphoTokens = pools.filter((pool) => { - return pool.tokens.find((token) => morphoVaultAddresses.includes(token.address)); - }); - - // For each of them get reward token APRs - const aprItems = poolsWithMorphoTokens.flatMap((pool) => { - const tokens = pool.tokens.filter((token) => morphoVaultAddresses.includes(token.address)); - const tvl = pool.tokens.map((t) => t.balanceUSD).reduce((a, b) => a + b, 0); - - const vaultRewards = tokens.flatMap((token) => { - const vaultApr = morphoApr[token.address]; - const weight = token.balanceUSD / tvl || 0; - - return { - // id: murmurhash.v3(`${pool.id}-${token.address}-${rewardToken.address}`).toString(36), - id: `${pool.id}-morphovault-rewards`, - chain: pool.chain, - poolId: pool.id, - title: 'MORPHO VAULT APR', - apr: vaultApr.rewardApy * weight, - type: PrismaPoolAprType.MERKL, - }; - }); - - return vaultRewards; - }); - - return aprItems; - } -} diff --git a/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts b/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts deleted file mode 100644 index 129956852..000000000 --- a/modules/pool/lib/apr-data-sources/nested-pool-apr.service.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { collectsYieldFee } from '../pool-utils'; - -export class NestedPoolAprService implements PoolAprService { - public getAprServiceName(): string { - return 'BoostedPoolAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const chain = pools[0].chain; - - // need to do multiple queries otherwise the nesting is too deep for many pools. Error: stack depth limit exceeded - const poolsWithNestedPool = await prisma.prismaPool.findMany({ - where: { - chain, - id: { in: pools.map((pool) => pool.id) }, - tokens: { some: { nestedPoolId: { not: null } } }, - }, - include: { - dynamicData: true, - tokens: { - orderBy: { index: 'asc' }, - include: { - nestedPool: true, - token: true, - }, - }, - }, - }); - - for (const pool of poolsWithNestedPool) { - const protocolYieldFeePercentage = parseFloat(pool.dynamicData?.protocolYieldFee || '0'); - const tokens = pool.tokens.filter((token) => { - // exclude the phantom bpt pool token itself - if (token.address === pool.address) { - return false; - } - }); - - const poolIds = tokens.map((token) => token.nestedPool?.id || ''); - // swap fee and IB yield is also earned on the parent pool - const aprItems = await prisma.prismaPoolAprItem.findMany({ - where: { - poolId: { in: poolIds }, - type: { in: ['IB_YIELD', 'SWAP_FEE'] }, - chain: pool.chain, - }, - }); - - for (const token of tokens) { - const tokenAprItems = aprItems.filter((item) => item.poolId === token.nestedPoolId); - - if ( - !pool.dynamicData || - !token.nestedPool || - !token.nestedPool.type || - token.balanceUSD === 0 || - pool.dynamicData.totalLiquidity === 0 - ) { - continue; - } - - for (const aprItem of tokenAprItems) { - const itemId = `${pool.id}-${aprItem.id}`; - //scale the apr as a % of total liquidity - - const apr = aprItem.apr * (token.balanceUSD / pool.dynamicData.totalLiquidity); - let userApr = apr; - - if ( - collectsYieldFee(pool) && - // nested tokens/bpts that dont have a rate provider, we don't take any fees - token.priceRate !== '1.0' - ) { - userApr = userApr * (1 - protocolYieldFeePercentage); - } - - const title = aprItem.type === 'SWAP_FEE' ? `${token.token.symbol} APR` : aprItem.title; - - await prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: itemId, chain: pool.chain } }, - create: { - id: itemId, - chain: pool.chain, - poolId: pool.id, - apr: userApr, - title: title, - group: aprItem.group, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }, - update: { - apr: userApr, - title: title, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }, - }); - } - } - } - } -} diff --git a/modules/pool/lib/apr-data-sources/quant-amm-apr-handler.ts b/modules/pool/lib/apr-data-sources/quant-amm-apr-handler.ts deleted file mode 100644 index ee4ba7c07..000000000 --- a/modules/pool/lib/apr-data-sources/quant-amm-apr-handler.ts +++ /dev/null @@ -1,163 +0,0 @@ -import _ from 'lodash'; -import { prisma } from '../../../../prisma/prisma-client'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { PoolAprService } from '../../pool-types'; -import moment from 'moment'; -import { PrismaTokenPrice } from '@prisma/client'; - -export class QuantAmmAprService implements PoolAprService { - public getAprServiceName(): string { - return 'QuantAmmAprServices'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const quantAmmPools = pools.filter((pool) => pool.type === 'QUANT_AMM_WEIGHTED'); - - if (quantAmmPools.length === 0) { - return; - } - const chain = quantAmmPools[0].chain; - - const poolsExpanded = await prisma.prismaPool.findMany({ - where: { chain, id: { in: quantAmmPools.map((pool) => pool.id) } }, - include: { - dynamicData: true, - tokens: true, - }, - }); - - const poolAddresses = poolsExpanded.map((pool) => pool.address.toLowerCase()); - - const tokensToPrice = poolsExpanded - .map((pool) => { - return pool.tokens.map((token) => token.address.toLowerCase()); - }) - .flat(); - - const uniqueTokensToPrice = _.uniq([...tokensToPrice, ...poolAddresses]); - - const midnightOneMonthAgo = moment().utc().startOf('day').subtract(30, 'days').unix(); - - // launch date of Quant AMM - const quantLaunchDate = moment('2025-05-15T00:00:00Z').unix(); - - const prices = await prisma.prismaTokenPrice.findMany({ - where: { - tokenAddress: { in: uniqueTokensToPrice }, - chain: chain, - timestamp: { gte: Math.max(midnightOneMonthAgo, quantLaunchDate) }, - }, - orderBy: { timestamp: 'asc' }, - }); - - const currentPrices = await prisma.prismaTokenCurrentPrice.findMany({ - where: { - tokenAddress: { in: uniqueTokensToPrice }, - chain: chain, - }, - }); - - const pricesByToken = _.groupBy(prices, 'tokenAddress'); - const pricesByTimestamp = _.groupBy(prices, 'timestamp'); - - for (const pool of poolsExpanded) { - const poolPrices = pricesByToken[pool.address.toLowerCase()]; - - if (!poolPrices || poolPrices.length === 0 || !pool.dynamicData?.totalLiquidity) { - continue; - } - - const poolTokenAddresses = pool.tokens.map((token) => token.address.toLowerCase()); - - // find oldest timestamp that has all prices - let startTokenPrices: PrismaTokenPrice[] = []; - let oldestIndexForAllPrices = 0; - for (oldestIndexForAllPrices = 0; oldestIndexForAllPrices < poolPrices.length; oldestIndexForAllPrices++) { - const poolPrice = poolPrices[oldestIndexForAllPrices]; - const foundPrices = pricesByTimestamp[poolPrice.timestamp].filter( - (price) => - price.tokenAddress !== pool.address.toLowerCase() && - poolTokenAddresses.includes(price.tokenAddress), - ); - if (foundPrices.length === poolTokenAddresses.length) { - startTokenPrices = foundPrices; - break; - } - } - - if (startTokenPrices.length === 0) { - console.error(`Quant AMM APR: No start prices found for pool ${pool.id} on chain ${chain}.`); - continue; - } - - const oldestEntryPoolPrice = poolPrices[oldestIndexForAllPrices]; - - const startLpPrice = oldestEntryPoolPrice; - - const endTokenPrices = currentPrices.filter( - (price) => - price.tokenAddress !== pool.address.toLowerCase() && - poolTokenAddresses.includes(price.tokenAddress), - ); - - if (endTokenPrices.length === 0) { - console.error(`Quant AMM APR: No end prices found for pool ${pool.id} on chain ${chain}.`); - } - - if (startTokenPrices.length !== endTokenPrices.length) { - console.error( - `Quant AMM APR: Mismatched price data for pool ${pool.id} on chain ${chain}. Start prices: ${startTokenPrices.length}, End prices: ${endTokenPrices.length}`, - ); - continue; - } - - const endLpPrice = currentPrices.filter((price) => price.tokenAddress === pool.address.toLowerCase())[0]; - - if (!endLpPrice) { - console.error(`Quant AMM APR: No end LP price found for pool ${pool.id} on chain ${chain}.`); - } - - const weight = 1 / pool.tokens.length; - - const sortedStartTokenPrices = _.sortBy(startTokenPrices, (price) => price.tokenAddress); - const sortedEndTokenPrices = _.sortBy(endTokenPrices, (price) => price.tokenAddress); - - const priceRatios = sortedEndTokenPrices.map((end, i) => end.price / sortedStartTokenPrices[i].price); - - const endWeightedValue = - startLpPrice.price * priceRatios.reduce((acc, ratio) => acc * Math.pow(ratio, weight), 1); - - const relativeReturn = endLpPrice.price / endWeightedValue - 1; - - const totalYearlyReturn = relativeReturn * 12; - - if (pool.address.toLowerCase() === '0x6b61d8680c4f9e560c8306807908553f95c749c5') { - // nice console log for debug - console.log(`Quant AMM APR for pool ${pool.id} on chain ${chain}`); - console.log(`Start timestamp: ${sortedStartTokenPrices[0].timestamp}`); - console.log(`End timestamp: ${sortedEndTokenPrices[0].timestamp}`); - console.log(`Start LP price: ${startLpPrice.price}`); - console.log(`End LP price: ${endLpPrice.price}`); - console.log(`Start token prices: ${sortedStartTokenPrices.map((price) => price.price)}`); - console.log(`End token prices: ${sortedEndTokenPrices.map((price) => price.price)}`); - console.log(`Price ratios: ${priceRatios}`); - console.log(`End weighted value: ${endWeightedValue}`); - console.log(`Yearly return: ${totalYearlyReturn}`); - } - - await prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.id}-quant-amm-apr`, chain: chain } }, - update: { apr: totalYearlyReturn }, - create: { - id: `${pool.id}-quant-amm-apr`, - chain: chain, - poolId: pool.id, - apr: totalYearlyReturn, - title: 'Quant AMM APR', - type: 'QUANT_AMM_UPLIFT', - group: null, - }, - }); - } - } -} diff --git a/modules/pool/lib/apr-data-sources/reliquary-farm-apr.service.ts b/modules/pool/lib/apr-data-sources/reliquary-farm-apr.service.ts deleted file mode 100644 index 5a1b725c3..000000000 --- a/modules/pool/lib/apr-data-sources/reliquary-farm-apr.service.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { addressesMatch } from '../../../web3/addresses'; -import { PrismaPoolAprType } from '@prisma/client'; -import { prisma } from '../../../../prisma/prisma-client'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { secondsPerYear } from '../../../common/time'; -import { tokenService } from '../../../token/token.service'; -import { PoolAprService } from '../../pool-types'; -import { networkContext } from '../../../network/network-context.service'; -import { ReliquarySubgraphService } from '../../../subgraphs/reliquary-subgraph/reliquary.service'; - -export class ReliquaryFarmAprService implements PoolAprService { - constructor(private readonly beetsAddress: string) {} - - public getAprServiceName(): string { - return 'ReliquaryFarmAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const chain = pools[0].chain; - const reliquarySubgraphService = new ReliquarySubgraphService(networkContext.data.subgraphs.reliquary!); - const allSubgraphFarms = await reliquarySubgraphService.getAllFarms({}); - - const filteredFarms = allSubgraphFarms.filter( - (farm) => !networkContext.data.reliquary!.excludedFarmIds.includes(farm.pid.toString()), - ); - - const expandedReliquaryPools = await prisma.prismaPool.findMany({ - where: { chain: chain, id: { in: pools.map((pool) => pool.id) } }, - include: { - dynamicData: true, - staking: { - include: { - reliquary: { - include: { - levels: { - orderBy: { level: 'asc' }, - }, - }, - }, - }, - }, - }, - }); - - const tokenPrices = await tokenService.getTokenPrices(chain); - const operations: any[] = []; - - for (const pool of expandedReliquaryPools) { - const subgraphFarm = filteredFarms.find((farm) => addressesMatch(pool.address, farm.poolTokenAddress)); - let farm; - for (const stake of pool.staking) { - farm = stake.reliquary; - } - - if (!subgraphFarm || !pool.dynamicData || !farm || subgraphFarm.totalBalance === '0') { - continue; - } - - const totalShares = parseFloat(pool.dynamicData.totalShares); - const totalLiquidity = pool.dynamicData?.totalLiquidity || 0; - const pricePerShare = totalLiquidity / totalShares; - - const beetsPrice = tokenService.getPriceForToken(tokenPrices, this.beetsAddress, chain); - const farmBeetsPerYear = parseFloat(farm.beetsPerSecond) * secondsPerYear; - const beetsValuePerYear = beetsPrice * farmBeetsPerYear; - - const totalWeightedSupply = subgraphFarm.levels.reduce( - (total, level) => total + level.allocationPoints * parseFloat(level.balance), - 0, - ); - - /* - on the pool overview & detail page, we only show min & max apr values, but on the - reliquary page we want to show apr values for each level, so we search for the min / max - apr values and add the as apr items and also update the apr for each level of the farm - */ - let minApr = 0; - let maxApr = 0; - - for (let farmLevel of subgraphFarm.levels) { - const levelSupply = parseFloat(farmLevel.balance); - const aprShare = (farmLevel.allocationPoints * levelSupply) / totalWeightedSupply; - const apr = levelSupply !== 0 ? (beetsValuePerYear * aprShare) / (levelSupply * pricePerShare) : 0; - - if (minApr === 0 && maxApr === 0) { - minApr = apr; - maxApr = apr; - } else if (apr !== 0 && apr < minApr) { - minApr = apr; - } else if (apr > maxApr) { - maxApr = apr; - } - operations.push( - prisma.prismaPoolStakingReliquaryFarmLevel.update({ - where: { - id_chain: { - id: `${subgraphFarm.pid}-${farmLevel.level}`, - chain: chain, - }, - }, - data: { - apr: apr || 0, - }, - }), - ); - } - - operations.push( - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.id}-beets-apr`, chain: chain } }, - update: { - range: { - update: { min: minApr, max: maxApr }, - }, - }, - create: { - id: `${pool.id}-beets-apr`, - chain: chain, - poolId: pool.id, - title: 'BEETS reward APR', - apr: maxApr, - range: { - create: { - id: `${pool.id}-beets-apr-range`, - min: minApr, - max: maxApr, - }, - }, - type: PrismaPoolAprType.NATIVE_REWARD, - group: null, - rewardTokenAddress: this.beetsAddress, - rewardTokenSymbol: 'BEETS', - }, - }), - ); - } - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/lib/apr-data-sources/swap-fee-apr-from-events.service.ts b/modules/pool/lib/apr-data-sources/swap-fee-apr-from-events.service.ts deleted file mode 100644 index 76d3a013c..000000000 --- a/modules/pool/lib/apr-data-sources/swap-fee-apr-from-events.service.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { Chain, PrismaPoolType } from '@prisma/client'; - -type PoolSwapFeeData = { - poolId: string; - chain: Chain; - fees_30d: number; - fees_7d: number; - fees_24h: number; -}; - -const query = (chain: Chain) => `WITH fee_data AS ( - SELECT - "poolId", - chain, - "blockTimestamp" as ts, - (payload->'fee'->>'valueUSD')::numeric AS fee_value - FROM - "PartitionedPoolEvent" - WHERE - "blockTimestamp" >= extract(epoch from now() - interval '30 days')::int -- Only include the last 30 days - AND chain = '${chain}' - AND type = 'SWAP' -) -SELECT - "poolId", - chain, - SUM(fee_value) AS fees_30d, - SUM(CASE when ts > extract(epoch FROM now() - interval '7 days')::int then fee_value else 0 end) AS fees_7d, - SUM(CASE when ts > extract(epoch FROM now() - interval '1 day')::int then fee_value else 0 end) AS fees_24h -FROM - fee_data -GROUP BY - 1, 2`; - -const MAX_DB_INT = 9223372036854775807; - -export class SwapFeeFromEventsAprService implements PoolAprService { - public getAprServiceName(): string { - return 'SwapFeeAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const chain = pools[0].chain; - - const typeMap = pools.reduce((acc, pool) => { - acc[pool.id] = pool.type; - return acc; - }, {} as Record); - - const dynamicData = await prisma.prismaPoolDynamicData.findMany({ - where: { chain, poolId: { in: pools.map((pool) => pool.id) } }, - }); - - // Fetch the swap fees for the last 30 days - const swapFeeData = await prisma.$queryRawUnsafe(query(chain)); - - // Map the swap fee data to the pool id - const swapFeeDataMap = swapFeeData.reduce((acc, data) => { - acc[data.poolId] = data; - return acc; - }, {} as Record); - - const operations = dynamicData.flatMap((pool) => { - let apr_24h = 0; - let apr_7d = 0; - let apr_30d = 0; - - if (pool.totalLiquidity > 0 && swapFeeDataMap[pool.poolId]) { - apr_24h = (pool.fees24h * 365) / pool.totalLiquidity; - apr_7d = (swapFeeDataMap[pool.poolId].fees_7d * 365) / 7 / pool.totalLiquidity; - apr_30d = (swapFeeDataMap[pool.poolId].fees_30d * 365) / 30 / pool.totalLiquidity; - } - - let protocolFee = parseFloat(pool.protocolSwapFee); - - if (typeMap[pool.poolId] === 'GYROE') { - // Gyro has custom protocol fee structure - protocolFee = parseFloat(pool.protocolYieldFee || '0'); - } - if (pool.isInRecoveryMode || typeMap[pool.poolId] === 'LIQUIDITY_BOOTSTRAPPING') { - // pool does not collect any protocol fees - protocolFee = 0; - } - - apr_24h = apr_24h * (1 - protocolFee); - apr_7d = apr_7d * (1 - protocolFee); - apr_30d = apr_30d * (1 - protocolFee); - - if (apr_24h > MAX_DB_INT) { - apr_24h = 0; - } - if (apr_7d > MAX_DB_INT) { - apr_7d = 0; - } - if (apr_30d > MAX_DB_INT) { - apr_30d = 0; - } - - return [ - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr`, chain } }, - create: { - id: `${pool.poolId}-swap-apr`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR', - apr: apr_24h, - type: 'SWAP_FEE', - }, - update: { apr: apr_24h }, - }), - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr-24h`, chain } }, - create: { - id: `${pool.poolId}-swap-apr-24h`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR (24h)', - apr: apr_24h, - type: 'SWAP_FEE_24H', - }, - update: { apr: apr_24h }, - }), - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr-7d`, chain } }, - create: { - id: `${pool.poolId}-swap-apr-7d`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR (7d)', - apr: apr_7d, - type: 'SWAP_FEE_7D', - }, - update: { apr: apr_7d }, - }), - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr-30d`, chain } }, - create: { - id: `${pool.poolId}-swap-apr-30d`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR (30d)', - apr: apr_30d, - type: 'SWAP_FEE_30D', - }, - update: { apr: apr_30d }, - }), - ]; - }); - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/lib/apr-data-sources/swap-fee-apr-from-snapshots.service.ts b/modules/pool/lib/apr-data-sources/swap-fee-apr-from-snapshots.service.ts deleted file mode 100644 index a438eec1d..000000000 --- a/modules/pool/lib/apr-data-sources/swap-fee-apr-from-snapshots.service.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { Chain, PrismaPoolType } from '@prisma/client'; -import { daysAgo, roundToMidnight } from '../../../common/time'; -import _ from 'lodash'; - -type PoolSwapFeeData = { - poolId: string; - chain: Chain; - fees_30d: number; - fees_7d: number; -}; - -const fetchSwapFeeData = async (chain: Chain) => { - const [snapshots30d, snapshots7d] = await Promise.all([ - prisma.prismaPoolSnapshot.findMany({ - where: { - chain, - timestamp: roundToMidnight(daysAgo(30)), - }, - select: { - poolId: true, - totalSwapFee: true, - }, - }), - prisma.prismaPoolSnapshot.findMany({ - where: { - chain, - timestamp: roundToMidnight(daysAgo(7)), - }, - select: { - poolId: true, - totalSwapFee: true, - }, - }), - ]); - - const poolIds = _.uniq([ - ...snapshots30d.map((snapshot) => snapshot.poolId), - ...snapshots7d.map((snapshot) => snapshot.poolId), - ]); - - const swapFeeData: PoolSwapFeeData[] = poolIds.map((poolId) => { - const snapshot30d = snapshots30d.find((s) => s.poolId === poolId); - const snapshot7d = snapshots7d.find((s) => s.poolId === poolId); - - return { - poolId, - chain, - fees_30d: snapshot30d ? snapshot30d.totalSwapFee : 0, - fees_7d: snapshot7d ? snapshot7d.totalSwapFee : 0, - }; - }); - - return swapFeeData; -}; - -const MAX_DB_INT = 9223372036854775807; - -export class SwapFeeFromSnapshotsAprService implements PoolAprService { - public getAprServiceName(): string { - return 'SwapFeeAprService'; - } - - // This service is used outside of main APRs look, only for 7,30 day swap fee aprs. - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - // It will receive one pool only, because data is refetched in the body later - const chain = pools[0].chain; - - // Get pool type map - const [typeMap, dynamicData, currentAprs] = await Promise.all([ - prisma.prismaPool.findMany({ select: { id: true, type: true }, where: { chain } }).then((records) => - records.reduce( - (acc, pool) => { - acc[pool.id] = pool.type; - return acc; - }, - {} as Record, - ), - ), - prisma.prismaPoolDynamicData.findMany({ where: { chain } }), - prisma.prismaPoolAprItem - .findMany({ - select: { id: true, apr: true }, - where: { chain, type: { in: ['SWAP_FEE_7D', 'SWAP_FEE_30D'] } }, - }) - .then((records) => Object.fromEntries(records.map((item) => [item.id, item.apr]))), - ]); - - // Fetch the swap fees for the last 30 days - const swapFeeData = await fetchSwapFeeData(chain); - - // Map the swap fee data to the pool id - const swapFeeDataMap = swapFeeData.reduce( - (acc, data) => { - acc[data.poolId] = data; - return acc; - }, - {} as Record, - ); - - const operations = dynamicData.flatMap((pool) => { - let apr_7d = 0; - let apr_30d = 0; - - if (pool.totalLiquidity > 0 && swapFeeDataMap[pool.poolId]) { - apr_7d = (swapFeeDataMap[pool.poolId].fees_7d * 365) / 7 / pool.totalLiquidity; - apr_30d = (swapFeeDataMap[pool.poolId].fees_30d * 365) / 30 / pool.totalLiquidity; - } - - let protocolFee = parseFloat(pool.protocolSwapFee); - - if (typeMap[pool.poolId] === 'GYROE') { - // Gyro has custom protocol fee structure - protocolFee = parseFloat(pool.protocolYieldFee || '0'); - } - if (pool.isInRecoveryMode || typeMap[pool.poolId] === 'LIQUIDITY_BOOTSTRAPPING') { - // pool does not collect any protocol fees - protocolFee = 0; - } - - apr_7d = apr_7d * (1 - protocolFee); - apr_30d = apr_30d * (1 - protocolFee); - - if (apr_7d > MAX_DB_INT) { - apr_7d = 0; - } - if (apr_30d > MAX_DB_INT) { - apr_30d = 0; - } - - return [ - ...(Math.abs((currentAprs[`${pool.poolId}-swap-apr-7d`] || 0) - apr_7d) > 0.0001 - ? [ - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr-7d`, chain } }, - create: { - id: `${pool.poolId}-swap-apr-7d`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR (7d)', - apr: apr_7d, - type: 'SWAP_FEE_7D', - }, - update: { apr: apr_7d }, - }), - ] - : []), - ...(Math.abs((currentAprs[`${pool.poolId}-swap-apr-30d`] || 0) - apr_30d) > 0.0001 - ? [ - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: `${pool.poolId}-swap-apr-30d`, chain } }, - create: { - id: `${pool.poolId}-swap-apr-30d`, - chain, - poolId: pool.poolId, - title: 'Swap fees APR (30d)', - apr: apr_7d, - type: 'SWAP_FEE_30D', - }, - update: { apr: apr_30d }, - }), - ] - : []), - ]; - }); - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/lib/apr-data-sources/swap-fee-apr.service.ts b/modules/pool/lib/apr-data-sources/swap-fee-apr.service.ts deleted file mode 100644 index 6f645f505..000000000 --- a/modules/pool/lib/apr-data-sources/swap-fee-apr.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; - -const MAX_DB_INT = 9223372036854775807; - -export class SwapFeeAprService implements PoolAprService { - public getAprServiceName(): string { - return 'SwapFeeAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const chain = pools[0].chain; - const operations: any[] = []; - - const existingAprItems = await prisma.prismaPoolAprItem - .findMany({ - select: { id: true, apr: true }, - where: { chain }, - }) - .then((records) => Object.fromEntries(records.map((item) => [item.id, item.apr]))); - - for (const pool of pools) { - if (pool.dynamicData) { - const apr = - pool.dynamicData.totalLiquidity > 0 - ? (pool.dynamicData.fees24h * 365) / pool.dynamicData.totalLiquidity - : 0; - - let protocolFee = parseFloat(pool.dynamicData.protocolSwapFee); - if (pool.type === 'GYROE') { - // Gyro has custom protocol fee structure - protocolFee = parseFloat(pool.dynamicData.protocolYieldFee || '0'); - } - - if (pool.protocolVersion === 3) { - protocolFee = parseFloat(pool.dynamicData.aggregateSwapFee); - } - - if (pool.dynamicData.isInRecoveryMode || pool.type === 'LIQUIDITY_BOOTSTRAPPING') { - // pool does not collect any protocol fees - protocolFee = 0; - } - - let userApr = apr * (1 - protocolFee); - - // TODO: clean this up - if (userApr > MAX_DB_INT) { - userApr = 0; - } - - const id = `${pool.id}-swap-apr-24h`; - - if (Math.abs((existingAprItems[id] || 0) - userApr) > 0.0001) { - operations.push( - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id, chain } }, - create: { - id, - chain, - poolId: pool.id, - title: 'Swap fees APR (24h)', - apr: userApr, - type: 'SWAP_FEE_24H', - }, - update: { apr: userApr }, - }), - ); - } - } - } - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts b/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts deleted file mode 100644 index 63c9fd7fd..000000000 --- a/modules/pool/lib/apr-data-sources/ve-bal-gauge-apr.service.ts +++ /dev/null @@ -1,181 +0,0 @@ -/** - * This service calculates the APR for a pool based on the gauge rewards - * - * Definitions: - * The “working supply” of the gauge - the effective total LP token amount after all deposits have been boosted. - * "Working balance" is 40% of a user balance in a gauge - used only for BAL rewards on v2 gauges on child gauges or on mainnet - */ -import { PoolAprService } from '../../pool-types'; -import { secondsPerYear } from '../../../common/time'; -import { Chain, PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType } from '@prisma/client'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { tokenService } from '../../../token/token.service'; - -export class GaugeAprService implements PoolAprService { - private readonly MAX_VEBAL_BOOST = 2.5; - - constructor() {} - - public getAprServiceName(): string { - return 'GaugeAprService'; - } - - public async updateAprForPools(pools: { id: string; chain: Chain }[]): Promise { - const itemOperations: any[] = []; - const rangeOperations: any[] = []; - - const chain = pools[0].chain; - - // Get the data - const tokenPrices = await tokenService.getTokenPrices(chain); - const stakings = await prisma.prismaPoolStaking.findMany({ - where: { - poolId: { in: pools.map((pool) => pool.id) }, - type: 'GAUGE', - chain, - }, - include: { - gauge: { - include: { - rewards: true, - }, - }, - pool: { - include: { - dynamicData: true, - }, - }, - }, - }); - - for (const stake of stakings) { - const { pool, gauge } = stake; - - if (!gauge || !gauge.rewards || !pool.dynamicData || pool.dynamicData.totalShares === '0') { - continue; - } - - // Get token rewards per year with data needed for the DB - const rewards = await Promise.allSettled( - gauge.rewards.map(async ({ id, tokenAddress, rewardPerSecond, isVeBalemissions }) => { - const price = tokenService.getPriceForToken(tokenPrices, tokenAddress, pool.chain); - if (!price) { - return Promise.reject(`Price not found for ${tokenAddress}`); - } - - let definition; - try { - definition = await prisma.prismaToken.findUniqueOrThrow({ - where: { address_chain: { address: tokenAddress, chain: pool.chain } }, - }); - } catch (e) { - //we don't have the reward token added as a token, only happens for testing tokens - return Promise.reject('Definition not found'); - } - - return { - id: id, - address: tokenAddress, - symbol: definition.symbol, - rewardPerYear: parseFloat(rewardPerSecond) * secondsPerYear * price, - isVeBalemissions: isVeBalemissions, - }; - }), - ); - - // Calculate APRs - const totalShares = parseFloat(pool.dynamicData.totalShares); - const gaugeTotalShares = parseFloat(gauge.totalSupply); - const bptPrice = pool.dynamicData.totalLiquidity / totalShares; - const gaugeTvl = gaugeTotalShares * bptPrice; - const workingSupply = parseFloat(gauge.workingSupply); - - const aprItems = rewards - .map((reward) => { - if (reward.status === 'rejected') { - console.error( - `Error: Failed to get reward data for ${gauge.id} on chain ${pool.chain}: ${reward.reason}`, - ); - return null; - } - - const { address, symbol, rewardPerYear, isVeBalemissions } = reward.value; - - const itemData: PrismaPoolAprItem = { - id: `${reward.value.id}-${symbol}-apr`, - chain: pool.chain, - poolId: pool.id, - title: `${symbol} reward APR`, - group: null, - apr: 0, - rewardTokenAddress: address, - rewardTokenSymbol: symbol, - type: isVeBalemissions ? PrismaPoolAprType.NATIVE_REWARD : PrismaPoolAprType.THIRD_PARTY_REWARD, - }; - - // veBAL rewards have a range associated with the item - // this is deprecated - if (isVeBalemissions && (pool.chain === 'MAINNET' || gauge.version === 2)) { - let minApr = 0; - if (gaugeTvl > 0) { - if (workingSupply > 0 && gaugeTotalShares > 0) { - minApr = (((gaugeTotalShares * 0.4) / workingSupply) * rewardPerYear) / gaugeTvl; - } else { - minApr = rewardPerYear / gaugeTvl; - } - } - - const aprRangeId = `${itemData.id}-range`; - - const rangeData = { - id: aprRangeId, - chain: pool.chain, - aprItemId: itemData.id, - min: minApr, - max: minApr * this.MAX_VEBAL_BOOST, - }; - - itemData.apr = minApr * this.MAX_VEBAL_BOOST; - - return [itemData, rangeData]; - } else { - itemData.apr = gaugeTvl > 0 ? rewardPerYear / gaugeTvl : 0; - - return itemData; - } - }) - .flat() - .filter((apr): apr is PrismaPoolAprItem | PrismaPoolAprRange => apr !== null); - - const items = aprItems.filter((item) => !item.id.includes('apr-range')); - const ranges = aprItems.filter((item) => item.id.includes('apr-range')); - - itemOperations.push( - ...items.map((item) => - prisma.prismaPoolAprItem.upsert({ - where: { - id_chain: { id: item.id, chain: pool.chain }, - }, - update: item, - create: item as PrismaPoolAprItem, - }), - ), - ); - rangeOperations.push( - ...ranges.map((range) => - prisma.prismaPoolAprRange.upsert({ - where: { - id_chain: { id: range.id, chain: pool.chain }, - }, - update: range, - create: range as PrismaPoolAprRange, - }), - ), - ); - } - - await prismaBulkExecuteOperations(itemOperations); - await prismaBulkExecuteOperations(rangeOperations); - } -} diff --git a/modules/pool/lib/apr-data-sources/vebal-protocol-apr.service.ts b/modules/pool/lib/apr-data-sources/vebal-protocol-apr.service.ts deleted file mode 100644 index 7eb74850e..000000000 --- a/modules/pool/lib/apr-data-sources/vebal-protocol-apr.service.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { multicallViem } from '../../../web3/multicaller-viem'; -import { mainnet } from 'viem/chains'; -import { createPublicClient, formatUnits, http, parseAbi } from 'viem'; - -const feeDistributorAbi = parseAbi([ - 'function getTokensDistributedInWeek(address token, uint timestamp) view returns (uint)', - 'function claimTokens(address user, address[] tokens) returns (uint256[])', - 'function claimToken(address user, address token) returns (uint256)', -]); - -const veBalAbi = parseAbi(['function totalSupply() view returns (uint)']); - -const feeDistributorAddress = '0xd3cf852898b21fc233251427c2dc93d3d604f3bb'; -const balAddress = '0xba100000625a3754423978a60c9317c58a424e3d'; -const vebalPool = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; -const vebalPoolAddress = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56'; -const vebalAddress = '0xc128a9954e6c874ea3d62ce62b468ba073093f25'; -const usdcAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; -const id = `${vebalPool}-protocol-apr`; -const chain = 'MAINNET'; - -const getPreviousWeek = (fromJSTimestamp: number): number => { - const weeksToGoBack = 1; - const midnight = new Date(Math.floor(fromJSTimestamp)); - midnight.setUTCHours(0); - midnight.setUTCMinutes(0); - midnight.setUTCSeconds(0); - midnight.setUTCMilliseconds(0); - - let daysSinceThursday = midnight.getUTCDay() - 4; - if (daysSinceThursday < 0) daysSinceThursday += 7; - - daysSinceThursday = daysSinceThursday + weeksToGoBack * 7; - - return Math.floor(midnight.getTime() / 1000) - daysSinceThursday * 86400; -}; - -const fetchRevenue = async (timestamp: number, rpcUrl: string) => { - const previousWeek = getPreviousWeek(timestamp); - - const viemClient = createPublicClient({ - chain: mainnet, - transport: http(rpcUrl), - }); - - const results = await multicallViem(viemClient, [ - { - path: 'balAmount', - address: feeDistributorAddress, - abi: feeDistributorAbi, - functionName: 'getTokensDistributedInWeek', - args: [balAddress, previousWeek], - }, - { - path: 'usdcAmount', - address: feeDistributorAddress, - abi: feeDistributorAbi, - functionName: 'getTokensDistributedInWeek', - args: [usdcAddress, previousWeek], - }, - { - path: 'veBalSupply', - address: vebalAddress, - abi: veBalAbi, - functionName: 'totalSupply', - }, - ]); - - const data = { - balAmount: parseFloat(formatUnits(results.balAmount, 18)), - usdcAmount: parseFloat(formatUnits(results.usdcAmount, 6)), - veBalSupply: parseFloat(formatUnits(results.veBalSupply, 18)), - usdcPrice: parseFloat('1.0'), - balAddress: balAddress, - }; - - return data; -}; - -export class VeBalProtocolAprService implements PoolAprService { - constructor(private rpcUrl: string) {} - - public getAprServiceName(): string { - return 'ProtocolAprService'; - } - - async getApr(): Promise { - const revenue = await fetchRevenue(Date.now(), this.rpcUrl); - - // Prices - const balPrice = await prisma.prismaTokenCurrentPrice.findFirst({ - where: { tokenAddress: balAddress, chain: 'MAINNET' }, - select: { price: true }, - }); - - const usdcPrice = await prisma.prismaTokenCurrentPrice.findFirst({ - where: { tokenAddress: usdcAddress, chain: 'MAINNET' }, - select: { price: true }, - }); - - const bptPrice = await prisma.prismaTokenCurrentPrice.findFirst({ - where: { tokenAddress: vebalPoolAddress, chain: 'MAINNET' }, - select: { price: true }, - }); - - if (!balPrice || !usdcPrice || !bptPrice) { - return 0; - } - - const lastWeekBalRevenue = revenue.balAmount * balPrice.price; - const lastWeekUsdcRevenue = revenue.usdcAmount * usdcPrice.price; - - const dailyRevenue = (lastWeekBalRevenue + lastWeekUsdcRevenue) / 7; - const apr = (365 * dailyRevenue) / (bptPrice.price * revenue.veBalSupply); - - return apr; - } - - async updateAprForPools(pools: PoolForAPRs[]): Promise { - const apr = await this.getApr(); - - await prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id, chain: 'MAINNET' } }, - create: { - id, - chain, - poolId: vebalPool, - apr, - title: 'Protocol APR', - type: 'LOCKING', - }, - update: { apr }, - }); - } -} diff --git a/modules/pool/lib/apr-data-sources/vebal-voting-apr.service.ts b/modules/pool/lib/apr-data-sources/vebal-voting-apr.service.ts deleted file mode 100644 index daedab5b4..000000000 --- a/modules/pool/lib/apr-data-sources/vebal-voting-apr.service.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { Chain } from '@prisma/client'; -import { prisma } from '../../../../prisma/prisma-client'; - -const HIDDEN_HAND_API_URL = 'https://api.hiddenhand.finance/proposal/balancer'; -const VEBAL = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56'; - -const vebalPool = '0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014'; -const id = `${vebalPool}-voting-apr`; -const chain = 'MAINNET'; - -type HiddenHandResponse = { - error: boolean; - data: { - poolId: string; - proposal: string; - proposalHash: string; - title: string; - proposalDeadline: number; - totalValue: number; - maxTotalValue: number; - voteCount: number; - valuePerVote: number; - maxValuePerVote: number; - bribes: { - token: string; - symbol: string; - decimals: number; - value: number; - maxValue: number; - amount: number; - maxTokensPerVote: number; - briber: string; - periodIndex: number; - chainId: number; - }[]; - }[]; -}; - -const fetchHiddenHandRound = async (timestamp?: number) => { - const response = await fetch(`${HIDDEN_HAND_API_URL}/${timestamp || ''}`); - const data = (await response.json()) as HiddenHandResponse; - if (data.error) { - throw new Error('Failed to fetch voting APR'); - } - - // Get sum of all incentivized votes and total value - const total = data.data.reduce((acc, proposal) => acc + proposal.totalValue, 0); - const votes = data.data - .filter((proposal) => proposal.totalValue > 0) - .reduce((acc, proposal) => acc + proposal.voteCount, 0); - - return { total, votes, timestamp: data.data[0].proposalDeadline }; -}; - -export const getHiddenHandAPR = async (timestamp: number) => { - const round = await fetchHiddenHandRound(timestamp); - - // Debugging purposes - console.log('Hiddenhand round', timestamp, round.timestamp, round.total, round.votes); - - timestamp = round.timestamp; - - const avgValuePerVote = round.total / round.votes; - - let veBalPrice; - // When the timestamp is older than 24 hours, we can fetch the historical price - if (timestamp < Math.ceil(+Date.now() / 1000) - 86400) { - veBalPrice = await prisma.prismaTokenPrice.findFirst({ - where: { - tokenAddress: VEBAL, - chain: Chain.MAINNET, - timestamp, - }, - }); - } - // Otherwise we fetch the current price - else { - veBalPrice = await prisma.prismaTokenCurrentPrice.findFirst({ - where: { - tokenAddress: VEBAL, - chain: Chain.MAINNET, - }, - }); - } - - if (!veBalPrice) { - throw new Error('Failed to fetch veBAL price'); - } - - const apr = (avgValuePerVote * 52) / veBalPrice.price; - - return apr; -}; - -export class VeBalVotingAprService implements PoolAprService { - constructor() {} - - public getAprServiceName(): string { - return 'VeBalVotingAprService'; - } - - async getApr(): Promise { - // Get APRs for last 3 weeks, if available - const timestamp = (await fetchHiddenHandRound()).timestamp; - - const aprs = await Promise.allSettled([ - getHiddenHandAPR(timestamp - 1 * 604800), - getHiddenHandAPR(timestamp - 2 * 604800), - getHiddenHandAPR(timestamp - 3 * 604800), - ]); - - // Average successfully fetched APRs - const avg = aprs - .filter((apr): apr is PromiseFulfilledResult => apr.status === 'fulfilled') - .map((apr) => apr.value); - - if (avg.length === 0) { - throw new Error('Failed to fetch APRs'); - } - - return avg.reduce((acc, val) => acc + val, 0) / avg.length; - } - - async updateAprForPools(pools: PoolForAPRs[]): Promise { - const apr = await this.getApr(); - - await prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id, chain } }, - create: { - id, - chain, - poolId: vebalPool, - apr, - title: 'Voting APR', - type: 'VOTING', - }, - update: { apr }, - }); - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts b/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts deleted file mode 100644 index cc3959bc7..000000000 --- a/modules/pool/lib/apr-data-sources/yb-tokens-apr.service.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { PoolAprService } from '../../pool-types'; -import { PoolForAPRs } from '../../../../prisma/prisma-types'; -import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; -import { Chain, PrismaPoolAprItemGroup, PrismaPoolAprType } from '@prisma/client'; -import { YbAprHandlers, TokenApr } from './yb-apr-handlers'; -import { tokenService } from '../../../token/token.service'; -import { collectsYieldFee, tokenCollectsYieldFee } from '../pool-utils'; -import { YbAprConfig } from '../../../network/apr-config-types'; - -export class YbTokensAprService implements PoolAprService { - private ybTokensAprHandlers: YbAprHandlers; - - constructor(private aprConfig: YbAprConfig, private chain: Chain) { - this.ybTokensAprHandlers = new YbAprHandlers(this.aprConfig, chain); - } - - getAprServiceName(): string { - return 'YbTokensAprService'; - } - - public async updateAprForPools(pools: PoolForAPRs[]): Promise { - const operations: any[] = []; - const chains = Array.from(new Set(pools.map((pool) => pool.chain))); - const tokenPrices = await tokenService.getCurrentTokenPrices(chains).then((prices) => - Object.fromEntries( - prices.map((price) => { - return [price.tokenAddress, price.price]; - }), - ), - ); - const aprs = await this.fetchYieldTokensApr(); - const aprKeysLowercase = Array.from(aprs.keys()).map((key) => key.toLowerCase()); - const aprKeysLowercaseSet = new Set(aprKeysLowercase); - - const poolsWithYbTokens = pools.filter((pool) => { - const addresses = new Set( - pool.tokens - .flatMap((token) => [ - token.token.underlyingTokenAddress?.toLowerCase(), - token.address.toLowerCase(), - ]) - .filter((address): address is string => address !== null && address !== undefined), - ); - - for (const address of addresses) { - if (aprKeysLowercaseSet.has(address)) { - return true; - } - } - return false; - }); - - for (const pool of poolsWithYbTokens) { - if (!pool.dynamicData) { - continue; - } - const totalLiquidity = pool.dynamicData?.totalLiquidity; - if (!totalLiquidity) { - continue; - } - - const tokenAprs = pool.tokens.map((token) => { - const tokenApr = aprs.get(token.address); - - // Wrapper + underlying case, we need to apply the underlying token APR on top of the lending protocol market APR - const underlyingApr = aprs.get(token.token.underlyingTokenAddress?.toLowerCase() || ''); - - let apr = tokenApr?.apr || 0; - if (underlyingApr) { - apr = (1 + apr) * (1 + underlyingApr.apr) - 1; - } - - return { - ...token, - apr, - group: tokenApr?.group, - share: (parseFloat(token.balance) * tokenPrices[token.address]) / totalLiquidity, - }; - }); - - for (const token of tokenAprs) { - if (!token.apr || !token.share) { - continue; - } - - let userApr = token.apr * token.share; - - let fee = 0; - if (collectsYieldFee(pool) && tokenCollectsYieldFee(token) && pool.dynamicData) { - fee = - pool.type === 'META_STABLE' - ? parseFloat(pool.dynamicData.protocolSwapFee || '0') - : pool.protocolVersion === 3 - ? parseFloat(pool.dynamicData.aggregateYieldFee || '0.1') - : parseFloat(pool.dynamicData.protocolYieldFee || '0'); - - userApr = userApr * (1 - fee); - } - - const yieldType: PrismaPoolAprType = 'IB_YIELD'; - - const itemId = `${token.poolId}-${token.address}-yield-apr`; - - const data = { - id: itemId, - chain: pool.chain, - poolId: pool.id, - title: `${token.token.symbol} APR`, - apr: userApr, - group: token.group as PrismaPoolAprItemGroup, - type: yieldType, - rewardTokenAddress: token.address, - rewardTokenSymbol: token.token.symbol, - }; - - operations.push( - prisma.prismaPoolAprItem.upsert({ - where: { id_chain: { id: itemId, chain: pool.chain } }, - create: data, - update: data, - }), - ); - } - } - await prismaBulkExecuteOperations(operations); - } - - private async fetchYieldTokensApr(): Promise> { - const data = await this.ybTokensAprHandlers.fetchAprsFromAllHandlers(); - return new Map( - data - .filter((tokenApr) => { - return !isNaN(tokenApr.apr); - }) - .map((apr) => [apr.address, apr]), - ); - } -} diff --git a/modules/pool/lib/pool-apr-updater.service.ts b/modules/pool/lib/pool-apr-updater.service.ts deleted file mode 100644 index 99545f9fd..000000000 --- a/modules/pool/lib/pool-apr-updater.service.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { prisma } from '../../../prisma/prisma-client'; -import { PoolForAPRs } from '../../../prisma/prisma-types'; -import { PoolAprService } from '../pool-types'; -import _ from 'lodash'; -import { prismaBulkExecuteOperations } from '../../../prisma/prisma-util'; -import { networkContext } from '../../network/network-context.service'; -import { Chain } from '@prisma/client'; - -export class PoolAprUpdaterService { - constructor() {} - - private get aprServices(): PoolAprService[] { - return networkContext.config.poolAprServices; - } - - async updatePoolAprs(chain: Chain) { - // Loading pools, their dynamic data, and tokens separately, then manually assembling them into `PoolForAPRs` objects to avoid the performance overhead of Prisma's nested includes. - const [pools, dynamicData, tokens] = await Promise.all([ - prisma.prismaPool.findMany({ - where: { chain }, - }), - prisma.prismaPoolDynamicData - .findMany({ where: { chain } }) - .then((records) => _.keyBy(records, 'id') as Record), - prisma.prismaPoolToken - .findMany({ - where: { chain }, - include: { token: true }, - }) - .then((records) => _.groupBy(records, 'poolId') as Record), - ]); - - const poolsWithData: PoolForAPRs[] = pools - .map((pool) => ({ - ...pool, - dynamicData: dynamicData[pool.id], - tokens: tokens[pool.id], - })) - // Filter needed for test pools on Sepolia - .filter((pool) => pool.tokens && pool.dynamicData); - - await this.updateAprsForPools(poolsWithData); - } - - async reloadAllPoolAprs(chain: Chain) { - await prisma.prismaPoolAprRange.deleteMany({ where: { chain: chain } }); - await prisma.prismaPoolAprItem.deleteMany({ where: { chain: chain } }); - await this.updatePoolAprs(chain); - } - - async updateAprsForPools(pools: PoolForAPRs[]) { - const failedAprServices = []; - - for (const aprService of this.aprServices) { - try { - await aprService.updateAprForPools(pools); - } catch (e) { - console.error(`Error during APR update of aprService:`, e); - failedAprServices.push(aprService.getAprServiceName()); - } - } - - if (failedAprServices.length > 0) { - throw new Error(`The following APR services failed: ${failedAprServices}`); - } - - await this.updateTotalApr(pools); - } - - private async updateTotalApr(pools: PoolForAPRs[]) { - const items = await prisma.prismaPoolAprItem.findMany({ - where: { - chain: pools[0].chain, - ...(pools.length > 10 ? {} : { poolId: { in: pools.map((p) => p.id) } }), - type: { - notIn: [ - 'SURPLUS', - 'SURPLUS_30D', - 'SURPLUS_7D', - 'SWAP_FEE_30D', - 'SWAP_FEE_7D', - 'DYNAMIC_SWAP_FEE_24H', - ], - }, - }, - }); - - const grouped = _.groupBy(items, 'poolId'); - let operations: any[] = []; - - // Select / update aprs in Dynamic Data - const dynamicData = _.keyBy( - pools.map((pool) => pool.dynamicData), - 'poolId', - ); - - //store the total APR on the dynamic data so we can sort by it - for (const poolId in grouped) { - const apr = _.sumBy(grouped[poolId], (item) => item.apr); - if (Math.abs(dynamicData[poolId]?.apr || 0 - apr) > 0.01 && dynamicData[poolId]?.chain) { - operations.push( - prisma.prismaPoolDynamicData.update({ - where: { id_chain: { id: poolId, chain: dynamicData[poolId].chain } }, - data: { apr }, - }), - ); - } - } - - await prismaBulkExecuteOperations(operations); - } -} diff --git a/modules/pool/pool.service.ts b/modules/pool/pool.service.ts index c14bc0dfb..9f1f5c80c 100644 --- a/modules/pool/pool.service.ts +++ b/modules/pool/pool.service.ts @@ -12,7 +12,6 @@ import { QueryPoolGetPoolsArgs, } from '../../apps/api/gql/generated-schema'; import { tokenService } from '../token/token.service'; -import { PoolAprUpdaterService } from './lib/pool-apr-updater.service'; import { PoolGqlLoaderService } from './lib/pool-gql-loader.service'; import { PoolOnChainDataService, PoolOnChainDataServiceOptions } from './lib/pool-on-chain-data.service'; import { PoolSnapshotService } from './lib/pool-snapshot.service'; @@ -41,7 +40,6 @@ export class PoolService { constructor( private readonly poolOnChainDataService: PoolOnChainDataService, private readonly poolGqlLoaderService: PoolGqlLoaderService, - private readonly poolAprUpdaterService: PoolAprUpdaterService, private readonly poolSnapshotService: PoolSnapshotService, ) {} @@ -160,16 +158,6 @@ export class PoolService { } } - public async updatePoolAprs(chain: Chain) { - await this.poolAprUpdaterService.updatePoolAprs(chain); - await syncIncentivizedCategory(); - } - - public async reloadAllPoolAprs(chain: Chain) { - await this.poolAprUpdaterService.reloadAllPoolAprs(chain); - await syncIncentivizedCategory(); - } - public async syncLatestReliquarySnapshotsForAllFarms(chain: Chain) { if (config[chain].subgraphs.reliquary) { const reliquarySnapshotService = new ReliquarySnapshotService( @@ -202,6 +190,5 @@ const optionsResolverForPoolOnChainDataService: () => PoolOnChainDataServiceOpti export const poolService = new PoolService( new PoolOnChainDataService(optionsResolverForPoolOnChainDataService), new PoolGqlLoaderService(), - new PoolAprUpdaterService(), new PoolSnapshotService(coingeckoDataService), ); diff --git a/tasks/index.ts b/tasks/index.ts index 837d6c179..6e0f428e6 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -3,7 +3,6 @@ import { SnapshotsController, UserBalancesController, CowAmmController, - AprsController, ContentController, FXPoolsController, PoolController, @@ -13,7 +12,6 @@ import { TokenController, QuantAmmController, } from '../modules/controllers'; -import { PoolAprUpdaterService } from '../modules/pool/lib/pool-apr-updater.service'; import { chainIdToChain } from '../modules/network/chain-id-to-chain'; import { poolService } from '../modules/pool/pool.service'; @@ -26,6 +24,10 @@ import { prisma } from '../prisma/prisma-client'; import { LBPController } from '../modules/controllers/lbp-controller'; import { request, gql } from 'graphql-request'; import _ from 'lodash'; +import { AprRepository } from '../modules/aprs/apr-repository'; +import { MerklAprHandler } from '../modules/aprs/handlers'; +import { SwapFeeApr7d30dHandler } from '../modules/aprs/handlers/swap-fee-apr'; +import { AprService } from '../modules/aprs'; // TODO needed? const sftmxController = SftmxController(); @@ -153,9 +155,17 @@ async function run(job: string = process.argv[2], chainId: string = process.argv } else if (job === 'sync-latest-fx-prices') { return FXPoolsController().syncLatestPrices(chain); } else if (job === 'sync-merkl') { - return AprsController().syncMerkl(); + const aprRepository = new AprRepository(); + const merklAprHandler = new MerklAprHandler(); + const pools = await aprRepository.getPoolsForAprCalculation(chain); + const aprs = await merklAprHandler.calculateAprForPools(pools); + return await aprRepository.savePoolAprItems(chain, aprs); } else if (job === 'update-7-30-days-swap-apr') { - return AprsController().update7And30DaysSwapAprs(chain); + const aprRepository = new AprRepository(); + const swapFee7d30dHandler = new SwapFeeApr7d30dHandler(); + const pools = await aprRepository.getPoolsForAprCalculation(chain); + const aprs = await swapFee7d30dHandler.calculateAprForPools(pools); + return await aprRepository.savePoolAprItems(chain, aprs); } else if (job === 'sync-rate-provider-reviews') { return ContentController().syncRateProviderReviews(); } else if (job === 'sync-hook-reviews') { @@ -169,28 +179,16 @@ async function run(job: string = process.argv[2], chainId: string = process.argv } else if (job === 'sync-sts-data') { return StakedSonicController().syncSonicStakingData(); } else if (job === 'reload-pool-aprs') { - initRequestScopedContext(); - setRequestScopedContextValue('chainId', chainId); - return poolService.reloadAllPoolAprs(chain); + const aprService = new AprService(); + return aprService.reloadAprs(chain); } else if (job === 'update-pool-aprs') { + const aprService = new AprService(); const chain = chainIdToChain[chainId]; const id = process.argv[4]; - const service = new PoolAprUpdaterService(); if (id) { - const pools = await prisma.prismaPool.findMany({ - where: { id: id, chain: chain }, - include: { - dynamicData: true, - tokens: { - include: { - token: true, - }, - }, - }, - }); - return service.updateAprsForPools(pools); + return aprService.updateAprForPool(chain, id); } else { - return service.updatePoolAprs(chain); + return aprService.updateAprs(chain); } } else if (job === 'update-prices') { await tokenService.syncTokenContentData(chain); From 2ffc0f7497fbb8c9dd0c4a086fcf2b6c60d86091 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 13:23:55 +0300 Subject: [PATCH 20/37] add surplus handler, move dynamic fee handler --- modules/aprs/handlers/create-handlers.ts | 1 + .../handlers/dynamic-swap-fee-apr/index.ts | 1 - modules/aprs/handlers/index.ts | 3 +- .../dynamic-swap-fee-apr-handler.ts | 0 modules/aprs/handlers/swap-fee-apr/index.ts | 2 + .../surplus-swap-fee-apr-handler.ts | 120 ++++++++++++++++++ 6 files changed, 124 insertions(+), 3 deletions(-) delete mode 100644 modules/aprs/handlers/dynamic-swap-fee-apr/index.ts rename modules/aprs/handlers/{dynamic-swap-fee-apr => swap-fee-apr}/dynamic-swap-fee-apr-handler.ts (100%) create mode 100644 modules/aprs/handlers/swap-fee-apr/surplus-swap-fee-apr-handler.ts diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 06ab1c24b..29bbe8755 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -17,6 +17,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.QuantAmmAprHandler()); handlerList.push(new handlers.LiquidityGaugeAprHandler()); handlerList.push(new handlers.MerklAprHandler()); + handlerList.push(new handlers.SurplusSwapFeeAprHandler()); // Mainnet specific handlers if (chain === Chain.MAINNET) { diff --git a/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts b/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts deleted file mode 100644 index 5bc3f169a..000000000 --- a/modules/aprs/handlers/dynamic-swap-fee-apr/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './dynamic-swap-fee-apr-handler'; diff --git a/modules/aprs/handlers/index.ts b/modules/aprs/handlers/index.ts index a17954cb6..9d285c57d 100644 --- a/modules/aprs/handlers/index.ts +++ b/modules/aprs/handlers/index.ts @@ -1,8 +1,7 @@ // Export all handlers implementations -export { SwapFeeAprHandler } from './swap-fee-apr'; +export { SwapFeeAprHandler, DynamicSwapFeeAprHandler, SurplusSwapFeeAprHandler } from './swap-fee-apr'; export { YbTokensAprHandler } from './yb-tokens'; export { AaveApiAprHandler } from './aave-api-apr'; -export { DynamicSwapFeeAprHandler } from './dynamic-swap-fee-apr'; export { BeetswarsGaugeVotingAprHandler, MaBeetsAprHandler } from './mabeets-apr'; export { MorphoRewardsAprHandler } from './morpho-apr-handler/morpho-rewards-apr-handler'; export { NestedPoolAprHandler } from './nested-pool-apr-handler'; diff --git a/modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts b/modules/aprs/handlers/swap-fee-apr/dynamic-swap-fee-apr-handler.ts similarity index 100% rename from modules/aprs/handlers/dynamic-swap-fee-apr/dynamic-swap-fee-apr-handler.ts rename to modules/aprs/handlers/swap-fee-apr/dynamic-swap-fee-apr-handler.ts diff --git a/modules/aprs/handlers/swap-fee-apr/index.ts b/modules/aprs/handlers/swap-fee-apr/index.ts index 21aafd127..e6a9bddc5 100644 --- a/modules/aprs/handlers/swap-fee-apr/index.ts +++ b/modules/aprs/handlers/swap-fee-apr/index.ts @@ -1,2 +1,4 @@ export * from './swap-fee-apr-handler'; export * from './swap-fee-apr-7d-30d-handler'; +export * from './dynamic-swap-fee-apr-handler'; +export * from './surplus-swap-fee-apr-handler'; diff --git a/modules/aprs/handlers/swap-fee-apr/surplus-swap-fee-apr-handler.ts b/modules/aprs/handlers/swap-fee-apr/surplus-swap-fee-apr-handler.ts new file mode 100644 index 000000000..63b73a247 --- /dev/null +++ b/modules/aprs/handlers/swap-fee-apr/surplus-swap-fee-apr-handler.ts @@ -0,0 +1,120 @@ +import { Chain, PrismaPoolAprItem, PrismaPoolAprType, PrismaPoolSnapshot } from '@prisma/client'; +import { AprHandler, PoolAPRData } from '../../types'; +import { prisma } from '../../../../prisma/prisma-client'; + +export class SurplusSwapFeeAprHandler implements AprHandler { + public getAprServiceName(): string { + return 'SurplusSwapFeeAprHandler'; + } + + midnight = (daysAgo: number) => Math.floor(Date.now() / 1000 / 86400) * 86400 - 86400 * daysAgo; + + getSnapshotsByTimestamp = async (timestamp: number, chain?: Chain, ids?: string[]) => { + // Leaving as an option to use raw SQL in case the prisma queries turn out to get slow + // const snapshots = await prisma.$queryRaw` + // SELECT DISTINCT ON ("poolId") * + // FROM "PrismaPoolSnapshot" + // JOIN "PrismaPool" ON "PrismaPoolSnapshot"."poolId" = "PrismaPool"."id" + // WHERE "PrismaPool"."type" = 'COW_AMM' + // AND "PrismaPoolSnapshot"."timestamp" = ${timestamp} + // ORDER BY "poolId", timestamp DESC; + // `; + + const snapshots = await prisma.prismaPool + .findMany({ + where: { + type: 'COW_AMM', + chain: chain, + ...(ids + ? { + id: { + in: ids, + }, + } + : {}), + }, + include: { + snapshots: { + where: { + timestamp: timestamp, + }, + }, + }, + }) + .then((pools) => pools.flatMap((pool) => pool.snapshots)); + + return snapshots; + }; + + public async calculateAprForPools( + pools: PoolAPRData[], + ): Promise[]> { + // Find the snapshot + // const latestSnapshots = await this.getSnapshotsByTimestamp(this.midnight(0), chain, ids); + // const snapshots7d = await this.getSnapshotsByTimestamp(this.midnight(7), chain, ids); + // const snapshots30d = await this.getSnapshotsByTimestamp(this.midnight(30), chain, ids); + + const dynamicData = pools + .filter((pool) => pool.type === 'COW_AMM') + .flatMap((pool) => ({ + poolId: pool.id, + chain: pool.chain, + totalLiquidity: pool.dynamicData?.totalLiquidity ?? 0, + surplus24h: pool.dynamicData?.surplus24h ?? 0, + })); + + // const mapLatestSnapshots = latestSnapshots.reduce((acc, snapshot) => { + // acc[snapshot.poolId] = snapshot; + // return acc; + // }, {} as Record); + + // For each pool, calculate the surplus APR for the last 7d and 30d + // const data7d = snapshots7d.map((snapshot) => ({ + // id: `${snapshot.poolId}-surplus-7d`, + // type: PrismaPoolAprType.SURPLUS_7D, + // title: `Surplus APR (7d)`, + // chain: snapshot.chain, + // poolId: snapshot.poolId, + // apr: + // !mapLatestSnapshots[snapshot.poolId] || + // mapLatestSnapshots[snapshot.poolId].totalSurplus <= 0 || + // snapshot.totalLiquidity === 0 + // ? 0 + // : ((mapLatestSnapshots[snapshot.poolId].totalSurplus - snapshot.totalSurplus) * 365) / + // 7 / + // (mapLatestSnapshots[snapshot.poolId].totalLiquidity + snapshot.totalLiquidity) / + // 2, + // })); + + // const data30d = snapshots30d.map((snapshot) => ({ + // id: `${snapshot.poolId}-surplus-30d`, + // type: PrismaPoolAprType.SURPLUS_30D, + // title: `Surplus APR (30d)`, + // chain: snapshot.chain, + // poolId: snapshot.poolId, + // apr: + // !mapLatestSnapshots[snapshot.poolId] || + // mapLatestSnapshots[snapshot.poolId].totalSurplus <= 0 || + // snapshot.totalLiquidity === 0 + // ? 0 + // : ((mapLatestSnapshots[snapshot.poolId].totalSurplus - snapshot.totalSurplus) * 365) / + // 30 / + // (mapLatestSnapshots[snapshot.poolId].totalLiquidity + snapshot.totalLiquidity) / + // 2, + // })); + + const data24h = dynamicData.map(({ poolId, chain, surplus24h, totalLiquidity }) => ({ + id: `${poolId}-surplus-24h`, + type: PrismaPoolAprType.SURPLUS_24H, + title: `Surplus APR`, + chain: chain, + poolId: poolId, + apr: surplus24h <= 0 || totalLiquidity === 0 ? 0 : (surplus24h * 365) / totalLiquidity, + rewardTokenAddress: null, + rewardTokenSymbol: null, + group: null, + })); + + return data24h; + } +} From 14c56f81c405dcad7ec35b141fd453dcc9bf6434 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 13:27:13 +0300 Subject: [PATCH 21/37] integrate surplus apr --- apps/worker/job-handlers.ts | 3 - modules/actions/cow-amm/index.ts | 1 - .../actions/cow-amm/update-surplus-aprs.ts | 149 ------------------ modules/controllers/cow-amm-controller.ts | 7 +- tasks/index.ts | 8 +- 5 files changed, 7 insertions(+), 161 deletions(-) delete mode 100644 modules/actions/cow-amm/update-surplus-aprs.ts diff --git a/apps/worker/job-handlers.ts b/apps/worker/job-handlers.ts index 05986c082..37d08a79c 100644 --- a/apps/worker/job-handlers.ts +++ b/apps/worker/job-handlers.ts @@ -312,9 +312,6 @@ const setupJobHandlers = async (name: string, chainId: string, res: any, next: N ); break; // APRs - case 'update-surplus-aprs': - await runIfNotAlreadyRunning(name, chainId, () => CowAmmController().updateSurplusAprs(), res, next); - break; case 'update-pool-apr': await runIfNotAlreadyRunning( name, diff --git a/modules/actions/cow-amm/index.ts b/modules/actions/cow-amm/index.ts index f75609b4d..5241397c5 100644 --- a/modules/actions/cow-amm/index.ts +++ b/modules/actions/cow-amm/index.ts @@ -2,4 +2,3 @@ export * from './upsert-pools'; export * from './fetch-changed-pools'; export * from './sync-swaps'; export * from './sync-join-exits'; -export * from './update-surplus-aprs'; diff --git a/modules/actions/cow-amm/update-surplus-aprs.ts b/modules/actions/cow-amm/update-surplus-aprs.ts deleted file mode 100644 index a652b0e0d..000000000 --- a/modules/actions/cow-amm/update-surplus-aprs.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Chain, PrismaPoolAprType, PrismaPoolSnapshot } from '@prisma/client'; -import { prisma } from '../../../prisma/prisma-client'; - -const midnight = (daysAgo: number) => Math.floor(Date.now() / 1000 / 86400) * 86400 - 86400 * daysAgo; - -const getSnapshotsByTimestamp = async (timestamp: number, chain?: Chain, ids?: string[]) => { - // Leaving as an option to use raw SQL in case the prisma queries turn out to get slow - // const snapshots = await prisma.$queryRaw` - // SELECT DISTINCT ON ("poolId") * - // FROM "PrismaPoolSnapshot" - // JOIN "PrismaPool" ON "PrismaPoolSnapshot"."poolId" = "PrismaPool"."id" - // WHERE "PrismaPool"."type" = 'COW_AMM' - // AND "PrismaPoolSnapshot"."timestamp" = ${timestamp} - // ORDER BY "poolId", timestamp DESC; - // `; - - const snapshots = await prisma.prismaPool - .findMany({ - where: { - type: 'COW_AMM', - chain: chain, - ...(ids - ? { - id: { - in: ids, - }, - } - : {}), - }, - include: { - snapshots: { - where: { - timestamp: timestamp, - }, - }, - }, - }) - .then((pools) => pools.flatMap((pool) => pool.snapshots)); - - return snapshots; -}; - -export const updateSurplusAPRs = async (chain?: Chain, ids?: string[]) => { - // Find the snapshot - const latestSnapshots = await getSnapshotsByTimestamp(midnight(0), chain, ids); - const snapshots7d = await getSnapshotsByTimestamp(midnight(7), chain, ids); - const snapshots30d = await getSnapshotsByTimestamp(midnight(30), chain, ids); - const dynamicData = await prisma.prismaPool - .findMany({ - where: { - type: 'COW_AMM', - chain: chain, - ...(ids - ? { - id: { - in: ids, - }, - } - : {}), - }, - include: { - dynamicData: true, - }, - }) - .then((pools) => - pools.flatMap((pool) => ({ - poolId: pool.id, - chain: pool.chain, - totalLiquidity: pool.dynamicData?.totalLiquidity ?? 0, - surplus24h: pool.dynamicData?.surplus24h ?? 0, - })), - ); - - const mapLatestSnapshots = latestSnapshots.reduce((acc, snapshot) => { - acc[snapshot.poolId] = snapshot; - return acc; - }, {} as Record); - - // For each pool, calculate the surplus APR for the last 7d and 30d - const data7d = snapshots7d.map((snapshot) => ({ - id: `${snapshot.poolId}-surplus-7d`, - type: PrismaPoolAprType.SURPLUS_7D, - title: `Surplus APR (7d)`, - chain: snapshot.chain, - poolId: snapshot.poolId, - apr: - !mapLatestSnapshots[snapshot.poolId] || - mapLatestSnapshots[snapshot.poolId].totalSurplus <= 0 || - snapshot.totalLiquidity === 0 - ? 0 - : ((mapLatestSnapshots[snapshot.poolId].totalSurplus - snapshot.totalSurplus) * 365) / - 7 / - (mapLatestSnapshots[snapshot.poolId].totalLiquidity + snapshot.totalLiquidity) / - 2, - })); - - const data30d = snapshots30d.map((snapshot) => ({ - id: `${snapshot.poolId}-surplus-30d`, - type: PrismaPoolAprType.SURPLUS_30D, - title: `Surplus APR (30d)`, - chain: snapshot.chain, - poolId: snapshot.poolId, - apr: - !mapLatestSnapshots[snapshot.poolId] || - mapLatestSnapshots[snapshot.poolId].totalSurplus <= 0 || - snapshot.totalLiquidity === 0 - ? 0 - : ((mapLatestSnapshots[snapshot.poolId].totalSurplus - snapshot.totalSurplus) * 365) / - 30 / - (mapLatestSnapshots[snapshot.poolId].totalLiquidity + snapshot.totalLiquidity) / - 2, - })); - - const data24h = dynamicData.map(({ poolId, chain, surplus24h, totalLiquidity }) => ({ - id: `${poolId}-surplus-24h`, - type: PrismaPoolAprType.SURPLUS_24H, - title: `Surplus APR`, - chain: chain, - poolId: poolId, - apr: surplus24h <= 0 || totalLiquidity === 0 ? 0 : (surplus24h * 365) / totalLiquidity, - })); - - const data = data24h.map((v) => ({ - ...v, - id: `${v.poolId}-surplus`, - type: PrismaPoolAprType.SURPLUS, - title: 'Surplus APR', - })); - - const operations = []; - for (const item of [...data, ...data24h, ...data7d, ...data30d]) { - operations.push( - prisma.prismaPoolAprItem.upsert({ - where: { - id_chain: { - id: item.id, - chain: item.chain, - }, - }, - create: item, - update: item, - }), - ); - } - - await prisma.$transaction(operations); - - return [...data, ...data24h, ...data7d, ...data30d].map(({ id, apr }) => ({ id, apr })); -}; diff --git a/modules/controllers/cow-amm-controller.ts b/modules/controllers/cow-amm-controller.ts index 3e10f7a24..b32ff29e1 100644 --- a/modules/controllers/cow-amm-controller.ts +++ b/modules/controllers/cow-amm-controller.ts @@ -2,7 +2,7 @@ import config from '../../config'; import { prisma } from '../../prisma/prisma-client'; import { getViemClient } from '../sources/viem-client'; import { getCowAmmSubgraphClient } from '../sources/subgraphs'; -import { fetchChangedPools, upsertPools, syncSwaps, syncJoinExits, updateSurplusAPRs } from '../actions/cow-amm'; +import { fetchChangedPools, upsertPools, syncSwaps, syncJoinExits } from '../actions/cow-amm'; import { syncSnapshots } from '../actions/snapshots/sync-snapshots'; import { Chain, PrismaLastBlockSyncedCategory } from '@prisma/client'; import { syncBptBalancesFromSubgraph } from '../actions/user/bpt-balances/helpers/sync-bpt-balances-from-subgraph'; @@ -103,7 +103,6 @@ export function CowAmmController(tracer?: any) { await upsertPools(ids, viemClient, subgraphClient, chain, latestBlock); await syncTokenPairs(ids, viemClient, routerAddress, chain); - await updateSurplusAPRs(chain, ids); // Sync balances for the pools const newIds = ids.filter((id) => !existingIds.includes(id)); await syncBptBalancesFromSubgraph(newIds, subgraphClient, chain); @@ -141,10 +140,6 @@ export function CowAmmController(tracer?: any) { .filter((value, index, self) => self.indexOf(value) === index); return poolIds; }, - async updateSurplusAprs() { - const aprs = await updateSurplusAPRs(); - return aprs; - }, async syncBalances(chain: Chain) { let subgraphClient: ReturnType; try { diff --git a/tasks/index.ts b/tasks/index.ts index 6e0f428e6..6a34370f5 100644 --- a/tasks/index.ts +++ b/tasks/index.ts @@ -26,7 +26,7 @@ import { request, gql } from 'graphql-request'; import _ from 'lodash'; import { AprRepository } from '../modules/aprs/apr-repository'; import { MerklAprHandler } from '../modules/aprs/handlers'; -import { SwapFeeApr7d30dHandler } from '../modules/aprs/handlers/swap-fee-apr'; +import { SurplusSwapFeeAprHandler, SwapFeeApr7d30dHandler } from '../modules/aprs/handlers/swap-fee-apr'; import { AprService } from '../modules/aprs'; // TODO needed? @@ -147,7 +147,11 @@ async function run(job: string = process.argv[2], chainId: string = process.argv } else if (job === 'sync-cow-amm-join-exits') { return CowAmmController().syncJoinExits(chain); } else if (job === 'update-surplus-aprs') { - return CowAmmController().updateSurplusAprs(); + const aprRepository = new AprRepository(); + const surplusSwapFeeAprHandler = new SurplusSwapFeeAprHandler(); + const pools = await aprRepository.getPoolsForAprCalculation(chain); + const aprs = await surplusSwapFeeAprHandler.calculateAprForPools(pools); + return await aprRepository.savePoolAprItems(chain, aprs); } else if (job === 'sync-cow-amm-balances') { return CowAmmController().syncBalances(chain); } else if (job === 'sync-categories') { From 092bb6150c54ebddd891f905f3bad7534301fb47 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 14:03:47 +0300 Subject: [PATCH 22/37] integrate apr items, get rid of apr group --- .../aave-api-apr/aave-api-apr-handler.ts | 1 - modules/aprs/handlers/create-handlers.ts | 2 +- .../liquidity-gauge-apr-handler.ts | 4 +- .../beetswars-gauge-voting-apr-handler.ts | 1 - .../mabeets-apr/mabeets-apr-handler.ts | 2 - .../handlers/merkl-apr/merkl-apr-handler.ts | 1 - .../morpho-rewards-apr-handler.ts | 1 - .../nested-pool-apr.service.ts | 1 - .../swap-fee-apr/swap-fee-apr-handler.ts | 1 - .../vebal-apr/vebal-protocol-apr-handler.ts | 1 - .../vebal-apr/vebal-voting-apr-handler.ts | 1 - .../yb-tokens/yb-tokens-apr-handler.ts | 1 - modules/pool/lib/pool-aggregator-loader.ts | 275 +--------- modules/pool/lib/pool-gql-loader.service.ts | 284 +---------- modules/pool/lib/pool-gql-mapper-helper.ts | 56 +- .../subgraphs/cow-amm/generated/types.ts | 172 +++++++ .../generated/balancer-subgraph-types.ts | 478 ++++++++++++++++++ .../migration.sql | 2 + .../migration.sql | 11 + prisma/schema/pool.prisma | 21 +- 20 files changed, 742 insertions(+), 574 deletions(-) create mode 100644 prisma/migrations/20250616105138_add_staking_apr_type/migration.sql create mode 100644 prisma/migrations/20250616105344_remove_group_from_apr_item/migration.sql diff --git a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts index 65334ae5c..5163fa8cb 100644 --- a/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts +++ b/modules/aprs/handlers/aave-api-apr/aave-api-apr-handler.ts @@ -75,7 +75,6 @@ export class AaveApiAprHandler implements AprHandler { type: 'MERKL', rewardTokenAddress: incentive.rewardToken.address, rewardTokenSymbol: incentive.rewardToken.symbol, - group: null, }); } } diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 29bbe8755..9373bfe2a 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -14,7 +14,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.SwapFeeAprHandler()); handlerList.push(new handlers.DynamicSwapFeeAprHandler()); handlerList.push(new handlers.NestedPoolAprHandler()); - handlerList.push(new handlers.QuantAmmAprHandler()); + // handlerList.push(new handlers.QuantAmmAprHandler()); handlerList.push(new handlers.LiquidityGaugeAprHandler()); handlerList.push(new handlers.MerklAprHandler()); handlerList.push(new handlers.SurplusSwapFeeAprHandler()); diff --git a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts index b586c68aa..67dad098e 100644 --- a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts +++ b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts @@ -88,11 +88,10 @@ export class LiquidityGaugeAprHandler implements AprHandler { chain: pool.chain, poolId: pool.id, title: `${symbol} reward APR`, - group: null, apr: 0, rewardTokenAddress: address, rewardTokenSymbol: symbol, - type: isVeBalemissions ? PrismaPoolAprType.VEBAL_EMISSIONS : PrismaPoolAprType.THIRD_PARTY_REWARD, + type: isVeBalemissions ? PrismaPoolAprType.VEBAL_EMISSIONS : PrismaPoolAprType.STAKING, }; // veBAL rewards have a min and max, we create two items for them @@ -114,7 +113,6 @@ export class LiquidityGaugeAprHandler implements AprHandler { chain: pool.chain, poolId: pool.id, title: `${symbol} reward APR`, - group: null, apr: minApr * this.MAX_VEBAL_BOOST, rewardTokenAddress: address, rewardTokenSymbol: symbol, diff --git a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts index fd61d1798..884646e04 100644 --- a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts @@ -39,7 +39,6 @@ export class BeetswarsGaugeVotingAprHandler implements AprHandler { title: 'Voting APR*', apr: minApr, type: PrismaPoolAprType.VOTING, - group: null, rewardTokenAddress: null, rewardTokenSymbol: null, }); diff --git a/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts index 81c02c1ba..1df46baef 100644 --- a/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts @@ -102,7 +102,6 @@ export class MaBeetsAprHandler implements AprHandler { title: 'BEETS reward APR', apr: minApr, type: PrismaPoolAprType.MABEETS_EMISSIONS, - group: null, rewardTokenAddress: this.beetsAddress, rewardTokenSymbol: 'BEETS', }); @@ -114,7 +113,6 @@ export class MaBeetsAprHandler implements AprHandler { title: 'BEETS reward APR', apr: maxApr, type: PrismaPoolAprType.STAKING_BOOST, - group: null, rewardTokenAddress: this.beetsAddress, rewardTokenSymbol: 'BEETS', }); diff --git a/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts index 4ec02819f..0409870a1 100644 --- a/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts +++ b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts @@ -121,7 +121,6 @@ export class MerklAprHandler implements AprHandler { apr: apr.apr, rewardTokenAddress: null, rewardTokenSymbol: null, - group: null, })); } diff --git a/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts b/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts index 29616195c..91a538d35 100644 --- a/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts +++ b/modules/aprs/handlers/morpho-apr-handler/morpho-rewards-apr-handler.ts @@ -40,7 +40,6 @@ export class MorphoRewardsAprHandler implements AprHandler { type: PrismaPoolAprType.MERKL, rewardTokenAddress: null, rewardTokenSymbol: null, - group: null, }; }); diff --git a/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts b/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts index 5d7c22fb7..ffce786f4 100644 --- a/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts +++ b/modules/aprs/handlers/nested-pool-apr-handler/nested-pool-apr.service.ts @@ -69,7 +69,6 @@ export class NestedPoolAprHandler implements AprHandler { apr: userApr, title: title, type: aprItem.type, - group: aprItem.group, rewardTokenAddress: aprItem.rewardTokenAddress, rewardTokenSymbol: aprItem.rewardTokenSymbol, }); diff --git a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts index 386c7fcc8..e8fc19e0a 100644 --- a/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts +++ b/modules/aprs/handlers/swap-fee-apr/swap-fee-apr-handler.ts @@ -48,7 +48,6 @@ export class SwapFeeAprHandler implements AprHandler { type: 'SWAP_FEE_24H', rewardTokenAddress: null, rewardTokenSymbol: null, - group: null, }); } } diff --git a/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts b/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts index 719770b4b..9b269a285 100644 --- a/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts +++ b/modules/aprs/handlers/vebal-apr/vebal-protocol-apr-handler.ts @@ -137,7 +137,6 @@ export class VeBalProtocolAprHandler implements AprHandler { type: PrismaPoolAprType.LOCKING, rewardTokenAddress: balAddress, rewardTokenSymbol: 'BAL', - group: null, }, ]; } diff --git a/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts b/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts index 59f21f57c..befa1cfb4 100644 --- a/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts +++ b/modules/aprs/handlers/vebal-apr/vebal-voting-apr-handler.ts @@ -138,7 +138,6 @@ export class VeBalVotingAprHandler implements AprHandler { type: PrismaPoolAprType.VOTING, rewardTokenAddress: null, rewardTokenSymbol: null, - group: null, }, ]; } diff --git a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts index deaea8f33..002058ac5 100644 --- a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts @@ -109,7 +109,6 @@ export class YbTokensAprHandler implements AprHandler { poolId: pool.id, title: `${token.token.symbol} APR`, apr: userApr, - group: token.group as PrismaPoolAprItemGroup, type: yieldType, rewardTokenAddress: token.address, rewardTokenSymbol: token.token.symbol, diff --git a/modules/pool/lib/pool-aggregator-loader.ts b/modules/pool/lib/pool-aggregator-loader.ts index b7938db77..065e6fcbf 100644 --- a/modules/pool/lib/pool-aggregator-loader.ts +++ b/modules/pool/lib/pool-aggregator-loader.ts @@ -24,7 +24,7 @@ import { fixedNumber } from '../../view-helpers/fixed-number'; import { ElementData, FxData, GyroData, StableData, QuantAmmWeightedData, ReclammData } from '../subgraph-mapper'; import { ZERO_ADDRESS } from '@balancer/sdk'; import { mapHookToGqlHook } from '../../sources/transformers'; -import { mapPoolToken, enrichWithErc4626Data } from './pool-gql-mapper-helper'; +import { mapPoolToken, enrichWithErc4626Data, mapAprItems } from './pool-gql-mapper-helper'; export class PoolAggregatorLoader { public async aggregatorPools(args: QueryAggregatorPoolsArgs): Promise { @@ -310,104 +310,7 @@ export class PoolAggregatorLoader { protocolYieldCapture48h, } = pool.dynamicData!; - const newAprItemsSchema = this.buildAprItems(pool); - - const allAprItems = pool.aprItems?.filter((item) => item.apr > 0 || (item.range?.max ?? 0 > 0)) || []; - const aprItems = allAprItems.filter( - (item) => item.type !== 'SWAP_FEE' && item.type !== 'SWAP_FEE_7D' && item.type !== 'SWAP_FEE_30D', - ); - const swapAprItems = aprItems.filter((item) => item.type === 'SWAP_FEE_24H'); - - // swap apr cannot have a range, so we can already sum it up - const aprItemsWithNoGroup = aprItems.filter((item) => !item.group); - - const hasAprRange = !!aprItems.find((item) => item.range); - let aprTotal = `${pool.dynamicData?.apr || 0}`; - let swapAprTotal = `0`; - let nativeRewardAprTotal = `0`; - let thirdPartyAprTotal = `0`; - - let aprRangeMin: string | undefined; - let aprRangeMax: string | undefined; - - let nativeAprRangeMin: string | undefined; - let nativeAprRangeMax: string | undefined; - - let thirdPartyAprRangeMin: string | undefined; - let thirdPartyAprRangeMax: string | undefined; - - let hasRewardApr = false; - - // It is likely that if either native or third party APR has a range, that both of them have a range - // therefore if there is a least one item with a range, we show both rewards in a range, although min and max might be identical - if (hasAprRange) { - let swapFeeApr = 0; - let currentAprRangeMinTotal = 0; - let currentAprRangeMaxTotal = 0; - let currentNativeAprRangeMin = 0; - let currentNativeAprRangeMax = 0; - let currentThirdPartyAprRangeMin = 0; - let currentThirdPartyAprRangeMax = 0; - - for (let aprItem of aprItems) { - let minApr: number; - let maxApr: number; - - if (aprItem.range) { - minApr = aprItem.range.min; - maxApr = aprItem.range.max; - } else { - minApr = aprItem.apr; - maxApr = aprItem.apr; - } - - currentAprRangeMinTotal += minApr; - currentAprRangeMaxTotal += maxApr; - - switch (aprItem.type) { - case PrismaPoolAprType.NATIVE_REWARD: { - currentNativeAprRangeMin += minApr; - currentNativeAprRangeMax += maxApr; - break; - } - case PrismaPoolAprType.THIRD_PARTY_REWARD: { - currentThirdPartyAprRangeMin += minApr; - currentThirdPartyAprRangeMax += maxApr; - break; - } - case PrismaPoolAprType.VOTING: { - currentThirdPartyAprRangeMin += minApr; - currentThirdPartyAprRangeMax += maxApr; - break; - } - case 'SWAP_FEE_24H': { - swapFeeApr += maxApr; - break; - } - } - } - swapAprTotal = `${swapFeeApr}`; - aprRangeMin = `${currentAprRangeMinTotal}`; - aprRangeMax = `${currentAprRangeMaxTotal}`; - nativeAprRangeMin = `${currentNativeAprRangeMin}`; - nativeAprRangeMax = `${currentNativeAprRangeMax}`; - thirdPartyAprRangeMin = `${currentThirdPartyAprRangeMin}`; - thirdPartyAprRangeMax = `${currentThirdPartyAprRangeMax}`; - hasRewardApr = currentNativeAprRangeMax > 0 || currentThirdPartyAprRangeMax > 0; - } else { - const nativeRewardAprItems = aprItems.filter((item) => item.type === 'NATIVE_REWARD'); - const thirdPartyRewardAprItems = aprItems.filter((item) => item.type === 'THIRD_PARTY_REWARD'); - swapAprTotal = `${_.sumBy(swapAprItems, 'apr')}`; - nativeRewardAprTotal = `${_.sumBy(nativeRewardAprItems, 'apr')}`; - thirdPartyAprTotal = `${_.sumBy(thirdPartyRewardAprItems, 'apr')}`; - hasRewardApr = nativeRewardAprItems.length > 0 || thirdPartyRewardAprItems.length > 0; - } - - const grouped = _.groupBy( - aprItems.filter((item) => item.group), - (item) => item.group, - ); - + const newAprItemsSchema = mapAprItems(pool); return { ...pool.dynamicData!, totalLiquidity: `${fixedNumber(totalLiquidity, 2)}`, @@ -448,178 +351,16 @@ export class PoolAggregatorLoader { protocolFees48h: `${fixedNumber(protocolFees48h || 0, 2)}`, aprItems: newAprItemsSchema, apr: { - apr: - typeof aprRangeMin !== 'undefined' && typeof aprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: aprRangeMin, - max: aprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: aprTotal }, - swapApr: swapAprTotal, - nativeRewardApr: - typeof nativeAprRangeMin !== 'undefined' && typeof nativeAprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: nativeAprRangeMin, - max: nativeAprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: nativeRewardAprTotal }, - thirdPartyApr: - typeof thirdPartyAprRangeMin !== 'undefined' && typeof thirdPartyAprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: thirdPartyAprRangeMin, - max: thirdPartyAprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: thirdPartyAprTotal }, - items: [ - ...aprItemsWithNoGroup.flatMap((item): GqlBalancePoolAprItem[] => { - if (item.range) { - return [ - { - id: item.id, - apr: { - __typename: 'GqlPoolAprRange', - min: item.range.min.toString(), - max: item.range.max.toString(), - }, - title: item.title, - subItems: [], - }, - ]; - } else { - return [ - { - ...item, - apr: { __typename: 'GqlPoolAprTotal', total: `${item.apr}` }, - subItems: [], - }, - ]; - } - }), - ..._.map(grouped, (items, group): GqlBalancePoolAprItem => { - // todo: might need to support apr ranges as well at some point - const subItems = items.map( - (item): GqlBalancePoolAprSubItem => ({ - ...item, - apr: { __typename: 'GqlPoolAprTotal', total: `${item.apr}` }, - }), - ); - let apr = 0; - for (const item of items) { - if ( - item.type === 'SWAP_FEE' || - item.type === 'SWAP_FEE_7D' || - item.type === 'SWAP_FEE_30D' || - item.type === 'SURPLUS_24H' || - item.type === 'SURPLUS_7D' || - item.type === 'SURPLUS_30D' - ) { - } else { - apr += item.apr; - } - } - const title = `${group.charAt(0) + group.slice(1).toLowerCase()} boosted APR`; - - return { - id: `${pool.id}-${group}`, - title, - apr: { __typename: 'GqlPoolAprTotal', total: `${apr}` }, - subItems, - }; - }), - ], - hasRewardApr, + apr: { total: '0' }, + swapApr: '0', + nativeRewardApr: { total: '0' }, + thirdPartyApr: { total: '0' }, + items: [], + hasRewardApr: false, }, }; } - private buildAprItems(pool: PrismaPoolMinimal): GqlPoolAprItem[] { - const aprItems: GqlPoolAprItem[] = []; - - for (const aprItem of pool.aprItems) { - // Skipping SWAP_FEE as the DB state is not updated, safe to remove after deployment of the patch, because all instances of SWAP_FEE_24H will be replaced with SWAP_FEE should be removed from the DB already - if (aprItem.type === 'SWAP_FEE') { - continue; - } - - // Skip 7D, 30D swap APRs - they aren't updated anymore, because noone was using them - if (['SWAP_FEE_7D', 'SWAP_FEE_30D'].includes(String(aprItem.type))) { - continue; - } - - if (aprItem.apr === 0 || (aprItem.range && aprItem.range.max === 0)) { - continue; - } - - let type: GqlPoolAprItemType; - switch (aprItem.type) { - case PrismaPoolAprType.NATIVE_REWARD: - if (pool.chain === 'FANTOM' || pool.chain === 'SONIC') { - type = 'MABEETS_EMISSIONS'; - } else { - type = 'VEBAL_EMISSIONS'; - } - break; - case PrismaPoolAprType.THIRD_PARTY_REWARD: - type = 'STAKING'; - break; - case null: - type = 'NESTED'; - break; - default: - type = aprItem.type; - break; - } - - if (aprItem.range) { - aprItems.push({ - id: aprItem.id, - title: aprItem.title, - apr: aprItem.range.min, - type: type, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - aprItems.push({ - id: `${aprItem.id}-boost`, - title: aprItem.title, - apr: aprItem.range.max - aprItem.range.min, - type: 'STAKING_BOOST', - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - } else { - aprItems.push({ - id: aprItem.id, - title: aprItem.title, - apr: aprItem.apr, - type: type, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - } - - // Adding deprecated SWAP_FEE for backwards compatibility - if (aprItem.type === 'SWAP_FEE_24H') { - aprItems.push({ - ...aprItem, - id: `${aprItem.id.replace('-24h', '')}`, - title: aprItem.title.replace(' (24h)', ''), - type: 'SWAP_FEE', - }); - } - } - - let filteredItems = aprItems; - if (pool.type === 'QUANT_AMM_WEIGHTED') { - filteredItems = aprItems.filter((item) => item.type !== 'QUANT_AMM_UPLIFT'); - } - - return filteredItems; - } - private getPoolInclude(userAddress?: string) { return { ...prismaPoolWithExpandedNesting.include, diff --git a/modules/pool/lib/pool-gql-loader.service.ts b/modules/pool/lib/pool-gql-loader.service.ts index 23f032f2b..ab61d7656 100644 --- a/modules/pool/lib/pool-gql-loader.service.ts +++ b/modules/pool/lib/pool-gql-loader.service.ts @@ -51,7 +51,7 @@ import { isWeightedPoolV2 } from './pool-utils'; import { addressesMatch } from '../../web3/addresses'; import { networkContext } from '../../network/network-context.service'; import { getWeightSnapshots } from '../../actions/quant-amm/get-weight-snapshots'; -import { mapPoolToken, enrichWithErc4626Data } from './pool-gql-mapper-helper'; +import { mapPoolToken, enrichWithErc4626Data, mapAprItems } from './pool-gql-mapper-helper'; const isToken = (text: string) => text.match(/^0x[0-9a-fA-F]{40}$/); const isPoolId = (text: string) => isToken(text) || text.match(/^0x[0-9a-fA-F]{64}$/); @@ -299,8 +299,8 @@ export class PoolGqlLoaderService { const query = Prisma.raw(`SELECT p.id, p.chain FROM "PrismaPool" p LEFT JOIN "PrismaPoolDynamicData" d on (p.id = d."poolId") WHERE p.search_vector @@ websearch_to_tsquery('simple', '${searchQuery}') AND d."totalSharesNum" > 0.000000000001 AND NOT ('BLACK_LISTED' = ANY(p.categories)) AND ${filters} ORDER BY d."${orderColumn}" ${ - args.orderDirection && args.orderDirection === 'asc' ? 'ASC' : 'DESC' - } LIMIT ${limit} OFFSET ${offset}`); + args.orderDirection && args.orderDirection === 'asc' ? 'ASC' : 'DESC' + } LIMIT ${limit} OFFSET ${offset}`); const searchResults = await prisma.$queryRaw<{ id: string }[]>(query); @@ -556,8 +556,8 @@ export class PoolGqlLoaderService { ...(where?.hasHook !== undefined && where.hasHook ? { hook: { path: ['address'], string_starts_with: '0x' } } : where?.hasHook !== undefined && !where.hasHook - ? { hook: { equals: Prisma.DbNull } } - : {}), + ? { hook: { equals: Prisma.DbNull } } + : {}), }; if (!textSearch) { @@ -1121,103 +1121,7 @@ export class PoolGqlLoaderService { protocolYieldCapture48h, } = pool.dynamicData!; - const newAprItemsSchema = this.buildAprItems(pool); - - const allAprItems = pool.aprItems?.filter((item) => item.apr > 0 || (item.range?.max ?? 0 > 0)) || []; - const aprItems = allAprItems.filter( - (item) => item.type !== 'SWAP_FEE' && item.type !== 'SWAP_FEE_7D' && item.type !== 'SWAP_FEE_30D', - ); - const swapAprItems = aprItems.filter((item) => item.type === 'SWAP_FEE_24H'); - - // swap apr cannot have a range, so we can already sum it up - const aprItemsWithNoGroup = aprItems.filter((item) => !item.group); - - const hasAprRange = !!aprItems.find((item) => item.range); - let aprTotal = `${pool.dynamicData?.apr || 0}`; - let swapAprTotal = `0`; - let nativeRewardAprTotal = `0`; - let thirdPartyAprTotal = `0`; - - let aprRangeMin: string | undefined; - let aprRangeMax: string | undefined; - - let nativeAprRangeMin: string | undefined; - let nativeAprRangeMax: string | undefined; - - let thirdPartyAprRangeMin: string | undefined; - let thirdPartyAprRangeMax: string | undefined; - - let hasRewardApr = false; - - // It is likely that if either native or third party APR has a range, that both of them have a range - // therefore if there is a least one item with a range, we show both rewards in a range, although min and max might be identical - if (hasAprRange) { - let swapFeeApr = 0; - let currentAprRangeMinTotal = 0; - let currentAprRangeMaxTotal = 0; - let currentNativeAprRangeMin = 0; - let currentNativeAprRangeMax = 0; - let currentThirdPartyAprRangeMin = 0; - let currentThirdPartyAprRangeMax = 0; - - for (let aprItem of aprItems) { - let minApr: number; - let maxApr: number; - - if (aprItem.range) { - minApr = aprItem.range.min; - maxApr = aprItem.range.max; - } else { - minApr = aprItem.apr; - maxApr = aprItem.apr; - } - - currentAprRangeMinTotal += minApr; - currentAprRangeMaxTotal += maxApr; - - switch (aprItem.type) { - case PrismaPoolAprType.NATIVE_REWARD: { - currentNativeAprRangeMin += minApr; - currentNativeAprRangeMax += maxApr; - break; - } - case PrismaPoolAprType.THIRD_PARTY_REWARD: { - currentThirdPartyAprRangeMin += minApr; - currentThirdPartyAprRangeMax += maxApr; - break; - } - case PrismaPoolAprType.VOTING: { - currentThirdPartyAprRangeMin += minApr; - currentThirdPartyAprRangeMax += maxApr; - break; - } - case 'SWAP_FEE_24H': { - swapFeeApr += maxApr; - break; - } - } - } - swapAprTotal = `${swapFeeApr}`; - aprRangeMin = `${currentAprRangeMinTotal}`; - aprRangeMax = `${currentAprRangeMaxTotal}`; - nativeAprRangeMin = `${currentNativeAprRangeMin}`; - nativeAprRangeMax = `${currentNativeAprRangeMax}`; - thirdPartyAprRangeMin = `${currentThirdPartyAprRangeMin}`; - thirdPartyAprRangeMax = `${currentThirdPartyAprRangeMax}`; - hasRewardApr = currentNativeAprRangeMax > 0 || currentThirdPartyAprRangeMax > 0; - } else { - const nativeRewardAprItems = aprItems.filter((item) => item.type === 'NATIVE_REWARD'); - const thirdPartyRewardAprItems = aprItems.filter((item) => item.type === 'THIRD_PARTY_REWARD'); - swapAprTotal = `${_.sumBy(swapAprItems, 'apr')}`; - nativeRewardAprTotal = `${_.sumBy(nativeRewardAprItems, 'apr')}`; - thirdPartyAprTotal = `${_.sumBy(thirdPartyRewardAprItems, 'apr')}`; - hasRewardApr = nativeRewardAprItems.length > 0 || thirdPartyRewardAprItems.length > 0; - } - - const grouped = _.groupBy( - aprItems.filter((item) => item.group), - (item) => item.group, - ); + const aprItems = mapAprItems(pool); return { ...pool.dynamicData!, @@ -1257,180 +1161,18 @@ export class PoolGqlLoaderService { protocolYieldCapture48h: `${fixedNumber(protocolYieldCapture48h || 0, 2)}`, protocolFees24h: `${fixedNumber(protocolFees24h || 0, 2)}`, protocolFees48h: `${fixedNumber(protocolFees48h || 0, 2)}`, - aprItems: newAprItemsSchema, + aprItems: aprItems, apr: { - apr: - typeof aprRangeMin !== 'undefined' && typeof aprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: aprRangeMin, - max: aprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: aprTotal }, - swapApr: swapAprTotal, - nativeRewardApr: - typeof nativeAprRangeMin !== 'undefined' && typeof nativeAprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: nativeAprRangeMin, - max: nativeAprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: nativeRewardAprTotal }, - thirdPartyApr: - typeof thirdPartyAprRangeMin !== 'undefined' && typeof thirdPartyAprRangeMax !== 'undefined' - ? { - __typename: 'GqlPoolAprRange', - min: thirdPartyAprRangeMin, - max: thirdPartyAprRangeMax, - } - : { __typename: 'GqlPoolAprTotal', total: thirdPartyAprTotal }, - items: [ - ...aprItemsWithNoGroup.flatMap((item): GqlBalancePoolAprItem[] => { - if (item.range) { - return [ - { - id: item.id, - apr: { - __typename: 'GqlPoolAprRange', - min: item.range.min.toString(), - max: item.range.max.toString(), - }, - title: item.title, - subItems: [], - }, - ]; - } else { - return [ - { - ...item, - apr: { __typename: 'GqlPoolAprTotal', total: `${item.apr}` }, - subItems: [], - }, - ]; - } - }), - ..._.map(grouped, (items, group): GqlBalancePoolAprItem => { - // todo: might need to support apr ranges as well at some point - const subItems = items.map( - (item): GqlBalancePoolAprSubItem => ({ - ...item, - apr: { __typename: 'GqlPoolAprTotal', total: `${item.apr}` }, - }), - ); - let apr = 0; - for (const item of items) { - if ( - item.type === 'SWAP_FEE' || - item.type === 'SWAP_FEE_7D' || - item.type === 'SWAP_FEE_30D' || - item.type === 'SURPLUS_24H' || - item.type === 'SURPLUS_7D' || - item.type === 'SURPLUS_30D' - ) { - } else { - apr += item.apr; - } - } - const title = `${group.charAt(0) + group.slice(1).toLowerCase()} boosted APR`; - - return { - id: `${pool.id}-${group}`, - title, - apr: { __typename: 'GqlPoolAprTotal', total: `${apr}` }, - subItems, - }; - }), - ], - hasRewardApr, + apr: { total: '0' }, + swapApr: '0', + nativeRewardApr: { total: '0' }, + thirdPartyApr: { total: '0' }, + items: [], + hasRewardApr: false, }, }; } - private buildAprItems(pool: PrismaPoolMinimal): GqlPoolAprItem[] { - const aprItems: GqlPoolAprItem[] = []; - - for (const aprItem of pool.aprItems) { - // Skipping SWAP_FEE as the DB state is not updated, safe to remove after deployment of the patch, because all instances of SWAP_FEE_24H will be replaced with SWAP_FEE should be removed from the DB already - if (aprItem.type === 'SWAP_FEE') { - continue; - } - - // Skip 7D, 30D swap APRs - they aren't updated anymore, because noone was using them - if (['SWAP_FEE_7D', 'SWAP_FEE_30D'].includes(String(aprItem.type))) { - continue; - } - - if (aprItem.apr === 0 || (aprItem.range && aprItem.range.max === 0)) { - continue; - } - - let type: GqlPoolAprItemType; - switch (aprItem.type) { - case PrismaPoolAprType.NATIVE_REWARD: - if (pool.chain === 'FANTOM' || pool.chain === 'SONIC') { - type = 'MABEETS_EMISSIONS'; - } else { - type = 'VEBAL_EMISSIONS'; - } - break; - case PrismaPoolAprType.THIRD_PARTY_REWARD: - type = 'STAKING'; - break; - case null: - type = 'NESTED'; - break; - default: - type = aprItem.type; - break; - } - - if (aprItem.range) { - aprItems.push({ - id: aprItem.id, - title: aprItem.title, - apr: aprItem.range.min, - type: type, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - aprItems.push({ - id: `${aprItem.id}-boost`, - title: aprItem.title, - apr: aprItem.range.max - aprItem.range.min, - type: 'STAKING_BOOST', - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - } else { - aprItems.push({ - id: aprItem.id, - title: aprItem.title, - apr: aprItem.apr, - type: type, - rewardTokenAddress: aprItem.rewardTokenAddress, - rewardTokenSymbol: aprItem.rewardTokenSymbol, - }); - } - - // Adding deprecated SWAP_FEE for backwards compatibility - if (aprItem.type === 'SWAP_FEE_24H') { - aprItems.push({ - ...aprItem, - id: `${aprItem.id.replace('-24h', '')}`, - title: aprItem.title.replace(' (24h)', ''), - type: 'SWAP_FEE', - }); - } - } - - let filteredItems = aprItems; - if (pool.type === 'QUANT_AMM_WEIGHTED') { - filteredItems = aprItems.filter((item) => item.type !== 'QUANT_AMM_UPLIFT'); - } - - return filteredItems; - } - private getPoolInvestConfig(pool: PrismaPoolWithExpandedNesting): GqlPoolInvestConfig { const poolTokens = pool.tokens.filter((token) => token.address !== pool.address); const supportsNativeAssetDeposit = pool.type !== 'COMPOSABLE_STABLE'; diff --git a/modules/pool/lib/pool-gql-mapper-helper.ts b/modules/pool/lib/pool-gql-mapper-helper.ts index 247223c4c..d33ddf3b1 100644 --- a/modules/pool/lib/pool-gql-mapper-helper.ts +++ b/modules/pool/lib/pool-gql-mapper-helper.ts @@ -1,14 +1,17 @@ -import { Chain } from '@prisma/client'; +import { Chain, PrismaPoolAprType } from '@prisma/client'; import { GqlPoolTokenDetail, GqlNestedPool, GqlHook, LiquidityManagement, + GqlPoolAprItemType, + GqlPoolAprItem, } from '../../../apps/api/gql/generated-schema'; import { PrismaPoolTokenWithExpandedNesting, PrismaNestedPoolWithSingleLayerNesting, HookData, + PrismaPoolMinimal, } from '../../../prisma/prisma-types'; import { floatToExactString } from '../../common/numbers'; import { chainToChainId } from '../../network/chain-id-to-chain'; @@ -16,6 +19,57 @@ import { StableData } from '../subgraph-mapper'; import { prisma } from '../../../prisma/prisma-client'; import { tokenService } from '../../token/token.service'; +export function mapAprItems(pool: PrismaPoolMinimal): GqlPoolAprItem[] { + const aprItems: GqlPoolAprItem[] = []; + + for (const aprItem of pool.aprItems) { + // Skip items with APR of 0 + if (aprItem.apr === 0) { + continue; + } + + let type: GqlPoolAprItemType; + switch (aprItem.type) { + case PrismaPoolAprType.NATIVE_REWARD: + if (pool.chain === 'FANTOM' || pool.chain === 'SONIC') { + type = 'MABEETS_EMISSIONS'; + } else { + type = 'VEBAL_EMISSIONS'; + } + break; + case PrismaPoolAprType.THIRD_PARTY_REWARD: + type = 'STAKING'; + break; + case null: + type = 'NESTED'; + break; + default: + type = aprItem.type; + break; + } + + aprItems.push({ + id: aprItem.id, + title: aprItem.title, + apr: aprItem.apr, + type: type, + rewardTokenAddress: aprItem.rewardTokenAddress, + rewardTokenSymbol: aprItem.rewardTokenSymbol, + }); + + // Adding deprecated SWAP_FEE for backwards compatibility + if (aprItem.type === 'SWAP_FEE_24H') { + aprItems.push({ + ...aprItem, + id: `${aprItem.id.replace('-24h', '')}`, + title: aprItem.title.replace(' (24h)', ''), + type: 'SWAP_FEE', + }); + } + } + return aprItems; +} + export function mapPoolToken(poolToken: PrismaPoolTokenWithExpandedNesting, nestedPercentage = 1): GqlPoolTokenDetail { const { nestedPool } = poolToken; diff --git a/modules/sources/subgraphs/cow-amm/generated/types.ts b/modules/sources/subgraphs/cow-amm/generated/types.ts index 46a5b4bd5..39c736dad 100644 --- a/modules/sources/subgraphs/cow-amm/generated/types.ts +++ b/modules/sources/subgraphs/cow-amm/generated/types.ts @@ -1076,6 +1076,178 @@ export type QueryUsersArgs = { where?: InputMaybe; }; +export type Subscription = { + __typename?: 'Subscription'; + /** Access to subgraph metadata */ + _meta?: Maybe<_Meta_>; + addRemove?: Maybe; + addRemoves: Array; + factories: Array; + factory?: Maybe; + pool?: Maybe; + poolShare?: Maybe; + poolShares: Array; + poolSnapshot?: Maybe; + poolSnapshots: Array; + poolToken?: Maybe; + poolTokens: Array; + pools: Array; + swap?: Maybe; + swaps: Array; + token?: Maybe; + tokens: Array; + user?: Maybe; + users: Array; +}; + +export type Subscription_MetaArgs = { + block?: InputMaybe; +}; + +export type SubscriptionAddRemoveArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionAddRemovesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionFactoriesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionFactoryArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolShareArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolSharesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolSnapshotArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolSnapshotsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionSwapArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionSwapsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionUserArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionUsersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + export type Swap = { __typename?: 'Swap'; blockNumber: Scalars['BigInt']; diff --git a/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts b/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts index aaa0c9a06..639599228 100644 --- a/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts +++ b/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts @@ -3954,6 +3954,484 @@ export type QueryUsersArgs = { where?: InputMaybe; }; +export type Subscription = { + __typename?: 'Subscription'; + /** Access to subgraph metadata */ + _meta?: Maybe<_Meta_>; + ampUpdate?: Maybe; + ampUpdates: Array; + balancer?: Maybe; + balancerSnapshot?: Maybe; + balancerSnapshots: Array; + balancers: Array; + circuitBreaker?: Maybe; + circuitBreakers: Array; + fxoracle?: Maybe; + fxoracles: Array; + gradualWeightUpdate?: Maybe; + gradualWeightUpdates: Array; + joinExit?: Maybe; + joinExits: Array; + latestPrice?: Maybe; + latestPrices: Array; + managementOperation?: Maybe; + managementOperations: Array; + pool?: Maybe; + poolContract?: Maybe; + poolContracts: Array; + poolHistoricalLiquidities: Array; + poolHistoricalLiquidity?: Maybe; + poolShare?: Maybe; + poolShares: Array; + poolSnapshot?: Maybe; + poolSnapshots: Array; + poolToken?: Maybe; + poolTokens: Array; + pools: Array; + priceRateProvider?: Maybe; + priceRateProviders: Array; + protocolIdData?: Maybe; + protocolIdDatas: Array; + swap?: Maybe; + swapFeeUpdate?: Maybe; + swapFeeUpdates: Array; + swaps: Array; + token?: Maybe; + tokenPrice?: Maybe; + tokenPrices: Array; + tokenSnapshot?: Maybe; + tokenSnapshots: Array; + tokens: Array; + tradePair?: Maybe; + tradePairSnapshot?: Maybe; + tradePairSnapshots: Array; + tradePairs: Array; + user?: Maybe; + userInternalBalance?: Maybe; + userInternalBalances: Array; + users: Array; +}; + +export type Subscription_MetaArgs = { + block?: InputMaybe; +}; + +export type SubscriptionAmpUpdateArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionAmpUpdatesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionBalancerArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionBalancerSnapshotArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionBalancerSnapshotsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionBalancersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionCircuitBreakerArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionCircuitBreakersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionFxoracleArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionFxoraclesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionGradualWeightUpdateArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionGradualWeightUpdatesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionJoinExitArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionJoinExitsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionLatestPriceArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionLatestPricesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionManagementOperationArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionManagementOperationsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolContractArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolContractsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolHistoricalLiquiditiesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolHistoricalLiquidityArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolShareArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolSharesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolSnapshotArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolSnapshotsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPriceRateProviderArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPriceRateProvidersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionProtocolIdDataArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionProtocolIdDatasArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionSwapArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionSwapFeeUpdateArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionSwapFeeUpdatesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionSwapsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTokenArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTokenPriceArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTokenPricesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTokenSnapshotArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTokenSnapshotsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTokensArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTradePairArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTradePairSnapshotArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionTradePairSnapshotsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionTradePairsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionUserArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionUserInternalBalanceArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionUserInternalBalancesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionUsersArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + export type Swap = { __typename?: 'Swap'; block?: Maybe; diff --git a/prisma/migrations/20250616105138_add_staking_apr_type/migration.sql b/prisma/migrations/20250616105138_add_staking_apr_type/migration.sql new file mode 100644 index 000000000..5cea0fd1b --- /dev/null +++ b/prisma/migrations/20250616105138_add_staking_apr_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "PrismaPoolAprType" ADD VALUE 'STAKING'; diff --git a/prisma/migrations/20250616105344_remove_group_from_apr_item/migration.sql b/prisma/migrations/20250616105344_remove_group_from_apr_item/migration.sql new file mode 100644 index 000000000..6ea05ea63 --- /dev/null +++ b/prisma/migrations/20250616105344_remove_group_from_apr_item/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `group` on the `PrismaPoolAprItem` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "PrismaPoolAprItem" DROP COLUMN "group"; + +-- DropEnum +DROP TYPE "PrismaPoolAprItemGroup"; diff --git a/prisma/schema/pool.prisma b/prisma/schema/pool.prisma index 03336282c..ae35fdfeb 100644 --- a/prisma/schema/pool.prisma +++ b/prisma/schema/pool.prisma @@ -169,7 +169,6 @@ model PrismaPoolAprItem { range PrismaPoolAprRange? type PrismaPoolAprType? - group PrismaPoolAprItemGroup? @@index([poolId, chain]) @@index([chain, type]) @@ -205,29 +204,12 @@ enum PrismaPoolAprType { SURPLUS_24H SURPLUS_7D SURPLUS_30D + STAKING STAKING_BOOST MABEETS_EMISSIONS VEBAL_EMISSIONS } -enum PrismaPoolAprItemGroup { - BEEFY - OVERNIGHT - REAPER - YEARN - IDLE - TRANCHESS - GEARBOX - AAVE - ANKR - TESSERA - TETU - OVIX - EULER - MAKER - DEFAULT - MORPHO -} model PrismaPoolExpandedTokens { @@id([tokenAddress, poolId, chain]) @@ -244,7 +226,6 @@ model PrismaPoolExpandedTokens { @@index([tokenAddress, chain]) } - model PrismaPoolFilter { @@id([id, chain]) From 20b6e5948d16fc572a0a9d2a2191e50c72efd946 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 14:09:05 +0300 Subject: [PATCH 23/37] get rid of apr range --- modules/aprs/apr-repository.ts | 1 - .../liquidity-gauge-apr-handler.ts | 3 +-- .../beetswars-gauge-voting-apr-handler.ts | 1 - .../handlers/quant-amm-apr/quant-amm-apr-handler.ts | 1 - .../aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts | 2 +- modules/pool/lib/pool-gql-mapper-helper.ts | 10 ---------- prisma/prisma-types.ts | 12 ++---------- prisma/schema/pool.prisma | 12 ------------ 8 files changed, 4 insertions(+), 38 deletions(-) diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index 803495e1e..3e3186059 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -138,7 +138,6 @@ export class AprRepository { * Delete all APR items for a chain */ async deleteAllPoolAprItems(chain: Chain): Promise { - await prisma.prismaPoolAprRange.deleteMany({ where: { chain } }); await prisma.prismaPoolAprItem.deleteMany({ where: { chain } }); } } diff --git a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts index 67dad098e..fff973c10 100644 --- a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts +++ b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts @@ -6,9 +6,8 @@ * "Working balance" is 40% of a user balance in a gauge - used only for BAL rewards on v2 gauges on child gauges or on mainnet */ import { secondsPerYear } from '../../../common/time'; -import { PrismaPoolAprItem, PrismaPoolAprRange, PrismaPoolAprType } from '@prisma/client'; +import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; -import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { tokenService } from '../../../token/token.service'; import { AprHandler, PoolAPRData } from '../../types'; diff --git a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts index 884646e04..448a32bab 100644 --- a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts @@ -50,7 +50,6 @@ export class BeetswarsGaugeVotingAprHandler implements AprHandler { title: 'Voting APR Boost', apr: maxApr, type: PrismaPoolAprType.STAKING_BOOST, - group: null, rewardTokenAddress: null, rewardTokenSymbol: null, }); diff --git a/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts b/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts index d59e095cd..e6f67ad08 100644 --- a/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts +++ b/modules/aprs/handlers/quant-amm-apr/quant-amm-apr-handler.ts @@ -130,7 +130,6 @@ export class QuantAmmAprHandler implements AprHandler { apr: totalYearlyReturn, title: 'Quant AMM APR', type: 'QUANT_AMM_UPLIFT', - group: null, rewardTokenAddress: null, rewardTokenSymbol: null, }); diff --git a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts index 002058ac5..e9c275be7 100644 --- a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts @@ -1,4 +1,4 @@ -import { Chain, PrismaPoolAprItem, PrismaPoolAprItemGroup, PrismaPoolAprType } from '@prisma/client'; +import { Chain, PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; import { YbAprHandlers } from '../../../pool/lib/apr-data-sources/yb-apr-handlers'; import { TokenApr, YbAprConfig } from './types'; import { PoolAPRData, AprHandler } from '../../types'; diff --git a/modules/pool/lib/pool-gql-mapper-helper.ts b/modules/pool/lib/pool-gql-mapper-helper.ts index d33ddf3b1..deb6c8f1e 100644 --- a/modules/pool/lib/pool-gql-mapper-helper.ts +++ b/modules/pool/lib/pool-gql-mapper-helper.ts @@ -56,16 +56,6 @@ export function mapAprItems(pool: PrismaPoolMinimal): GqlPoolAprItem[] { rewardTokenAddress: aprItem.rewardTokenAddress, rewardTokenSymbol: aprItem.rewardTokenSymbol, }); - - // Adding deprecated SWAP_FEE for backwards compatibility - if (aprItem.type === 'SWAP_FEE_24H') { - aprItems.push({ - ...aprItem, - id: `${aprItem.id.replace('-24h', '')}`, - title: aprItem.title.replace(' (24h)', ''), - type: 'SWAP_FEE', - }); - } } return aprItems; } diff --git a/prisma/prisma-types.ts b/prisma/prisma-types.ts index 6ca7465d4..caeb88c64 100644 --- a/prisma/prisma-types.ts +++ b/prisma/prisma-types.ts @@ -113,11 +113,7 @@ export const prismaPoolWithExpandedNesting = Prisma.validator( }, }, }, - aprItems: { - include: { - range: true, - }, - }, + aprItems: true, tokens: { orderBy: { index: 'asc' }, include: { diff --git a/prisma/schema/pool.prisma b/prisma/schema/pool.prisma index ae35fdfeb..317b0800b 100644 --- a/prisma/schema/pool.prisma +++ b/prisma/schema/pool.prisma @@ -166,7 +166,6 @@ model PrismaPoolAprItem { rewardTokenAddress String? rewardTokenSymbol String? apr Float - range PrismaPoolAprRange? type PrismaPoolAprType? @@ -174,17 +173,6 @@ model PrismaPoolAprItem { @@index([chain, type]) } -model PrismaPoolAprRange { - @@id([id, chain]) - @@unique([aprItemId, chain]) - - id String - chain Chain - aprItemId String - aprItem PrismaPoolAprItem @relation(fields:[aprItemId, chain], references: [id, chain], onDelete: Cascade) - min Float - max Float -} enum PrismaPoolAprType { SWAP_FEE From 783a4e7a2b212634cc1e42bb408930ee506275ba Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 14:09:59 +0300 Subject: [PATCH 24/37] types --- .../balancer-v3-pools/generated/types.ts | 208 ++++++++ .../generated/balancer-subgraph-types.ts | 478 ------------------ 2 files changed, 208 insertions(+), 478 deletions(-) diff --git a/modules/sources/subgraphs/balancer-v3-pools/generated/types.ts b/modules/sources/subgraphs/balancer-v3-pools/generated/types.ts index 488925f9d..a43f60271 100644 --- a/modules/sources/subgraphs/balancer-v3-pools/generated/types.ts +++ b/modules/sources/subgraphs/balancer-v3-pools/generated/types.ts @@ -1531,6 +1531,214 @@ export enum StableSurgeParams_OrderBy { SurgeThresholdPercentage = 'surgeThresholdPercentage', } +export type Subscription = { + __typename?: 'Subscription'; + /** Access to subgraph metadata */ + _meta?: Maybe<_Meta_>; + factories: Array; + factory?: Maybe; + gyro2Params?: Maybe; + gyro2Params_collection: Array; + gyroEParams?: Maybe; + gyroEParams_collection: Array; + lbpparams?: Maybe; + lbpparams_collection: Array; + pool?: Maybe; + pools: Array; + quantAMMWeightedDetail?: Maybe; + quantAMMWeightedDetails: Array; + quantAMMWeightedParams?: Maybe; + quantAMMWeightedParams_collection: Array; + reClammParams?: Maybe; + reClammParams_collection: Array; + stableParams?: Maybe; + stableParams_collection: Array; + stableSurgeParams?: Maybe; + stableSurgeParams_collection: Array; + weightedParams?: Maybe; + weightedParams_collection: Array; +}; + +export type Subscription_MetaArgs = { + block?: InputMaybe; +}; + +export type SubscriptionFactoriesArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionFactoryArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionGyro2ParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionGyro2Params_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionGyroEParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionGyroEParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionLbpparamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionLbpparams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionPoolArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionPoolsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionQuantAmmWeightedDetailArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionQuantAmmWeightedDetailsArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionQuantAmmWeightedParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionQuantAmmWeightedParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionReClammParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionReClammParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionStableParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionStableParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionStableSurgeParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionStableSurgeParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + +export type SubscriptionWeightedParamsArgs = { + block?: InputMaybe; + id: Scalars['ID']; + subgraphError?: _SubgraphErrorPolicy_; +}; + +export type SubscriptionWeightedParams_CollectionArgs = { + block?: InputMaybe; + first?: InputMaybe; + orderBy?: InputMaybe; + orderDirection?: InputMaybe; + skip?: InputMaybe; + subgraphError?: _SubgraphErrorPolicy_; + where?: InputMaybe; +}; + export type WeightedParams = { __typename?: 'WeightedParams'; /** Unique identifier for the WeightedPoolParams */ diff --git a/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts b/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts index 639599228..aaa0c9a06 100644 --- a/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts +++ b/modules/subgraphs/balancer-subgraph/generated/balancer-subgraph-types.ts @@ -3954,484 +3954,6 @@ export type QueryUsersArgs = { where?: InputMaybe; }; -export type Subscription = { - __typename?: 'Subscription'; - /** Access to subgraph metadata */ - _meta?: Maybe<_Meta_>; - ampUpdate?: Maybe; - ampUpdates: Array; - balancer?: Maybe; - balancerSnapshot?: Maybe; - balancerSnapshots: Array; - balancers: Array; - circuitBreaker?: Maybe; - circuitBreakers: Array; - fxoracle?: Maybe; - fxoracles: Array; - gradualWeightUpdate?: Maybe; - gradualWeightUpdates: Array; - joinExit?: Maybe; - joinExits: Array; - latestPrice?: Maybe; - latestPrices: Array; - managementOperation?: Maybe; - managementOperations: Array; - pool?: Maybe; - poolContract?: Maybe; - poolContracts: Array; - poolHistoricalLiquidities: Array; - poolHistoricalLiquidity?: Maybe; - poolShare?: Maybe; - poolShares: Array; - poolSnapshot?: Maybe; - poolSnapshots: Array; - poolToken?: Maybe; - poolTokens: Array; - pools: Array; - priceRateProvider?: Maybe; - priceRateProviders: Array; - protocolIdData?: Maybe; - protocolIdDatas: Array; - swap?: Maybe; - swapFeeUpdate?: Maybe; - swapFeeUpdates: Array; - swaps: Array; - token?: Maybe; - tokenPrice?: Maybe; - tokenPrices: Array; - tokenSnapshot?: Maybe; - tokenSnapshots: Array; - tokens: Array; - tradePair?: Maybe; - tradePairSnapshot?: Maybe; - tradePairSnapshots: Array; - tradePairs: Array; - user?: Maybe; - userInternalBalance?: Maybe; - userInternalBalances: Array; - users: Array; -}; - -export type Subscription_MetaArgs = { - block?: InputMaybe; -}; - -export type SubscriptionAmpUpdateArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionAmpUpdatesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionBalancerArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionBalancerSnapshotArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionBalancerSnapshotsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionBalancersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionCircuitBreakerArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionCircuitBreakersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionFxoracleArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionFxoraclesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionGradualWeightUpdateArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionGradualWeightUpdatesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionJoinExitArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionJoinExitsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionLatestPriceArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionLatestPricesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionManagementOperationArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionManagementOperationsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolContractArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolContractsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolHistoricalLiquiditiesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolHistoricalLiquidityArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolShareArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolSharesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolSnapshotArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolSnapshotsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolTokenArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPoolTokensArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPoolsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionPriceRateProviderArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionPriceRateProvidersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionProtocolIdDataArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionProtocolIdDatasArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionSwapArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionSwapFeeUpdateArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionSwapFeeUpdatesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionSwapsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionTokenArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionTokenPriceArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionTokenPricesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionTokenSnapshotArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionTokenSnapshotsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionTokensArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionTradePairArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionTradePairSnapshotArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionTradePairSnapshotsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionTradePairsArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionUserArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionUserInternalBalanceArgs = { - block?: InputMaybe; - id: Scalars['ID']; - subgraphError?: _SubgraphErrorPolicy_; -}; - -export type SubscriptionUserInternalBalancesArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - -export type SubscriptionUsersArgs = { - block?: InputMaybe; - first?: InputMaybe; - orderBy?: InputMaybe; - orderDirection?: InputMaybe; - skip?: InputMaybe; - subgraphError?: _SubgraphErrorPolicy_; - where?: InputMaybe; -}; - export type Swap = { __typename?: 'Swap'; block?: Maybe; From 01c94cae6322852d0b2bc8a74df03bf1eb54258c Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 14:48:51 +0300 Subject: [PATCH 25/37] cleanup and move yb apr handlers --- config/mainnet.ts | 43 ----- config/polygon.ts | 9 -- config/zkevm.ts | 12 -- .../yb-tokens}/yb-apr-handlers/index.ts | 23 +-- .../sources/aave-apr-handler.ts | 4 +- .../sources/aave-auto-apr-handler.ts | 0 .../sources/abis/ReaperCrypt.json | 0 .../sources/abis/ReaperCryptStrategy.json | 0 .../sources/abis/bloom-bps-feed.ts | 0 .../sources/abis/dforce-susx.ts | 0 .../sources/abis/euler-utils-lens.ts | 0 .../sources/abis/euler-vault.ts | 0 .../yb-apr-handlers/sources/abis/maker-pot.ts | 0 .../yb-apr-handlers/sources/abis/oErc20.ts | 0 .../sources/abis/reaperStrategy.ts | 0 .../yb-apr-handlers/sources/abis/silo-lens.ts | 0 .../sources/avalon-apr-handler.ts | 4 +- .../sources/beefy-apr-handler.ts | 4 +- .../sources/default-apr-handler.ts | 4 +- .../sources/defillama-apr-handler.ts | 4 +- .../sources/dforce-apr-handler.ts | 4 +- .../sources/etherfi-apr-handler.ts | 0 .../sources/euler-apr-handler.ts | 4 +- .../yb-apr-handlers/sources/extra-handler.ts | 4 +- .../sources/fluid-apr-handler.ts | 4 +- .../yb-apr-handlers/sources/index.ts | 11 -- .../sources/maker-apr-handler.ts | 4 +- .../sources/maker-gnosis-apr-handler.ts | 0 .../sources/maple-apr-handler.ts | 0 .../sources/morpho-apr-handler.ts | 4 +- .../sources/silo-apr-handler.ts | 0 .../sources/stakewise-apr-handler.ts | 0 .../sources/sts-apr-handler.ts | 4 +- .../sources/susds-apr-handler.ts | 4 +- .../yb-apr-handlers/sources/teth.ts | 4 +- .../sources/usdl-apr-handler.ts | 4 +- .../sources/yieldnest-apr-handler.ts | 0 .../yb-tokens}/yb-apr-handlers/types.ts | 5 +- .../yb-tokens/yb-tokens-apr-handler.ts | 2 +- .../sources/bloom-apr-handler.ts | 38 ----- .../sources/gearbox-apr-handler.ts | 45 ------ .../sources/idle-apr-handler.ts | 52 ------ .../sources/ovix-apr-handler.ts | 48 ------ .../sources/reaper-crypt-apr-handler.ts | 148 ------------------ .../sources/sftmx-apr-handler.ts | 107 ------------- .../yb-apr-handlers/sources/sv-eth.ts | 40 ----- .../sources/tetu-apr-handler.ts | 45 ------ .../sources/tranchess-apr-handler.ts | 45 ------ .../sources/yearn-apr-handler.ts | 46 ------ 49 files changed, 38 insertions(+), 741 deletions(-) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/index.ts (80%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/aave-apr-handler.ts (97%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/aave-auto-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/ReaperCrypt.json (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/ReaperCryptStrategy.json (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/bloom-bps-feed.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/dforce-susx.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/euler-utils-lens.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/euler-vault.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/maker-pot.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/oErc20.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/reaperStrategy.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/abis/silo-lens.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/avalon-apr-handler.ts (97%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/beefy-apr-handler.ts (93%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/default-apr-handler.ts (96%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/defillama-apr-handler.ts (91%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/dforce-apr-handler.ts (93%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/etherfi-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/euler-apr-handler.ts (98%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/extra-handler.ts (92%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/fluid-apr-handler.ts (87%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/index.ts (64%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/maker-apr-handler.ts (92%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/maker-gnosis-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/maple-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/morpho-apr-handler.ts (94%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/silo-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/stakewise-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/sts-apr-handler.ts (82%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/susds-apr-handler.ts (92%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/teth.ts (92%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/usdl-apr-handler.ts (90%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/sources/yieldnest-apr-handler.ts (100%) rename modules/{pool/lib/apr-data-sources => aprs/handlers/yb-tokens}/yb-apr-handlers/types.ts (83%) delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/bloom-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/gearbox-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/idle-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/ovix-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/reaper-crypt-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sftmx-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sv-eth.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tetu-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tranchess-apr-handler.ts delete mode 100644 modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yearn-apr-handler.ts diff --git a/config/mainnet.ts b/config/mainnet.ts index ae6b46baf..1a71f49c9 100644 --- a/config/mainnet.ts +++ b/config/mainnet.ts @@ -246,14 +246,6 @@ export default { }, }, }, - bloom: { - tokens: { - tbyFeb1924: { - address: '0xc4cafefbc3dfea629c589728d648cb6111db3136', - feedAddress: '0xde1f5f2d69339171d679fb84e4562febb71f36e6', - }, - }, - }, defillama: [ { defillamaPoolId: '5a9c2073-2190-4002-9654-8c245d1e8534', @@ -264,44 +256,9 @@ export default { tokenAddress: '0xf073bac22dab7faf4a3dd6c6189a70d54110525c', }, ], - gearbox: { - sourceUrl: 'https://charts-server.fly.dev/api/pools', - tokens: { - dDAI: { address: '0x6cfaf95457d7688022fc53e7abe052ef8dfbbdba' }, - dUSDC: { address: '0xc411db5f5eb3f7d552f9b8454b2d74097ccde6e3' }, - }, - }, - idle: { - sourceUrl: 'https://api.idle.finance/junior-rates/', - authorizationHeader: - 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnRJZCI6IkFwcDciLCJpYXQiOjE2NzAyMzc1Mjd9.L12KJEt8fW1Cvy3o7Nl4OJ2wtEjzlObaAYJ9aC_CY6M', - tokens: { - idleDAI: { - address: '0xec9482040e6483b7459cc0db05d51dfa3d3068e1', - wrapped4626Address: '0x0c80f31b840c6564e6c5e18f386fad96b63514ca', - }, - idleUSDC: { - address: '0xdc7777c771a6e4b3a82830781bdde4dbc78f320e', - wrapped4626Address: '0xc3da79e0de523eef7ac1e4ca9abfe3aac9973133', - }, - idleUSDT: { - address: '0xfa3afc9a194babd56e743fa3b7aa2ccbed3eaaad', - wrapped4626Address: '0x544897a3b944fdeb1f94a0ed973ea31a80ae18e1', - }, - }, - }, maker: { sdai: '0x83f20f44975d03b1b09e64809b757c47f942beea', }, - tranchess: { - sourceUrl: 'https://tranchess.com/eth/api/v3/funds', - tokens: { - qETH: { - address: '0x93ef1ea305d11a9b2a3ebb9bb4fcc34695292e7d', - underlyingAssetName: 'WETH', - }, - }, - }, stakewise: { url: 'https://mainnet-graph.stakewise.io/subgraphs/name/stakewise/stakewise', token: '0xf1c9acdc66974dfb6decb12aa385b9cd01190e38', diff --git a/config/polygon.ts b/config/polygon.ts index de9f1ee7d..aed5fbed8 100644 --- a/config/polygon.ts +++ b/config/polygon.ts @@ -147,15 +147,6 @@ export default { }, }, }, - tetu: { - sourceUrl: 'https://api.tetu.io/api/v1/reader/compoundAPRs?network=MATIC', - tokens: { - tUSDC: { address: '0x113f3d54c31ebc71510fd664c8303b34fbc2b355' }, - tUSDT: { address: '0x236975da9f0761e9cf3c2b0f705d705e22829886' }, - tDAI: { address: '0xace2ac58e1e5a7bfe274916c4d82914d490ed4a5' }, - tetuStQI: { address: '0x4cd44ced63d9a6fef595f6ad3f7ced13fceac768' }, - }, - }, defaultHandlers: { wstETH: { tokenAddress: '0x03b54a6e9a984069379fae1a4fc4dbae93b3bccd', diff --git a/config/zkevm.ts b/config/zkevm.ts index db2c438e8..0b8984b06 100644 --- a/config/zkevm.ts +++ b/config/zkevm.ts @@ -68,18 +68,6 @@ export default { avgBlockSpeed: 1, aprHandlers: { ybAprHandler: { - ovix: { - tokens: { - USDT: { - yieldAddress: '0xad41c77d99e282267c1492cdefe528d7d5044253', - wrappedAddress: '0x550d3bb1f77f97e4debb45d4f817d7b9f9a1affb', - }, - USDC: { - yieldAddress: '0x68d9baa40394da2e2c1ca05d30bf33f52823ee7b', - wrappedAddress: '0x3a6789fc7c05a83cfdff5d2f9428ad9868b4ff85', - }, - }, - }, defaultHandlers: { wstETH: { tokenAddress: '0x5d8cff95d7a57c0bf50b30b43c7cc0d52825d4a9', diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/index.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/index.ts similarity index 80% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/index.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/index.ts index 84db50de6..33dca402b 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/index.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/index.ts @@ -1,8 +1,8 @@ import * as sources from './sources'; import { YbAprConfig } from '../../../../network/apr-config-types'; import { Chain } from '@prisma/client'; -import { AprHandler, AprHandlerConstructor, TokenApr } from './types'; -export type { AprHandler, AprHandlerConstructor, TokenApr }; +import { YbAprHandler, AprHandlerConstructor, TokenApr } from './types'; +export type { YbAprHandler as AprHandler, AprHandlerConstructor, TokenApr }; const sourceToHandler = { aave: sources.AaveAprHandler, @@ -11,47 +11,34 @@ const sourceToHandler = { euler: sources.EulerAprHandler, fluid: sources.FluidAprHandler, extra: sources.ExtraHandler, - // gearbox: sources.GearboxAprHandler, // Removed, endpoint is down - // idle: sources.IdleAprHandler, // Removed, endpoint is down maker: sources.MakerAprHandler, - // reaper: sources.ReaperCryptAprHandler, // Removed, pools rekt - tetu: sources.TetuAprHandler, - tranchess: sources.TranchessAprHandler, - yearn: sources.YearnAprHandler, defaultHandlers: sources.DefaultAprHandler, stakewise: sources.Stakewise, maple: sources.Maple, yieldnest: sources.Yieldnest, etherfi: sources.Etherfi, - // sveth: sources.svEthAprHandler, // Savvy migrated to arbitrum dforce: sources.DForce, defillama: sources.Defillama, teth: sources.TreehouseAprHandler, morpho: sources.MorphoAprHandler, usdl: sources.UsdlAprHandler, - ovix: sources.OvixAprHandler, - bloom: sources.BloomAprHandler, - sftmx: sources.SftmxAprHandler, sts: sources.StsAprHandler, silo: sources.SiloAprHandler, susds: sources.SUSDSAprHandler, }; export class YbAprHandlers { - private handlers: AprHandler[] = []; + private handlers: YbAprHandler[] = []; fixedAprTokens?: { [tokenName: string]: { address: string; apr: number; group?: string; isIbYield?: boolean } }; - constructor( - aprConfig: YbAprConfig, - private chain?: Chain, - ) { + constructor(aprConfig: YbAprConfig, private chain?: Chain) { const { fixedAprHandler, ...config } = aprConfig; this.handlers = this.buildAprHandlers(config); this.fixedAprTokens = fixedAprHandler; } private buildAprHandlers(aprConfig: YbAprConfig) { - const handlers: AprHandler[] = []; + const handlers: YbAprHandler[] = []; // Add handlers from global configuration for (const [source, config] of Object.entries(aprConfig)) { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/aave-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/aave-apr-handler.ts similarity index 97% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/aave-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/aave-apr-handler.ts index 8f6c6a505..1e7b479cf 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/aave-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/aave-apr-handler.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; -export class AaveAprHandler implements AprHandler { +export class AaveAprHandler implements YbAprHandler { tokens: { [assetName: string]: { underlyingAssetAddress: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/aave-auto-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/aave-auto-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/aave-auto-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/aave-auto-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/ReaperCrypt.json b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/ReaperCrypt.json similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/ReaperCrypt.json rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/ReaperCrypt.json diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/ReaperCryptStrategy.json b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/ReaperCryptStrategy.json similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/ReaperCryptStrategy.json rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/ReaperCryptStrategy.json diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/bloom-bps-feed.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/bloom-bps-feed.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/bloom-bps-feed.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/bloom-bps-feed.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/dforce-susx.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/dforce-susx.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/dforce-susx.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/dforce-susx.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/euler-utils-lens.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/euler-utils-lens.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/euler-utils-lens.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/euler-utils-lens.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/euler-vault.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/euler-vault.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/euler-vault.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/euler-vault.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/maker-pot.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/maker-pot.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/maker-pot.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/maker-pot.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/oErc20.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/oErc20.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/oErc20.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/oErc20.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/reaperStrategy.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/reaperStrategy.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/reaperStrategy.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/reaperStrategy.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/silo-lens.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/silo-lens.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/abis/silo-lens.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/abis/silo-lens.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/avalon-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/avalon-apr-handler.ts similarity index 97% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/avalon-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/avalon-apr-handler.ts index e73249c13..d2fb439da 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/avalon-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/avalon-apr-handler.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; -export class AvalonAprHandler implements AprHandler { +export class AvalonAprHandler implements YbAprHandler { tokens: { [assetName: string]: { underlyingAssetAddress: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/beefy-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/beefy-apr-handler.ts similarity index 93% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/beefy-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/beefy-apr-handler.ts index 8018f7361..d10626242 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/beefy-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/beefy-apr-handler.ts @@ -1,8 +1,8 @@ import { BeefyAprConfig } from '../../../../../network/apr-config-types'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import axios from 'axios'; -export class BeefyAprHandler implements AprHandler { +export class BeefyAprHandler implements YbAprHandler { tokens: { [tokenName: string]: { address: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/default-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/default-apr-handler.ts similarity index 96% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/default-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/default-apr-handler.ts index 35b4297a4..157320fe9 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/default-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/default-apr-handler.ts @@ -1,6 +1,6 @@ -import type { AprHandler } from '../types'; +import type { YbAprHandler } from '../types'; -export class DefaultAprHandler implements AprHandler { +export class DefaultAprHandler implements YbAprHandler { tokenAddress: string; url: string; path: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/defillama-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/defillama-apr-handler.ts similarity index 91% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/defillama-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/defillama-apr-handler.ts index e171781dd..855998871 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/defillama-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/defillama-apr-handler.ts @@ -1,4 +1,4 @@ -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import { YbAprConfig } from '../../../../../network/apr-config-types'; const query = ` @@ -22,7 +22,7 @@ interface Response { const baseURL = 'https://yields.llama.fi/chart/'; -export class Defillama implements AprHandler { +export class Defillama implements YbAprHandler { constructor(private config: YbAprConfig['defillama']) {} async getAprs() { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/dforce-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/dforce-apr-handler.ts similarity index 93% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/dforce-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/dforce-apr-handler.ts index c93e3ed5b..0440e9f55 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/dforce-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/dforce-apr-handler.ts @@ -2,7 +2,7 @@ import { abi } from './abis/dforce-susx'; import { createPublicClient, http } from 'viem'; import config from '../../../../../../config'; import { arbitrum } from 'viem/chains'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import { YbAprConfig } from '../../../../../network/apr-config-types'; // Initialize the client for Arbitrum network @@ -14,7 +14,7 @@ const client = createPublicClient({ const functionName: 'currentAPY' = 'currentAPY'; const isIbYield = true; -export class DForce implements AprHandler { +export class DForce implements YbAprHandler { constructor(private config: YbAprConfig['dforce']) {} async getAprs() { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/etherfi-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/etherfi-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/etherfi-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/etherfi-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/euler-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/euler-apr-handler.ts similarity index 98% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/euler-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/euler-apr-handler.ts index 8fa17b8af..f86911f4a 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/euler-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/euler-apr-handler.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import { EulerAprConfig } from '../../../../../network/apr-config-types'; import { Chain } from '@prisma/client'; import { prisma } from '../../../../../../prisma/prisma-client'; @@ -19,7 +19,7 @@ type VaultsResponse = { type ComputeAPYs = AbiParametersToPrimitiveTypes['outputs']>; -export class EulerAprHandler implements AprHandler { +export class EulerAprHandler implements YbAprHandler { /* best to query the computeAPYs function of the utils lens contract: https://github.com/euler-xyz/evk-periphery/blob/af4a193813574715532dcd8cc5e55198820941cb/src/Lens/UtilsLens.sol#L18 diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/extra-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/extra-handler.ts similarity index 92% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/extra-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/extra-handler.ts index f90a442a4..1a4f45f46 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/extra-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/extra-handler.ts @@ -1,5 +1,5 @@ import { YbAprConfig } from '../../../../../network/apr-config-types'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; interface ExtraAPIResponse { op: { @@ -19,7 +19,7 @@ const wrappers = { '0x98efe85735f253a0ed0be8e2915ff39f9e4aff0f': 'USR', }; -export class ExtraHandler implements AprHandler { +export class ExtraHandler implements YbAprHandler { url: string; constructor(config: NonNullable) { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/fluid-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/fluid-apr-handler.ts similarity index 87% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/fluid-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/fluid-apr-handler.ts index 6f53b1553..dc72fe822 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/fluid-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/fluid-apr-handler.ts @@ -1,5 +1,5 @@ import { YbAprConfig } from '../../../../../network/apr-config-types'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; interface FluidAPIResponse { data: { @@ -8,7 +8,7 @@ interface FluidAPIResponse { }[]; } -export class FluidAprHandler implements AprHandler { +export class FluidAprHandler implements YbAprHandler { url: string; constructor(config: NonNullable) { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/index.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/index.ts similarity index 64% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/index.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/index.ts index 9f15975a8..bb24038a4 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/index.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/index.ts @@ -4,30 +4,19 @@ export * from './extra-handler'; export * from './aave-apr-handler'; export * from './avalon-apr-handler'; export * from './euler-apr-handler'; -export * from './gearbox-apr-handler'; -export * from './idle-apr-handler'; -export * from './tetu-apr-handler'; -export * from './tranchess-apr-handler'; -export * from './yearn-apr-handler'; -// export * from './reaper-crypt-apr-handler'; // Removed, pools rekt export * from './beefy-apr-handler'; export * from './maker-apr-handler'; export * from './stakewise-apr-handler'; export * from './maple-apr-handler'; export * from './yieldnest-apr-handler'; export * from './etherfi-apr-handler'; -// export * from './sv-eth'; // Savvy migrated to arbitrum export * from './dforce-apr-handler'; export * from './defillama-apr-handler'; export * from './teth'; export * from './morpho-apr-handler'; export * from './usdl-apr-handler'; export * from './susds-apr-handler'; -// These need a refactor, because they depend on the network context -export * from './sftmx-apr-handler'; export * from './sts-apr-handler'; -export * from './ovix-apr-handler'; -export * from './bloom-apr-handler'; export * as MakerGnosis from './maker-gnosis-apr-handler'; // Not used, not sure why it's not referenced anywhere ??? export * from './silo-apr-handler'; export * as AaveAuto from './aave-auto-apr-handler'; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maker-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maker-apr-handler.ts similarity index 92% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maker-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maker-apr-handler.ts index 7b2fc848f..af608785c 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maker-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maker-apr-handler.ts @@ -1,4 +1,4 @@ -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import { MakerAprConfig } from '../../../../../network/apr-config-types'; import { abi as makerPotAbi } from './abis/maker-pot'; import config from '../../../../../../config'; @@ -12,7 +12,7 @@ const client = createPublicClient({ const potAddress = '0x197e90f9fad81970ba7976f33cbd77088e5d7cf7'; -export class MakerAprHandler implements AprHandler { +export class MakerAprHandler implements YbAprHandler { group = 'MAKER'; private sdai: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maker-gnosis-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maker-gnosis-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maker-gnosis-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maker-gnosis-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maple-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maple-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/maple-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/maple-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/morpho-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/morpho-apr-handler.ts similarity index 94% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/morpho-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/morpho-apr-handler.ts index dabcd7588..af8000db1 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/morpho-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/morpho-apr-handler.ts @@ -1,5 +1,5 @@ import { gql, request } from 'graphql-request'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import { Chain } from '@prisma/client'; const url = 'https://blue-api.morpho.org/graphql'; @@ -39,7 +39,7 @@ type BlueApiResponse = { }; }; -export class MorphoAprHandler implements AprHandler { +export class MorphoAprHandler implements YbAprHandler { group = 'MORPHO'; constructor() {} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/silo-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/silo-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/silo-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/silo-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/stakewise-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/stakewise-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/stakewise-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/stakewise-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sts-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/sts-apr-handler.ts similarity index 82% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sts-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/sts-apr-handler.ts index e5f51e231..1c07bad65 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sts-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/sts-apr-handler.ts @@ -1,8 +1,8 @@ import { YbAprConfig } from '../../../../../network/apr-config-types'; import { prisma } from '../../../../../../prisma/prisma-client'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; -export class StsAprHandler implements AprHandler { +export class StsAprHandler implements YbAprHandler { constructor(private config: YbAprConfig['sts']) {} async getAprs() { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/susds-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/susds-apr-handler.ts similarity index 92% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/susds-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/susds-apr-handler.ts index 83dcf2934..843a27d8f 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/susds-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/susds-apr-handler.ts @@ -1,4 +1,4 @@ -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; import config from '../../../../../../config'; import { createPublicClient, http, parseAbiItem } from 'viem'; import { base } from 'viem/chains'; @@ -10,7 +10,7 @@ const client = createPublicClient({ const ssrOracle = '0x65d946e533748a998b1f0e430803e39a6388f7a1'; -export class SUSDSAprHandler implements AprHandler { +export class SUSDSAprHandler implements YbAprHandler { group = 'MAKER'; private oracle: string; private token: string; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/teth.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/teth.ts similarity index 92% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/teth.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/teth.ts index 76628c1c5..60a49ad53 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/teth.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/teth.ts @@ -1,10 +1,10 @@ -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; const url = 'https://api.treehouse.finance/rate/mey'; // The apr config needs to be custom made as the resulting value // is equal to Lido's wstETH APR plus the data from the below query. -export class TreehouseAprHandler implements AprHandler { +export class TreehouseAprHandler implements YbAprHandler { constructor(private config: { address: string }) {} async getAprs() { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/usdl-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/usdl-apr-handler.ts similarity index 90% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/usdl-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/usdl-apr-handler.ts index 8ce82ceaf..bf13a43ea 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/usdl-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/usdl-apr-handler.ts @@ -1,5 +1,5 @@ import { gql, request } from 'graphql-request'; -import { AprHandler } from '../types'; +import { YbAprHandler } from '../types'; const url = 'https://blue-api.morpho.org/graphql'; const query = gql` @@ -24,7 +24,7 @@ type USDLResponse = { }; }; -export class UsdlAprHandler implements AprHandler { +export class UsdlAprHandler implements YbAprHandler { constructor() {} async getAprs() { diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yieldnest-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/yieldnest-apr-handler.ts similarity index 100% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yieldnest-apr-handler.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/sources/yieldnest-apr-handler.ts diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/types.ts b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/types.ts similarity index 83% rename from modules/pool/lib/apr-data-sources/yb-apr-handlers/types.ts rename to modules/aprs/handlers/yb-tokens/yb-apr-handlers/types.ts index 85e6879cb..bbb7214b8 100644 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/types.ts +++ b/modules/aprs/handlers/yb-tokens/yb-apr-handlers/types.ts @@ -1,10 +1,10 @@ import { Chain } from '@prisma/client'; export interface AprHandlerConstructor { - new (config?: any): AprHandler; + new (config?: any): YbAprHandler; } -export interface AprHandler { +export interface YbAprHandler { group?: string; getAprs(chain?: Chain): Promise<{ [tokenAddress: string]: { @@ -20,5 +20,4 @@ export type TokenApr = { apr: number; address: string; isIbYield: boolean; - group?: string; }; diff --git a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts index e9c275be7..224bdaec0 100644 --- a/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts +++ b/modules/aprs/handlers/yb-tokens/yb-tokens-apr-handler.ts @@ -1,8 +1,8 @@ import { Chain, PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; -import { YbAprHandlers } from '../../../pool/lib/apr-data-sources/yb-apr-handlers'; import { TokenApr, YbAprConfig } from './types'; import { PoolAPRData, AprHandler } from '../../types'; import { collectsYieldFee, tokenCollectsYieldFee } from '../../../pool/lib/pool-utils'; +import { YbAprHandlers } from './yb-apr-handlers'; /** * Calculator for yield-bearing tokens APR diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/bloom-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/bloom-apr-handler.ts deleted file mode 100644 index 4bf21408f..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/bloom-apr-handler.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Chain } from '@prisma/client'; -import { AprHandler } from '..'; -import { BloomAprConfig } from '../../../../../network/apr-config-types'; -import { abi as bloomBpsFeed } from './abis/bloom-bps-feed'; -import { getViemClient } from '../../../../../sources/viem-client'; - -export class BloomAprHandler implements AprHandler { - group = 'DEFAULT'; - - tokens: BloomAprConfig['tokens']; - - constructor(config: BloomAprConfig) { - this.tokens = config.tokens; - } - - async getAprs(chain: Chain) { - const client = getViemClient(chain); - - const addresses = Object.values(this.tokens).map(({ feedAddress }) => feedAddress as `0x${string}`); - const contracts = addresses.map((address) => ({ - address, - abi: bloomBpsFeed, - functionName: 'currentRate', - })); - const rates = await client.multicall({ contracts, allowFailure: false }); - - const entries = Object.values(this.tokens).map(({ address, isIbYield }, index) => [ - address, - { - apr: (Number(rates[index]) - 10000) / 10000, - isIbYield: isIbYield ?? false, - group: this.group, - }, - ]); - - return Object.fromEntries(entries); - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/gearbox-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/gearbox-apr-handler.ts deleted file mode 100644 index 75fb37be2..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/gearbox-apr-handler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from 'axios'; -import { AprHandler } from '../types'; -import { GearBoxAprConfig } from '../../../../../network/apr-config-types'; - -export class GearboxAprHandler implements AprHandler { - url: string; - tokens: { [key: string]: { address: string; isIbYield?: boolean } }; - readonly group = 'GEARBOX'; - - constructor(aprHandlerConfig: GearBoxAprConfig) { - this.tokens = aprHandlerConfig.tokens; - this.url = aprHandlerConfig.sourceUrl; - } - - async getAprs() { - try { - const { data } = await axios.get(this.url); - const json = data as { data: { dieselToken: string; depositAPY_RAY: string }[] }; - - const aprEntries = json.data - .filter((t) => - Object.values(this.tokens) - .map(({ address }) => address) - .includes(t.dieselToken.toLowerCase()), - ) - .map(({ dieselToken, depositAPY_RAY }) => { - const tokenObj = Object.values(this.tokens).find( - ({ address }) => address === dieselToken.toLowerCase(), - ); - return [ - dieselToken, - { - apr: Number(depositAPY_RAY.slice(0, 27)) / 1e27, - isIbYield: tokenObj?.isIbYield ?? false, - group: this.group, - }, - ]; - }); - return Object.fromEntries(aprEntries); - } catch (error) { - console.error('Failed to fetch Gearbox APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/idle-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/idle-apr-handler.ts deleted file mode 100644 index 7e990b5b9..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/idle-apr-handler.ts +++ /dev/null @@ -1,52 +0,0 @@ -import axios from 'axios'; -import { AprHandler } from '../types'; -import { IdleAprConfig } from '../../../../../network/apr-config-types'; - -export class IdleAprHandler implements AprHandler { - tokens: { - [tokenName: string]: { - address: string; - wrapped4626Address: string; - isIbYield?: boolean; - }; - }; - url: string; - authorizationHeader: string; - readonly group = 'IDLE'; - - constructor(aprHandlerConfig: IdleAprConfig) { - this.tokens = aprHandlerConfig.tokens; - this.url = aprHandlerConfig.sourceUrl; - this.authorizationHeader = aprHandlerConfig.authorizationHeader; - } - - async getAprs() { - try { - const aprPromises = Object.values(this.tokens).map(async ({ address, wrapped4626Address, isIbYield }) => { - const { data } = await axios.get([this.url, address, '?isRisk=false&order=desc&limit=1'].join(''), { - headers: { - Authorization: this.authorizationHeader, - }, - }); - const [json] = data as { idleRate: string }[]; - const value = Number(json.idleRate) / 1e20; - return [ - wrapped4626Address, - { - apr: value, - isIbYield: isIbYield ?? false, - group: this.group, - }, - ]; - }); - const res = Array(Object.keys(this.tokens).length); - for (const [index, aprPromise] of aprPromises.entries()) { - res[index] = await aprPromise; - } - return Object.fromEntries(res); - } catch (error) { - console.error('Failed to fetch Idle APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/ovix-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/ovix-apr-handler.ts deleted file mode 100644 index 0536b601f..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/ovix-apr-handler.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { abi } from './abis/oErc20'; -import { AprHandler } from '..'; -import { OvixAprConfig } from '../../../../../network/apr-config-types'; -import { getViemClient } from '../../../../../sources/viem-client'; -import { Chain } from '@prisma/client'; - -export class OvixAprHandler implements AprHandler { - tokens: { - [tokenName: string]: { - yieldAddress: string; - wrappedAddress: string; - isIbYield?: boolean; - }; - }; - readonly group = 'OVIX'; - - constructor(aprHandlerConfig: OvixAprConfig) { - this.tokens = aprHandlerConfig.tokens; - } - - async getAprs(chain: Chain) { - const client = getViemClient(chain); - - try { - const addresses = Object.values(this.tokens).map(({ yieldAddress }) => yieldAddress as `0x${string}`); - const contracts = addresses.map((address) => ({ - address, - abi, - functionName: 'borrowRatePerTimestamp', - })); - const rates = await client.multicall({ contracts, allowFailure: false }); - - const aprEntries = Object.values(this.tokens).map(({ wrappedAddress, isIbYield }, index) => [ - wrappedAddress, - { - apr: Math.pow(1 + Number(rates[index]) / 1e18, 365 * 24 * 60 * 60) - 1, - isIbYield: isIbYield ?? false, - group: this.group, - }, - ]); - - return Object.fromEntries(await Promise.all(aprEntries)); - } catch (error) { - console.error('Failed to fetch Ovix APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/reaper-crypt-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/reaper-crypt-apr-handler.ts deleted file mode 100644 index 3119f96b3..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/reaper-crypt-apr-handler.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { AprHandler } from '..'; -import { getContractAt } from '../../../../../web3/contract'; -import ReaperCryptStrategyAbi from './abis/ReaperCryptStrategy.json'; -import axios from 'axios'; -import ReaperCryptAbi from './abis/ReaperCrypt.json'; -import { ReaperAprConfig } from '../../../../../network/apr-config-types'; - -const APR_PERCENT_DIVISOR = 10_000; - -const sFTMxBaseApr = 0.046; -export class ReaperCryptAprHandler implements AprHandler { - tokensWithSubgraphSource?: { - [tokenName: string]: { - address: string; - isSftmX?: boolean; - isWstETH?: boolean; - isIbYield?: boolean; - }; - }; - tokensWithOnChainSource?: { - [tokenName: string]: { - address: string; - isSftmX?: boolean; - isWstETH?: boolean; - isIbYield?: boolean; - }; - }; - subgraphUrl?: string; - averageAPRAcrossLastNHarvests?: number; - wstETHBaseApr: number = 0; - - readonly query = `query getVaults($ids: [ID!]) { - vaults(where:{id_in: $ids}){ - id - apr - } - }`; - readonly group = 'REAPER'; - - constructor(aprConfig: ReaperAprConfig) { - this.tokensWithSubgraphSource = aprConfig.subgraphSource?.tokens; - this.tokensWithOnChainSource = aprConfig.onchainSource?.tokens; - this.subgraphUrl = aprConfig.subgraphSource?.subgraphUrl; - this.averageAPRAcrossLastNHarvests = aprConfig.onchainSource?.averageAPRAcrossLastNHarvests; - } - - async getAprs() { - let multiStrategyAprs = {}; - let singleStrategyAprs = {}; - this.wstETHBaseApr = await this.getWstEthBaseApr(); - if (this.tokensWithSubgraphSource !== undefined) { - multiStrategyAprs = await this.getAprFromSubgraph(this.tokensWithSubgraphSource); - } - if (this.tokensWithOnChainSource !== undefined) { - singleStrategyAprs = await this.getOnChainCryptApr(this.tokensWithOnChainSource); - } - return { ...multiStrategyAprs, ...singleStrategyAprs }; - } - - private async getOnChainCryptApr(tokens: { - [tokenName: string]: { address: string; isSftmX?: boolean; isWstETH?: boolean; isIbYield?: boolean }; - }): Promise<{ [tokenAddress: string]: { apr: number; isIbYield: boolean } }> { - const aprs: { [tokenAddress: string]: { apr: number; isIbYield: boolean } } = {}; - for (const { address, isSftmX, isWstETH, isIbYield } of Object.values(tokens)) { - try { - const tokenContract = getContractAt(address, ReaperCryptAbi); - const strategyAddress = await tokenContract.strategy(); - const strategyContract = getContractAt(strategyAddress, ReaperCryptStrategyAbi); - let avgAprAcrossXHarvests = 0; - - avgAprAcrossXHarvests = - (await strategyContract.averageAPRAcrossLastNHarvests(this.averageAPRAcrossLastNHarvests)) / - APR_PERCENT_DIVISOR; - // TODO hanlde this outside - if (isSftmX) { - avgAprAcrossXHarvests = avgAprAcrossXHarvests * (1 + sFTMxBaseApr); - } - if (isWstETH) { - avgAprAcrossXHarvests = avgAprAcrossXHarvests * (1 + this.wstETHBaseApr); - } - aprs[address] = { apr: avgAprAcrossXHarvests, isIbYield: isIbYield ?? false }; - } catch (error) { - console.error(`Reaper IB APR handler failed for onChain source: `, error); - return {}; - } - } - - return aprs; - } - - private async getAprFromSubgraph(tokens: { - [tokenName: string]: { address: string; isSftmX?: boolean; isWstETH?: boolean; isIbYield?: boolean }; - }): Promise<{ [tokenAddress: string]: number }> { - try { - const requestQuery = { - operationName: 'getVaults', - query: this.query, - variables: { - ids: Object.values(tokens).map(({ address }) => address), - }, - }; - const { - data: { data }, - }: { data: { data: MultiStratResponse } } = await axios({ - method: 'post', - url: this.subgraphUrl, - data: JSON.stringify(requestQuery), - }); - return data.vaults.reduce((acc, { id, apr }) => { - const token = Object.values(tokens).find((token) => token.address.toLowerCase() === id.toLowerCase()); - if (!token) { - return acc; - } - let tokenApr = parseFloat(apr) / APR_PERCENT_DIVISOR; - if (token.isSftmX) { - tokenApr = tokenApr * (1 + sFTMxBaseApr); - } - if (token.isWstETH) { - tokenApr = tokenApr * (1 + this.wstETHBaseApr); - } - return { - ...acc, - [id]: { - apr: tokenApr, - isIbYield: token.isIbYield ?? false, - group: this.group, - }, - }; - }, {}); - } catch (error) { - console.error(`Reaper IB APR handler failed for subgraph source: `, error); - return {}; - } - } - - private async getWstEthBaseApr(): Promise { - const { data } = await axios.get<{ - data: { aprs: [{ timeUnix: number; apr: number }]; smaApr: number }; - }>('https://eth-api.lido.fi/v1/protocol/steth/apr/sma'); - return data.data.smaApr / 100; - } -} -type MultiStratResponse = { - vaults: { - id: string; - apr: string; - }[]; -}; diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sftmx-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sftmx-apr-handler.ts deleted file mode 100644 index 333a7ecf0..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sftmx-apr-handler.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { AprHandler } from '..'; -import { SftmxAprConfig } from '../../../../../network/apr-config-types'; -import { formatFixed } from '@ethersproject/bignumber'; -import FTMStaking from '../../../../../sources/contracts/abis/FTMStaking'; -import SftmxVault from '../../../../../sources/contracts/abis/SftmxVault'; -import { Chain } from '@prisma/client'; -import { getViemClient } from '../../../../../sources/viem-client'; - -export class SftmxAprHandler implements AprHandler { - tokens: { - [underlyingAssetName: string]: { - address: string; - ftmStakingAddress: string; - }; - }; - - constructor(config: SftmxAprConfig) { - this.tokens = config.tokens; - } - - async getAprs(chain: Chain) { - const client = getViemClient(chain); - const baseApr = 0.018; - const maxLockApr = 0.06; - const validatorFee = 0.15; - const sftmxFee = 0.1; - const aprs: { - [tokenAddress: string]: { - apr: number; - isIbYield: boolean; - group?: string; - }; - } = {}; - try { - const addresses = Object.keys(this.tokens).map( - (tokenAddress) => this.tokens[tokenAddress].ftmStakingAddress as `0x${string}`, - ); - const contracts = addresses.flatMap((address) => [ - { - address, - abi: FTMStaking, - functionName: 'totalFTMWorth', - }, - { - address, - abi: FTMStaking, - functionName: 'getPoolBalance', - }, - { - address, - abi: FTMStaking, - functionName: 'getMaturedVaultLength', - }, - ]); - // @ts-ignore - const results = (await client.multicall({ contracts, allowFailure: false })) as bigint[]; - for (let i = 0; i < results.length; i += 3) { - const ftmStakingAddress = addresses[i]; - const totalFtm = results[i]; - const poolFtm = results[i + 1]; - const maturedVaultCount = results[i + 2]; - - if (maturedVaultCount === 0n) { - continue; - } - - const vaultAddressesCalls = Array.from({ length: Number(maturedVaultCount) }).map((_, index) => ({ - address: ftmStakingAddress as `0x${string}`, - abi: FTMStaking, - functionName: 'getMaturedVault', - args: [index], - })); - const vaultAddresses = (await client.multicall({ - contracts: vaultAddressesCalls, - allowFailure: false, - })) as string[]; - const amountCalls = vaultAddresses.map((vaultAddress) => ({ - address: vaultAddress as `0x${string}`, - abi: SftmxVault, - functionName: 'currentStakeValue', - })); - const amounts = (await client.multicall({ contracts: amountCalls, allowFailure: false })) as bigint[]; - const maturedFtmAmount = amounts.reduce((acc, amount) => acc + amount, 0n); - - const totalFtmNum = parseFloat(formatFixed(totalFtm.toString(), 18)); - const poolFtmNum = parseFloat(formatFixed(poolFtm.toString(), 18)); - const maturedFtmNum = parseFloat(formatFixed(maturedFtmAmount.toString(), 18)); - const stakedFtmNum = totalFtmNum - poolFtmNum - maturedFtmNum; - - const totalMaxLockApr = - (stakedFtmNum / totalFtmNum) * (maxLockApr * (1 - validatorFee)) * (1 - sftmxFee); - const totalBaseApr = (maturedFtmNum / totalFtmNum) * (baseApr * (1 - validatorFee)) * (1 - sftmxFee); - - const totalSftmxApr = totalMaxLockApr + totalBaseApr; - - aprs[Object.values(this.tokens)[i].address] = { - apr: totalSftmxApr, - isIbYield: true, - }; - } - return aprs; - } catch (error) { - console.error('Failed to fetch sftmx APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sv-eth.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sv-eth.ts deleted file mode 100644 index 2f6969451..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/sv-eth.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { createPublicClient, formatEther, http, parseAbi } from 'viem'; -import { mainnet } from 'viem/chains'; -import type { AprHandler } from '../types'; -import config from '../../../../../../config/mainnet'; - -const client = createPublicClient({ - chain: mainnet, - transport: http(config.rpcUrl), -}); - -const distributor = '0xc93ab6aca2c14422a65a31010ac2b4baa86a21b3' as `0x${string}`; -const vETH = '0x38d64ce1bdf1a9f24e0ec469c9cade61236fb4a0' as `0x${string}`; -const svETH = '0x6733f0283711f225a447e759d859a70b0c0fd2bc' as `0x${string}`; - -const distributorAbi = parseAbi(['function svETHRewardPerEpoch() view returns (uint256)']); -const vETHAbi = parseAbi(['function balanceOf(address) view returns (uint256)']); - -export class svEthAprHandler implements AprHandler { - async getAprs() { - const rate = (await client.readContract({ - address: distributor, - abi: distributorAbi, - functionName: 'svETHRewardPerEpoch', - })) as bigint; - - const balance = (await client.readContract({ - address: vETH, - abi: vETHAbi, - functionName: 'balanceOf', - args: [svETH], - })) as bigint; - - return { - [svETH]: { - apr: (parseFloat(formatEther(rate)) * 1095) / parseFloat(formatEther(balance)), - isIbYield: true, - }, - }; - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tetu-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tetu-apr-handler.ts deleted file mode 100644 index b08bdb482..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tetu-apr-handler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from 'axios'; -import { AprHandler } from '../types'; -import { TetuAprConfig } from '../../../../../network/apr-config-types'; - -export class TetuAprHandler implements AprHandler { - sourceUrl: string; - tokens: { - [tokenName: string]: { - address: string; - isIbYield?: boolean; - }; - }; - readonly group = 'TETU'; - - constructor(aprHandlerConfig: TetuAprConfig) { - this.sourceUrl = aprHandlerConfig.sourceUrl; - this.tokens = aprHandlerConfig.tokens; - } - - async getAprs() { - try { - const { data } = await axios.get(this.sourceUrl); - const json = data as { vault: string; apr: number }[]; - const aprs = json - .filter(({ vault }) => - Object.values(this.tokens) - .map(({ address }) => address) - .includes(vault.toLowerCase()), - ) - .map((t) => [ - t.vault, - { - apr: t.apr / 100, - isIbYield: - Object.values(this.tokens).find(({ address }) => address === t.vault)?.isIbYield ?? false, - group: this.group, - }, - ]); - return Object.fromEntries(aprs); - } catch (error) { - console.error('Failed to fetch Tetu APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tranchess-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tranchess-apr-handler.ts deleted file mode 100644 index 39a195089..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/tranchess-apr-handler.ts +++ /dev/null @@ -1,45 +0,0 @@ -import axios from 'axios'; -import { AprHandler } from '../types'; -import { TranchessAprConfig } from '../../../../../network/apr-config-types'; - -export class TranchessAprHandler implements AprHandler { - url: string; - tokens: { - [tokenName: string]: { - address: string; - underlyingAssetName: string; - isIbYield?: boolean; - }; - }; - readonly group = 'TRANCHESS'; - - constructor(aprHandlerConfig: TranchessAprConfig) { - this.tokens = aprHandlerConfig.tokens; - this.url = aprHandlerConfig.sourceUrl; - } - - async getAprs() { - try { - const { data } = await axios.get('https://tranchess.com/eth/api/v3/funds'); - // const [{ weeklyAveragePnlPercentage }] = data as { weeklyAveragePnlPercentage: string }[]; - const aprEntries = Object.values(this.tokens).map(({ address, underlyingAssetName, isIbYield }) => { - const weeklyAveragePnlPercentage = ( - data as { weeklyAveragePnlPercentage: string; name: string }[] - ).filter(({ name }) => name === underlyingAssetName)[0].weeklyAveragePnlPercentage; - return [ - address, - { - apr: (365 * Number(weeklyAveragePnlPercentage)) / 1e18, - isIbYield: isIbYield ?? false, - group: this.group, - }, - ]; - }); - // The key weeklyAveragePnlPercentage is the daily yield of qETH in 18 decimals, timing 365 should give you the APR. - return Object.fromEntries(aprEntries); - } catch (error) { - console.error('Failed to fetch Tranchess APR:', error); - return {}; - } - } -} diff --git a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yearn-apr-handler.ts b/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yearn-apr-handler.ts deleted file mode 100644 index e47ade8b6..000000000 --- a/modules/pool/lib/apr-data-sources/yb-apr-handlers/sources/yearn-apr-handler.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { AprHandler } from '../types'; -import axios from 'axios'; -import { YearnAprConfig } from '../../../../../network/apr-config-types'; - -export class YearnAprHandler implements AprHandler { - sourceUrl: string; - isIbYield?: boolean; - group: string = 'YEARN'; - - constructor(aprHandlerConfig: YearnAprConfig) { - this.sourceUrl = aprHandlerConfig.sourceUrl; - this.isIbYield = aprHandlerConfig.isIbYield; - } - async getAprs() { - try { - const { data } = await axios.get(this.sourceUrl); - const aprs = Object.fromEntries( - data.map(({ address, apy: { net_apy } }) => { - return [ - address.toLowerCase(), - { - apr: net_apy, - isIbYield: this.isIbYield ?? false, - group: this.group, - }, - ]; - }), - ); - return aprs; - } catch (error) { - console.error(`Yearn IB APR handler failed: `, error); - return {}; - } - } -} - -import { Dictionary } from 'lodash'; - -interface YearnVault { - address: string; - apy: YearnVaultApy; -} - -interface YearnVaultApy { - net_apy: number; -} From 14e9c29f47161ee9f2dfa15ae449d34a754121fc Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 14:53:22 +0300 Subject: [PATCH 26/37] drop apr range --- .../20250616115312_remove_apr_range/migration.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 prisma/migrations/20250616115312_remove_apr_range/migration.sql diff --git a/prisma/migrations/20250616115312_remove_apr_range/migration.sql b/prisma/migrations/20250616115312_remove_apr_range/migration.sql new file mode 100644 index 000000000..7ccbc277b --- /dev/null +++ b/prisma/migrations/20250616115312_remove_apr_range/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the `PrismaPoolAprRange` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "PrismaPoolAprRange" DROP CONSTRAINT "PrismaPoolAprRange_aprItemId_chain_fkey"; + +-- DropTable +DROP TABLE "PrismaPoolAprRange"; From 23d16e50fe304990e12a012963ee0f8dfb525e62 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 15:04:44 +0300 Subject: [PATCH 27/37] fix merkl query --- modules/aprs/apr-service.ts | 2 +- modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aprs/apr-service.ts b/modules/aprs/apr-service.ts index 3898a63f0..f5d729973 100644 --- a/modules/aprs/apr-service.ts +++ b/modules/aprs/apr-service.ts @@ -33,7 +33,7 @@ export class AprService { */ async updateAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - const changedIds = manager.updateAprs(chain); + const changedIds = await manager.updateAprs(chain); await syncIncentivizedCategory(chain); return changedIds; } diff --git a/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts index 0409870a1..5e25b615e 100644 --- a/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts +++ b/modules/aprs/handlers/merkl-apr/merkl-apr-handler.ts @@ -44,7 +44,7 @@ export class MerklAprHandler implements AprHandler { private async fetchForwardedMerklOpportunities(chainId: string) { const response = await fetch( - `https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&items=2000&chainId=${chainId}`, + `https://api.merkl.xyz/v4/opportunities/?test=false&status=LIVE&campaigns=true&items=100&chainId=${chainId}`, ); const data = (await response.json()) as MerklOpportunity[]; From f3c1d8abc8af56ae1d30854850d4b354b0d09597 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 15:04:59 +0300 Subject: [PATCH 28/37] fix await --- modules/aprs/apr-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aprs/apr-service.ts b/modules/aprs/apr-service.ts index f5d729973..37003234d 100644 --- a/modules/aprs/apr-service.ts +++ b/modules/aprs/apr-service.ts @@ -44,7 +44,7 @@ export class AprService { */ async reloadAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - const changedIds = manager.reloadAllPoolAprs(chain); + const changedIds = await manager.reloadAllPoolAprs(chain); await syncIncentivizedCategory(chain); return changedIds; } From 7f1bc1eb07fb62a2dc0541ca28e79034955d2260 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 15:32:06 +0300 Subject: [PATCH 29/37] fix update total apr --- modules/aprs/apr-repository.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/aprs/apr-repository.ts b/modules/aprs/apr-repository.ts index 3e3186059..7a687b389 100644 --- a/modules/aprs/apr-repository.ts +++ b/modules/aprs/apr-repository.ts @@ -44,9 +44,6 @@ export class AprRepository { ): Promise { if (newAprItems.length === 0) return []; - // Get unique pool IDs from the items - const poolIds = [...new Set(newAprItems.map((item) => item.poolId))]; - // Fetch all existing APR items const existingItems = await prisma.prismaPoolAprItem.findMany({ where: { @@ -127,7 +124,7 @@ export class AprRepository { ) AS sub WHERE dyn."poolId" = sub."poolId" AND dyn."chain" = sub."chain" - AND dyn.chain = ${chain} + AND dyn."chain" = ${chain}::"Chain" AND dyn."poolId" = ANY(${poolIds}); `; From a0e0097a58b2c95dc9d178f1c5af321323992124 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 16:07:19 +0300 Subject: [PATCH 30/37] fix beetswars --- .../mabeets-apr/beetswars-gauge-voting-apr-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts index 448a32bab..16952d983 100644 --- a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts @@ -34,7 +34,7 @@ export class BeetswarsGaugeVotingAprHandler implements AprHandler { aprItems.push({ id: itemId, - chain: networkContext.chain, + chain: 'SONIC', poolId: this.FRESH_BEETS_POOL_ID, title: 'Voting APR*', apr: minApr, @@ -45,7 +45,7 @@ export class BeetswarsGaugeVotingAprHandler implements AprHandler { aprItems.push({ id: `${itemId}-boost`, - chain: networkContext.chain, + chain: 'SONIC', poolId: this.FRESH_BEETS_POOL_ID, title: 'Voting APR Boost', apr: maxApr, From 7288a78abc3cb038027afc4e6c2e0e0261af2d05 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 16:37:05 +0300 Subject: [PATCH 31/37] fux yusd apr --- config/arbitrum.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/arbitrum.ts b/config/arbitrum.ts index 14d689007..6e60241bd 100644 --- a/config/arbitrum.ts +++ b/config/arbitrum.ts @@ -191,8 +191,8 @@ export default { }, yUSD2: { tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', - sourceUrl: 'https://api.yield.fi/t/7Dapy', - path: '7d-apy[0].weighted_apy_7d_avg', + sourceUrl: 'https://api.yield.fi/t/yusd/apy', + path: 'apy', isIbYield: true, }, usdm: { From cdd30ec3f3f9fce83e2b30784cdc4a3cbade66a3 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 16:45:54 +0300 Subject: [PATCH 32/37] fix yusd2 apr base --- config/base.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/base.ts b/config/base.ts index 8b54268aa..08cba0f45 100644 --- a/config/base.ts +++ b/config/base.ts @@ -141,8 +141,8 @@ export default { }, yUSD2: { tokenAddress: '0x4772d2e014f9fc3a820c444e3313968e9a5c8121', - sourceUrl: 'https://api.yield.fi/t/7Dapy', - path: '7d-apy[0].weighted_apy_7d_avg', + sourceUrl: 'https://api.yield.fi/t/yusd/apy', + path: 'apy', isIbYield: true, }, cbETH: { From 5dba0530f348d5d1ddf783aec15163627a8834bd Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 16:55:48 +0300 Subject: [PATCH 33/37] changeset --- .changeset/cool-spiders-bake.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cool-spiders-bake.md diff --git a/.changeset/cool-spiders-bake.md b/.changeset/cool-spiders-bake.md new file mode 100644 index 000000000..3e83f3f91 --- /dev/null +++ b/.changeset/cool-spiders-bake.md @@ -0,0 +1,5 @@ +--- +'backend': minor +--- + +extract all APRs into APR-module From 798ad99e8bf0f0cabb94b6a6d17199e90c92a790 Mon Sep 17 00:00:00 2001 From: franz Date: Mon, 16 Jun 2025 16:59:03 +0300 Subject: [PATCH 34/37] add back aave for avax --- config/avalanche.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/config/avalanche.ts b/config/avalanche.ts index 8a613fb59..5962ca292 100644 --- a/config/avalanche.ts +++ b/config/avalanche.ts @@ -74,6 +74,7 @@ export default { multicall3: '0xca11bde05977b3631167028862be2a173976ca11', avgBlockSpeed: 2, aprHandlers: { + aaveRewardsAprHandler: true, ybAprConfig: { aave: { v3: { From 93c9680c7ae7a440659512d90c65eed084454bc5 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 19 Jun 2025 09:44:29 +0200 Subject: [PATCH 35/37] move logic to controller --- apps/api/gql/resolvers/pool.resolvers.ts | 4 ++-- apps/worker/job-handlers.ts | 4 ++-- modules/aprs/apr-service.ts | 9 ++------- modules/controllers/aprs-controller.ts | 20 ++++++++++++++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 modules/controllers/aprs-controller.ts diff --git a/apps/api/gql/resolvers/pool.resolvers.ts b/apps/api/gql/resolvers/pool.resolvers.ts index b5ab2e545..abee694ad 100644 --- a/apps/api/gql/resolvers/pool.resolvers.ts +++ b/apps/api/gql/resolvers/pool.resolvers.ts @@ -16,7 +16,7 @@ import { GraphQLError } from 'graphql'; import { upsertLastSyncedBlock } from '../../../../modules/actions/last-synced-block'; import { PrismaLastBlockSyncedCategory } from '@prisma/client'; import graphqlFields from 'graphql-fields'; -import { AprService } from '../../../../modules/aprs'; +import { AprsController } from '../../../../modules/controllers/aprs-controller'; const balancerResolvers: Resolvers = { Query: { @@ -121,7 +121,7 @@ const balancerResolvers: Resolvers = { poolReloadAllPoolAprs: async (parent, { chain }, context) => { isAdminRoute(context); - await new AprService().reloadAprs(chain); + await AprsController().reloadAprsAndIncentivizedCategory(chain); return 'success'; }, diff --git a/apps/worker/job-handlers.ts b/apps/worker/job-handlers.ts index 37d08a79c..7374e0934 100644 --- a/apps/worker/job-handlers.ts +++ b/apps/worker/job-handlers.ts @@ -32,7 +32,7 @@ import { TokenController } from '../../modules/controllers/token-controller'; import { SubgraphMonitorController } from '../../modules/controllers/subgraph-monitor-controller'; import config from '../../config'; import { LBPController } from '../../modules/controllers/lbp-controller'; -import { AprService } from '../../modules/aprs'; +import { AprsController } from '../../modules/controllers/aprs-controller'; const runningJobs: Set = new Set(); @@ -318,7 +318,7 @@ const setupJobHandlers = async (name: string, chainId: string, res: any, next: N chainId, () => { const chain = chainIdToChain[chainId]; - return new AprService().updateAprs(chain); + return AprsController().updateAprsAndIncentivizedCategory(chain); }, res, next, diff --git a/modules/aprs/apr-service.ts b/modules/aprs/apr-service.ts index 37003234d..900e1a70a 100644 --- a/modules/aprs/apr-service.ts +++ b/modules/aprs/apr-service.ts @@ -3,7 +3,6 @@ import { AprHandler } from './types'; import { AprRepository } from './apr-repository'; import { AprManager } from './apr-manager'; import { createHandlers } from './handlers'; -import { syncIncentivizedCategory } from '../actions/pool/sync-incentivized-category'; export class AprService { private readonly aprRepository: AprRepository; @@ -33,9 +32,7 @@ export class AprService { */ async updateAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - const changedIds = await manager.updateAprs(chain); - await syncIncentivizedCategory(chain); - return changedIds; + return await manager.updateAprs(chain); } /** @@ -44,9 +41,7 @@ export class AprService { */ async reloadAprs(chain: Chain): Promise { const manager = this.getManagerForChain(chain); - const changedIds = await manager.reloadAllPoolAprs(chain); - await syncIncentivizedCategory(chain); - return changedIds; + return manager.reloadAllPoolAprs(chain); } /** diff --git a/modules/controllers/aprs-controller.ts b/modules/controllers/aprs-controller.ts new file mode 100644 index 000000000..bf91fc82d --- /dev/null +++ b/modules/controllers/aprs-controller.ts @@ -0,0 +1,20 @@ +import { Chain } from '@prisma/client'; +import { AprService } from '../aprs'; +import { syncIncentivizedCategory } from '../actions/pool/sync-incentivized-category'; + +export function AprsController(tracer?: any) { + // Setup tracing + // ... + return { + async updateAprsAndIncentivizedCategory(chain: Chain) { + const aprService = new AprService(); + await aprService.updateAprs(chain); + await syncIncentivizedCategory(chain); + }, + async reloadAprsAndIncentivizedCategory(chain: Chain) { + const aprService = new AprService(); + await aprService.reloadAprs(chain); + await syncIncentivizedCategory(chain); + }, + }; +} From c63f0a267c47761a624af2e4f3a5ac928d1461b6 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 19 Jun 2025 09:51:40 +0200 Subject: [PATCH 36/37] extract tokenservice from handlers for DI --- modules/aprs/handlers/create-handlers.ts | 7 +++++-- .../liquidity-gauge-apr/liquidity-gauge-apr-handler.ts | 6 +++--- .../mabeets-apr/beetswars-gauge-voting-apr-handler.ts | 1 - modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts | 8 ++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/aprs/handlers/create-handlers.ts b/modules/aprs/handlers/create-handlers.ts index 9373bfe2a..29e3df0d8 100644 --- a/modules/aprs/handlers/create-handlers.ts +++ b/modules/aprs/handlers/create-handlers.ts @@ -3,6 +3,7 @@ import { AprHandler } from '../types'; import * as handlers from '.'; import config from '../../../config'; import { chainToChainId } from '../../network/chain-id-to-chain'; +import { tokenService } from '../../token/token.service'; /** * Creates handler instances for a specific chain @@ -15,7 +16,7 @@ export function createHandlers(chain: Chain): AprHandler[] { handlerList.push(new handlers.DynamicSwapFeeAprHandler()); handlerList.push(new handlers.NestedPoolAprHandler()); // handlerList.push(new handlers.QuantAmmAprHandler()); - handlerList.push(new handlers.LiquidityGaugeAprHandler()); + handlerList.push(new handlers.LiquidityGaugeAprHandler(tokenService)); handlerList.push(new handlers.MerklAprHandler()); handlerList.push(new handlers.SurplusSwapFeeAprHandler()); @@ -26,7 +27,9 @@ export function createHandlers(chain: Chain): AprHandler[] { } if (config[chain].aprHandlers.maBeetsAprHandler) { - handlerList.push(new handlers.MaBeetsAprHandler(config[chain].aprHandlers.maBeetsAprHandler.beetsAddress)); + handlerList.push( + new handlers.MaBeetsAprHandler(config[chain].aprHandlers.maBeetsAprHandler.beetsAddress, tokenService), + ); handlerList.push(new handlers.BeetswarsGaugeVotingAprHandler()); } diff --git a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts index fff973c10..a96da15a5 100644 --- a/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts +++ b/modules/aprs/handlers/liquidity-gauge-apr/liquidity-gauge-apr-handler.ts @@ -8,13 +8,13 @@ import { secondsPerYear } from '../../../common/time'; import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; -import { tokenService } from '../../../token/token.service'; +import { TokenService, tokenService } from '../../../token/token.service'; import { AprHandler, PoolAPRData } from '../../types'; export class LiquidityGaugeAprHandler implements AprHandler { private readonly MAX_VEBAL_BOOST = 2.5; - constructor() {} + constructor(private readonly tokenService: TokenService) {} public getAprServiceName(): string { return 'LiquidityGaugeAprHandler'; @@ -26,7 +26,7 @@ export class LiquidityGaugeAprHandler implements AprHandler { const chain = pools[0].chain; // Get the data - const tokenPrices = await tokenService.getTokenPrices(chain); + const tokenPrices = await this.tokenService.getTokenPrices(chain); const aprItems: Omit[] = []; diff --git a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts index 16952d983..95f833167 100644 --- a/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/beetswars-gauge-voting-apr-handler.ts @@ -1,5 +1,4 @@ import axios from 'axios'; -import { networkContext } from '../../../network/network-context.service'; import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; import { AprHandler, PoolAPRData } from '../../types'; diff --git a/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts index 1df46baef..2f3d5cfd4 100644 --- a/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts +++ b/modules/aprs/handlers/mabeets-apr/mabeets-apr-handler.ts @@ -3,13 +3,13 @@ import { PrismaPoolAprItem, PrismaPoolAprType } from '@prisma/client'; import { prisma } from '../../../../prisma/prisma-client'; import { prismaBulkExecuteOperations } from '../../../../prisma/prisma-util'; import { secondsPerYear } from '../../../common/time'; -import { tokenService } from '../../../token/token.service'; +import { TokenService } from '../../../token/token.service'; import { ReliquarySubgraphService } from '../../../subgraphs/reliquary-subgraph/reliquary.service'; import { AprHandler, PoolAPRData } from '../../types'; import config from '../../../../config'; export class MaBeetsAprHandler implements AprHandler { - constructor(private readonly beetsAddress: string) {} + constructor(private readonly beetsAddress: string, private readonly tokenService: TokenService) {} public getAprServiceName(): string { return 'MaBeetsAprHandler'; @@ -30,7 +30,7 @@ export class MaBeetsAprHandler implements AprHandler { const filteredFarms = allSubgraphFarms.filter((farm) => !excludedFarmIds.includes(farm.pid.toString())); - const tokenPrices = await tokenService.getTokenPrices(chain); + const tokenPrices = await this.tokenService.getTokenPrices(chain); const operations: any[] = []; const aprItems: Omit[] = []; @@ -50,7 +50,7 @@ export class MaBeetsAprHandler implements AprHandler { const totalLiquidity = pool.dynamicData?.totalLiquidity || 0; const pricePerShare = totalLiquidity / totalShares; - const beetsPrice = tokenService.getPriceForToken(tokenPrices, this.beetsAddress, chain); + const beetsPrice = this.tokenService.getPriceForToken(tokenPrices, this.beetsAddress, chain); const farmBeetsPerYear = parseFloat(farm.beetsPerSecond) * secondsPerYear; const beetsValuePerYear = beetsPrice * farmBeetsPerYear; From eec5cd0478d9a6869f9e0df637466c24191418c9 Mon Sep 17 00:00:00 2001 From: franz Date: Thu, 19 Jun 2025 09:55:55 +0200 Subject: [PATCH 37/37] add stability apr --- config/sonic.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/config/sonic.ts b/config/sonic.ts index 82a1ceb06..60b018995 100644 --- a/config/sonic.ts +++ b/config/sonic.ts @@ -199,6 +199,20 @@ export default { scale: 100, isIbYield: true, }, + wmetaUSDC: { + tokenAddress: '0xeeeeeee6d95e55a468d32feb5d6648754d10a967', + sourceUrl: 'https://api.stability.farm/', + path: 'metaVaults.146.0x22222222780038f8817b3de825a070225e6d9874.APR', + scale: 100, + isIbYield: true, + }, + wmetaSCUSD: { + tokenAddress: '0xeeeeeee6d95e55a468d32feb5d6648754d10a967', + sourceUrl: 'https://api.stability.farm/', + path: 'metaVaults.146.0x33333333c480194b5b651987b7d00b20ddcbd287.APR', + scale: 100, + isIbYield: true, + }, }, }, },