Skip to content

Commit

Permalink
Merge pull request #33 from strkfarm/base-aprs
Browse files Browse the repository at this point in the history
Base aprs [Ekubo]
  • Loading branch information
akiraonstarknet authored May 19, 2024
2 parents ed899c2 + 6791f62 commit 1b073c3
Show file tree
Hide file tree
Showing 4 changed files with 655 additions and 16 deletions.
2 changes: 1 addition & 1 deletion src/components/Pools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export default function Pools() {
return (
<Flex width={'100%'} key={split.title}>
<Text key="1" width={'70%'}>
{split.title}:
{split.title} {split.description ? `(${split.description})` : ''}
</Text>
<Text width={'30%'} textAlign={'right'} key="2">
{split.apr === 'Err' ? split.apr : (split.apr * 100).toFixed(2)}
Expand Down
22 changes: 22 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const CONSTANTS = {
EKUBO: {
CLAIMS_URL:
'/ekubo/airdrops/{{address}}?token=0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
BASE_APR_API: '/ekubo/pair',
BASE_PRICE_API: '/ekubo/price',
},
CONTRACTS: {
Master: '0x50314707690c31597849ed66a494fb4279dc060f8805f21593f52906846e28e',
Expand All @@ -41,6 +43,16 @@ const CONSTANTS = {
};

export const TOKENS: TokenInfo[] = [
{
token: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7',
name: 'ETH',
decimals: 18,
displayDecimals: 2,
logo: CONSTANTS.LOGOS.ETH,
minAmount: MyNumber.fromEther('10', 18),
maxAmount: MyNumber.fromEther('10000', 18),
stepAmount: MyNumber.fromEther('10', 18),
},
{
token: '0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
name: 'STRK',
Expand Down Expand Up @@ -81,6 +93,16 @@ export const TOKENS: TokenInfo[] = [
maxAmount: MyNumber.fromEther('10000', 6),
stepAmount: MyNumber.fromEther('10', 6),
},
{
token: '0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8',
name: 'USDT',
decimals: 6,
displayDecimals: 2,
logo: CONSTANTS.LOGOS.USDT,
minAmount: MyNumber.fromEther('10', 6),
maxAmount: MyNumber.fromEther('10000', 6),
stepAmount: MyNumber.fromEther('10', 6),
},
{
token: '0x047ad51726d891f972e74e4ad858a261b43869f7126ce7436ee0b2529a98f486',
name: 'zUSDC',
Expand Down
182 changes: 173 additions & 9 deletions src/store/ekobu.store.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,78 @@
'use client';

import CONSTANTS, { TokenName } from '@/constants';
import CONSTANTS, { TOKENS, TokenName } from '@/constants';
import {
APRSplit,
Category,
PoolInfo,
PoolMetadata,
PoolType,
ProtocolAtoms,
StrkDexIncentivesAtom,
} from './pools';
import { atom } from 'jotai';
import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query';
import { TokenInfo } from '@/strategies/IStrategy';
import { IDapp } from './IDapp.store';
const fetcher = (...args: any[]) => {
return fetch(args[0], args[1]).then((res) => res.json());
};

export class Ekubo {
interface EkuboBaseAprDoc {
[key: string]: Pool[];
}

interface TokenPrice {
timestamp: string;
price: string;
}

interface Pool {
fee: string;
tick_spacing: number;
extension: string;
volume0_24h: string;
volume1_24h: string;
fees0_24h: string;
fees1_24h: string;
tvl0_total: string;
tvl1_total: string;
tvl0_delta_24h: string;
tvl1_delta_24h: string;
price0: string;
price1: string;
decimals0: number;
decimals1: number;
}

interface PoolsData {
topPools: Pool[];
}

type IndexedTokenPrices = Record<string, TokenPrice>;
type IndexedPools = Record<string, Pool[]>;

const POOL_NAMES: string[] = ['STRK/USDC', 'STRK/ETH', 'ETH/USDC', 'USDC/USDT'];
const PRICE_PAIRS: string[] = [
'STRK/USDC',
'ETH/USDC',
'USDC/USDC',
'USDT/USDC',
];

export class Ekubo extends IDapp<EkuboBaseAprDoc> {
name = 'Ekubo';
link = 'https://app.ekubo.org/positions';
logo = 'https://app.ekubo.org/logo.svg';

incentiveDataKey = 'Ekubo';

_computePoolsInfo(data: any) {
try {
const myData = data[this.incentiveDataKey];
if (!myData) return [];
const pools: PoolInfo[] = [];

Object.keys(myData)
.filter(this.commonVaultFilter)
.forEach((poolName) => {
Expand Down Expand Up @@ -52,11 +101,6 @@ export class Ekubo {
apr: arr[arr.length - 1].apr,
tvl: arr[arr.length - 1].tvl_usd,
aprSplits: [
{
apr: 0,
title: 'Base APR',
description: 'Subject to position range',
},
{
apr: arr[arr.length - 1].apr,
title: 'STRK rewards',
Expand Down Expand Up @@ -98,15 +142,135 @@ export class Ekubo {
// return !poolName.includes('DAI') && !poolName.includes('WSTETH') && !poolName.includes('BTC');
return supportedPools.includes(poolName);
}

getBaseAPY(p: PoolInfo, data: AtomWithQueryResult<EkuboBaseAprDoc, Error>) {
let baseAPY: number | 'Err' = 'Err';
let splitApr: APRSplit | null = null;
const metadata: PoolMetadata | null = null;
if (data.isSuccess) {
const poolName = p.pool.name;
const pools: Pool[] = data.data[poolName];

if (pools.length) {
const baseAPRs: number[] = pools.map((pool) => {
const fees0 =
(parseInt(pool.fees0_24h, 10) * parseFloat(pool.price0)) /
10 ** pool.decimals0;
const fees1 =
(parseInt(pool.fees1_24h, 10) * parseFloat(pool.price1)) /
10 ** pool.decimals1;
const tvl0 =
(parseInt(pool.tvl0_total, 10) * parseFloat(pool.price0)) /
10 ** pool.decimals0;
const tvl1 =
(parseInt(pool.tvl1_total, 10) * parseFloat(pool.price1)) /
10 ** pool.decimals1;

return 365 * ((fees0 + fees1) / (tvl0 + tvl1));
});

baseAPY = Math.max(...baseAPRs);
} else {
baseAPY = 0;
}

splitApr = {
apr: baseAPY,
title: 'Base APR',
description: 'Subject to position range',
};
}

return {
baseAPY,
splitApr,
metadata,
};
}
}

const fetchData = async <T>(
items: string[],
apiPath: keyof typeof CONSTANTS.EKUBO,
tokensMetadata: Record<string, TokenInfo>,
processResponse: (
item: string,
data: any,
tokensMetadata: Record<string, TokenInfo>,
) => [string, T],
): Promise<Record<string, T>> => {
const responses = await Promise.all(
items.map(async (item) => {
const [token0Name, token1Name] = item.split('/');
const token0 = tokensMetadata[token0Name];
const token1 = tokensMetadata[token1Name];

const response = await fetch(
`${CONSTANTS.EKUBO[apiPath]}/${token0.token}/${token1.token}`,
);
const data = await response.json();

return processResponse(item, data, tokensMetadata);
}),
);

return Object.fromEntries(responses);
};

export const ekubo = new Ekubo();
const EkuboAtoms: ProtocolAtoms = {
baseAPRs: atomWithQuery((get) => ({
queryKey: ['ekubo_base_aprs'],
queryFn: async ({ queryKey }) => {
const tokensMetadata: Record<string, TokenInfo> = Object.fromEntries(
TOKENS.map((token) => [token.name, token]),
);

const indexedTokenPrices: IndexedTokenPrices = await fetchData(
PRICE_PAIRS,
'BASE_PRICE_API',
tokensMetadata,
(pair, priceData) => [pair.split('/')[0], priceData],
);

const indexedPools: IndexedPools = await fetchData(
POOL_NAMES,
'BASE_APR_API',
tokensMetadata,
(poolName, poolsData: PoolsData) => {
const [token0Name, token1Name] = poolName.split('/');
const filterResponseData = poolsData.topPools
.filter(
(pool) =>
pool.fees0_24h !== '0' &&
pool.fees1_24h !== '0' &&
pool.tvl0_total !== '0' &&
pool.tvl1_total !== '0',
)
.map((pool) => ({
...pool,
price0: indexedTokenPrices[token0Name].price,
price1: indexedTokenPrices[token1Name].price,
decimals0: tokensMetadata[token0Name].decimals,
decimals1: tokensMetadata[token1Name].decimals,
}));

return [poolName, filterResponseData];
},
);
return indexedPools;
},
})),
pools: atom((get) => {
const poolsInfo = get(StrkDexIncentivesAtom);
const empty: PoolInfo[] = [];
console.log('ekubo', poolsInfo);
if (poolsInfo.data) return ekubo._computePoolsInfo(poolsInfo.data);
if (!EkuboAtoms.baseAPRs) return empty;
const baseInfo = get(EkuboAtoms.baseAPRs);
if (poolsInfo.data) {
const pools = ekubo._computePoolsInfo(poolsInfo.data);
return ekubo.addBaseAPYs(pools, baseInfo);
}

return empty;
}),
};
Expand Down
Loading

0 comments on commit 1b073c3

Please sign in to comment.