Skip to content

Commit 2cc9b8a

Browse files
authored
feat: add auto slippage endpoint + 0x migration (#1863)
* update quote route * lint * change api url for testing * feat: add auto slippage route * lint * add buffer * fix response * remove some earn-old components * integrate auto slippage * fix slippage rounding errors * update slippage on is auto changes * remove logs * re-add production url * remove logs
1 parent 2a23693 commit 2cc9b8a

File tree

14 files changed

+250
-551
lines changed

14 files changed

+250
-551
lines changed

src/app/api/quote/route.ts

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import { EthAddress, QuoteToken } from '@indexcoop/flash-mint-sdk'
1+
import { EthAddress, type QuoteToken } from '@indexcoop/flash-mint-sdk'
22
import {
33
getTokenByChainAndAddress,
44
isAddressEqual,
55
isProductToken,
66
} from '@indexcoop/tokenlists'
7-
import { NextRequest, NextResponse } from 'next/server'
8-
import { Address } from 'viem'
7+
import { NextResponse } from 'next/server'
98

10-
import { isBaseToken } from '@/lib/utils/tokens'
9+
import { getQuote } from '@/app/api/quote/utils'
10+
11+
import type { NextRequest } from 'next/server'
12+
import type { Address } from 'viem'
1113

1214
export interface IndexQuoteRequest {
1315
chainId: number
1416
account: string
1517
inputToken: string
1618
outputToken: string
17-
inputAmount?: string
18-
outputAmount?: string
19+
inputAmount: string
20+
outputAmount: string
1921
slippage: number
2022
}
2123

@@ -34,12 +36,6 @@ export async function POST(req: NextRequest) {
3436

3537
const inputToken = getQuoteToken(inputTokenAddress, chainId)
3638
const outputToken = getQuoteToken(outputTokenAddress, chainId)
37-
const isBtcOnBase = isBaseToken(
38-
chainId,
39-
inputTokenAddress,
40-
outputTokenAddress,
41-
)
42-
const isMintingIcUsd = outputToken?.quoteToken.symbol === 'icUSD'
4339

4440
if (
4541
!inputToken ||
@@ -49,42 +45,26 @@ export async function POST(req: NextRequest) {
4945
return BadRequest('Bad Request')
5046
}
5147

52-
const isMinting = outputToken.isIndex
5348
const quoteRequest: {
5449
account: string
5550
chainId: string
5651
inputToken: string
5752
outputToken: string
58-
inputAmount?: string
59-
outputAmount?: string
53+
inputAmount: string
54+
outputAmount: string
6055
slippage: string
6156
} = {
6257
account,
6358
chainId: String(chainId),
6459
inputToken: inputToken.quoteToken.address,
6560
outputToken: outputToken.quoteToken.address,
61+
// At the moment, we always wanna set both (inputAmount and indexTokenAmount)
62+
inputAmount,
63+
outputAmount,
6664
slippage: String(slippage),
6765
}
68-
if (isMinting) {
69-
quoteRequest.outputAmount = outputAmount
70-
} else {
71-
quoteRequest.inputAmount = inputAmount
72-
}
73-
if (isMintingIcUsd || isBtcOnBase) {
74-
quoteRequest.inputAmount = inputAmount ?? '0'
75-
}
76-
77-
const query = new URLSearchParams(quoteRequest).toString()
78-
const url = `https://api.indexcoop.com/v2/quote?${query}`
79-
const response = await fetch(url, {
80-
method: 'GET',
81-
headers: {
82-
'Content-Type': 'application/json',
83-
'x-api-key': process.env.INDEX_COOP_API_V2_KEY,
84-
} as HeadersInit,
85-
})
8666

87-
const quote = await response.json()
67+
const quote = await getQuote(quoteRequest)
8868

8969
if (!quote) {
9070
return NextResponse.json({ message: 'No quote found.' }, { status: 404 })

src/app/api/quote/utils.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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+
}
10+
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+
})
21+
22+
const quote = await response.json()
23+
return quote
24+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {
2+
getTokenByChainAndAddress,
3+
getTokenByChainAndSymbol,
4+
} from '@indexcoop/tokenlists'
5+
import { headers } from 'next/headers'
6+
import { NextResponse } from 'next/server'
7+
import { isAddress, parseUnits } from 'viem'
8+
9+
import { getQuote } from '@/app/api/quote/utils'
10+
import { formatWei } from '@/lib/utils'
11+
import { fetchTokenMetrics } from '@/lib/utils/api/index-data-provider'
12+
13+
import type { NextRequest } from 'next/server'
14+
15+
const slippageDefault = 0.5
16+
const slippageMax = 5.5
17+
18+
export async function GET(
19+
_: NextRequest,
20+
{ params }: { params: { slug: string[] } },
21+
): Promise<Response> {
22+
try {
23+
const { slug } = params
24+
const chainId = Number(slug[0])
25+
const address = slug[1]
26+
27+
if (!address || !isAddress(address)) {
28+
return NextResponse.json('Bad Request', { status: 400 })
29+
}
30+
31+
const token = getTokenByChainAndAddress(chainId, address)
32+
const usdc = getTokenByChainAndSymbol(chainId, 'USDC')
33+
34+
if (!token || !usdc) {
35+
return NextResponse.json('Bad Request', { status: 400 })
36+
}
37+
38+
const headersList = await headers()
39+
const host = headersList.get('host')
40+
const metrics = await fetchTokenMetrics({
41+
hostname:
42+
process.env.NODE_ENV === 'development'
43+
? `http://${host}`
44+
: `https://${host}`,
45+
chainId,
46+
tokenAddress: address,
47+
metrics: ['nav'],
48+
})
49+
50+
const nav = metrics?.NetAssetValue
51+
const inputAmount = nav === undefined ? '1000' : (nav * 1.1).toString()
52+
53+
if (!nav) {
54+
return NextResponse.json(
55+
{
56+
chainId,
57+
address,
58+
slippage: slippageDefault,
59+
},
60+
{ status: 200 },
61+
)
62+
}
63+
64+
const quoteRequest: {
65+
account: string
66+
chainId: string
67+
inputToken: string
68+
outputToken: string
69+
inputAmount: string
70+
outputAmount: string
71+
slippage: string
72+
} = {
73+
account: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
74+
chainId: String(chainId),
75+
inputToken: usdc.address,
76+
outputToken: token.address,
77+
// At the moment, we always wanna set both (inputAmount and indexTokenAmount)
78+
inputAmount: parseUnits(inputAmount.toString(), usdc.decimals).toString(),
79+
outputAmount: parseUnits('1', token.decimals).toString(),
80+
slippage: String(0.5),
81+
}
82+
83+
const quote = await getQuote(quoteRequest)
84+
85+
const quoteUsd = Number.parseFloat(
86+
formatWei(quote.inputAmount, usdc.decimals),
87+
)
88+
89+
const buffer = 1.2 // +20% on top of calc slippage
90+
const slippage = ((quoteUsd - nav) / nav) * buffer
91+
92+
const slippagePercentage = slippage * 100
93+
const slippageOrMax =
94+
slippagePercentage > slippageMax ? slippageMax : slippagePercentage
95+
96+
return NextResponse.json(
97+
{
98+
chainId,
99+
address,
100+
slippage: slippage < 0 ? slippageDefault : slippageOrMax,
101+
},
102+
{ status: 200 },
103+
)
104+
} catch (error) {
105+
console.log(error)
106+
return NextResponse.json(
107+
{ message: 'Internal Server Error' },
108+
{ status: 500 },
109+
)
110+
}
111+
}

src/app/earn-old/components/earn-widget/components/base-token-selector.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/app/earn-old/components/earn-widget/components/summary.tsx

Lines changed: 0 additions & 121 deletions
This file was deleted.

0 commit comments

Comments
 (0)