extract jupiter logic into hook

This commit is contained in:
tjs 2022-08-03 17:46:37 -04:00
parent 4bcf4dee72
commit 8ec490c042
15 changed files with 176 additions and 116 deletions

View File

@ -7,7 +7,7 @@ import React, { ChangeEvent, useCallback, useMemo, useState } from 'react'
import mangoStore from '../../store/state'
import { ModalProps } from '../../types/modal'
import { notify } from '../../utils/notifications'
import { formatFixedDecimals } from '../../utils/numbers'
import { floorToDecimal, formatFixedDecimals } from '../../utils/numbers'
import { TokenAccount } from '../../utils/tokens'
import ButtonGroup from '../forms/ButtonGroup'
import Input from '../forms/Input'
@ -28,17 +28,22 @@ type ModalCombinedProps = DepositModalProps & ModalProps
const walletBalanceForToken = (
walletTokens: TokenAccount[],
token: string
): number => {
): { maxAmount: number; maxDecimals: number } => {
const group = mangoStore.getState().group
const bank = group?.banksMap.get(token)
if (!bank) return 0
const tokenMint = bank?.mint
const walletToken = tokenMint
? walletTokens.find((t) => t.mint.toString() === tokenMint.toString())
: null
let walletToken
if (bank) {
const tokenMint = bank?.mint
walletToken = tokenMint
? walletTokens.find((t) => t.mint.toString() === tokenMint.toString())
: null
}
return walletToken ? walletToken.uiAmount : 0
return {
maxAmount: walletToken ? walletToken.uiAmount : 0,
maxDecimals: bank?.mintDecimals || 6,
}
}
function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
@ -57,15 +62,18 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
}, [walletTokens, selectedToken])
const setMax = useCallback(() => {
setInputAmount(tokenMax.toString())
setInputAmount(tokenMax.maxAmount.toString())
}, [tokenMax])
const handleSizePercentage = useCallback(
(percentage: string) => {
setSizePercentage(percentage)
const max = tokenMax
const amount = (Number(percentage) / 100) * max
let amount = (Number(percentage) / 100) * tokenMax.maxAmount
if (percentage !== '100') {
amount = floorToDecimal(amount, tokenMax.maxDecimals)
}
setInputAmount(amount.toString())
},
[tokenMax]
@ -124,10 +132,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
<h2 className="mb-4 text-center">{t('select-token')}</h2>
<DepositTokenList onSelect={handleSelectToken} />
</EnterBottomExitBottom>
<FadeInFadeOut
className="flex h-[430px] flex-col justify-between"
show={isOpen}
>
<FadeInFadeOut className="flex flex-col justify-between" show={isOpen}>
<div>
<h2 className="mb-4 text-center">{t('deposit')}</h2>
<div className="grid grid-cols-2 pb-6">
@ -138,7 +143,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
{t('wallet-balance')}
</span>
<span className="text-th-fgd-1 underline">
{formatFixedDecimals(tokenMax)}
{formatFixedDecimals(tokenMax.maxAmount)}
</span>
</LinkButton>
</div>
@ -183,7 +188,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
/>
</div>
</div>
<div className="space-y-2 border-y border-th-bkg-3 py-4">
{/* <div className="space-y-2 border-y border-th-bkg-3 py-4">
<div className="flex justify-between">
<p>{t('health-impact')}</p>
<p className="text-th-green">+12%</p>
@ -205,7 +210,7 @@ function DepositModal({ isOpen, onClose, token }: ModalCombinedProps) {
<p>{t('collateral-value')}</p>
<p className="text-th-fgd-1">$800.00</p>
</div>
</div>
</div> */}
</div>
<Button
onClick={handleDeposit}

View File

@ -31,9 +31,9 @@ const DepositTokenItem = ({
{formatDecimal(bank.getDepositRate().toNumber(), 2)}%
</p>
</div>
<div className="col-span-1 flex justify-end">
{/* <div className="col-span-1 flex justify-end">
<p className="text-th-fgd-1">0.8x</p>
</div>
</div> */}
</button>
)
}

View File

@ -18,11 +18,11 @@ const DepositTokenList = ({ onSelect }: { onSelect: (x: any) => void }) => {
<div className="col-span-1 flex justify-end">
<p className="text-xs">{t('rate')}</p>
</div>
<div className="col-span-1 flex justify-end">
{/* <div className="col-span-1 flex justify-end">
<p className="whitespace-nowrap text-xs">
{t('collateral-multiplier')}
</p>
</div>
</div> */}
</div>
<div className="space-y-2">
{banks.map((bank, index) => (

View File

@ -19,7 +19,6 @@ type JupiterRoutesProps = {
slippage: number
submitting: boolean
handleSwap: (x: TransactionInstruction[]) => void
setAmountOut: (x?: number) => void
onClose: () => void
jupiter: Jupiter | undefined
routes: RouteInfo[] | undefined

View File

@ -24,7 +24,7 @@ const LeverageSlider = ({
if (inputToken && outputToken) {
max = toUiDecimals(
mangoAccount
.getMaxSourceForTokenSwap(group, inputToken, outputToken, 1)
.getMaxSourceForTokenSwap(group, inputToken, outputToken, 0.9)
.toNumber()
)
} else {

View File

@ -1,26 +1,31 @@
import { useState, ChangeEvent, useCallback, useEffect, useMemo } from 'react'
import { TransactionInstruction } from '@solana/web3.js'
import { ArrowDownIcon } from '@heroicons/react/solid'
import mangoStore, { CLUSTER } from '../../store/state'
import { Jupiter, RouteInfo } from '@jup-ag/core'
import mangoStore from '../../store/state'
import { RouteInfo } from '@jup-ag/core'
import { Token } from '../../types/jupiter'
import ContentBox from '../shared/ContentBox'
import { notify } from '../../utils/notifications'
import JupiterRoutes from './JupiterRoutes'
import TokenSelect from '../TokenSelect'
import useDebounce from '../shared/useDebounce'
import { formatFixedDecimals, numberFormat } from '../../utils/numbers'
import {
floorToDecimal,
formatFixedDecimals,
numberFormat,
} from '../../utils/numbers'
import LeverageSlider from './LeverageSlider'
import Input from '../forms/Input'
import { useTranslation } from 'next-i18next'
import SelectToken from './SelectToken'
import { Transition } from '@headlessui/react'
import Switch from '../forms/Switch'
import Button, { IconButton, LinkButton } from '../shared/Button'
import Button, { LinkButton } from '../shared/Button'
import ButtonGroup from '../forms/ButtonGroup'
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
import Loading from '../shared/Loading'
import { EnterBottomExitBottom } from '../shared/Transitions'
import useJupiter from './useJupiter'
const getBestRoute = (routesInfos: RouteInfo[]) => {
return routesInfos[0]
@ -40,14 +45,14 @@ const MaxWalletBalance = ({
const group = mangoStore.getState().group
const bank = group?.banksMap.get(inputToken)
if (!group || !bank || !mangoAccount) return 0
if (!group || !bank || !mangoAccount) return 0.0
const balance = mangoAccount.getUi(bank)
return balance
return floorToDecimal(balance, bank.mintDecimals)
}, [inputToken, mangoAccount])
const setMaxInputAmount = () => {
setAmountIn(tokenInMax.toString())
setAmountIn(tokenInMax)
}
return (
@ -62,12 +67,8 @@ const MaxWalletBalance = ({
const Swap = () => {
const { t } = useTranslation('common')
const [jupiter, setJupiter] = useState<Jupiter>()
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>()
const [outputTokenInfo, setOutputTokenInfo] = useState<Token>()
const [routes, setRoutes] = useState<RouteInfo[]>()
const [amountIn, setAmountIn] = useState('')
const [amountOut, setAmountOut] = useState<number>()
const [submitting, setSubmitting] = useState(false)
const [animateSwitchArrow, setAnimateSwitchArrow] = useState(0)
const [showTokenSelect, setShowTokenSelect] = useState('')
@ -83,80 +84,17 @@ const Swap = () => {
const connected = mangoStore((s) => s.connected)
const debouncedAmountIn = useDebounce(amountIn, 400)
useEffect(() => {
const connection = mangoStore.getState().connection
const loadJupiter = async () => {
const jupiter = await Jupiter.load({
connection,
cluster: CLUSTER,
// platformFeeAndAccounts: NO_PLATFORM_FEE,
routeCacheDuration: 10_000, // Will not refetch data on computeRoutes for up to 10 seconds
})
setJupiter(jupiter)
}
try {
loadJupiter()
} catch (e) {
console.warn(e)
}
}, [])
const { amountOut, jupiter, outputTokenInfo, routes } = useJupiter({
inputTokenSymbol: inputToken,
outputTokenSymbol: outputToken,
inputAmount: Number(debouncedAmountIn),
slippage,
})
useEffect(() => {
const group = mangoStore.getState().group
if (!group) return
const tokens = mangoStore.getState().jupiterTokens
const loadRoutes = async () => {
const inputBank = group!.banksMap.get(inputToken)
const outputBank = group!.banksMap.get(outputToken)
if (!inputBank || !outputBank) return
if (!debouncedAmountIn) {
setAmountOut(undefined)
setSelectedRoute(undefined)
} else {
try {
const computedRoutes = await jupiter
?.computeRoutes({
inputMint: inputBank.mint, // Mint address of the input token
outputMint: outputBank.mint, // Mint address of the output token
inputAmount:
Number(debouncedAmountIn) * 10 ** inputBank.mintDecimals, // raw input amount of tokens
slippage, // The slippage in % terms
filterTopNResult: 10,
onlyDirectRoutes: true,
})
.catch((e) => {
console.log('Error loading Jupiter:', e)
return
})
const tokenOut = tokens.find(
(t: any) => t.address === outputBank.mint.toString()
)
setOutputTokenInfo(tokenOut)
const routesInfosWithoutRaydium = computedRoutes?.routesInfos.filter(
(r) => {
if (r.marketInfos.length > 1) {
for (const mkt of r.marketInfos) {
if (mkt.amm.label === 'Raydium') return false
}
}
return true
}
)
if (routesInfosWithoutRaydium?.length) {
setRoutes(routesInfosWithoutRaydium)
const bestRoute = getBestRoute(computedRoutes!.routesInfos)
setSelectedRoute(bestRoute)
setAmountOut(toUiDecimals(bestRoute.outAmount, tokenOut?.decimals))
}
} catch (e) {
console.warn(e)
}
}
}
loadRoutes()
}, [inputToken, outputToken, jupiter, slippage, debouncedAmountIn])
console.log('setting selected route')
setSelectedRoute(routes[0])
}, [routes])
const handleAmountInChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
@ -274,7 +212,6 @@ const Swap = () => {
slippage={slippage}
handleSwap={handleSwap}
submitting={submitting}
setAmountOut={setAmountOut}
outputTokenInfo={outputTokenInfo}
jupiter={jupiter}
routes={routes}

View File

@ -0,0 +1,114 @@
import { toUiDecimals } from '@blockworks-foundation/mango-v4'
import { Jupiter, RouteInfo } from '@jup-ag/core'
import { useEffect, useState } from 'react'
import mangoStore, { CLUSTER } from '../../store/state'
import { Token } from '../../types/jupiter'
type useJupiterPropTypes = {
inputTokenSymbol: string
outputTokenSymbol: string
inputAmount: number
slippage: number
}
type RouteParams = {
routes: RouteInfo[]
outputTokenInfo: Token | undefined
amountOut: number
}
const defaultComputedInfo = {
routes: [],
outputTokenInfo: undefined,
amountOut: 0,
}
const useJupiter = ({
inputTokenSymbol,
outputTokenSymbol,
inputAmount,
slippage,
}: useJupiterPropTypes) => {
const [jupiter, setJupiter] = useState<Jupiter>()
const [computedInfo, setComputedInfo] =
useState<RouteParams>(defaultComputedInfo)
useEffect(() => {
const connection = mangoStore.getState().connection
const loadJupiter = async () => {
const jupiter = await Jupiter.load({
connection,
cluster: CLUSTER,
// platformFeeAndAccounts: NO_PLATFORM_FEE,
routeCacheDuration: 10_000, // Will not refetch data on computeRoutes for up to 10 seconds
})
setJupiter(jupiter)
}
try {
loadJupiter()
} catch (e) {
console.warn(e)
}
}, [])
useEffect(() => {
const group = mangoStore.getState().group
if (!group) return
const tokens = mangoStore.getState().jupiterTokens
const loadRoutes = async () => {
const inputBank = group.banksMap.get(inputTokenSymbol)
const outputBank = group.banksMap.get(outputTokenSymbol)
if (!inputBank || !outputBank) return
if (!inputAmount) {
setComputedInfo(defaultComputedInfo)
} else {
try {
const computedRoutes = await jupiter
?.computeRoutes({
inputMint: inputBank.mint, // Mint address of the input token
outputMint: outputBank.mint, // Mint address of the output token
inputAmount: inputAmount * 10 ** inputBank.mintDecimals, // raw input amount of tokens
slippage, // The slippage in % terms
filterTopNResult: 10,
onlyDirectRoutes: true,
})
.catch((e) => {
console.error('Error computing Jupiter routes:', e)
return
})
const tokenOut = tokens.find(
(t: any) => t.address === outputBank.mint.toString()
)
const routesInfosWithoutRaydium = computedRoutes?.routesInfos.filter(
(r) => {
if (r.marketInfos.length > 1) {
for (const mkt of r.marketInfos) {
if (mkt.amm.label === 'Raydium') return false
}
}
return true
}
)
if (routesInfosWithoutRaydium?.length) {
const bestRoute = routesInfosWithoutRaydium[0]
setComputedInfo({
routes: routesInfosWithoutRaydium,
outputTokenInfo: tokenOut,
amountOut: toUiDecimals(bestRoute.outAmount, tokenOut?.decimals),
})
}
} catch (e) {
console.warn(e)
}
}
}
loadRoutes()
}, [inputTokenSymbol, outputTokenSymbol, jupiter, slippage, inputAmount])
return { jupiter, ...computedInfo }
}
export default useJupiter

View File

@ -11,7 +11,7 @@
"prepare": "husky install"
},
"dependencies": {
"@blockworks-foundation/mango-v4": "git+https://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:x-oauth-basic@github.com/blockworks-foundation/mango-v4.git",
"@blockworks-foundation/mango-v4": "git+https://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:x-oauth-basic@github.com/blockworks-foundation/mango-v4.git#main",
"@headlessui/react": "^1.6.6",
"@heroicons/react": "^1.0.6",
"@jup-ag/core": "^1.0.0-beta.27",

View File

@ -65,6 +65,7 @@
"trade": "Trade",
"update": "Update",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Disconnected from wallet",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
}

View File

@ -65,6 +65,7 @@
"trade": "Trade",
"update": "Update",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "Billetera desconectada",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
}

View File

@ -65,6 +65,7 @@
"trade": "Trade",
"update": "Update",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "断开钱包连结",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
}

View File

@ -65,6 +65,7 @@
"trade": "Trade",
"update": "Update",
"wallet-balance": "Wallet Balance",
"wallet-disconnected": "斷開錢包連結",
"withdraw": "Withdraw",
"withdrawal-value": "Withdrawal Value"
}

View File

@ -23,9 +23,7 @@ import { Token } from '../types/jupiter'
import { getProfilePicture, ProfilePicture } from '@solflare-wallet/pfp'
import { TOKEN_LIST_URL } from '@jup-ag/core'
const DEVNET_GROUP = new PublicKey(
'A9XhGqUUjV992cD36qWDY8wDiZnGuCaUWtSE3NGXjDCb'
)
const GROUP = new PublicKey('A9XhGqUUjV992cD36qWDY8wDiZnGuCaUWtSE3NGXjDCb')
export const connection = new web3.Connection(
'https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88',
@ -173,7 +171,8 @@ const mangoStore = create<MangoStore>(
try {
const set = get().set
const client = get().client
const group = await client.getGroup(DEVNET_GROUP)
const group = await client.getGroup(GROUP)
const markets = await client.serum3GetMarkets(
group,
group.banksMap.get('BTC')?.tokenIndex,
@ -189,8 +188,8 @@ const mangoStore = create<MangoStore>(
}
},
fetchMangoAccount: async (wallet) => {
const set = get().set
try {
const set = get().set
const group = get().group
if (!group) throw new Error('Group not loaded')
@ -334,7 +333,7 @@ const mangoStore = create<MangoStore>(
try {
const set = get().set
const client = get().client
const group = await client.getGroup(DEVNET_GROUP)
const group = await client.getGroup(GROUP)
set((state) => {
state.group = group

View File

@ -18,7 +18,7 @@ export const numberFormat = new Intl.NumberFormat('en', {
maximumSignificantDigits: 7,
})
const floorToDecimal = (value: number, decimals: number) => {
export const floorToDecimal = (value: number, decimals: number) => {
return Math.floor(value * 10 ** decimals) / 10 ** decimals
}

View File

@ -34,6 +34,8 @@ export async function getTokenAccountsByOwnerWithWrappedSol(
// fetch data
const [solResp, tokenResp] = await Promise.all([solReq, tokenReq])
console.log(tokenResp.value)
// parse token accounts
const tokenAccounts = tokenResp.value.map((t) => {
return {