Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
yarn run lint:fix && yarn run format:fix && git add .
#!/bin/sh

# Get list of staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)

# Run lint and format
yarn run lint:fix && yarn run format:fix

# Re-add only the files that were originally staged
echo "$STAGED_FILES" | xargs -r git add
6 changes: 5 additions & 1 deletion next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ const nextConfig = {
{
source: '/api/price/:path*',
destination: 'https://cache-server-t2me.onrender.com/api/price/:path*',
}
},
{
source: '/endur/:path*',
destination: 'https://app.endur.fi/:path*',
},
];
},
async redirects() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"react-share": "5.1.0",
"sharp": "0.33.4",
"starknet": "8.5.2",
"starknetkit": "^3.0.3",
"starknetkit": "3.3.0",
"swr": "2.2.5",
"wonka": "6.3.4"
},
Expand Down
6 changes: 2 additions & 4 deletions src/app/api/get-calls/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from 'next/server';
import { RpcProvider } from 'starknet';
import { getStrategies } from '@/store/strategies.atoms';
import MyNumber from '@/utils/MyNumber';
import {
Expand All @@ -8,13 +7,12 @@ import {
Quote,
} from '@avnu/avnu-sdk';
import { TOKENS } from '@/constants';
import { getProvider } from '@/lib/provider';

export const revalidate = 0;
export const dynamic = 'force-dynamic';

const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL || 'https://starknet-mainnet.public.blastapi.io',
});
const provider = getProvider();

