Skip to content

Commit 19f1412

Browse files
authored
FEAT: Widget Quote Error handling (#1868)
* add error message for no quote to trade and earn * reset api url for quote * use kubb for fetching quote * restyle the error * fix lint * fix lint * linting issues in vercel * bigint in slippage?
1 parent c171f1c commit 19f1412

File tree

8 files changed

+139
-84
lines changed

8 files changed

+139
-84
lines changed

src/app/api/quote/utils.ts

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
export interface FlashMintQuoteRequest {
2-
chainId: number
3-
account: string
4-
inputToken: string
5-
outputToken: string
6-
inputAmount: string
7-
outputAmount: string
8-
slippage: number
9-
}
1+
import { getApiV2Quote, GetApiV2QuoteQueryParams } from '@/gen'
2+
3+
export interface FlashMintQuoteRequest extends GetApiV2QuoteQueryParams {}
104

11-
export async function getQuote(quoteRequest: Record<string, string>) {
12-
const query = new URLSearchParams(quoteRequest).toString()
13-
const url = `https://api.indexcoop.com/v2/quote?${query}`
14-
const response = await fetch(url, {
15-
method: 'GET',
16-
headers: {
17-
'Content-Type': 'application/json',
18-
'x-api-key': process.env.INDEX_COOP_API_V2_KEY,
19-
} as HeadersInit,
20-
})
5+
export async function getQuote(quoteRequest: FlashMintQuoteRequest) {
6+
const response = await getApiV2Quote(quoteRequest)
217

22-
const quote = await response.json()
23-
return quote
8+
return response.data
249
}

src/app/api/slippage/[...slug]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function GET(
8383
const quote = await getQuote(quoteRequest)
8484

8585
const quoteUsd = Number.parseFloat(
86-
formatWei(quote.inputAmount, usdc.decimals),
86+
formatWei(BigInt(quote.inputAmount), usdc.decimals),
8787
)
8888

8989
const buffer = 1.2 // +20% on top of calc slippage

src/app/earn/components/earn-widget/index.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { useAtom } from 'jotai'
44
import range from 'lodash/range'
5-
import { useCallback, useEffect } from 'react'
5+
import { useCallback, useEffect, useMemo } from 'react'
66
import { isAddressEqual } from 'viem'
77

88
import { useQueryParams } from '@/app/earn-old/use-query-params'
@@ -48,6 +48,7 @@ export function EarnWidget() {
4848
products,
4949
outputToken,
5050
reset,
51+
refetchQuote,
5152
toggleIsMinting,
5253
quoteResult,
5354
} = useEarnContext()
@@ -88,6 +89,11 @@ export function EarnWidget() {
8889
}
8990
}, [tradeState, reset, resetData, sendTradeEvent])
9091

92+
const hasFetchingError = useMemo(
93+
() => tradeState.matches('quoteNotFound'),
94+
[tradeState],
95+
)
96+
9197
return (
9298
<div className='earn-widget flex h-fit flex-col gap-6'>
9399
<DepositWithdraw
@@ -117,10 +123,15 @@ export function EarnWidget() {
117123
product={selectedProduct}
118124
isMinting={isMinting}
119125
/>
120-
126+
{hasFetchingError && (
127+
<div className='flex justify-center gap-2 text-sm text-red-400'>
128+
<p className='font-semibold'>Error fetching quote:</p>
129+
{tradeState.context.quoteError}
130+
</div>
131+
)}
121132
<SmartTradeButton
122133
contract={contract ?? ''}
123-
hasFetchingError={false}
134+
hasFetchingError={hasFetchingError}
124135
hasInsufficientFunds={hasInsufficientFunds}
125136
hiddenWarnings={hiddenLeverageWarnings}
126137
inputTokenAmount={inputTokenAmount}
@@ -145,7 +156,7 @@ export function EarnWidget() {
145156
}
146157
}
147158
onOpenTransactionReview={() => sendTradeEvent({ type: 'REVIEW' })}
148-
onRefetchQuote={() => {}}
159+
onRefetchQuote={refetchQuote}
149160
/>
150161
<SelectTokenModal
151162
isDarkMode={true}

src/app/earn/provider.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface Context {
4949
onSelectInputToken: (tokenSymbol: string, chainId: number) => void
5050
onSelectOutputToken: (tokenSymbol: string, chainId: number) => void
5151
reset: () => void
52+
refetchQuote: ReturnType<typeof useQuoteResult>['refetchQuote'] | (() => void)
5253
toggleIsMinting: () => void
5354
}
5455

@@ -71,6 +72,7 @@ export const EarnContext = createContext<Context>({
7172
onSelectInputToken: () => {},
7273
onSelectOutputToken: () => {},
7374
reset: () => {},
75+
refetchQuote: () => {},
7476
toggleIsMinting: () => {},
7577
})
7678

@@ -143,16 +145,17 @@ export function EarnProvider(props: {
143145
[inputToken, inputValue],
144146
)
145147

146-
const { isFetchingQuote, quoteResult, resetQuote } = useQuoteResult({
147-
address,
148-
chainId,
149-
isMinting,
150-
inputToken,
151-
outputToken,
152-
inputTokenAmount,
153-
inputValue,
154-
slippage,
155-
})
148+
const { isFetchingQuote, quoteResult, resetQuote, refetchQuote } =
149+
useQuoteResult({
150+
address,
151+
chainId,
152+
isMinting,
153+
inputToken,
154+
outputToken,
155+
inputTokenAmount,
156+
inputValue,
157+
slippage,
158+
})
156159

157160
const onChangeInputTokenAmount = useCallback(
158161
(input: string) => {
@@ -244,6 +247,7 @@ export function EarnProvider(props: {
244247
onSelectInputToken,
245248
onSelectOutputToken,
246249
reset,
250+
refetchQuote,
247251
toggleIsMinting,
248252
}}
249253
>

src/app/leverage/components/leverage-widget/index.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client'
22

3+
import { ExclamationCircleIcon } from '@heroicons/react/20/solid'
34
import { useAtom } from 'jotai'
4-
import { useCallback, useEffect } from 'react'
5+
import { useCallback, useEffect, useMemo } from 'react'
56

67
import { TradeInputSelector } from '@/app/leverage/components/leverage-widget/trade-input-selector'
78
import { supportedNetworks } from '@/app/leverage/constants'
@@ -50,6 +51,7 @@ export function LeverageWidget() {
5051
onSelectOutputToken,
5152
outputToken,
5253
reset,
54+
refetchQuote,
5355
supportedLeverageTypes,
5456
toggleIsMinting,
5557
} = useLeverageToken()
@@ -99,6 +101,11 @@ export function LeverageWidget() {
99101
}
100102
}, [tradeState, reset, resetData, sendTradeEvent])
101103

104+
const hasFetchingError = useMemo(
105+
() => tradeState.matches('quoteNotFound'),
106+
[tradeState],
107+
)
108+
102109
return (
103110
<div
104111
className='flex w-full flex-col gap-4 rounded-lg border border-white/15 bg-zinc-900 px-4 pb-5 pt-4'
@@ -140,10 +147,20 @@ export function LeverageWidget() {
140147
selectedOutputToken={outputToken}
141148
onSelectToken={onOpenSelectOutputToken}
142149
/>
150+
{hasFetchingError && (
151+
<div className='flex items-center justify-center gap-2 text-sm text-red-400'>
152+
<div className='flex items-center gap-1'>
153+
<ExclamationCircleIcon className='size-5' />
154+
<h3 className='font-semibold'>Quote Error</h3>
155+
{':'}
156+
</div>
157+
<p> {tradeState.context.quoteError}</p>
158+
</div>
159+
)}
143160
<Summary />
144161
<SmartTradeButton
145162
contract={contract ?? ''}
146-
hasFetchingError={false}
163+
hasFetchingError={hasFetchingError}
147164
hasInsufficientFunds={hasInsufficientFunds}
148165
hiddenWarnings={hiddenLeverageWarnings}
149166
inputTokenAmount={inputTokenAmount}
@@ -157,7 +174,7 @@ export function LeverageWidget() {
157174
[TradeButtonState.default]: 'Review Transaction',
158175
}}
159176
onOpenTransactionReview={() => sendTradeEvent({ type: 'REVIEW' })}
160-
onRefetchQuote={() => {}}
177+
onRefetchQuote={refetchQuote}
161178
/>
162179
<SelectTokenModal
163180
isDarkMode={true}

src/app/leverage/provider.tsx

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export interface TokenContext {
5555
onSelectLeverageType: (type: LeverageType) => void
5656
onSelectOutputToken: (tokenSymbol: string) => void
5757
reset: () => void
58+
refetchQuote:
59+
| ReturnType<typeof useQuoteResult>['refetchQuote']
60+
| (() => Promise<void>)
5861
toggleIsMinting: (force?: boolean) => void
5962
}
6063

@@ -80,6 +83,7 @@ export const LeverageTokenContext = createContext<TokenContext>({
8083
onSelectLeverageType: () => {},
8184
onSelectOutputToken: () => {},
8285
reset: () => {},
86+
refetchQuote: async () => {},
8387
toggleIsMinting: () => {},
8488
})
8589

@@ -158,16 +162,17 @@ export function LeverageProvider(props: { children: any }) {
158162
[inputToken, inputValue],
159163
)
160164

161-
const { isFetchingQuote, quoteResult, resetQuote } = useQuoteResult({
162-
address,
163-
chainId,
164-
isMinting,
165-
inputToken,
166-
outputToken,
167-
inputTokenAmount,
168-
inputValue,
169-
slippage,
170-
})
165+
const { isFetchingQuote, quoteResult, resetQuote, refetchQuote } =
166+
useQuoteResult({
167+
address,
168+
chainId,
169+
isMinting,
170+
inputToken,
171+
outputToken,
172+
inputTokenAmount,
173+
inputValue,
174+
slippage,
175+
})
171176

172177
const indexTokensBasedOnSymbol = useMemo(() => {
173178
return indexTokens.filter((token) => {
@@ -331,6 +336,7 @@ export function LeverageProvider(props: { children: any }) {
331336
onSelectLeverageType,
332337
onSelectOutputToken,
333338
reset,
339+
refetchQuote,
334340
toggleIsMinting,
335341
}}
336342
>

src/app/store/trade-machine.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { TransactionReceipt } from 'viem'
1010
export interface TradeMachineContext {
1111
isModalOpen: boolean
1212
quoteResult: QuoteResult | null
13+
quoteError: string
1314
trade: PostApiV2Trade200['trade'] | null
1415
position: PostApiV2Trade200['position'] | null
1516
transactionReview: TransactionReview | null
@@ -19,6 +20,7 @@ export interface TradeMachineContext {
1920
export type TradeMachineEvent =
2021
| { type: 'FETCHING_QUOTE' }
2122
| { type: 'QUOTE'; quoteResult: QuoteResult; quoteType: QuoteType }
23+
| { type: 'QUOTE_NOT_FOUND'; reason: string }
2224
| { type: 'REVIEW' }
2325
| { type: 'SUBMIT' }
2426
| { type: 'TRADE_FAILED' }
@@ -44,6 +46,7 @@ const createTradeMachine = () =>
4446
position: null,
4547
transactionReview: null,
4648
transactionStatus: null,
49+
quoteError: '',
4750
},
4851
states: {
4952
idle: {
@@ -83,16 +86,18 @@ const createTradeMachine = () =>
8386
guard: ({ event }) =>
8487
Boolean(event.quoteResult && event.quoteResult.quote),
8588
},
89+
QUOTE_NOT_FOUND: {
90+
target: 'quoteNotFound',
91+
actions: assign({
92+
quoteError: ({ event }) => event.reason,
93+
}),
94+
},
8695
},
8796
},
8897
quote: {
8998
on: {
9099
FETCHING_QUOTE: {
91100
target: 'idle',
92-
actions: assign({
93-
quoteResult: null,
94-
transactionReview: null,
95-
}),
96101
},
97102
REVIEW: {
98103
target: 'review',
@@ -102,6 +107,13 @@ const createTradeMachine = () =>
102107
},
103108
},
104109
},
110+
quoteNotFound: {
111+
on: {
112+
FETCHING_QUOTE: {
113+
target: 'idle',
114+
},
115+
},
116+
},
105117
review: {
106118
on: {
107119
SUBMIT: {
@@ -169,6 +181,7 @@ const createTradeMachine = () =>
169181
position: null,
170182
transactionReview: null,
171183
transactionStatus: null,
184+
quoteError: '',
172185
}),
173186
},
174187
})

0 commit comments

Comments
 (0)