2021-12-09 09:23:19 -08:00
|
|
|
import { useEffect, useMemo, useState, FunctionComponent } from 'react'
|
|
|
|
import { useJupiter, RouteInfo } from '@jup-ag/react-hook'
|
2021-12-18 13:04:19 -08:00
|
|
|
import { TOKEN_LIST_URL } from '@jup-ag/core'
|
2021-12-09 09:23:19 -08:00
|
|
|
import { PublicKey } from '@solana/web3.js'
|
|
|
|
import useMangoStore from '../stores/useMangoStore'
|
|
|
|
import {
|
|
|
|
actionsSelector,
|
|
|
|
connectionSelector,
|
|
|
|
walletConnectedSelector,
|
|
|
|
walletSelector,
|
|
|
|
} from '../stores/selectors'
|
|
|
|
import { sortBy, sum } from 'lodash'
|
|
|
|
import {
|
|
|
|
ChevronDownIcon,
|
|
|
|
ChevronUpIcon,
|
|
|
|
SwitchVerticalIcon,
|
|
|
|
} from '@heroicons/react/outline'
|
|
|
|
import { sleep } from '../utils'
|
|
|
|
import SwapTokenSelect from './SwapTokenSelect'
|
|
|
|
import { notify } from '../utils/notifications'
|
2021-12-18 13:04:19 -08:00
|
|
|
import { Token } from '../@types/types'
|
2021-12-19 10:17:43 -08:00
|
|
|
import { zeroKey } from '@blockworks-foundation/mango-client'
|
2021-12-09 09:23:19 -08:00
|
|
|
|
|
|
|
type UseJupiterProps = Parameters<typeof useJupiter>[0]
|
|
|
|
|
|
|
|
const JupiterForm: FunctionComponent = () => {
|
|
|
|
const wallet = useMangoStore(walletSelector)
|
|
|
|
const connection = useMangoStore(connectionSelector)
|
|
|
|
const connected = useMangoStore(walletConnectedSelector)
|
|
|
|
const actions = useMangoStore(actionsSelector)
|
|
|
|
const walletTokens = useMangoStore((s) => s.wallet.tokens)
|
|
|
|
|
|
|
|
const [routesToShow, setRoutesToShow] = useState<number>(2)
|
|
|
|
const [maxHeight, setMaxHeight] = useState<string>('170px')
|
|
|
|
const [depositAndFee, setDepositAndFee] = useState(null)
|
|
|
|
const [selectedRoute, setSelectedRoute] = useState<RouteInfo>(null)
|
|
|
|
const [showInputTokenSelect, setShowInputTokenSelect] = useState(false)
|
|
|
|
const [showOutputTokenSelect, setShowOutputTokenSelect] = useState(false)
|
2021-12-18 13:04:19 -08:00
|
|
|
const [tokens, setTokens] = useState<Token[]>([])
|
2021-12-19 10:17:43 -08:00
|
|
|
const [inputTokenStats, setInputTokenStats] = useState(null)
|
|
|
|
const [outputTokenStats, setOutputTokenStats] = useState(null)
|
|
|
|
const [coinGeckoList, setCoinGeckoList] = useState(null)
|
2021-12-09 09:23:19 -08:00
|
|
|
const [formValue, setFormValue] = useState<UseJupiterProps>({
|
|
|
|
amount: null,
|
|
|
|
inputMint: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'),
|
|
|
|
outputMint: new PublicKey('MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'),
|
|
|
|
slippage: 0.5,
|
|
|
|
})
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const [inputTokenInfo, outputTokenInfo] = useMemo(() => {
|
|
|
|
return [
|
2021-12-18 13:04:19 -08:00
|
|
|
tokens.find(
|
2021-12-18 21:53:20 -08:00
|
|
|
(item) => item?.address === formValue.inputMint?.toBase58() || ''
|
2021-12-18 13:04:19 -08:00
|
|
|
),
|
|
|
|
tokens.find(
|
2021-12-18 21:53:20 -08:00
|
|
|
(item) => item?.address === formValue.outputMint?.toBase58() || ''
|
2021-12-18 13:04:19 -08:00
|
|
|
),
|
2021-12-09 09:23:19 -08:00
|
|
|
]
|
|
|
|
}, [
|
|
|
|
formValue.inputMint?.toBase58(),
|
|
|
|
formValue.outputMint?.toBase58(),
|
2021-12-18 13:04:19 -08:00
|
|
|
tokens,
|
2021-12-09 09:23:19 -08:00
|
|
|
])
|
|
|
|
|
2021-12-19 10:17:43 -08:00
|
|
|
useEffect(() => {
|
|
|
|
const fetchCoinGeckoList = async () => {
|
|
|
|
const response = await fetch(
|
|
|
|
'https://api.coingecko.com/api/v3/coins/list'
|
|
|
|
)
|
|
|
|
const data = await response.json()
|
|
|
|
setCoinGeckoList(data)
|
|
|
|
}
|
|
|
|
|
|
|
|
fetchCoinGeckoList()
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!coinGeckoList?.length) return
|
|
|
|
|
|
|
|
const fetchInputTokenStats = async () => {
|
|
|
|
const id = coinGeckoList.find(
|
|
|
|
(x) =>
|
|
|
|
x?.symbol?.toLowerCase() === inputTokenInfo?.symbol?.toLowerCase()
|
|
|
|
)?.id
|
|
|
|
const results = await fetch(
|
|
|
|
`https://api.coingecko.com/api/v3/coins/${id}/market_chart?vs_currency=usd&days=1`
|
|
|
|
)
|
|
|
|
const json = await results.json()
|
|
|
|
console.log('json i', json)
|
|
|
|
|
|
|
|
setInputTokenStats(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
const fetchOutputTokenStats = async () => {
|
|
|
|
const id = coinGeckoList.find(
|
|
|
|
(x) =>
|
|
|
|
x?.symbol?.toLowerCase() === outputTokenInfo?.symbol?.toLowerCase()
|
|
|
|
)?.id
|
|
|
|
const results = await fetch(
|
|
|
|
`https://api.coingecko.com/api/v3/coins/${id}/market_chart?vs_currency=usd&days=1`
|
|
|
|
)
|
|
|
|
const json = await results.json()
|
|
|
|
console.log('json o', json)
|
|
|
|
|
|
|
|
setOutputTokenStats(json)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inputTokenInfo) {
|
|
|
|
fetchInputTokenStats()
|
|
|
|
}
|
|
|
|
if (outputTokenInfo) {
|
|
|
|
fetchOutputTokenStats()
|
|
|
|
}
|
|
|
|
}, [inputTokenInfo, outputTokenInfo, coinGeckoList])
|
|
|
|
|
2021-12-09 09:23:19 -08:00
|
|
|
const amountInDecimal = useMemo(() => {
|
|
|
|
return formValue.amount * 10 ** (inputTokenInfo?.decimals || 1)
|
|
|
|
}, [inputTokenInfo, formValue.amount])
|
|
|
|
|
|
|
|
const { routeMap, allTokenMints, routes, loading, exchange, error } =
|
|
|
|
useJupiter({
|
|
|
|
...formValue,
|
|
|
|
amount: amountInDecimal,
|
|
|
|
slippage: formValue.slippage,
|
|
|
|
})
|
|
|
|
|
|
|
|
useEffect(() => {
|
2021-12-18 13:04:19 -08:00
|
|
|
// Fetch token list from Jupiter API
|
|
|
|
fetch(TOKEN_LIST_URL['mainnet-beta'])
|
|
|
|
.then((response) => response.json())
|
|
|
|
.then((result) => {
|
|
|
|
const tokens = allTokenMints.map((mint) =>
|
2021-12-18 21:53:20 -08:00
|
|
|
result.find((item) => item?.address === mint)
|
2021-12-18 13:04:19 -08:00
|
|
|
)
|
|
|
|
setTokens(tokens)
|
|
|
|
})
|
|
|
|
}, [allTokenMints])
|
2021-12-09 09:23:19 -08:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (routes) {
|
|
|
|
setSelectedRoute(routes[0])
|
|
|
|
}
|
|
|
|
}, [routes])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const getDepositAndFee = async () => {
|
|
|
|
const fees = await selectedRoute.getDepositAndFee()
|
|
|
|
setDepositAndFee(fees)
|
|
|
|
}
|
2021-12-18 21:53:20 -08:00
|
|
|
if (selectedRoute && connected) {
|
2021-12-09 09:23:19 -08:00
|
|
|
getDepositAndFee()
|
|
|
|
}
|
|
|
|
}, [selectedRoute])
|
|
|
|
|
2021-12-18 21:53:20 -08:00
|
|
|
const outputTokenMints = useMemo(() => {
|
|
|
|
if (routeMap.size && formValue.inputMint) {
|
|
|
|
const routeOptions = routeMap.get(formValue.inputMint.toString())
|
|
|
|
|
|
|
|
const routeOptionTokens = routeOptions.map((address) => {
|
|
|
|
return tokens.find((t) => {
|
|
|
|
return t?.address === address
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return routeOptionTokens
|
|
|
|
} else {
|
|
|
|
return sortedTokenMints
|
|
|
|
}
|
|
|
|
}, [routeMap, tokens, formValue.inputMint])
|
|
|
|
|
|
|
|
const inputWalletBalance = useMemo(() => {
|
2021-12-09 09:23:19 -08:00
|
|
|
if (walletTokens.length) {
|
|
|
|
const walletToken = walletTokens.find((t) => {
|
2021-12-18 21:53:20 -08:00
|
|
|
return t.account.mint.toString() === inputTokenInfo?.address
|
2021-12-09 09:23:19 -08:00
|
|
|
})
|
|
|
|
|
2021-12-18 21:53:20 -08:00
|
|
|
return walletToken?.uiBalance
|
2021-12-09 09:23:19 -08:00
|
|
|
}
|
|
|
|
return 0.0
|
2021-12-18 21:53:20 -08:00
|
|
|
}, [inputTokenInfo])
|
|
|
|
|
|
|
|
const outputWalletBalance = useMemo(() => {
|
|
|
|
if (walletTokens.length) {
|
|
|
|
const walletToken = walletTokens.find((t) => {
|
|
|
|
return t.account.mint.toString() === outputTokenInfo?.address
|
|
|
|
})
|
|
|
|
|
|
|
|
return walletToken?.uiBalance
|
|
|
|
}
|
|
|
|
return 0.0
|
|
|
|
}, [outputTokenInfo])
|
2021-12-09 09:23:19 -08:00
|
|
|
|
2021-12-19 10:17:43 -08:00
|
|
|
const [inputTokenPrice, inputTokenChange] = useMemo(() => {
|
|
|
|
if (inputTokenStats?.prices?.length) {
|
|
|
|
const price =
|
|
|
|
inputTokenStats.prices[inputTokenStats.prices.length - 1][1].toFixed(2)
|
|
|
|
const change =
|
|
|
|
((inputTokenStats.prices[0][1] -
|
|
|
|
inputTokenStats.prices[inputTokenStats.prices.length - 1][1]) /
|
|
|
|
inputTokenStats.prices[0][1]) *
|
|
|
|
100
|
|
|
|
|
|
|
|
return [price, change]
|
|
|
|
}
|
|
|
|
return [0, 0]
|
|
|
|
}, [inputTokenStats])
|
|
|
|
|
|
|
|
const [outputTokenPrice, outputTokenChange] = useMemo(() => {
|
|
|
|
if (outputTokenStats?.prices?.length) {
|
|
|
|
const price =
|
|
|
|
outputTokenStats.prices[outputTokenStats.prices.length - 1][1].toFixed(
|
|
|
|
2
|
|
|
|
)
|
|
|
|
const change =
|
|
|
|
((outputTokenStats.prices[0][1] -
|
|
|
|
outputTokenStats.prices[outputTokenStats.prices.length - 1][1]) /
|
|
|
|
outputTokenStats.prices[0][1]) *
|
|
|
|
100
|
|
|
|
return [price, change]
|
|
|
|
}
|
|
|
|
return [0, 0]
|
|
|
|
}, [outputTokenStats])
|
|
|
|
|
2021-12-09 09:23:19 -08:00
|
|
|
const handleSelectRoute = (route) => {
|
|
|
|
setSelectedRoute(route)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleSwitchMints = () => {
|
|
|
|
setFormValue((val) => ({
|
|
|
|
...val,
|
|
|
|
inputMint: formValue.outputMint,
|
|
|
|
outputMint: formValue.inputMint,
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleShowMore = () => {
|
|
|
|
setRoutesToShow(routes.length)
|
|
|
|
setMaxHeight('340px')
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleShowLess = () => {
|
|
|
|
setMaxHeight('160px')
|
|
|
|
sleep(700).then(() => {
|
|
|
|
setRoutesToShow(2)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-12-18 13:04:19 -08:00
|
|
|
const sortedTokenMints = sortBy(tokens, (token) => {
|
|
|
|
return token?.symbol?.toLowerCase()
|
2021-12-09 09:23:19 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
const displayedRoutes = routes ? routes.slice(0, routesToShow) : []
|
|
|
|
|
|
|
|
const outAmountUi = selectedRoute
|
|
|
|
? selectedRoute.outAmount / 10 ** (outputTokenInfo?.decimals || 1)
|
|
|
|
: null
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="max-w-md mx-auto">
|
|
|
|
<div className="mt-8 bg-th-bkg-2 rounded-lg px-6 pt-8 pb-8">
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<label htmlFor="inputMint" className="block text-sm font-medium">
|
|
|
|
You pay
|
|
|
|
</label>
|
|
|
|
<div>
|
|
|
|
<label htmlFor="amount" className="text-th-fgd-4">
|
2021-12-18 21:53:20 -08:00
|
|
|
Balance {inputWalletBalance}
|
2021-12-09 09:23:19 -08:00
|
|
|
</label>
|
2021-12-18 21:53:20 -08:00
|
|
|
{connected ? (
|
|
|
|
<button
|
|
|
|
className="ml-1 bg-th-bkg-4 rounded-lg px-2 text-th-fgd-4 text-xs cursor-pointer hover:text-th-primary"
|
|
|
|
onClick={() => {
|
|
|
|
setFormValue((val) => ({
|
|
|
|
...val,
|
|
|
|
amount: inputWalletBalance,
|
|
|
|
}))
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
MAX
|
|
|
|
</button>
|
|
|
|
) : null}
|
2021-12-09 09:23:19 -08:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex justify-between items-center my-2 bg-th-bkg-1 rounded-md py-3">
|
|
|
|
<button
|
|
|
|
className="hover:bg-th-bkg-3 ml-2 px-2 py-2"
|
|
|
|
onClick={() => setShowInputTokenSelect(true)}
|
|
|
|
>
|
|
|
|
<div className="flex items-center">
|
2021-12-18 13:04:19 -08:00
|
|
|
{inputTokenInfo?.logoURI ? (
|
|
|
|
<img
|
|
|
|
src={inputTokenInfo?.logoURI}
|
|
|
|
width="24"
|
|
|
|
height="24"
|
|
|
|
alt={inputTokenInfo?.symbol}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
<div className="text-lg ml-3">{inputTokenInfo?.symbol}</div>
|
2021-12-09 09:23:19 -08:00
|
|
|
<ChevronDownIcon className="h-4 w-4 ml-1" />
|
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
<div>
|
|
|
|
<input
|
|
|
|
name="amount"
|
|
|
|
id="amount"
|
|
|
|
className="bg-th-bkg-1 pr-4 focus:outline-none sm:text-sm rounded-md text-right"
|
|
|
|
value={formValue.amount || ''}
|
|
|
|
placeholder="0.00"
|
|
|
|
type="text"
|
|
|
|
pattern="[0-9]*"
|
|
|
|
onInput={(e: any) => {
|
|
|
|
let newValue = Number(e.target?.value || 0)
|
|
|
|
newValue = Number.isNaN(newValue) ? 0 : newValue
|
|
|
|
setFormValue((val) => ({
|
|
|
|
...val,
|
|
|
|
amount: newValue ? Math.max(newValue, 0) : null,
|
|
|
|
}))
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex justify-center my-4">
|
|
|
|
<button onClick={handleSwitchMints}>
|
|
|
|
<SwitchVerticalIcon className="h-8 w-8 border rounded-full p-1 border-th-fgd-4 text-th-fgd-4 hover:border-th-primary hover:text-th-primary" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<label htmlFor="outputMint" className="font-medium">
|
|
|
|
You receive
|
|
|
|
</label>
|
2021-12-18 21:53:20 -08:00
|
|
|
<span className="text-th-fgd-4">Balance {outputWalletBalance}</span>
|
2021-12-09 09:23:19 -08:00
|
|
|
</div>
|
|
|
|
<div className="flex items-center mt-2">
|
|
|
|
<button
|
|
|
|
className="flex items-center hover:bg-th-bkg-3 px-2 py-2"
|
|
|
|
onClick={() => setShowOutputTokenSelect(true)}
|
|
|
|
>
|
2021-12-18 13:04:19 -08:00
|
|
|
{outputTokenInfo?.logoURI ? (
|
|
|
|
<img
|
|
|
|
src={outputTokenInfo?.logoURI}
|
|
|
|
width="24"
|
|
|
|
height="24"
|
|
|
|
alt={outputTokenInfo?.symbol}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
<div className="text-lg ml-3">{outputTokenInfo?.symbol}</div>
|
2021-12-09 09:23:19 -08:00
|
|
|
<ChevronDownIcon className="h-4 w-4 ml-1" />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{routes ? (
|
|
|
|
<div>
|
|
|
|
<div className="text-th-fgd-4 text-center text-xs">
|
|
|
|
{routes?.length} routes found!
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className="transition-all duration-700 mt-4 max-h-80 overflow-x-hidden overflow-y-auto thin-scroll pr-1"
|
|
|
|
style={{ maxHeight: maxHeight }}
|
|
|
|
>
|
|
|
|
{displayedRoutes.map((route, index) => {
|
|
|
|
const selected = selectedRoute === route
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
key={index}
|
|
|
|
className={`border border-th-fgd-4 rounded mb-2 ${
|
|
|
|
selected
|
|
|
|
? 'border-th-primary text-th-primary hover:border-th-primary'
|
|
|
|
: 'hover:border-th-fgd-3'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
<button
|
|
|
|
className="p-4 w-full"
|
|
|
|
onClick={() => handleSelectRoute(route)}
|
|
|
|
>
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<div className="flex flex-col text-left">
|
|
|
|
<div className="whitespace-nowrap overflow-ellipsis">
|
|
|
|
{route.marketInfos.map((info, index) => {
|
|
|
|
let includeSeparator = false
|
|
|
|
if (
|
|
|
|
route.marketInfos.length > 1 &&
|
|
|
|
index !== route.marketInfos.length - 1
|
|
|
|
) {
|
|
|
|
includeSeparator = true
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<span key={index}>{`${
|
|
|
|
info.marketMeta.amm.label
|
|
|
|
} ${includeSeparator ? 'x ' : ''}`}</span>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
<div className="text-th-fgd-4 text-xs font-normal">
|
2021-12-18 13:04:19 -08:00
|
|
|
{inputTokenInfo?.symbol} →{' '}
|
2021-12-09 09:23:19 -08:00
|
|
|
{route.marketInfos.map((r, index) => {
|
|
|
|
const showArrow =
|
|
|
|
index !== route.marketInfos.length - 1
|
|
|
|
? true
|
|
|
|
: false
|
|
|
|
return (
|
|
|
|
<span key={index}>
|
|
|
|
<span>
|
|
|
|
{
|
2021-12-18 13:04:19 -08:00
|
|
|
tokens.find(
|
|
|
|
(item) =>
|
2021-12-18 21:53:20 -08:00
|
|
|
item?.address ===
|
|
|
|
r?.outputMint?.toString()
|
2021-12-18 13:04:19 -08:00
|
|
|
)?.symbol
|
2021-12-09 09:23:19 -08:00
|
|
|
}
|
|
|
|
</span>
|
|
|
|
{showArrow ? ' → ' : ''}
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="text-lg">
|
|
|
|
{route.outAmount /
|
|
|
|
10 ** (outputTokenInfo?.decimals || 1)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between text-th-fgd-4 mx-1 mt-2">
|
2021-12-18 21:53:20 -08:00
|
|
|
{routes.length ? (
|
|
|
|
<div>
|
|
|
|
{routes.length > displayedRoutes.length ? (
|
|
|
|
<div className="flex items-center">
|
|
|
|
<ChevronDownIcon className="h-4 w-4" />
|
|
|
|
<button className="text-xs ml-2" onClick={handleShowMore}>
|
|
|
|
Show more
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="flex items-center">
|
|
|
|
<ChevronUpIcon className="h-4 w-4" />
|
|
|
|
<button className="text-xs ml-2" onClick={handleShowLess}>
|
|
|
|
Show less
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
{routes.length ? (
|
|
|
|
<div className="text-xs">
|
|
|
|
from{' '}
|
|
|
|
{routes[routes.length - 1].outAmount /
|
|
|
|
10 ** (outputTokenInfo?.decimals || 1)}{' '}
|
|
|
|
to{' '}
|
|
|
|
{routes[0].outAmount / 10 ** (outputTokenInfo?.decimals || 1)}
|
|
|
|
</div>
|
|
|
|
) : null}
|
2021-12-09 09:23:19 -08:00
|
|
|
</div>
|
|
|
|
<div className="flex justify-center text-xs text-th-fgd-4 mt-4 -mb-4">
|
|
|
|
<a
|
|
|
|
href="https://jup.ag/swap/USDC-MNGO"
|
|
|
|
target="_blank"
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
className="text-th-fgd-4"
|
|
|
|
>
|
|
|
|
Powered by Jupiter
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
{error && <div>Error in Jupiter, try changing your input</div>}
|
|
|
|
</div>
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
disabled={loading}
|
|
|
|
onClick={async () => {
|
2021-12-19 10:17:43 -08:00
|
|
|
if (!connected && zeroKey !== wallet?.publicKey) {
|
|
|
|
wallet.connect()
|
|
|
|
} else if (!loading && selectedRoute && connected) {
|
2021-12-09 09:23:19 -08:00
|
|
|
const swapResult = await exchange({
|
|
|
|
wallet: wallet,
|
|
|
|
route: selectedRoute,
|
|
|
|
confirmationWaiterFactory: async (txid) => {
|
|
|
|
notify({
|
|
|
|
type: 'confirm',
|
|
|
|
title: 'Confirming Transaction',
|
|
|
|
txid,
|
|
|
|
})
|
|
|
|
await connection.confirmTransaction(txid)
|
|
|
|
|
|
|
|
return await connection.getTransaction(txid, {
|
|
|
|
commitment: 'confirmed',
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
actions.fetchWalletTokens()
|
|
|
|
if ('error' in swapResult) {
|
|
|
|
console.log('Error:', swapResult.error)
|
|
|
|
notify({
|
|
|
|
type: 'error',
|
|
|
|
title: swapResult.error.name,
|
|
|
|
description: swapResult.error.message,
|
|
|
|
txid: swapResult.error.txid,
|
|
|
|
})
|
|
|
|
} else if ('txid' in swapResult) {
|
|
|
|
notify({
|
|
|
|
type: 'success',
|
|
|
|
title: 'Swap Successful',
|
|
|
|
description: `Swapped ${
|
|
|
|
swapResult.inputAmount / 10 ** (inputTokenInfo?.decimals || 1)
|
|
|
|
} ${inputTokenInfo?.symbol} to ${
|
|
|
|
swapResult.outputAmount /
|
|
|
|
10 ** (outputTokenInfo?.decimals || 1)
|
|
|
|
} ${outputTokenInfo?.symbol}`,
|
|
|
|
txid: swapResult.txid,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
className="mt-6 p-4 bg-th-bkg-2 hover:bg-th-primary w-full"
|
|
|
|
>
|
|
|
|
{connected ? 'Swap' : 'Connect Wallet'}
|
|
|
|
</button>
|
2021-12-19 10:17:43 -08:00
|
|
|
{inputTokenStats?.prices?.length && outputTokenStats?.prices?.length ? (
|
|
|
|
<>
|
|
|
|
<div className="flex items-center justify-between mt-4">
|
|
|
|
<div className="flex items-center">
|
|
|
|
{inputTokenInfo?.logoURI ? (
|
|
|
|
<img
|
|
|
|
src={inputTokenInfo?.logoURI}
|
|
|
|
width="36"
|
|
|
|
height="36"
|
|
|
|
alt={inputTokenInfo?.symbol}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
<div className="ml-2">{inputTokenInfo?.name}</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="">${inputTokenPrice}</div>
|
|
|
|
<div
|
|
|
|
className={`ml-4 ${
|
|
|
|
inputTokenChange <= 0 ? 'text-th-green' : 'text-th-red'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{(inputTokenChange * -1).toFixed(2)}%
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center justify-between mt-4">
|
|
|
|
<div className="flex items-center">
|
|
|
|
{outputTokenInfo?.logoURI ? (
|
|
|
|
<img
|
|
|
|
src={outputTokenInfo?.logoURI}
|
|
|
|
width="36"
|
|
|
|
height="36"
|
|
|
|
alt={outputTokenInfo?.symbol}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
<div className="ml-2">{outputTokenInfo?.name}</div>
|
|
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
|
|
<div className="">${outputTokenPrice}</div>
|
|
|
|
<div
|
|
|
|
className={`ml-4 ${
|
|
|
|
outputTokenChange <= 0 ? 'text-th-green' : 'text-th-red'
|
|
|
|
}`}
|
|
|
|
>
|
|
|
|
{(outputTokenChange * -1).toFixed(2)}%
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
2021-12-09 09:23:19 -08:00
|
|
|
{selectedRoute ? (
|
|
|
|
<div className="flex flex-col space-y-3 mt-4 text-th-fgd-4 text-xs">
|
|
|
|
<div className="my-2 font-bold">Price Info</div>
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<span>Rate</span>
|
|
|
|
<span>
|
|
|
|
1 {outputTokenInfo?.symbol} ≈{' '}
|
|
|
|
{(formValue?.amount / outAmountUi).toFixed(6)}{' '}
|
|
|
|
{inputTokenInfo?.symbol}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<span>Price Impact</span>
|
|
|
|
<span>
|
|
|
|
{selectedRoute.priceImpactPct * 100 < 0.1
|
|
|
|
? '< 0.1%'
|
|
|
|
: `~ ${(selectedRoute.priceImpactPct * 100).toFixed(4)}%`}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<span>Minimum Received</span>
|
|
|
|
<span>
|
|
|
|
{(
|
|
|
|
selectedRoute.outAmountWithSlippage /
|
|
|
|
10 ** (outputTokenInfo?.decimals || 1)
|
|
|
|
).toFixed(6)}{' '}
|
|
|
|
{outputTokenInfo?.symbol}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
{selectedRoute.marketInfos.map((info, index) => {
|
2021-12-18 13:04:19 -08:00
|
|
|
const feeToken = tokens.find(
|
2021-12-18 21:53:20 -08:00
|
|
|
(item) => item?.address === info.lpFee?.mint
|
2021-12-18 13:04:19 -08:00
|
|
|
)
|
2021-12-09 09:23:19 -08:00
|
|
|
return (
|
|
|
|
<div className="flex justify-between" key={index}>
|
|
|
|
<span>Fees paid to {info.marketMeta?.amm?.label}</span>
|
|
|
|
<span>
|
|
|
|
{(
|
|
|
|
info.lpFee?.amount / Math.pow(10, feeToken?.decimals)
|
|
|
|
).toFixed(6)}{' '}
|
|
|
|
{feeToken?.symbol} ({info.lpFee?.pct * 100}%)
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
})}
|
|
|
|
{connected ? (
|
|
|
|
<>
|
|
|
|
<div className="flex justify-between">
|
|
|
|
<span>Transaction Fee</span>
|
|
|
|
<span>
|
|
|
|
{depositAndFee
|
|
|
|
? depositAndFee?.signatureFee / Math.pow(10, 9)
|
|
|
|
: '-'}{' '}
|
|
|
|
SOL
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
<span>Deposit</span>
|
|
|
|
<div className="flex flex-col text-right">
|
|
|
|
<span>
|
|
|
|
{depositAndFee?.ataDeposit / Math.pow(10, 9)} SOL for{' '}
|
|
|
|
{depositAndFee?.ataDepositLength} ATA Account
|
|
|
|
</span>
|
|
|
|
{depositAndFee?.openOrdersDeposits?.length ? (
|
|
|
|
<span>
|
|
|
|
{sum(depositAndFee?.openOrdersDeposits) / Math.pow(10, 9)}{' '}
|
|
|
|
SOL for {depositAndFee?.openOrdersDeposits.length} Serum
|
|
|
|
OpenOrders Account
|
|
|
|
</span>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
{showInputTokenSelect ? (
|
|
|
|
<SwapTokenSelect
|
|
|
|
isOpen={showInputTokenSelect}
|
|
|
|
onClose={() => setShowInputTokenSelect(false)}
|
|
|
|
sortedTokenMints={sortedTokenMints}
|
|
|
|
onTokenSelect={(token) => {
|
|
|
|
setShowInputTokenSelect(false)
|
|
|
|
setFormValue((val) => ({
|
|
|
|
...val,
|
2021-12-18 21:53:20 -08:00
|
|
|
inputMint: new PublicKey(token?.address),
|
2021-12-09 09:23:19 -08:00
|
|
|
}))
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
{showOutputTokenSelect ? (
|
|
|
|
<SwapTokenSelect
|
|
|
|
isOpen={showOutputTokenSelect}
|
|
|
|
onClose={() => setShowOutputTokenSelect(false)}
|
2021-12-18 21:53:20 -08:00
|
|
|
sortedTokenMints={outputTokenMints}
|
2021-12-09 09:23:19 -08:00
|
|
|
onTokenSelect={(token) => {
|
|
|
|
setShowOutputTokenSelect(false)
|
|
|
|
setFormValue((val) => ({
|
|
|
|
...val,
|
2021-12-18 21:53:20 -08:00
|
|
|
outputMint: new PublicKey(token?.address),
|
2021-12-09 09:23:19 -08:00
|
|
|
}))
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
) : null}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default JupiterForm
|