Merge pull request #61 from blockworks-foundation/feature/mang-router-v2

mango router
This commit is contained in:
tylersssss 2023-01-17 23:00:43 -05:00 committed by GitHub
commit 5e17c81df7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 271 additions and 109 deletions

View File

@ -23,7 +23,7 @@ import { Transition } from '@headlessui/react'
import Button, { IconButton } from '../shared/Button'
import Loading from '../shared/Loading'
import { EnterBottomExitBottom } from '../shared/Transitions'
import useJupiterRoutes from './useJupiterRoutes'
import useQuoteRoutes from './useQuoteRoutes'
import SheenLoader from '../shared/SheenLoader'
import { HealthType } from '@blockworks-foundation/mango-v4'
import {
@ -80,7 +80,7 @@ const SwapForm = () => {
const [debouncedAmountIn] = useDebounce(amountInFormValue, 300)
const [debouncedAmountOut] = useDebounce(amountOutFormValue, 300)
const { mangoAccount } = useMangoAccount()
const { connected } = useWallet()
const { connected, publicKey } = useWallet()
const amountInAsDecimal: Decimal | null = useMemo(() => {
return Number(debouncedAmountIn)
@ -94,12 +94,13 @@ const SwapForm = () => {
: new Decimal(0)
}, [debouncedAmountOut])
const { bestRoute, routes } = useJupiterRoutes({
const { bestRoute, routes } = useQuoteRoutes({
inputMint: inputBank?.mint.toString() || USDC_MINT,
outputMint: outputBank?.mint.toString() || MANGO_MINT,
amount: swapMode === 'ExactIn' ? debouncedAmountIn : debouncedAmountOut,
slippage,
swapMode,
wallet: publicKey?.toBase58(),
})
const setAmountInFormValue = useCallback(

View File

@ -42,6 +42,7 @@ import Tooltip from '@components/shared/Tooltip'
import { Disclosure } from '@headlessui/react'
import RoutesModal from './RoutesModal'
import useMangoAccount from 'hooks/useMangoAccount'
import { createAssociatedTokenAccountIdempotentInstruction } from '@blockworks-foundation/mango-v4'
type JupiterRouteInfoProps = {
amountIn: Decimal
@ -78,6 +79,40 @@ const deserializeJupiterIxAndAlt = async (
return [decompiledMessage.instructions, addressLookupTables]
}
const prepareMangoRouterInstructions = async (
selectedRoute: RouteInfo,
inputMint: PublicKey,
outputMint: PublicKey,
userPublicKey: PublicKey
): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => {
if (!selectedRoute || !selectedRoute.mints || !selectedRoute.instructions) {
return [[], []]
}
const mintsToFilterOut = [inputMint, outputMint]
const filteredOutMints = [
...selectedRoute.mints.filter(
(routeMint) =>
!mintsToFilterOut.find((filterOutMint) =>
filterOutMint.equals(routeMint)
)
),
]
const additionalInstructions = []
for (const mint of filteredOutMints) {
const ix = await createAssociatedTokenAccountIdempotentInstruction(
userPublicKey,
userPublicKey,
mint
)
additionalInstructions.push(ix)
}
const instructions = [
...additionalInstructions,
...selectedRoute.instructions,
]
return [instructions, []]
}
const fetchJupiterTransaction = async (
connection: Connection,
selectedRoute: RouteInfo,
@ -217,14 +252,22 @@ const SwapReviewRouteInfo = ({
if (!mangoAccount || !group || !inputBank || !outputBank) return
setSubmitting(true)
const [ixs, alts] = await fetchJupiterTransaction(
connection,
selectedRoute,
mangoAccount.owner,
slippage,
inputBank.mint,
outputBank.mint
)
const [ixs, alts] =
selectedRoute.routerName === 'Mango'
? await prepareMangoRouterInstructions(
selectedRoute,
inputBank.mint,
outputBank.mint,
mangoAccount.owner
)
: await fetchJupiterTransaction(
connection,
selectedRoute,
mangoAccount.owner,
slippage,
inputBank.mint,
outputBank.mint
)
try {
const tx = await client.marginTrade({

View File

@ -1,95 +0,0 @@
import { useQuery } from '@tanstack/react-query'
import Decimal from 'decimal.js'
import { RouteInfo } from 'types/jupiter'
import useJupiterSwapData from './useJupiterSwapData'
type useJupiterPropTypes = {
inputMint: string
outputMint: string
amount: string
slippage: number
swapMode: string
}
const fetchJupiterRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0
) => {
{
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
feeBps: feeBps.toString(),
swapMode,
}).toString()
const response = await fetch(
`https://quote-api.jup.ag/v4/quote?${paramsString}`
)
const res = await response.json()
const data = res.data
return {
routes: res.data,
bestRoute: data[0],
}
}
}
const useJupiterRoutes = ({
inputMint,
outputMint,
amount,
slippage,
swapMode,
}: useJupiterPropTypes) => {
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
const decimals =
swapMode === 'ExactIn'
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount =
amount && !Number.isNaN(+amount)
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, amount, slippage, swapMode],
async () =>
fetchJupiterRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode
),
{
enabled: amount ? true : false,
}
)
return amount
? {
...(res.data ?? {
routes: [],
bestRoute: undefined,
}),
isLoading: res.isLoading,
}
: {
routes: [],
bestRoute: undefined,
isLoading: false,
}
}
export default useJupiterRoutes

View File

@ -0,0 +1,207 @@
import { PublicKey } from '@solana/web3.js'
import { useQuery } from '@tanstack/react-query'
import Decimal from 'decimal.js'
import { RouteInfo } from 'types/jupiter'
import { MANGO_ROUTER_API_URL } from 'utils/constants'
import useJupiterSwapData from './useJupiterSwapData'
type useQuoteRoutesPropTypes = {
inputMint: string
outputMint: string
amount: string
slippage: number
swapMode: string
wallet: string | undefined | null
}
const fetchJupiterRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0
) => {
{
const paramsString = new URLSearchParams({
inputMint: inputMint.toString(),
outputMint: outputMint.toString(),
amount: amount.toString(),
slippageBps: Math.ceil(slippage * 100).toString(),
feeBps: feeBps.toString(),
swapMode,
}).toString()
const response = await fetch(
`https://quote-api.jup.ag/v4/quote?${paramsString}`
)
const res = await response.json()
const data = res.data
return {
routes: res.data as RouteInfo[],
bestRoute: (data.length ? data[0] : null) as RouteInfo | null,
}
}
}
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 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}/swap?${paramsString}`)
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,
}
}
}
const handleGetRoutes = async (
inputMint = 'So11111111111111111111111111111111111111112',
outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
amount = 0,
slippage = 50,
swapMode = 'ExactIn',
feeBps = 0,
wallet: string | undefined | null
) => {
wallet ||= PublicKey.default.toBase58()
const results = await Promise.allSettled([
fetchMangoRoutes(
inputMint,
outputMint,
amount,
slippage,
swapMode,
feeBps,
wallet
),
fetchJupiterRoutes(
inputMint,
outputMint,
amount,
slippage,
swapMode,
feeBps
),
])
const responses = results
.filter((x) => x.status === 'fulfilled' && x.value.bestRoute !== null)
.map((x) => (x as any).value)
const sortedByBiggestOutAmount = (
responses as {
routes: RouteInfo[]
bestRoute: RouteInfo
}[]
).sort(
(a, b) => Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount)
)
return {
routes: sortedByBiggestOutAmount[0].routes,
bestRoute: sortedByBiggestOutAmount[0].bestRoute,
}
}
const useQuoteRoutes = ({
inputMint,
outputMint,
amount,
slippage,
swapMode,
wallet,
}: useQuoteRoutesPropTypes) => {
const { inputTokenInfo, outputTokenInfo } = useJupiterSwapData()
const decimals =
swapMode === 'ExactIn'
? inputTokenInfo?.decimals || 6
: outputTokenInfo?.decimals || 6
const nativeAmount =
amount && !Number.isNaN(+amount)
? new Decimal(amount).mul(10 ** decimals)
: new Decimal(0)
const res = useQuery<{ routes: RouteInfo[]; bestRoute: RouteInfo }, Error>(
['swap-routes', inputMint, outputMint, amount, slippage, swapMode, wallet],
async () =>
handleGetRoutes(
inputMint,
outputMint,
nativeAmount.toNumber(),
slippage,
swapMode,
0,
wallet
),
{
enabled: amount ? true : false,
}
)
return amount
? {
...(res.data ?? {
routes: [],
bestRoute: undefined,
}),
isLoading: res.isLoading,
}
: {
routes: [],
bestRoute: undefined,
isLoading: false,
}
}
export default useQuoteRoutes

View File

@ -1,13 +1,14 @@
import { AccountMeta } from '@solana/web3.js'
import { AccountInfo } from '@solana/web3.js'
import { AccountInfo, PublicKey, TransactionInstruction } from '@solana/web3.js'
import Decimal from 'decimal.js'
export declare type SideType = typeof Side.Ask | typeof Side.Bid
export declare const Side: {
Bid: {
// eslint-disable-next-line @typescript-eslint/ban-types
bid: {}
}
Ask: {
// eslint-disable-next-line @typescript-eslint/ban-types
ask: {}
}
}
@ -57,7 +58,7 @@ export interface ExactOutSwapParams extends SwapParams {
}
export declare type AccountInfoMap = Map<string, AccountInfo<Buffer> | null>
declare type AmmLabel =
export declare type AmmLabel =
| 'Aldrin'
| 'Crema'
| 'Cropper'
@ -127,6 +128,9 @@ export interface RouteInfo {
priceImpactPct: number
slippageBps: number
swapMode: SwapMode
instructions?: TransactionInstruction[]
mints?: PublicKey[]
routerName?: 'Mango'
}
export type Routes = {

View File

@ -58,6 +58,8 @@ export const PROFILE_CATEGORIES = [
export const CHART_DATA_FEED = `https://dry-ravine-67635.herokuapp.com/tv`
export const MANGO_ROUTER_API_URL = 'https://api.mngo.cloud/router/v1'
export const DEFAULT_MARKET_NAME = 'SOL/USDC'
export const MIN_SOL_BALANCE = 0.001