From 94685898c485c933488016dc81abb6936495135e Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sat, 14 Sep 2024 19:28:10 +0100 Subject: [PATCH 1/3] feat: handle api errors gracefully --- package.json | 3 +- src/components/TncModal.tsx | 10 ++++- src/components/TxButton.tsx | 11 ++++- src/store/carmine.store.ts | 43 ++++++++++++++----- src/store/ekobu.store.ts | 44 +++++++++++++------ src/store/haiko.store.ts | 8 +++- src/store/jedi.store.ts | 15 ++++--- src/store/myswap.store.ts | 30 ++++++++----- src/store/pools.ts | 50 ++++++++++++++++------ src/store/utils.atoms.ts | 36 +++++++++++----- src/strategies/auto_strk.strat.ts | 69 +++++++++++++++++++----------- src/strategies/delta_neutral_mm.ts | 46 +++++++++++++------- 12 files changed, 260 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 11f801e1..9b7dafee 100755 --- a/package.json +++ b/package.json @@ -86,5 +86,6 @@ }, "engines": { "node": "20.x" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/components/TncModal.tsx b/src/components/TncModal.tsx index 3545134b..9dcf778f 100644 --- a/src/components/TncModal.tsx +++ b/src/components/TncModal.tsx @@ -15,6 +15,7 @@ import { import { useAccount, useSignTypedData } from '@starknet-react/core'; import axios from 'axios'; +import toast from 'react-hot-toast'; const exampleData = { types: { @@ -103,10 +104,15 @@ const TncModal: React.FC = ({ isOpen, onClose }) => { if (res && res?.toString().length > 0) { onClose(); - await axios.post('/api/tnc/signUser', { + try { + await axios.post('/api/tnc/signUser', { address, message: res?.toString(), - }); + }); + } catch (error: any) { + console.error('Error signing user: ', error); + toast.error('Error signing user: ', error.message); + } } }; diff --git a/src/components/TxButton.tsx b/src/components/TxButton.tsx index b60e14c9..4b9ecaef 100755 --- a/src/components/TxButton.tsx +++ b/src/components/TxButton.tsx @@ -23,6 +23,7 @@ import mixpanel from 'mixpanel-browser'; import { usePathname } from 'next/navigation'; import { useEffect, useMemo, useState } from 'react'; import { isMobile } from 'react-device-detect'; +import toast from 'react-hot-toast'; import { TwitterShareButton } from 'react-share'; import { Call } from 'starknet'; @@ -127,7 +128,8 @@ export default function TxButton(props: TxButtonProps) { const getUser = async () => { if (props.buttonText === 'Deposit') { - const data = await axios.get(`/api/tnc/getUser/${address}`); + try { + const data = await axios.get(`/api/tnc/getUser/${address}`); if ( (data.data.user.isTncSigned && @@ -138,7 +140,12 @@ export default function TxButton(props: TxButtonProps) { return true; } - return false; + return false; + } catch (error: any) { + console.error('Error fetching user: ', error); + toast.error('Error fetching user: ', error.message); + return false; + } } }; diff --git a/src/store/carmine.store.ts b/src/store/carmine.store.ts index 525fe588..e352a586 100755 --- a/src/store/carmine.store.ts +++ b/src/store/carmine.store.ts @@ -6,6 +6,19 @@ import { Jediswap } from './jedi.store'; import { atomWithQuery } from 'jotai-tanstack-query'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +type CarminePoolData = { + week: number; + week_annualized: number; + launch: number; + launch_annualized: number; +}; + +type CarmineAPRData = { + tvl: number; + allocation: number; + apy: number; +}; + const poolConfigs = [ { name: 'STRK/USDC Call Pool (STRK)', tokenA: 'STRK', tokenB: 'USDC' }, { name: 'STRK/USDC Put Pool (USDC)', tokenA: 'STRK', tokenB: 'USDC' }, @@ -122,18 +135,28 @@ const poolEndpoints = [ export const CarmineAtom = atomWithQuery((get) => ({ queryKey: ['isCarmine'], queryFn: async ({ queryKey }) => { - const fetchPool = async (endpoint: any) => { - const res = await fetch(`${CONSTANTS.CARMINE_URL}/${endpoint}/apy`); - let data = await res.text(); - data = data.replaceAll('NaN', '0'); - return JSON.parse(data); + const fetchPool = async (endpoint: string): Promise<{data: CarminePoolData}> => { + try { + const res = await fetch(`${CONSTANTS.CARMINE_URL}/${endpoint}/apy`); + let data = await res.text(); + data = data.replaceAll('NaN', '0'); + return JSON.parse(data); + } catch (error) { + console.error(`Error fetching pool data for endpoint ${endpoint}:`, error); + return { data: { week_annualized: 0, week: 0, launch_annualized: 0, launch: 0 } }; + } }; - const fetchRewardApr = async () => { - const res = await fetch(CONSTANTS.CARMINE_INCENTIVES_URL); - let data = await res.text(); - data = data.replaceAll('NaN', '0'); - return JSON.parse(data); + const fetchRewardApr = async (): Promise<{data: CarmineAPRData}> => { + try { + const res = await fetch(CONSTANTS.CARMINE_INCENTIVES_URL); + let data = await res.text(); + data = data.replaceAll('NaN', '0'); + return JSON.parse(data); + } catch (error) { + console.error('Error fetching reward APR data:', error); + return { data: { apy: 0, tvl: 0, allocation: 0 } }; + } }; const rewardAprData = await fetchRewardApr(); diff --git a/src/store/ekobu.store.ts b/src/store/ekobu.store.ts index 792bd2a5..ed2d9ba9 100755 --- a/src/store/ekobu.store.ts +++ b/src/store/ekobu.store.ts @@ -13,10 +13,6 @@ import { } from './pools'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; -const _fetcher = async (...args: any[]) => { - return fetch(args[0], args[1]).then((res) => res.json()); -}; - interface EkuboBaseAprDoc { tokens: Token[]; defiSpringData: DefiSpringData; @@ -349,28 +345,52 @@ const getData = async (): Promise => { ] = await Promise.all([ fetch(`${CONSTANTS.EKUBO.BASE_API}/tokens`).then((response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching ekubo tokens', err); + return []; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/defi-spring-incentives`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching ekubo defi spring incentives', err); + return { strkPrice: 0, totalStrk: 0, pairs: [] }; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/overview/pairs`).then((response) => response.json(), - ), + ).catch(err => { + console.error('Error fetching ekubo pair overview: ', err) + return { topPairs: [] } + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH?period=21600`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching ETH prices', err); + return { timestamp: 0, prices: [] }; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK?period=21600`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching STRK prices', err); + return { timestamp: 0, prices: [] }; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/price/USDC?period=21600`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching USDC prices', err); + return { timestamp: 0, prices: [] }; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK/USDC?period=21600`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching STRK/USDC price', err); + return { timestamp: 0, price: 0 }; + }), fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH/USDC?period=21600`).then( (response) => response.json(), - ), + ).catch((err) => { + console.error('Error fetching ETH/USDC price', err); + return { timestamp: 0, price: 0 }; + }), ]); return { diff --git a/src/store/haiko.store.ts b/src/store/haiko.store.ts index 8483a789..dfb9116c 100755 --- a/src/store/haiko.store.ts +++ b/src/store/haiko.store.ts @@ -187,13 +187,19 @@ export const haiko = new Haiko(); const HaikoAtoms: ProtocolAtoms = { baseAPRs: atomWithQuery((get) => ({ queryKey: ['haiko_base_aprs'], - queryFn: async ({ queryKey }) => { + queryFn: async ({ queryKey }): Promise => { + try{ const response = await fetch(`${CONSTANTS.HAIKO.BASE_APR_API}`); const data = await response.json(); return data; + } catch(error){ + console.error('Error fetching haiko pools: ', error); + return []; + } }, })), + pools: atom((get) => { const poolsInfo = get(StrkDexIncentivesAtom); const empty: PoolInfo[] = []; diff --git a/src/store/jedi.store.ts b/src/store/jedi.store.ts index fac01afa..f1d61815 100755 --- a/src/store/jedi.store.ts +++ b/src/store/jedi.store.ts @@ -147,15 +147,20 @@ async function getVolumes(block: number) { variables: {}, }); - const res = await fetch(CONSTANTS.JEDI.BASE_API, { - method: 'POST', + try{ + const res = await fetch(CONSTANTS.JEDI.BASE_API, { + method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, - body: data, - }); - return res.json(); + body: data, + }); + return res.json(); + } catch (error) { + console.error('Error fetching jedi volumes: ', error); + return { data: { pairs: [] } }; + } } export const jedi = new Jediswap(); diff --git a/src/store/myswap.store.ts b/src/store/myswap.store.ts index bcf4d8c0..4c412122 100755 --- a/src/store/myswap.store.ts +++ b/src/store/myswap.store.ts @@ -181,10 +181,15 @@ export class MySwap extends IDapp { } } -const fetch_pools = async () => { - const response = await fetch(`${CONSTANTS.MY_SWAP.POOLS_API}`); - const data = await response.json(); - return data; +const fetch_pools = async (): Promise => { + try { + const response = await fetch(`${CONSTANTS.MY_SWAP.POOLS_API}`); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching myswap pools: ', error); + return { pools: [] }; + } }; const getPoolKeys = (pools: Pools, poolNames: string[]): IndexedPools => { @@ -213,14 +218,19 @@ const fetchAprData = async ( if (pool_keys.length) { const pools = await Promise.all( pool_keys.map(async (pool_key) => { - const response = await fetch( - `${CONSTANTS.MY_SWAP.BASE_APR_API}/${pool_key}/overview.json`, - ); + try { + const response = await fetch( + `${CONSTANTS.MY_SWAP.BASE_APR_API}/${pool_key}/overview.json`, + ); - const data = await response.json(); - return data; + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching apr data: ', error); + return null; + } }), - ); + ).then((results) => results.filter((result) => result !== null)); return [pool_name, pools]; } diff --git a/src/store/pools.ts b/src/store/pools.ts index e6c0d60e..ac7e6d64 100755 --- a/src/store/pools.ts +++ b/src/store/pools.ts @@ -59,6 +59,25 @@ export interface PoolInfo extends PoolMetadata { }; } +type NostraPoolData = { + id?: string; + address?: string; + isDegen?: boolean; + tokenA?: string; + tokenAAddress?: string; + tokenB?: string; + tokenBAddress?: string; + volume?: string; + fee?: string; + swap?: number; + tvl?: string; + baseApr?: string; + rewardApr?: string; + rewardAllocation?: string; +}; + +type NostraPools = Record; + export interface ProtocolAtoms { pools: Atom; baseAPRs?: Atom>; @@ -86,20 +105,25 @@ export const StrkDexIncentivesAtom = atom((get) => { export const StrkIncentivesAtom = atomWithQuery((get) => ({ queryKey: get(StrkIncentivesQueryKeyAtom), - queryFn: async ({ queryKey }) => { - const res = await fetch(CONSTANTS.NOSTRA_DEGEN_INCENTIVE_URL); - let data = await res.text(); - data = data.replaceAll('NaN', '0'); - const parsedData = JSON.parse(data); - - if (queryKey[1] === 'isNostraDex') { - // Filter the data to include only the specific nostra dex pools we are tracking - return Object.values(parsedData).filter((item: any) => { - const id = item.id; - return id === 'ETH-USDC' || id === 'STRK-ETH' || id === 'STRK-USDC'; - }); + queryFn: async ({ queryKey }): Promise => { + try { + const res = await fetch(CONSTANTS.NOSTRA_DEGEN_INCENTIVE_URL); + let data = await res.text(); + data = data.replaceAll('NaN', '0'); + const parsedData: NostraPools = JSON.parse(data); + + if (queryKey[1] === 'isNostraDex') { + // Filter the data to include only the specific nostra dex pools we are tracking + return Object.values(parsedData).filter((item: any) => { + const id = item.id; + return id === 'ETH-USDC' || id === 'STRK-ETH' || id === 'STRK-USDC'; + }); + } + return parsedData; + } catch (error) { + console.error('Error fetching nostra incentives: ', error); + return []; } - return parsedData; }, })); diff --git a/src/store/utils.atoms.ts b/src/store/utils.atoms.ts index 7c78f458..d65181b4 100755 --- a/src/store/utils.atoms.ts +++ b/src/store/utils.atoms.ts @@ -71,8 +71,13 @@ interface DAppStats { export const dAppStatsAtom = atomWithQuery((get) => ({ queryKey: ['stats'], queryFn: async (): Promise => { - const res = await axios.get('/api/stats'); - return res.data; + try { + const res = await axios.get('/api/stats'); + return res.data; + } catch (error) { + console.error('Error fetching dApp stats: ', error); + return { tvl: 0 }; + } }, })); @@ -95,8 +100,13 @@ export const userStatsAtom = atomWithQuery((get) => ({ if (!addr) { return null; } - const res = await axios.get(`/api/stats/${addr}`); - return res.data; + try { + const res = await axios.get(`/api/stats/${addr}`); + return res.data; + } catch (error) { + console.error('Error fetching user stats: ', error); + return null; + } }, })); @@ -164,16 +174,22 @@ export const blockInfoMinus1DAtom = atomWithQuery((get) => ({ variables: {}, }); console.log('jedi base', 'data', data); + + try { const res = await fetch(CONSTANTS.JEDI.BASE_API, { method: 'POST', headers: { Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: data, - }); - console.log('jedi base', 'data2', res.json()); - return res.json(); + 'Content-Type': 'application/json', + }, + body: data, + }); + console.log('jedi base', 'data2', res.json()); + return res.json(); + } catch (error) { + console.error('Error fetching block minus 1d: ', error); + return { data: { blocks: [] } }; + } }, })); diff --git a/src/strategies/auto_strk.strat.ts b/src/strategies/auto_strk.strat.ts index 92445590..4a8b09e0 100755 --- a/src/strategies/auto_strk.strat.ts +++ b/src/strategies/auto_strk.strat.ts @@ -124,25 +124,35 @@ export class AutoTokenStrategy extends IStrategy { tokenInfo: this.token, }; - // returns zToken - const balanceInfo = await getBalance(this.holdingTokens[0], user); - if (!balanceInfo.tokenInfo) { + try { + // returns zToken + const balanceInfo = await getBalance(this.holdingTokens[0], user); + if (!balanceInfo.tokenInfo) { + return { + amount: MyNumber.fromEther('0', this.token.decimals), + usdValue: 0, + tokenInfo: this.token, + }; + } + + const priceInfo = await axios.get( + `https://api.coinbase.com/v2/prices/${this.token.name}-USDT/spot`, + ); + const price = Number(priceInfo.data.data.amount); + console.log('getUserTVL autoc', price, balanceInfo.amount.toEtherStr()); + return { + amount: balanceInfo.amount, + usdValue: Number(balanceInfo.amount.toEtherStr()) * price, + tokenInfo: balanceInfo.tokenInfo, + }; + } catch (error) { + console.error('Error fetching user TVL:', error); return { amount: MyNumber.fromEther('0', this.token.decimals), usdValue: 0, tokenInfo: this.token, }; } - const priceInfo = await axios.get( - `https://api.coinbase.com/v2/prices/${this.token.name}-USDT/spot`, - ); - const price = Number(priceInfo.data.data.amount); - console.log('getUserTVL autoc', price, balanceInfo.amount.toEtherStr()); - return { - amount: balanceInfo.amount, - usdValue: Number(balanceInfo.amount.toEtherStr()) * price, - tokenInfo: balanceInfo.tokenInfo, - }; }; getTVL = async () => { @@ -153,17 +163,28 @@ export class AutoTokenStrategy extends IStrategy { tokenInfo: this.token, }; - const zTokenInfo = getTokenInfoFromName(this.lpTokenName); - const bal = await getERC20Balance(zTokenInfo, this.strategyAddress); - const priceInfo = await axios.get( - `https://api.coinbase.com/v2/prices/${this.token.name}-USDT/spot`, - ); - const price = Number(priceInfo.data.data.amount); - return { - amount: bal.amount, - usdValue: Number(bal.amount.toEtherStr()) * price, - tokenInfo: this.token, - }; + try { + const zTokenInfo = getTokenInfoFromName(this.lpTokenName); + const bal = await getERC20Balance(zTokenInfo, this.strategyAddress); + + const priceInfo = await axios.get( + `https://api.coinbase.com/v2/prices/${this.token.name}-USDT/spot`, + ); + const price = Number(priceInfo.data.data.amount); + + return { + amount: bal.amount, + usdValue: Number(bal.amount.toEtherStr()) * price, + tokenInfo: this.token, + }; + } catch (error) { + console.error('Error fetching TVL:', error); + return { + amount: MyNumber.fromEther('0', this.token.decimals), + usdValue: 0, + tokenInfo: this.token, + }; + } }; // postSolve() { diff --git a/src/strategies/delta_neutral_mm.ts b/src/strategies/delta_neutral_mm.ts index c4e9e804..e0505c14 100755 --- a/src/strategies/delta_neutral_mm.ts +++ b/src/strategies/delta_neutral_mm.ts @@ -290,24 +290,35 @@ export class DeltaNeutralMM extends IStrategy { usdValue: 0, tokenInfo: this.token, }; - const balanceInfo = await getBalance(this.holdingTokens[0], user); - if (!balanceInfo.tokenInfo) { + + try { + const balanceInfo = await getBalance(this.holdingTokens[0], user); + if (!balanceInfo.tokenInfo) { + return { + amount: MyNumber.fromEther('0', this.token.decimals), + usdValue: 0, + tokenInfo: this.token, + }; + } + + const priceInfo = await axios.get( + `https://api.coinbase.com/v2/prices/${balanceInfo.tokenInfo.name}-USDT/spot`, + ); + const price = Number(priceInfo.data.data.amount); + console.log('getUserTVL dnmm', price, balanceInfo.amount.toEtherStr()); + return { + amount: balanceInfo.amount, + usdValue: Number(balanceInfo.amount.toEtherStr()) * price, + tokenInfo: balanceInfo.tokenInfo, + }; + } catch (error) { + console.error('Error fetching user TVL:', error); return { amount: MyNumber.fromEther('0', this.token.decimals), usdValue: 0, tokenInfo: this.token, }; } - const priceInfo = await axios.get( - `https://api.coinbase.com/v2/prices/${balanceInfo.tokenInfo.name}-USDT/spot`, - ); - const price = Number(priceInfo.data.data.amount); - console.log('getUserTVL dnmm', price, balanceInfo.amount.toEtherStr()); - return { - amount: balanceInfo.amount, - usdValue: Number(balanceInfo.amount.toEtherStr()) * price, - tokenInfo: balanceInfo.tokenInfo, - }; }; getTVL = async () => { @@ -328,6 +339,7 @@ export class DeltaNeutralMM extends IStrategy { const discountFactor = this.stepAmountFactors[4]; const amount = bal.amount.operate('div', 1 + discountFactor); console.log('getTVL1', amount.toString()); + const priceInfo = await axios.get( `https://api.coinbase.com/v2/prices/${mainTokenName}-USDT/spot`, ); @@ -338,9 +350,13 @@ export class DeltaNeutralMM extends IStrategy { usdValue: Number(amount.toEtherStr()) * price, tokenInfo: this.token, }; - } catch (e) { - console.log('getTVL err', e); - throw e; + } catch (error) { + console.error('Error fetching TVL:', error); + return { + amount: MyNumber.fromEther('0', this.token.decimals), + usdValue: 0, + tokenInfo: this.token, + }; } }; From 9aa76e6ecac7ea69c26961a6d70ffcf8b0a145a3 Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Sat, 14 Sep 2024 19:30:15 +0100 Subject: [PATCH 2/3] formating --- src/components/TncModal.tsx | 4 +- src/components/TxButton.tsx | 16 +++--- src/store/carmine.store.ts | 20 +++++-- src/store/ekobu.store.ts | 94 +++++++++++++++---------------- src/store/haiko.store.ts | 14 ++--- src/store/jedi.store.ts | 10 ++-- src/store/utils.atoms.ts | 10 ++-- src/strategies/auto_strk.strat.ts | 4 +- 8 files changed, 92 insertions(+), 80 deletions(-) diff --git a/src/components/TncModal.tsx b/src/components/TncModal.tsx index 9dcf778f..15d32a12 100644 --- a/src/components/TncModal.tsx +++ b/src/components/TncModal.tsx @@ -106,8 +106,8 @@ const TncModal: React.FC = ({ isOpen, onClose }) => { onClose(); try { await axios.post('/api/tnc/signUser', { - address, - message: res?.toString(), + address, + message: res?.toString(), }); } catch (error: any) { console.error('Error signing user: ', error); diff --git a/src/components/TxButton.tsx b/src/components/TxButton.tsx index 4b9ecaef..900b47a5 100755 --- a/src/components/TxButton.tsx +++ b/src/components/TxButton.tsx @@ -131,14 +131,14 @@ export default function TxButton(props: TxButtonProps) { try { const data = await axios.get(`/api/tnc/getUser/${address}`); - if ( - (data.data.user.isTncSigned && - data.data.user.tncDocVersion !== LATEST_TNC_DOC_VERSION) || - !data.data.user.isTncSigned - ) { - setShowTncModal(true); - return true; - } + if ( + (data.data.user.isTncSigned && + data.data.user.tncDocVersion !== LATEST_TNC_DOC_VERSION) || + !data.data.user.isTncSigned + ) { + setShowTncModal(true); + return true; + } return false; } catch (error: any) { diff --git a/src/store/carmine.store.ts b/src/store/carmine.store.ts index e352a586..709dcfb3 100755 --- a/src/store/carmine.store.ts +++ b/src/store/carmine.store.ts @@ -135,19 +135,31 @@ const poolEndpoints = [ export const CarmineAtom = atomWithQuery((get) => ({ queryKey: ['isCarmine'], queryFn: async ({ queryKey }) => { - const fetchPool = async (endpoint: string): Promise<{data: CarminePoolData}> => { + const fetchPool = async ( + endpoint: string, + ): Promise<{ data: CarminePoolData }> => { try { const res = await fetch(`${CONSTANTS.CARMINE_URL}/${endpoint}/apy`); let data = await res.text(); data = data.replaceAll('NaN', '0'); return JSON.parse(data); } catch (error) { - console.error(`Error fetching pool data for endpoint ${endpoint}:`, error); - return { data: { week_annualized: 0, week: 0, launch_annualized: 0, launch: 0 } }; + console.error( + `Error fetching pool data for endpoint ${endpoint}:`, + error, + ); + return { + data: { + week_annualized: 0, + week: 0, + launch_annualized: 0, + launch: 0, + }, + }; } }; - const fetchRewardApr = async (): Promise<{data: CarmineAPRData}> => { + const fetchRewardApr = async (): Promise<{ data: CarmineAPRData }> => { try { const res = await fetch(CONSTANTS.CARMINE_INCENTIVES_URL); let data = await res.text(); diff --git a/src/store/ekobu.store.ts b/src/store/ekobu.store.ts index ed2d9ba9..6c655565 100755 --- a/src/store/ekobu.store.ts +++ b/src/store/ekobu.store.ts @@ -343,54 +343,54 @@ const getData = async (): Promise => { priceOfStrk, priceOfEth, ] = await Promise.all([ - fetch(`${CONSTANTS.EKUBO.BASE_API}/tokens`).then((response) => - response.json(), - ).catch((err) => { - console.error('Error fetching ekubo tokens', err); - return []; + fetch(`${CONSTANTS.EKUBO.BASE_API}/tokens`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching ekubo tokens', err); + return []; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/defi-spring-incentives`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching ekubo defi spring incentives', err); + return { strkPrice: 0, totalStrk: 0, pairs: [] }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/overview/pairs`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching ekubo pair overview: ', err); + return { topPairs: [] }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH?period=21600`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching ETH prices', err); + return { timestamp: 0, prices: [] }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK?period=21600`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching STRK prices', err); + return { timestamp: 0, prices: [] }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/price/USDC?period=21600`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching USDC prices', err); + return { timestamp: 0, prices: [] }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK/USDC?period=21600`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching STRK/USDC price', err); + return { timestamp: 0, price: 0 }; + }), + fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH/USDC?period=21600`) + .then((response) => response.json()) + .catch((err) => { + console.error('Error fetching ETH/USDC price', err); + return { timestamp: 0, price: 0 }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/defi-spring-incentives`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching ekubo defi spring incentives', err); - return { strkPrice: 0, totalStrk: 0, pairs: [] }; - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/overview/pairs`).then((response) => - response.json(), - ).catch(err => { - console.error('Error fetching ekubo pair overview: ', err) - return { topPairs: [] } - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH?period=21600`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching ETH prices', err); - return { timestamp: 0, prices: [] }; - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK?period=21600`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching STRK prices', err); - return { timestamp: 0, prices: [] }; - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/USDC?period=21600`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching USDC prices', err); - return { timestamp: 0, prices: [] }; - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK/USDC?period=21600`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching STRK/USDC price', err); - return { timestamp: 0, price: 0 }; - }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH/USDC?period=21600`).then( - (response) => response.json(), - ).catch((err) => { - console.error('Error fetching ETH/USDC price', err); - return { timestamp: 0, price: 0 }; - }), ]); return { diff --git a/src/store/haiko.store.ts b/src/store/haiko.store.ts index dfb9116c..1d554e56 100755 --- a/src/store/haiko.store.ts +++ b/src/store/haiko.store.ts @@ -187,19 +187,19 @@ export const haiko = new Haiko(); const HaikoAtoms: ProtocolAtoms = { baseAPRs: atomWithQuery((get) => ({ queryKey: ['haiko_base_aprs'], - queryFn: async ({ queryKey }): Promise => { - try{ - const response = await fetch(`${CONSTANTS.HAIKO.BASE_APR_API}`); - const data = await response.json(); + queryFn: async ({ queryKey }): Promise => { + try { + const response = await fetch(`${CONSTANTS.HAIKO.BASE_APR_API}`); + const data = await response.json(); - return data; - } catch(error){ + return data; + } catch (error) { console.error('Error fetching haiko pools: ', error); return []; } }, })), - + pools: atom((get) => { const poolsInfo = get(StrkDexIncentivesAtom); const empty: PoolInfo[] = []; diff --git a/src/store/jedi.store.ts b/src/store/jedi.store.ts index f1d61815..95bc2c8f 100755 --- a/src/store/jedi.store.ts +++ b/src/store/jedi.store.ts @@ -147,13 +147,13 @@ async function getVolumes(block: number) { variables: {}, }); - try{ + try { const res = await fetch(CONSTANTS.JEDI.BASE_API, { method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, body: data, }); return res.json(); diff --git a/src/store/utils.atoms.ts b/src/store/utils.atoms.ts index d65181b4..883bab19 100755 --- a/src/store/utils.atoms.ts +++ b/src/store/utils.atoms.ts @@ -174,12 +174,12 @@ export const blockInfoMinus1DAtom = atomWithQuery((get) => ({ variables: {}, }); console.log('jedi base', 'data', data); - + try { - const res = await fetch(CONSTANTS.JEDI.BASE_API, { - method: 'POST', - headers: { - Accept: 'application/json', + const res = await fetch(CONSTANTS.JEDI.BASE_API, { + method: 'POST', + headers: { + Accept: 'application/json', 'Content-Type': 'application/json', }, body: data, diff --git a/src/strategies/auto_strk.strat.ts b/src/strategies/auto_strk.strat.ts index 4a8b09e0..127faf7f 100755 --- a/src/strategies/auto_strk.strat.ts +++ b/src/strategies/auto_strk.strat.ts @@ -166,12 +166,12 @@ export class AutoTokenStrategy extends IStrategy { try { const zTokenInfo = getTokenInfoFromName(this.lpTokenName); const bal = await getERC20Balance(zTokenInfo, this.strategyAddress); - + const priceInfo = await axios.get( `https://api.coinbase.com/v2/prices/${this.token.name}-USDT/spot`, ); const price = Number(priceInfo.data.data.amount); - + return { amount: bal.amount, usdValue: Number(bal.amount.toEtherStr()) * price, From 9fb0f736efc6284c7849a675ed31f04a6abb5322 Mon Sep 17 00:00:00 2001 From: Ikem Peter Date: Mon, 30 Sep 2024 17:17:33 +0100 Subject: [PATCH 3/3] implemented fetch with retry --- src/app/community/page.tsx | 10 ++++-- src/store/carmine.store.ts | 38 +++++++++++--------- src/store/ekobu.store.ts | 70 +++++++++++++++++++++++++++---------- src/store/haiko.store.ts | 13 +++---- src/store/jedi.store.ts | 26 +++++++------- src/store/myswap.store.ts | 36 +++++++++---------- src/store/pools.ts | 8 ++++- src/store/utils.atoms.ts | 48 ++++++++++--------------- src/utils/fetchWithRetry.ts | 28 +++++++++++++++ 9 files changed, 170 insertions(+), 107 deletions(-) create mode 100644 src/utils/fetchWithRetry.ts diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx index b6ff6631..a2ad6385 100644 --- a/src/app/community/page.tsx +++ b/src/app/community/page.tsx @@ -35,6 +35,7 @@ import { Text, useDisclosure, } from '@chakra-ui/react'; +import fetchWithRetry from '@/utils/fetchWithRetry'; interface OGNFTUserData { address: string; @@ -50,8 +51,13 @@ const isOGNFTEligibleAtom = atomWithQuery((get) => { queryFn: async ({ _queryKey }: any): Promise => { const address = get(addressAtom) || '0x0'; if (!address) return null; - const data = await fetch(`/api/users/ognft/${address}`); - return data.json(); + const data = await fetchWithRetry( + `/api/users/ognft/${address}`, + {}, + 'Error fetching OG NFT eligibility', + ); + if (!data) return null; + return await data.json(); }, refetchInterval: 5000, }; diff --git a/src/store/carmine.store.ts b/src/store/carmine.store.ts index 709dcfb3..097a3745 100755 --- a/src/store/carmine.store.ts +++ b/src/store/carmine.store.ts @@ -5,6 +5,7 @@ import { PoolInfo, ProtocolAtoms } from './pools'; import { Jediswap } from './jedi.store'; import { atomWithQuery } from 'jotai-tanstack-query'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; type CarminePoolData = { week: number; @@ -138,16 +139,13 @@ export const CarmineAtom = atomWithQuery((get) => ({ const fetchPool = async ( endpoint: string, ): Promise<{ data: CarminePoolData }> => { - try { - const res = await fetch(`${CONSTANTS.CARMINE_URL}/${endpoint}/apy`); - let data = await res.text(); - data = data.replaceAll('NaN', '0'); - return JSON.parse(data); - } catch (error) { - console.error( - `Error fetching pool data for endpoint ${endpoint}:`, - error, - ); + const res = await fetchWithRetry( + `${CONSTANTS.CARMINE_URL}/${endpoint}/apy`, + {}, + 'Failed to fetch Carmine data', + ); + + if (!res) { return { data: { week_annualized: 0, @@ -157,18 +155,24 @@ export const CarmineAtom = atomWithQuery((get) => ({ }, }; } + let data = await res.text(); + data = data.replaceAll('NaN', '0'); + return JSON.parse(data); }; const fetchRewardApr = async (): Promise<{ data: CarmineAPRData }> => { - try { - const res = await fetch(CONSTANTS.CARMINE_INCENTIVES_URL); - let data = await res.text(); - data = data.replaceAll('NaN', '0'); - return JSON.parse(data); - } catch (error) { - console.error('Error fetching reward APR data:', error); + const res = await fetchWithRetry( + CONSTANTS.CARMINE_INCENTIVES_URL, + {}, + 'Failed to fetch Carmine incentives data', + ); + + if (!res) { return { data: { apy: 0, tvl: 0, allocation: 0 } }; } + let data = await res.text(); + data = data.replaceAll('NaN', '0'); + return JSON.parse(data); }; const rewardAprData = await fetchRewardApr(); diff --git a/src/store/ekobu.store.ts b/src/store/ekobu.store.ts index 6c655565..fe8fb0e2 100755 --- a/src/store/ekobu.store.ts +++ b/src/store/ekobu.store.ts @@ -12,6 +12,7 @@ import { StrkDexIncentivesAtom, } from './pools'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; interface EkuboBaseAprDoc { tokens: Token[]; @@ -343,50 +344,83 @@ const getData = async (): Promise => { priceOfStrk, priceOfEth, ] = await Promise.all([ - fetch(`${CONSTANTS.EKUBO.BASE_API}/tokens`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/tokens`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching ekubo tokens') + .then((response) => response?.json() ?? []) .catch((err) => { - console.error('Error fetching ekubo tokens', err); + console.error('Error fetching ekubo tokens: ', err); return []; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/defi-spring-incentives`) - .then((response) => response.json()) + + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/defi-spring-incentives`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching ekubo defi spring incentives') + .then((response) => response?.json() ?? { strkPrice: 0, totalStrk: 0, pairs: [] }) .catch((err) => { - console.error('Error fetching ekubo defi spring incentives', err); + console.error('Error fetching ekubo defi spring incentives: ', err); return { strkPrice: 0, totalStrk: 0, pairs: [] }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/overview/pairs`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/overview/pairs`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching ekubo pair overview') + .then((response) => response?.json() ?? { topPairs: [] }) .catch((err) => { console.error('Error fetching ekubo pair overview: ', err); return { topPairs: [] }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH?period=21600`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/price/ETH?period=21600`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching ETH prices') + .then((response) => response?.json() ?? { timestamp: 0, prices: [] }) .catch((err) => { console.error('Error fetching ETH prices', err); return { timestamp: 0, prices: [] }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK?period=21600`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/price/STRK?period=21600`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching STRK prices') + .then((response) => response?.json() ?? { timestamp: 0, prices: [] }) .catch((err) => { console.error('Error fetching STRK prices', err); return { timestamp: 0, prices: [] }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/USDC?period=21600`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/price/USDC?period=21600`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching USDC prices') + .then((response) => response?.json() ?? { timestamp: 0, prices: [] }) .catch((err) => { console.error('Error fetching USDC prices', err); return { timestamp: 0, prices: [] }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/STRK/USDC?period=21600`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/price/STRK/USDC?period=21600`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching STRK/USDC price') + .then((response) => response?.json() ?? { timestamp: 0, price: 0 }) .catch((err) => { console.error('Error fetching STRK/USDC price', err); return { timestamp: 0, price: 0 }; }), - fetch(`${CONSTANTS.EKUBO.BASE_API}/price/ETH/USDC?period=21600`) - .then((response) => response.json()) + fetchWithRetry(`${CONSTANTS.EKUBO.BASE_API}/price/ETH/USDC?period=21600`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching ETH/USDC price') + .then((response) => response?.json() ?? { timestamp: 0, price: 0 }) .catch((err) => { console.error('Error fetching ETH/USDC price', err); return { timestamp: 0, price: 0 }; diff --git a/src/store/haiko.store.ts b/src/store/haiko.store.ts index 1d554e56..b422f3b2 100755 --- a/src/store/haiko.store.ts +++ b/src/store/haiko.store.ts @@ -12,6 +12,7 @@ import { atom } from 'jotai'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { IDapp } from './IDapp.store'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; type Token = { address: string; @@ -188,15 +189,15 @@ const HaikoAtoms: ProtocolAtoms = { baseAPRs: atomWithQuery((get) => ({ queryKey: ['haiko_base_aprs'], queryFn: async ({ queryKey }): Promise => { - try { - const response = await fetch(`${CONSTANTS.HAIKO.BASE_APR_API}`); + const response = await fetchWithRetry(`${CONSTANTS.HAIKO.BASE_APR_API}`, { + headers: { + 'Content-Type': 'application/json', + }, + }, 'Error fetching haiko base APRs'); + if (!response) return []; const data = await response.json(); return data; - } catch (error) { - console.error('Error fetching haiko pools: ', error); - return []; - } }, })), diff --git a/src/store/jedi.store.ts b/src/store/jedi.store.ts index 95bc2c8f..799a3aaf 100755 --- a/src/store/jedi.store.ts +++ b/src/store/jedi.store.ts @@ -13,6 +13,7 @@ import { IDapp } from './IDapp.store'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { BlockInfo, getBlock } from './utils.atoms'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; interface MyBaseAprDoc { id: string; @@ -147,20 +148,17 @@ async function getVolumes(block: number) { variables: {}, }); - try { - const res = await fetch(CONSTANTS.JEDI.BASE_API, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: data, - }); - return res.json(); - } catch (error) { - console.error('Error fetching jedi volumes: ', error); - return { data: { pairs: [] } }; - } + const res = await fetchWithRetry(CONSTANTS.JEDI.BASE_API, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: data, + }, 'Error fetching jedi volumes'); + + if (!res) return { data: { pairs: [] } }; + return await res.json(); } export const jedi = new Jediswap(); diff --git a/src/store/myswap.store.ts b/src/store/myswap.store.ts index 4c412122..69bfbd4e 100755 --- a/src/store/myswap.store.ts +++ b/src/store/myswap.store.ts @@ -12,6 +12,7 @@ import { IDapp } from './IDapp.store'; import CONSTANTS, { TokenName } from '@/constants'; import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; interface Token { symbol: string; @@ -182,14 +183,14 @@ export class MySwap extends IDapp { } const fetch_pools = async (): Promise => { - try { - const response = await fetch(`${CONSTANTS.MY_SWAP.POOLS_API}`); - const data = await response.json(); - return data; - } catch (error) { - console.error('Error fetching myswap pools: ', error); - return { pools: [] }; - } + const response = await fetchWithRetry( + `${CONSTANTS.MY_SWAP.POOLS_API}`, + {}, + 'Error fetching myswap pools', + ); + if (!response) return { pools: [] }; + const data = await response.json(); + return data; }; const getPoolKeys = (pools: Pools, poolNames: string[]): IndexedPools => { @@ -218,17 +219,14 @@ const fetchAprData = async ( if (pool_keys.length) { const pools = await Promise.all( pool_keys.map(async (pool_key) => { - try { - const response = await fetch( - `${CONSTANTS.MY_SWAP.BASE_APR_API}/${pool_key}/overview.json`, - ); - - const data = await response.json(); - return data; - } catch (error) { - console.error('Error fetching apr data: ', error); - return null; - } + const response = await fetchWithRetry( + `${CONSTANTS.MY_SWAP.BASE_APR_API}/${pool_key}/overview.json`, + {}, + 'Error fetching myswap apr data', + ); + if (!response) return null; + const data = await response.json(); + return data; }), ).then((results) => results.filter((result) => result !== null)); diff --git a/src/store/pools.ts b/src/store/pools.ts index ac7e6d64..1d80cf82 100755 --- a/src/store/pools.ts +++ b/src/store/pools.ts @@ -4,6 +4,7 @@ import { AtomWithQueryResult, atomWithQuery } from 'jotai-tanstack-query'; import { CustomAtomWithQueryResult } from '@/utils/customAtomWithQuery'; import { customAtomWithFetch } from '@/utils/customAtomWithFetch'; import { StrategyLiveStatus } from '@/strategies/IStrategy'; +import fetchWithRetry from '@/utils/fetchWithRetry'; export enum Category { Stable = 'Stable Pools', @@ -107,7 +108,12 @@ export const StrkIncentivesAtom = atomWithQuery((get) => ({ queryKey: get(StrkIncentivesQueryKeyAtom), queryFn: async ({ queryKey }): Promise => { try { - const res = await fetch(CONSTANTS.NOSTRA_DEGEN_INCENTIVE_URL); + const res = await fetchWithRetry( + CONSTANTS.NOSTRA_DEGEN_INCENTIVE_URL, + {}, + 'Error fetching nostra incentives', + ); + if (!res) return []; let data = await res.text(); data = data.replaceAll('NaN', '0'); const parsedData: NostraPools = JSON.parse(data); diff --git a/src/store/utils.atoms.ts b/src/store/utils.atoms.ts index 883bab19..43c90f58 100755 --- a/src/store/utils.atoms.ts +++ b/src/store/utils.atoms.ts @@ -1,8 +1,8 @@ import CONSTANTS from '@/constants'; -import axios from 'axios'; import { atomWithQuery } from 'jotai-tanstack-query'; import { atomWithStorage, createJSONStorage } from 'jotai/utils'; import { addressAtom } from './claims.atoms'; +import fetchWithRetry from '@/utils/fetchWithRetry'; export interface BlockInfo { data: { @@ -71,13 +71,9 @@ interface DAppStats { export const dAppStatsAtom = atomWithQuery((get) => ({ queryKey: ['stats'], queryFn: async (): Promise => { - try { - const res = await axios.get('/api/stats'); - return res.data; - } catch (error) { - console.error('Error fetching dApp stats: ', error); - return { tvl: 0 }; - } + const res = await fetchWithRetry('/api/stats', {}, 'Error fetching dApp stats'); + if (!res) return { tvl: 0 }; + return await res.json(); }, })); @@ -100,13 +96,9 @@ export const userStatsAtom = atomWithQuery((get) => ({ if (!addr) { return null; } - try { - const res = await axios.get(`/api/stats/${addr}`); - return res.data; - } catch (error) { - console.error('Error fetching user stats: ', error); - return null; - } + const res = await fetchWithRetry(`/api/stats/${addr}`, {}, 'Error fetching user stats'); + if (!res) return null; + return await res.json(); }, })); @@ -175,21 +167,17 @@ export const blockInfoMinus1DAtom = atomWithQuery((get) => ({ }); console.log('jedi base', 'data', data); - try { - const res = await fetch(CONSTANTS.JEDI.BASE_API, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: data, - }); - console.log('jedi base', 'data2', res.json()); - return res.json(); - } catch (error) { - console.error('Error fetching block minus 1d: ', error); - return { data: { blocks: [] } }; - } + const res = await fetchWithRetry(CONSTANTS.JEDI.BASE_API, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: data, + }, 'Error fetching block minus 1d'); + if (!res) return { data: { blocks: [] } }; + console.log('jedi base', 'data2', res.json()); + return await res.json(); }, })); diff --git a/src/utils/fetchWithRetry.ts b/src/utils/fetchWithRetry.ts new file mode 100644 index 00000000..3e1b3cf9 --- /dev/null +++ b/src/utils/fetchWithRetry.ts @@ -0,0 +1,28 @@ +import toast from 'react-hot-toast'; + +async function fetchWithRetry(url: string, options: RequestInit = {}, errorToast: string = 'Failed to fetch'): Promise { + const maxRetries = 3; + const delay = 1000; + + for (let i = 0; i < maxRetries; i++) { + try { + const response = await fetch(url, options); + if (response.ok) { + return response; + } + throw new Error(`Failed to fetch ${url}, ${response.statusText}`, { + cause: response.status, + }); + } catch (error) { + if (i === maxRetries - 1) { + console.error(`Error fetching ${url} : `, error); + toast.error(errorToast); + return null; + } + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + return null; +} + +export default fetchWithRetry;