interface GetCallsRequest {
strategyId: string;
Expand Down
40 changes: 28 additions & 12 deletions src/app/api/lib.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
import { TrovesStrategyAPIResult } from '@/store/troves.atoms';
import { UniversalStrategies } from '@strkfarm/sdk';
import { Redis } from '@upstash/redis';
import { Contract, RpcProvider } from 'starknet';
import { Contract } from 'starknet';
import { getProvider } from '@/lib/provider';

const kvRedis = new Redis({
url: process.env.VK_REDIS_KV_REST_API_URL,
token: process.env.VK_REDIS_KV_REST_API_TOKEN,
});
let kvRedis: Redis | null = null;

function getRedisClient() {
if (
!process.env.VK_REDIS_KV_REST_API_URL ||
!process.env.VK_REDIS_KV_REST_API_TOKEN
) {
return null;
}

if (!kvRedis) {
kvRedis = new Redis({
url: process.env.VK_REDIS_KV_REST_API_URL,
token: process.env.VK_REDIS_KV_REST_API_TOKEN,
});
}

return kvRedis;
}

export async function getDataFromRedis(
key: string,
Expand All @@ -18,11 +34,12 @@ export async function getDataFromRedis(
return null;
}

if (!process.env.VK_REDIS_KV_REST_API_URL) {
const redis = getRedisClient();
if (!redis) {
return null;
}

const cacheData: any = await kvRedis.get(key);
const cacheData: any = await redis.get(key);
if (
cacheData &&
new Date().getTime() - new Date(cacheData.lastUpdated).getTime() <
Expand All @@ -36,11 +53,12 @@ export async function getDataFromRedis(
}

export async function setDataToRedis(key: string, data: any) {
if (!process.env.VK_REDIS_KV_REST_API_URL) {
const redis = getRedisClient();
if (!redis) {
return;
}

await kvRedis.set(key, data);
await redis.set(key, data);
console.log(`Cache set for ${key}`);
}

Expand Down Expand Up @@ -96,9 +114,7 @@ export const getRewardsInfo = async (
};
});

const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL!,
});
const provider = getProvider();

const rewardsInfo: {
id: string;
Expand Down
18 changes: 9 additions & 9 deletions src/app/api/stats/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getStrategies } from '@/store/strategies.atoms';
import { NextResponse } from 'next/server';
import { getDataFromRedis } from '../lib';
import { getDataFromRedis, setDataToRedis } from '../lib';
import { getStrategies } from '@/store/strategies.atoms';

export const revalidate = 1800;
export const dynamic = 'force-dynamic';
Expand All @@ -20,18 +20,16 @@ export async function GET(_req: Request) {

const strategies = getStrategies();

console.log('strategies', strategies.length);

const values = strategies.map(async (strategy, index) => {
if (strategy.isLive()) {
let retry = 0;
while (retry < 3) {
try {
const tvlInfo = await strategy.getTVL();
console.log('tvlInfo', index, tvlInfo);
console.log('[Stats] tvlInfo', index, tvlInfo);
return tvlInfo.usdValue;
} catch (e) {
console.log(e);
console.log('[Stats] Error fetching TVL:', e);
if (retry < 3) {
await new Promise((resolve) => setTimeout(resolve, 1000));
retry++;
Expand All @@ -47,14 +45,16 @@ export async function GET(_req: Request) {

const result = await Promise.all(values);

const response = NextResponse.json({
const data = {
tvl: result.reduce((a, b) => a + b, 0),
lastUpdated: new Date().toISOString(),
});
};

await setDataToRedis(REDIS_KEY, data);
const response = NextResponse.json(data);
response.headers.set(
'Cache-Control',
`s-maxage=${revalidate}, stale-while-revalidate=180`,
`s-maxage=${revalidate}, stale-while-revalidate=300`,
);
return response;
}
103 changes: 44 additions & 59 deletions src/app/api/strategies/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { NextResponse } from 'next/server';
import { atom } from 'jotai';
import { PoolInfo, PoolType } from '@/store/pools';
import { RpcProvider } from 'starknet';
import { getLiveStatusNumber, getStrategies } from '@/store/strategies.atoms';
import MyNumber from '@/utils/MyNumber';
import { IStrategy, NFTInfo, TokenInfo } from '@/strategies/IStrategy';
Expand All @@ -11,6 +10,7 @@ import VesuAtoms, { vesu } from '@/store/vesu.store';
import EndurAtoms, { endur } from '@/store/endur.store';
import { setDataToRedis, getDataFromRedis, getRewardsInfo } from '../lib';
import { DEFAULT_APY_METHODLOGY } from '@/constants';
import { getProvider } from '@/lib/provider';

export const revalidate = 1800; // 30 minutes
export const dynamic = 'force-dynamic';
Expand All @@ -25,8 +25,6 @@ const allPoolsAtom = atom<PoolInfo[]>((get) => {

async function getPools(store: any, retry = 0) {
const allPools: PoolInfo[] | undefined = store.get(allPoolsAtom);

console.log('allPools', allPools?.length);
// undo
const minProtocolsRequired: string[] = [vesu.name, endur.name];
const hasRequiredPools = minProtocolsRequired.every((p) => {
Expand All @@ -51,13 +49,12 @@ async function getPools(store: any, retry = 0) {
return allPools;
}

const provider = new RpcProvider({
nodeUrl: process.env.RPC_URL || 'https://starknet-mainnet.public.blastapi.io',
});
const provider = getProvider();

async function getStrategyInfo(
strategy: IStrategy<any>,
): Promise<TrovesStrategyAPIResult> {
// Get TVL directly from strategy
const tvl = await strategy.getTVL();

const defaultAPYMethodology = DEFAULT_APY_METHODLOGY;
Expand Down Expand Up @@ -97,7 +94,7 @@ async function getStrategyInfo(
logos: strategy.metadata.depositTokens.map((t) => t.logo),
isAudited: strategy.settings.auditUrl ? true : false,
auditUrl: strategy.settings.auditUrl,
actions: strategy.actions.map((action) => {
actions: (strategy.actions || []).map((action) => {
return {
name: action.name || '',
protocol: {
Expand All @@ -106,15 +103,18 @@ async function getStrategyInfo(
},
token: {
name: action.pool.pool.name,
logo: action.pool.pool.logos[0],
logo: action.pool.pool.logos?.[0] || '',
},
amount: action.amount,
isDeposit: action.isDeposit,
apy: action.isDeposit ? action.pool.apr : -action.pool.borrow.apr,
apy: action.isDeposit
? action.pool.apr
: -(action.pool.borrow?.apr || 0),
};
}),
investmentFlows: strategy.investmentFlows,
curator: strategy.metadata.curator,
tags: strategy.settings.tags || [],
};

const rewardsInfo = await getRewardsInfo([
Expand All @@ -135,80 +135,65 @@ const REDIS_KEY = `${process.env.VK_REDIS_PREFIX}::strategies`;

export async function GET(req: Request) {
console.log('GET /api/strategies', req.url);
const cacheData = await getDataFromRedis(REDIS_KEY, req.url, revalidate);
if (cacheData) {
const resp = NextResponse.json(cacheData);
resp.headers.set(
'Cache-Control',
`s-maxage=${revalidate}, stale-while-revalidate=300`,
);
return resp;
}

const allPools = await getPools(MY_STORE);
const strategies = getStrategies();
try {
const cacheData = await getDataFromRedis(REDIS_KEY, req.url, revalidate);
if (cacheData) {
const resp = NextResponse.json(cacheData);
resp.headers.set(
'Cache-Control',
`s-maxage=${revalidate}, stale-while-revalidate=300`,
);
return resp;
}

const proms = strategies.map((strategy) => {
if (!strategy.isLive()) return;
return strategy.solve(allPools, '1000');
});
const allPools = await getPools(MY_STORE);
const strategies = getStrategies();

await Promise.all(proms);
// strategies.forEach((strategy) => {
// try {
// strategy.solve(allPools, '1000');
// } catch (err) {
// console.error('Error solving strategy', strategy.name, err);
// }
// });

const stratsDataProms: Promise<TrovesStrategyAPIResult>[] = [];
for (let i = 0; i < strategies.length; i++) {
stratsDataProms.push(getStrategyInfo(strategies[i]));
}
const stratsData = await Promise.all(stratsDataProms);

const _strats = stratsData.sort((a, b) => {
// sort based on risk factor, live status and apy
// const aRisk = a.riskFactor;
// const bRisk = b.riskFactor;

// Priority: status < 5 (priority 0), then status 5 (priority 1), then others (priority 2)
// console.log('statusNumber', a.status, b.status, a.name, b.name);
const getPriority = (statusNumber: number) => {
if (statusNumber < 5) return 0;
if (statusNumber === 5) return 1;
return 2;
};
const proms = strategies.map((strategy) => {
if (!strategy.isLive()) return;
return strategy.solve(allPools, '1000');
});

const aPriority = getPriority(a.status.number);
const bPriority = getPriority(b.status.number);
await Promise.all(proms);

// if (aPriority !== bPriority) return aPriority - bPriority;
// if (aRisk !== bRisk) return aRisk - bRisk;
return b.apy - a.apy;
});
const stratsDataProms: Promise<TrovesStrategyAPIResult>[] = [];
for (let i = 0; i < strategies.length; i++) {
stratsDataProms.push(getStrategyInfo(strategies[i]));
}
const stratsData = await Promise.all(stratsDataProms);

const _strats = stratsData.sort((a, b) => {
return b.apy - a.apy;
});

try {
const data = {
status: true,
strategies: _strats,
lastUpdated: new Date().toISOString(),
};

await setDataToRedis(REDIS_KEY, data);

const response = NextResponse.json(data);
response.headers.set(
'Cache-Control',
`s-maxage=${revalidate}, stale-while-revalidate=300`,
);
return response;
} catch (err) {
console.error('Error /api/strategies', err);
console.error('Error /api/strategies:', err);
console.error(
'Error stack:',
err instanceof Error ? err.stack : 'No stack',
);

const errorResponse = NextResponse.json(
{
status: false,
strategies: [],
lastUpdated: new Date().toISOString(),
error: err instanceof Error ? err.message : 'Internal server error',
},
{ status: 500 },
);
Expand Down
Loading
Loading