extract jupiter logic into hook
This commit is contained in:
parent
4bcf4dee72
commit
8ec490c042
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"trade": "Trade",
|
||||
"update": "Update",
|
||||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "Disconnected from wallet",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawal-value": "Withdrawal Value"
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"trade": "Trade",
|
||||
"update": "Update",
|
||||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "Billetera desconectada",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawal-value": "Withdrawal Value"
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"trade": "Trade",
|
||||
"update": "Update",
|
||||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "断开钱包连结",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawal-value": "Withdrawal Value"
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
"trade": "Trade",
|
||||
"update": "Update",
|
||||
"wallet-balance": "Wallet Balance",
|
||||
"wallet-disconnected": "斷開錢包連結",
|
||||
"withdraw": "Withdraw",
|
||||
"withdrawal-value": "Withdrawal Value"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue