Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APR Services - Add some yield tokens APR to linear pools #396

Merged
merged 48 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e6a65b3
Created a service to fetch yield tokens APR and push into DB
Jun 30, 2023
1f18d5c
Merge remote-tracking branch 'origin/v3-canary' into non-reaper-yield…
Jun 30, 2023
27ca26d
Adding Apr Service to Mainnet network
Jul 3, 2023
3d84c4b
Added yield tokens fetch funcionality directly on beethovenx-backend,…
Jul 7, 2023
6120e3a
Adapting contracts from apr handler to ethers instead of viem;
Jul 8, 2023
65211ed
Changing apr type if pool type is linear;
Jul 8, 2023
304988e
removing viem
Jul 8, 2023
5cc075b
Merge commit 'fccc29b6e65ffd3df41b7b7de0c2e407e8de2c59' into non-reap…
Jul 13, 2023
11cd519
Fixing Ovix APR;
Jul 13, 2023
a1a20dd
Redesigning Ib Apr Handlers to be more scalable and open to new netwo…
Jul 28, 2023
b3d5575
adding interface to default, ankr and aave apr handlers;
Jul 28, 2023
65047bb
Adding multichain support;
Aug 1, 2023
19a48fc
Types files for ankr and default-fetch; (Missing in the last commit)
Aug 1, 2023
bfe33c1
Merge commit 'a0eb08ebd453441e8f566fb03cc684ec8b4e324d' into non-reap…
Aug 1, 2023
a7b904b
Creating new migration file;
Aug 2, 2023
4cff5b7
Removing test route;
Aug 2, 2023
1e74f53
removing unused imports;
Aug 7, 2023
6b00046
Removing ethers-multicall-provider package
Aug 7, 2023
8c2432e
removing undefined from "group" type
Aug 7, 2023
500f6c4
Moving tokens and types to apr-handler file;
Aug 9, 2023
4d97f48
Using prismaId instead of chain id;
Aug 11, 2023
37366a4
Checking if the pool is linear boosted by the token address;
Aug 18, 2023
32b5a52
Renaming ib-yield-apr-handlers to base-apr-handlers
Aug 21, 2023
74d2bce
Removing ib-yield-apr-handlers folder
Aug 21, 2023
8c04318
Refactoring all code to be more scalable;
Aug 24, 2023
8f040cd
removing aave tokens unused;
Aug 24, 2023
85f76fd
Renaming aprConfig to IbAprConfig;
Aug 29, 2023
c509044
Removing Reaper Crypt Apr Service;
Aug 29, 2023
3d568ca
Removing services that are already in the ib-yield service;
Aug 29, 2023
ae0d54c
Removing test route;
Aug 29, 2023
7913842
Merge branch 'v3-canary' into non-reaper-yield-tokens-apr
Aug 29, 2023
98e9df6
fixing base network incompatibilities;
Aug 29, 2023
fd7f136
fixing optional beefy property in network config;
Aug 29, 2023
a836720
Removing all unused functions of liquid staked base apr, left just xB…
Aug 29, 2023
114386d
Renaming AprConfig to IbAprConfig;
Aug 29, 2023
9ac7a10
clean up types
franzns Sep 1, 2023
a5ca109
simplify ib-tokens-apr service
franzns Sep 1, 2023
97a795f
change default handler, adapt token lists
franzns Sep 4, 2023
ea59577
fix percentage
franzns Sep 4, 2023
1a9352e
add try catch
franzns Sep 4, 2023
c174806
Changing the ib yield filter to network config;
Sep 4, 2023
b00f57d
ibyield flag
franzns Sep 5, 2023
21cd9ae
cleanup
franzns Sep 5, 2023
1261ec6
dont use nested query for apr
franzns Sep 5, 2023
5563895
Merge branch 'v3-canary' into non-reaper-yield-tokens-apr
franzns Sep 5, 2023
e68f06a
lower case all addresses in configs
franzns Sep 5, 2023
c511a9b
Merge branch 'v3-canary' into non-reaper-yield-tokens-apr
franzns Sep 5, 2023
57099bf
adding rocket eth apr
franzns Sep 6, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions modules/balancer/loadRestRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { Express } from 'express';
import { IbTokensAprService } from "../pool/lib/apr-data-sources/ib-tokens-apr.service";
import { networkContext } from "../network/network-context.service";
import { prismaPoolWithExpandedNesting } from "../../prisma/prisma-types";
import { prisma } from "../../prisma/prisma-client";
import { ibYieldAprHandlers } from "../pool/lib/apr-data-sources/ib-yield-apr-handlers/ib-yield-apr-handlers";

