refactor swaps routing function + add more fallback orcales (#426)

* cleanup + overloads for routing functions, new types for routing functions

* add new routing route

* add fallback oracles

* yarn deduplicate
This commit is contained in:
Adrian Brzeziński 2024-05-20 01:57:10 +02:00 committed by GitHub
parent 79a5ac30e8
commit d4d38aef15
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 295 additions and 203 deletions

View File

@ -344,8 +344,6 @@ const ListToken = ({ goBack }: { goBack: () => void }) => {
undefined, // mangoAccount
onlyDirect ? 'JUPITER_DIRECT' : 'JUPITER',
connection,
null,
false,
)
},
[wallet.publicKey, connection],

View File

@ -83,26 +83,7 @@ const MarketSwapForm = ({
swapMode,
wallet: publicKey?.toBase58(),
mangoAccount,
mangoAccountSwap: true,
enabled: () =>
!!(
inputBank?.mint &&
outputBank?.mint &&
quoteAmount &&
!isDraggingSlider
),
})
const { bestRoute: bestDirectRoute } = useQuoteRoutes({
inputMint: inputBank?.mint.toString(),
outputMint: outputBank?.mint.toString(),
amount: quoteAmount,
slippage,
swapMode,
wallet: publicKey?.toBase58(),
mangoAccount,
mangoAccountSwap: true,
mode: 'JUPITER_DIRECT',
routingMode: 'ALL_AND_JUPITER_DIRECT',
enabled: () =>
!!(
inputBank?.mint &&
@ -233,11 +214,8 @@ const MarketSwapForm = ({
depending on the swapMode and set those values in state
*/
useEffect(() => {
if (
typeof bestRoute !== 'undefined' ||
typeof bestDirectRoute !== 'undefined'
) {
const newRoute = bestRoute || bestDirectRoute
if (typeof bestRoute !== 'undefined') {
const newRoute = bestRoute
setSelectedRoute(newRoute)
if (inputBank && swapMode === 'ExactOut' && newRoute?.inAmount) {
@ -252,7 +230,7 @@ const MarketSwapForm = ({
setAmountOutFormValue(outAmount)
}
}
}, [bestRoute, bestDirectRoute, swapMode, inputBank, outputBank])
}, [bestRoute, swapMode, inputBank, outputBank])
const handleSwitchTokens = useCallback(() => {
if (amountInAsDecimal?.gt(0) && amountOutAsDecimal.gte(0)) {
@ -318,10 +296,8 @@ const MarketSwapForm = ({
onSuccess={onSuccess}
refetchRoute={refetchRoute}
routes={
bestRoute || bestDirectRoute
? ([bestRoute, bestDirectRoute].filter(
(x) => x && !x.error,
) as JupiterV6RouteInfo[])
bestRoute
? ([bestRoute].filter((x) => x && !x.error) as JupiterV6RouteInfo[])
: undefined
}
selectedRoute={selectedRoute}

View File

@ -69,8 +69,7 @@ const WalletSwapForm = ({ setShowTokenSelect }: WalletSwapFormProps) => {
swapMode,
wallet: publicKey?.toBase58(),
mangoAccount: undefined,
mangoAccountSwap: false,
mode: 'JUPITER',
routingMode: 'JUPITER',
enabled: () =>
!!(
inputBank?.mint &&

View File

@ -3,7 +3,7 @@ import { Connection, PublicKey } from '@solana/web3.js'
import { useQuery } from '@tanstack/react-query'
import Decimal from 'decimal.js'
import { JupiterV6RouteInfo } from 'types/jupiter'
// import { MANGO_ROUTER_API_URL } from 'utils/constants'
import { MANGO_ROUTER_API_URL } from 'utils/constants'
import useJupiterSwapData from './useJupiterSwapData'
import { useMemo } from 'react'
import { JUPITER_V6_QUOTE_API_MAINNET } from 'utils/constants'
@ -12,79 +12,110 @@ import { findRaydiumPoolInfo, getSwapTransaction } from 'utils/swap/raydium'
import mangoStore from '@store/mangoStore'
import { fetchJupiterTransaction } from './SwapReviewRouteInfo'
type SwapModes = 'ALL' | 'JUPITER' | 'MANGO' | 'JUPITER_DIRECT' | 'RAYDIUM'
type SwapModes = 'ExactIn' | 'ExactOut'
type MultiRoutingMode = 'ALL' | 'ALL_AND_JUPITER_DIRECT'
type JupiterRoutingMode = 'JUPITER_DIRECT' | 'JUPITER'
type RaydiumRoutingMode = 'RAYDIUM'
type MangoRoutingMode = 'MANGO'
type RoutingMode =
| MultiRoutingMode
| JupiterRoutingMode
| RaydiumRoutingMode
| MangoRoutingMode
type useQuoteRoutesPropTypes = {
inputMint: string | undefined
outputMint: string | undefined
amount: string
slippage: number
swapMode: string
swapMode: SwapModes
wallet: string | undefined
mangoAccount: MangoAccount | undefined
mode?: SwapModes
mangoAccountSwap: boolean
routingMode: RoutingMode
enabled?: () => boolean
}
function isMultiRoutingMode(value: RoutingMode): value is MultiRoutingMode {
return ['ALL', 'ALL_AND_JUPITER_DIRECT'].includes(value)
}
function isRaydiumRoutingMode(value: RoutingMode): value is RaydiumRoutingMode {
return value === 'RAYDIUM'
}
const fetchJupiterRoute = async (
inputMint: string | undefined,
outputMint: string | undefined,
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
swapMode: SwapModes = 'ExactIn',
onlyDirectRoutes = true,
maxAccounts = 64,
connection: Connection,
wallet: string,
) => {
if (!inputMint || !outputMint) return
try {
{
const paramObj: {
inputMint: string
outputMint: string
amount: string
slippageBps: string
swapMode: string
onlyDirectRoutes: string
maxAccounts?: string
} = {
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
swapMode,
onlyDirectRoutes: `${onlyDirectRoutes}`,
}
//exact out is not supporting max account
if (swapMode === 'ExactIn') {
paramObj.maxAccounts = maxAccounts.toString()
}
const paramsString = new URLSearchParams(paramObj).toString()
const response = await fetch(
`${JUPITER_V6_QUOTE_API_MAINNET}/quote?${paramsString}`,
)
const res: JupiterV6RouteInfo = await response.json()
const [ixes] = await fetchJupiterTransaction(
connection,
res,
new PublicKey(wallet),
slippage,
new PublicKey(inputMint),
new PublicKey(outputMint),
)
return {
bestRoute:
[...ixes.flatMap((x) => x.keys.flatMap((k) => k.pubkey))].length <=
return new Promise<{ bestRoute: JupiterV6RouteInfo }>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
try {
if (!inputMint || !outputMint) return
const paramObj: {
inputMint: string
outputMint: string
amount: string
slippageBps: string
swapMode: string
onlyDirectRoutes: string
maxAccounts?: string
} = {
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
swapMode,
onlyDirectRoutes: `${onlyDirectRoutes}`,
}
//exact out is not supporting max account
if (swapMode === 'ExactIn') {
paramObj.maxAccounts = maxAccounts.toString()
}
const paramsString = new URLSearchParams(paramObj).toString()
const response = await fetch(
`${JUPITER_V6_QUOTE_API_MAINNET}/quote?${paramsString}`,
)
const res: JupiterV6RouteInfo = await response.json()
if (res.error) {
throw res.error
}
const [ixes] = await fetchJupiterTransaction(
connection,
res,
new PublicKey(wallet),
slippage,
new PublicKey(inputMint),
new PublicKey(outputMint),
)
if (
[...ixes.flatMap((x) => x.keys.flatMap((k) => k.pubkey))].length >
maxAccounts
? res
: undefined,
) {
throw 'Max accounts exceeded'
}
resolve({
bestRoute: res,
})
} catch (e) {
console.log('jupiter route error:', e)
reject(e)
}
}
} catch (e) {
console.log('error fetching jupiter route', e)
}
},
)
}
const fetchRaydiumRoute = async (
@ -94,102 +125,134 @@ const fetchRaydiumRoute = async (
slippage = 50,
connection: Connection,
wallet: string,
mangoAccountSwap: boolean,
isInWalletSwap: boolean,
) => {
if (!inputMint || !outputMint) return
try {
const poolKeys = await findRaydiumPoolInfo(
connection,
outputMint,
inputMint,
)
return new Promise<{ bestRoute: JupiterV6RouteInfo }>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
try {
if (!inputMint || !outputMint) return
if (poolKeys) {
return await getSwapTransaction(
connection,
outputMint,
amount,
poolKeys!,
slippage,
new PublicKey(wallet),
mangoAccountSwap,
)
}
} catch (e) {
console.log('error fetching raydium route', e)
}
const poolKeys = await findRaydiumPoolInfo(
connection,
outputMint,
inputMint,
)
if (poolKeys) {
const resp = await getSwapTransaction(
connection,
outputMint,
amount,
poolKeys!,
slippage,
new PublicKey(wallet),
isInWalletSwap,
)
resolve(resp as unknown as { bestRoute: JupiterV6RouteInfo })
} else {
throw 'No route found'
}
} catch (e) {
console.log('raydium route error:', e)
reject(e)
}
},
)
}
// const fetchMangoRoutes = async (
// inputMint = 'So11111111111111111111111111111111111111112',
// outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
// amount = 0,
// slippage = 50,
// swapMode = 'ExactIn',
// feeBps = 0,
// wallet = PublicKey.default.toBase58(),
// ) => {
// {
// const defaultOtherAmount =
// swapMode === 'ExactIn' ? 0 : Number.MAX_SAFE_INTEGER
const fetchMangoRoute = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
) => {
return new Promise<{ bestRoute: JupiterV6RouteInfo }>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
try {
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
mode: swapMode,
}).toString()
// const paramsString = new URLSearchParams({
// inputMint: inputMint.toString(),
// outputMint: outputMint.toString(),
// amount: amount.toString(),
// slippage: ((slippage * 1) / 100).toString(),
// feeBps: feeBps.toString(),
// mode: swapMode,
// wallet: wallet,
// otherAmountThreshold: defaultOtherAmount.toString(),
// }).toString()
const response = await fetch(
`${MANGO_ROUTER_API_URL}/quote?${paramsString}`,
)
// const response = await fetch(`${MANGO_ROUTER_API_URL}/swap?${paramsString}`)
if (response.status === 500) {
throw 'No route found'
}
const res = await response.json()
if (res.outAmount) {
resolve({
bestRoute: res,
})
} else {
reject('No route found')
}
} catch (e) {
console.log('mango router error:', e)
reject(e)
}
},
)
}
// const res = await response.json()
// const data: RouteInfo[] = res.map((route: any) => ({
// ...route,
// priceImpactPct: route.priceImpact,
// slippageBps: slippage,
// marketInfos: route.marketInfos.map((mInfo: any) => ({
// ...mInfo,
// lpFee: {
// ...mInfo.fee,
// pct: mInfo.fee.rate,
// },
// })),
// mints: route.mints.map((x: string) => new PublicKey(x)),
// instructions: route.instructions.map((ix: any) => ({
// ...ix,
// programId: new PublicKey(ix.programId),
// data: Buffer.from(ix.data, 'base64'),
// keys: ix.keys.map((key: any) => ({
// ...key,
// pubkey: new PublicKey(key.pubkey),
// })),
// })),
// routerName: 'Mango',
// }))
// return {
// routes: data,
// bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
// }
// }
// }
export async function handleGetRoutes(
inputMint: string | undefined,
outputMint: string | undefined,
amount: number,
slippage: number,
swapMode: SwapModes,
wallet: string | undefined,
mangoAccount: MangoAccount | undefined,
routingMode: MultiRoutingMode | RaydiumRoutingMode,
connection: Connection,
inputTokenDecimals: number,
): Promise<{ bestRoute: JupiterV6RouteInfo }>
export const handleGetRoutes = async (
export async function handleGetRoutes(
inputMint: string | undefined,
outputMint: string | undefined,
amount: number,
slippage: number,
swapMode: SwapModes,
wallet: string | undefined,
mangoAccount: MangoAccount | undefined,
routingMode: JupiterRoutingMode | MangoRoutingMode,
connection: Connection,
): Promise<{ bestRoute: JupiterV6RouteInfo }>
export async function handleGetRoutes(
inputMint: string | undefined,
outputMint: string | undefined,
amount: number,
slippage: number,
swapMode: 'ExactIn',
wallet: string | undefined,
mangoAccount: MangoAccount | undefined,
routingMode: RaydiumRoutingMode,
connection: Connection,
inputTokenDecimals: number,
): Promise<{ bestRoute: JupiterV6RouteInfo }>
export async function handleGetRoutes(
inputMint: string | undefined,
outputMint: string | undefined,
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
swapMode: SwapModes,
wallet: string | undefined,
mangoAccount: MangoAccount | undefined,
mode: SwapModes = 'ALL',
routingMode: RoutingMode = 'ALL',
connection: Connection,
inputTokenDecimals: number | null,
mangoAccountSwap: boolean,
) => {
inputTokenDecimals?: number,
) {
try {
wallet ||= PublicKey.default.toBase58()
@ -208,47 +271,74 @@ export const handleGetRoutes = async (
const routes = []
if (
connection &&
inputTokenDecimals &&
swapMode === 'ExactIn' &&
(mode === 'ALL' || mode === 'RAYDIUM')
(isMultiRoutingMode(routingMode) || isRaydiumRoutingMode(routingMode))
) {
const raydiumRoute = fetchRaydiumRoute(
inputMint,
outputMint,
toUiDecimals(amount, inputTokenDecimals),
toUiDecimals(amount, inputTokenDecimals!),
slippage,
connection,
wallet,
mangoAccountSwap,
!mangoAccount,
)
if (raydiumRoute) {
routes.push(raydiumRoute)
}
routes.push(raydiumRoute)
}
if (mode === 'ALL' || mode === 'JUPITER' || mode === 'JUPITER_DIRECT') {
if (
routingMode === 'ALL_AND_JUPITER_DIRECT' ||
routingMode === 'JUPITER_DIRECT'
) {
const jupiterDirectRoute = fetchJupiterRoute(
inputMint,
outputMint,
amount,
slippage,
swapMode,
true,
maxAccounts,
connection,
wallet,
)
routes.push(jupiterDirectRoute)
}
if (isMultiRoutingMode(routingMode) || routingMode === 'JUPITER') {
const jupiterRoute = fetchJupiterRoute(
inputMint,
outputMint,
amount,
slippage,
swapMode,
mode === 'JUPITER_DIRECT' ? true : false,
false,
maxAccounts,
connection,
wallet,
)
if (jupiterRoute) {
routes.push(jupiterRoute)
}
routes.push(jupiterRoute)
}
if (isMultiRoutingMode(routingMode) || routingMode === 'MANGO') {
const mangoRoute = fetchMangoRoute(
inputMint,
outputMint,
amount,
slippage,
swapMode,
)
routes.push(mangoRoute)
}
const results = await Promise.allSettled(routes)
const responses = results
.filter((x) => x.status === 'fulfilled' && x.value?.bestRoute !== null)
.map((x) => (x as any).value)
if (!responses.length) {
throw 'No route found'
}
const sortedByBiggestOutAmount = (
responses as {
bestRoute: JupiterV6RouteInfo
@ -258,9 +348,10 @@ export const handleGetRoutes = async (
? Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
: Number(a.bestRoute.inAmount) - Number(b.bestRoute.inAmount),
)
return {
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
bestRoute: sortedByBiggestOutAmount.length
? sortedByBiggestOutAmount[0]?.bestRoute
: null,
}
} catch (e) {
return {
@ -277,8 +368,7 @@ const useQuoteRoutes = ({
swapMode,
wallet,
mangoAccount,
mode = 'ALL',
mangoAccountSwap,
routingMode = 'ALL',
enabled,
}: useQuoteRoutesPropTypes) => {
const connection = mangoStore((s) => s.connection)
@ -297,29 +387,54 @@ const useQuoteRoutes = ({
const res = useQuery<{ bestRoute: JupiterV6RouteInfo | null }, Error>(
[
'swap-routes',
[
'swap-routes',
nativeAmount.toString(),
inputMint,
outputMint,
swapMode,
wallet,
routingMode,
],
inputMint,
outputMint,
amount,
slippage,
swapMode,
wallet,
mode,
routingMode,
],
async () =>
handleGetRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode,
wallet,
mangoAccount,
mode,
connection,
decimals,
mangoAccountSwap,
),
async () => {
if (
isMultiRoutingMode(routingMode) ||
isRaydiumRoutingMode(routingMode)
) {
return handleGetRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode,
wallet,
mangoAccount,
routingMode,
connection,
decimals,
)
} else {
return handleGetRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode,
wallet,
mangoAccount,
routingMode,
connection,
)
}
},
{
cacheTime: 1000 * 60,
staleTime: 1000 * 3,

View File

@ -231,8 +231,7 @@ export default function SpotMarketOrderSwapForm() {
swapMode: 'ExactIn',
wallet: publicKey?.toBase58(),
mangoAccount,
mangoAccountSwap: true,
mode: 'JUPITER',
routingMode: 'ALL_AND_JUPITER_DIRECT',
enabled: () =>
!!(
inputBank?.mint &&

View File

@ -130,6 +130,10 @@ const initMangoClient = (
new PublicKey('AFrYBhb5wKQtxRS9UA9YRS4V3dwFm7SqmS6DHKq6YVgo'), //Bsol
new PublicKey('7yyaeuJ1GGtVBLT2z2xub5ZWYKaNhF28mj1RdV4VDFVk'), //jitoSol
new PublicKey('E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'), //mSol
new PublicKey('8ihFLu5FimgTQ1Unh4dVyEHUGodJ5gJQCrQf4KUVB9bN'), //bonk
new PublicKey('AwpALBTXcaz2t6BayXvQQu7eZ6h7u2UNRCQNmD9ShY7Z'), //blze
new PublicKey('6ABgrEZk8urs6kJ1JNdC1sspH5zKXRqxy8sg3ZG2cQps'), //wif
new PublicKey('4dusJxxxiYrMTLGYS6cCAyu3gPn2xXLBjS7orMToZHi1'), //mnde
],
multipleConnections: opts.multipleConnections,
idsSource: 'api',

View File

@ -105,7 +105,7 @@ export const PROFILE_CATEGORIES = [
'yolo',
]
export const MANGO_ROUTER_API_URL = 'https://api.mngo.cloud/router/v1'
export const MANGO_ROUTER_API_URL = 'https://router-1.fly.dev'
export const MANGO_DATA_API_URL = 'https://api.mngo.cloud/data/v4'

View File

@ -269,6 +269,7 @@ export const getSwapTransaction = async (
slippage,
directionIn,
)
console.log(bestRoute, '@@@')
const tokenInAta = getAssociatedTokenAddressSync(
new PublicKey(directionIn ? bestRoute.outputMint : bestRoute.inputMint),