export function loadRestRoutesBalancer(app: Express) {
app.use('/health', (req, res) => res.sendStatus(200));
app.use('/test', async (req, res) => {
const pools = await prisma.prismaPool.findMany({
...prismaPoolWithExpandedNesting,
where: { chain: networkContext.chain },
});
const ibTokensAprService = new IbTokensAprService(ibYieldAprHandlers);
await ibTokensAprService.updateAprForPools(pools);

return res.sendStatus(200)
});

}
5 changes: 5 additions & 0 deletions modules/network/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { GithubContentService } from '../content/github-content.service';
import { gaugeSubgraphService } from '../subgraphs/gauge-subgraph/gauge-subgraph.service';
import { coingeckoService } from '../coingecko/coingecko.service';
import { CoingeckoPriceHandlerService } from '../token/lib/token-price-handlers/coingecko-price-handler.service';
import { IbTokensAprService } from "../pool/lib/apr-data-sources/ib-tokens-apr.service";
import {
ibYieldAprHandlers
} from "../pool/lib/apr-data-sources/ib-yield-apr-handlers/ib-yield-apr-handlers";

const mainnetNetworkData: NetworkData = {
chain: {
Expand Down Expand Up @@ -157,6 +161,7 @@ export const mainnetNetworkConfig: NetworkConfig = {
contentService: new GithubContentService(),
provider: new ethers.providers.JsonRpcProvider(mainnetNetworkData.rpcUrl),
poolAprServices: [
new IbTokensAprService(ibYieldAprHandlers),
new WstethAprService(tokenService, mainnetNetworkData.lido!.wstEthContract),
new ReaperCryptAprService(
mainnetNetworkData.reaper.linearPoolFactories,
Expand Down
2 changes: 2 additions & 0 deletions modules/pool/lib/apr-data-sources/apr-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Dictionary } from 'lodash'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unused

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still there :)

export interface YearnVault {
inception: number;
address: string;
Expand Down
63 changes: 63 additions & 0 deletions modules/pool/lib/apr-data-sources/ib-tokens-apr.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { PoolAprService } from "../../pool-types";
import { PrismaPoolWithExpandedNesting } from "../../../../prisma/prisma-types";
import { prisma } from "../../../../prisma/prisma-client";
import { networkContext } from "../../../network/network-context.service";
import { prismaBulkExecuteOperations } from "../../../../prisma/prisma-util";
import { PrismaPoolAprItemGroup } from "@prisma/client";
import { TokenApr } from "./ib-yield-apr-handlers/types";
import { IbYieldAprHandlers } from "./ib-yield-apr-handlers/ib-yield-apr-handlers";

export class IbTokensAprService implements PoolAprService {

constructor(private readonly ibYieldAprHandlers: IbYieldAprHandlers) {}

getAprServiceName(): string {
return "IbTokensAprService";
}

public async updateAprForPools(pools: PrismaPoolWithExpandedNesting[]): Promise<void> {
const operations: any[] = [];
const aprs = await this.fetchYieldTokensApr();
const tokenYieldPools = pools.filter((pool) => {
return pool.tokens.find((token) => {
return Array.from(aprs.keys()).map((key) => key.toLowerCase()).includes(token.address.toLowerCase());
})
}
);
for (const pool of tokenYieldPools) {
for (const token of pool.tokens) {
if ((aprs.get(token.address) !== undefined)) {
const tokenSymbol = token.token.symbol;
const itemId = `${ pool.id }-${ tokenSymbol }-yield-apr`

operations.push(prisma.prismaPoolAprItem.upsert({
where: { id_chain: { id: itemId, chain: networkContext.chain } },
create: {
id: itemId,
chain: networkContext.chain,
poolId: pool.id,
title: `${ tokenSymbol } APR`,
apr: aprs.get(token.address)?.val ?? 0,
group: (aprs.get(token.address)?.group as PrismaPoolAprItemGroup) ?? null,
type: pool.type === 'LINEAR' ? 'LINEAR_BOOSTED' : 'IB_YIELD',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is not enough, you'll need to check if it is the wrapped token when it is in a linear pool. only then it is linear_boosted, otherwise its IB_yield. E.g. for a wsteth/rfWSTETH linear pool

},
update: {
title: `${ tokenSymbol } APR`,
apr: aprs.get(token.address)?.val
},
}));
}
}
}

await prismaBulkExecuteOperations(operations);
}

private async fetchYieldTokensApr(): Promise<Map<string, TokenApr>> {
const data = await this.ibYieldAprHandlers.getHandlersAprs()
return new Map<string, TokenApr>(
data.filter((apr) => !isNaN(apr.val)).map((apr) => [apr.address, apr])
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AprHandler, TokenApr } from "./types";
import { aprHandlers } from "./sources";

export class IbYieldAprHandlers {

private handlers: AprHandler[] = [];
constructor(handlers: AprHandler[]) {
this.handlers = handlers;
}

getHandlersAprs = async (): Promise<TokenApr[]> => {
const aprPromises = this.handlers.map(async (handler) => {
const fetchedResponse: { [key: string]: number } = await handler.getAprs()
return Object.entries(fetchedResponse).map(([address, aprValue]) => ({
val: aprValue,
group: handler.group,
address
}))
});
const res = Array(this.handlers.length)
for (const [index, aprPromise] of aprPromises.entries()) {
res[index] = await aprPromise
}
return res.flat();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a proper function of this class


export const ibYieldAprHandlers = new IbYieldAprHandlers(aprHandlers);
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import axios from "axios";
import { ReserveResponse } from "./types";
import {
aaveTokensV2Mainnet,
aaveTokensV2Polygon,
aaveTokensV3Arbitrum,
aaveTokensV3Mainnet,
aaveTokensV3Polygon, underlyingTokensArbitrum,
underlyingTokensMainnet,
underlyingTokensPolygon,
wrappedAaveTokensV2Mainnet,
wrappedAaveTokensV2Polygon,
wrappedAaveTokensV3Arbitrum,
wrappedAaveTokensV3Mainnet,
wrappedAaveTokensV3Polygon
} from "./tokens";
import { AprHandler } from "../../types";

class AaveAprHandler implements AprHandler {

wrappedTokens: Map<string, string>
aaveTokens: Map<string, string>
underlyingTokens: Map<string, string>
subgraphUrl: string

readonly group = 'AAVE';

readonly query = `query getReserves($aTokens: [String!], $underlyingAssets: [Bytes!]) {
reserves(
where: {
aToken_in: $aTokens
underlyingAsset_in: $underlyingAssets
isActive: true
}
) {
id
underlyingAsset
liquidityRate
}
}`

constructor(wrappedTokens: Map<string, string>,
aaveTokens: Map<string, string>,
underlyingTokens: Map<string, string>,
subgraphUrl: string
) {
this.wrappedTokens = wrappedTokens
this.aaveTokens = aaveTokens
this.underlyingTokens = underlyingTokens
this.subgraphUrl = subgraphUrl
}

async getAprs() {
try {
const requestQuery = {
operationName: 'getReserves',
query: this.query,
variables: {
aTokens: Array.from(this.aaveTokens.values()),
underlyingAssets: Array.from(this.underlyingTokens.values()),
},
}
const { data } = await axios({
url: this.subgraphUrl,
method: "post",
data: requestQuery,
headers: { "Content-Type": "application/json" }
})
const {
data: { reserves },
} = data as ReserveResponse

const aprsByUnderlyingAddress = Object.fromEntries(reserves.map((r) => [
r.underlyingAsset,
// Note: our assumption is frontend usage, this service is not a good source where more accuracy is needed.
// Converting from aave ray number (27 digits) to bsp
// essentially same as here:
// https://github.com/aave/aave-utilities/blob/master/packages/math-utils/src/formatters/reserve/index.ts#L231
Number(r.liquidityRate.slice(0, 27)) / 1e27,
]))
const aprEntries = Object.fromEntries(
Array.from(this.underlyingTokens.entries())
//Removing undefined aprs
.filter(([, address]) => !!aprsByUnderlyingAddress[address])
//Mapping aprs by wrapped instead of underlying addresses
.map(([underlyingTokenName, underlyingTokenAddress]) => [
this.wrappedTokens.get('wa' + underlyingTokenName) as string,
aprsByUnderlyingAddress[underlyingTokenAddress],
]))
return aprEntries;
} catch (e) {
console.error(`Failed to fetch Aave APR in subgraph ${ this.subgraphUrl }:`, e)
return {}
}
}
}

export const aaveV2MainnetAprHandler = new AaveAprHandler(wrappedAaveTokensV2Mainnet, aaveTokensV2Mainnet, underlyingTokensMainnet, 'https://api.thegraph.com/subgraphs/name/aave/protocol-v2')
export const aaveV2PolygonAprHandler = new AaveAprHandler(wrappedAaveTokensV2Polygon, aaveTokensV2Polygon, underlyingTokensPolygon, 'https://api.thegraph.com/subgraphs/name/aave/aave-v2-matic')
export const aaveV3MainnetAprHandler = new AaveAprHandler(wrappedAaveTokensV3Mainnet, aaveTokensV3Mainnet, underlyingTokensMainnet, 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3')
export const aaveV3PolygonAprHandler = new AaveAprHandler(wrappedAaveTokensV3Polygon, aaveTokensV3Polygon, underlyingTokensPolygon, 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3-polygon');
export const aaveV3ArbitrumAprHandler = new AaveAprHandler(wrappedAaveTokensV3Arbitrum, aaveTokensV3Arbitrum, underlyingTokensArbitrum, 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3-arbitrum')

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
export const wrappedAaveTokensV2Mainnet = new Map<string, string>(Object.entries({
waUSDT: '0xf8fd466f12e236f4c96f7cce6c79eadb819abf58',
waUSDC: '0xd093fa4fb80d09bb30817fdcd442d4d02ed3e5de',
waDAI: '0x02d60b84491589974263d922d9cc7a3152618ef6',
}));

export const aaveTokensV2Mainnet = new Map<string, string>(Object.entries({
aUSDT: '0x3ed3b47dd13ec9a98b44e6204a523e766b225811',
aUSDC: '0xbcca60bb61934080951369a648fb03df4f96263c',
aDAI: '0x028171bca77440897b824ca71d1c56cac55b68a3',
}));

export const wrappedAaveTokensV2Polygon = new Map<string, string>(Object.entries({
waUSDT: '0x19c60a251e525fa88cd6f3768416a8024e98fc19',
waUSDC: '0x221836a597948dce8f3568e044ff123108acc42a',
waDAI: '0xee029120c72b0607344f35b17cdd90025e647b00',
}));

export const aaveTokensV2Polygon = new Map<string, string>(Object.entries({
aUSDT: '0x60d55f02a771d515e077c9c2403a1ef324885cec',
aUSDC: '0x1a13f4ca1d028320a707d99520abfefca3998b7f',
aDAI: '0x27f8d03b3a2196956ed754badc28d73be8830a6e',
}));

export const wrappedAaveTokensV3Mainnet = new Map<string, string>(Object.entries({
waUSDT: '0xa7e0e66f38b8ad8343cff67118c1f33e827d1455',
waUSDC: '0x57d20c946a7a3812a7225b881cdcd8431d23431c',
waDAI: '0x098256c06ab24f5655c5506a6488781bd711c14b',
waWETH: '0x59463bb67ddd04fe58ed291ba36c26d99a39fbc6',
}));

export const aaveTokensV3Mainnet = new Map<string, string>(Object.entries({
aUSDT: '0x23878914efe38d27c4d67ab83ed1b93a74d4086a',
aUSDC: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c',
aDAI: '0x018008bfb33d285247a21d44e50697654f754e63',
aWETH: '0x4d5f47fa6a74757f35c14fd3a6ef8e3c9bc514e8'
}));


export const wrappedAaveTokensV3Polygon = new Map<string, string>(Object.entries({
waMATIC: '0x0d6135b2cfbae3b1c58368a93b855fa54fa5aae1',
waUSDT: '0x7c76b6b3fe14831a39c0fec908da5f17180df677',
waUSDC: '0x9719d867a500ef117cc201206b8ab51e794d3f82',
waDAI: '0x27f8d03b3a2196956ed754badc28d73be8830a6e',
waWETH: '0xa5bbf0f46b9dc8a43147862ba35c8134eb45f1f5',
}));

export const aaveTokensV3Polygon = new Map<string, string>(Object.entries({
aMATIC: '0x6d80113e533a2c0fe82eabd35f1875dcea89ea97',
aUSDT: '0x60d55f02a771d515e077c9c2403a1ef324885cec',
aUSDC: '0x1a13f4ca1d028320a707d99520abfefca3998b7f',
aDAI: '0x27f8d03b3a2196956ed754badc28d73be8830a6e',
aWETH: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8',
}));


export const wrappedAaveTokensV3Arbitrum = new Map<string, string>(Object.entries({
waUSDT: '0x3c7680dfe7f732ca0279c39ff30fe2eafdae49db',
waUSDC: '0xe719aef17468c7e10c0c205be62c990754dff7e5',
waDAI: '0x345a864ac644c82c2d649491c905c71f240700b2',
waWETH: '0x18c100415988bef4354effad1188d1c22041b046'
}));

export const aaveTokensV3Arbitrum = new Map<string, string>(Object.entries({
aUSDT: '0x6ab707aca953edaefbc4fd23ba73294241490620',
aUSDC: '0x625e7708f30ca75bfd92586e17077590c60eb4cd',
aDAI: '0x82e64f49ed5ec1bc6e43dad4fc8af9bb3a2312ee',
aWETH: '0xe50fa9b3c56ffb159cb0fca61f5c9d750e8128c8',
}));
export const underlyingTokensMainnet = new Map<string, string>(Object.entries({
USDT: '0xdac17f958d2ee523a2206206994597c13d831ec7',
USDC: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
DAI: '0x6b175474e89094c44da98b954eedeac495271d0f',
WETH: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
}));
export const underlyingTokensPolygon = new Map<string, string>(Object.entries({
MATIC: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270',
USDT: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
USDC: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174',
DAI: '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063',
WETH: '0x7ceb23fd6bc0add59e62ac25578270cff1b9f619'
}));

export const underlyingTokensArbitrum = new Map<string, string>(Object.entries({
USDT: '0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9',
USDC: '0xff970a61a04b1ca14834a43f5de4533ebddb5cc8',
DAI: '0xda10009cbd5d07dd0cecc66161fc93d7c9000da1',
WETH: '0x82af49447d8a07e3bd95bd0d56f35241523fbab1',
}));

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface ReserveResponse {
data: {
reserves: [
{
underlyingAsset: string
liquidityRate: string
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const abi = [
{
inputs: [],
name: "borrowRatePerTimestamp",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256"
}
],
stateMutability: "view",
type: "function"
}
] as const
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const abi = [
{
inputs: [
{
internalType: 'int256',
name: '_n',
type: 'int256',
},
],
name: 'averageAPRAcrossLastNHarvests',
outputs: [
{
internalType: 'int256',
name: '',
type: 'int256',
},
],
stateMutability: 'view',
type: 'function',
},
] as const
Loading