diff --git a/components/HeroTokenButton.tsx b/components/HeroTokenButton.tsx index cf6cad7..7809ec6 100644 --- a/components/HeroTokenButton.tsx +++ b/components/HeroTokenButton.tsx @@ -11,7 +11,7 @@ import { } from '@heroicons/react/20/solid' export const HERO_TOKEN_BUTTON_CLASSES = - 'inner-shadow-bottom default-transition relative w-full rounded-xl border border-th-bkg-3 bg-th-bkg-1 px-6 py-4 text-th-fgd-1 focus:outline-none focus-visible:border-th-fgd-4 md:hover:bg-th-bkg-2 md:hover:focus-visible:border-th-fgd-4' + 'inner-shadow-bottom default-transition relative w-full rounded-xl border border-th-bkg-3 bg-th-bkg-1 px-6 py-4 text-th-fgd-1 focus:outline-none focus-visible:border-th-fgd-4 md:hover:bg-th-bkg-2 md:hover:focus-visible:border-th-fgd-4 font-semibold' export const HERO_TOKEN_IMAGE_WRAPPER_CLASSES = 'inner-shadow-bottom-sm mb-2 flex h-14 w-14 items-center justify-center rounded-full border border-th-bkg-2 bg-gradient-to-b from-th-bkg-1 to-th-bkg-2 shrink-0' @@ -103,11 +103,11 @@ const HeroTokenButton = ({ -

{name}

+

{name}

Max APY

-
+
{emoji ? ( { actions.estimatePriorityFee(priorityFeeMultiplier) } }, - (slowNetwork ? 60 : 10) * SECONDS, + (slowNetwork ? 60 : 20) * SECONDS, ) // The websocket library solana/web3.js uses closes its websocket connection when the subscription list diff --git a/components/Positions.tsx b/components/Positions.tsx index 53ce91c..908d7e8 100644 --- a/components/Positions.tsx +++ b/components/Positions.tsx @@ -96,7 +96,7 @@ const Positions = ({
-
+
setShowAddRemove('')} size="small" isPrimary> diff --git a/components/Stake.tsx b/components/Stake.tsx index 17ee369..64ec712 100644 --- a/components/Stake.tsx +++ b/components/Stake.tsx @@ -1,16 +1,8 @@ import { useCallback, useMemo, useState } from 'react' import StakeForm from '@components/StakeForm' import mangoStore from '@store/mangoStore' -import { - formatTokenSymbol, - getStakableTokensDataForTokenName, -} from 'utils/tokens' -import { useViewport } from 'hooks/useViewport' -import { - ArrowLeftIcon, - ArrowTopRightOnSquareIcon, - XMarkIcon, -} from '@heroicons/react/20/solid' +import { getStakableTokensDataForTokenName } from 'utils/tokens' +import { ArrowLeftIcon, XMarkIcon } from '@heroicons/react/20/solid' import DespositForm from './DepositForm' import { EnterBottomExitBottom } from './shared/Transitions' import TokenSelect from './TokenSelect' @@ -43,7 +35,6 @@ const Stake = () => { const [showTokenSelect, setShowTokenSelect] = useState(false) const selectedToken = mangoStore((s) => s.selectedToken) // const walletTokens = mangoStore((s) => s.wallet.tokens) - const { isDesktop } = useViewport() const { stakeableTokens } = useStakeableTokens() const handleTokenSelect = useCallback((token: string) => { @@ -86,7 +77,7 @@ const Stake = () => { }) }, [stakeableTokens]) - const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true` + // const swapUrl = `https://app.mango.markets/swap?in=USDC&out=${selectedToken}&walletSwap=true` return ( <> @@ -125,17 +116,17 @@ const Stake = () => {
{selectableTokens.length ? ( !selectedToken ? ( <> -
-
+
+

Let's Boost!

Leverage up your liquid staking yield.

-
+

Select your yield

@@ -180,7 +171,7 @@ const Stake = () => {
{tokensToShow ? ( -
+

Select a token to Boost!

@@ -210,8 +201,8 @@ const Stake = () => { ) : null} ) : ( -
-
+ <> +
set((state) => { @@ -225,13 +216,6 @@ const Stake = () => {

Boost! {selectedToken}

- {/*
-
*/} {selectedToken === 'USDC' ? ( { } /> )} -
+ ) ) : (
@@ -259,7 +243,7 @@ const Stake = () => { )}
- {selectedToken ? ( + {/* {selectedToken ? (
{isDesktop ? ( {
)}
- ) : null} + ) : null} */} ) } diff --git a/components/StakeForm.tsx b/components/StakeForm.tsx index 708005b..a80899b 100644 --- a/components/StakeForm.tsx +++ b/components/StakeForm.tsx @@ -34,7 +34,11 @@ import Link from 'next/link' import LeverageSlider from './shared/LeverageSlider' import useMangoGroup from 'hooks/useMangoGroup' import FormatNumericValue from './shared/FormatNumericValue' -import { getNextAccountNumber, stakeAndCreate } from 'utils/transactions' +import { + getNextAccountNumber, + stakeAndCreate, + walletSwap, +} from 'utils/transactions' // import { MangoAccount } from '@blockworks-foundation/mango-v4' import useBankRates from 'hooks/useBankRates' import { Disclosure } from '@headlessui/react' @@ -50,6 +54,12 @@ import { JLP_BORROW_TOKEN, LST_BORROW_TOKEN, } from 'utils/constants' +import { + HERO_TOKEN_BUTTON_CLASSES, + HERO_TOKEN_IMAGE_WRAPPER_CLASSES, +} from './HeroTokenButton' +import Image from 'next/image' +import useQuoteRoutes from 'hooks/useQuoteRoutes' const set = mangoStore.getState().set @@ -98,6 +108,7 @@ export const walletBalanceForToken = ( function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { const { t } = useTranslation(['common', 'account']) + const [depositToken, setDepositToken] = useState(selectedToken) const [inputAmount, setInputAmount] = useState('') const [sizePercentage, setSizePercentage] = useState('') const submitting = mangoStore((s) => s.submittingBoost) @@ -105,7 +116,8 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { const [refreshingWalletTokens, setRefreshingWalletTokens] = useState(false) const { maxSolDeposit } = useSolBalance() const { ipAllowed } = useIpAddress() - + const wallet = useWallet() + const connection = mangoStore((s) => s.connection) const storedLeverage = mangoStore((s) => s.leverage) const { usedTokens, totalTokens } = useMangoAccountAccounts() const { jlpGroup, lstGroup } = useMangoGroup() @@ -115,8 +127,10 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { leverage, ) const leverageMax = useLeverageMax(selectedToken) + const { connected, publicKey } = useWallet() + const walletTokens = mangoStore((s) => s.wallet.tokens) - const [stakeBank, borrowBank] = useMemo(() => { + const [stakeBank, borrowBank, depositBank] = useMemo(() => { const stakeBank = clientContext === 'jlp' ? jlpGroup?.banksMapByName.get(selectedToken)?.[0] @@ -125,9 +139,37 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { clientContext === 'jlp' ? jlpGroup?.banksMapByName.get(JLP_BORROW_TOKEN)?.[0] : lstGroup?.banksMapByName.get(LST_BORROW_TOKEN)?.[0] - return [stakeBank, borrowBank] - }, [selectedToken, jlpGroup, lstGroup, clientContext]) + const backupBank = + clientContext === 'jlp' + ? jlpGroup?.banksMapByName.get('USDC')?.[0] + : lstGroup?.banksMapByName.get('SOL')?.[0] + const depositBank = depositToken !== selectedToken ? backupBank : stakeBank + return [stakeBank, borrowBank, depositBank] + }, [selectedToken, jlpGroup, lstGroup, clientContext, depositToken]) + const isSwapMode = depositToken !== selectedToken + const { + bestRoute, + isFetching: fetchingRoute, + // refetch: refetchRoute, + } = useQuoteRoutes({ + inputMint: depositBank?.mint.toString(), + outputMint: stakeBank?.mint.toString(), + amount: inputAmount, + slippage: 0.5, + swapMode: 'ExactIn', + wallet: publicKey?.toBase58(), + mangoAccount: undefined, + routingMode: 'ALL', + inDecimals: depositBank?.mintDecimals, + outDecimals: stakeBank?.mintDecimals, + enabled: () => + !!(stakeBank?.mint && depositBank?.mint && inputAmount && isSwapMode), + }) + const uiOutAmount = + bestRoute?.outAmount && stakeBank + ? toUiDecimals(bestRoute.outAmount, stakeBank.mintDecimals) * 0.999 + : 0 const liquidationPrice = useMemo(() => { let price if (borrowBank?.name == 'SOL') { @@ -154,47 +196,70 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { return hasTokenPosition ? false : usedTokens.length >= totalTokens.length }, [stakeBank, usedTokens, totalTokens]) - const { connected, publicKey } = useWallet() - const walletTokens = mangoStore((s) => s.wallet.tokens) - const tokenMax = useMemo(() => { - return walletBalanceForToken(walletTokens, selectedToken, clientContext) - }, [walletTokens, selectedToken, clientContext]) + if (!depositBank) return { maxAmount: 0, maxDecimals: 0 } + return walletBalanceForToken(walletTokens, depositBank.name, clientContext) + }, [walletTokens, depositBank, clientContext]) const setMax = useCallback(() => { - const max = floorToDecimal(tokenMax.maxAmount, 6) + if (!depositBank) return + let max = new Decimal(0) + + if (depositBank.name === 'SOL') { + max = floorToDecimal(tokenMax.maxAmount - 0.01, depositBank.mintDecimals) + } else { + max = floorToDecimal(tokenMax.maxAmount, depositBank.mintDecimals) + } setInputAmount(max.toFixed()) setSizePercentage('100') - }, [tokenMax]) + }, [depositBank, tokenMax]) const handleSizePercentage = useCallback( (percentage: string) => { - if (!stakeBank) return + if (!depositBank) return setSizePercentage(percentage) - const amount = floorToDecimal( - new Decimal(percentage).div(100).mul(tokenMax.maxAmount), - stakeBank.mintDecimals, - ) + let amount = new Decimal(0) + if (depositBank.name === 'SOL' && percentage === '100') { + amount = floorToDecimal( + new Decimal(percentage).div(100).mul(tokenMax.maxAmount), + depositBank.mintDecimals, + ).sub(0.01) + } else { + amount = floorToDecimal( + new Decimal(percentage).div(100).mul(tokenMax.maxAmount), + depositBank.mintDecimals, + ) + } setInputAmount(amount.toFixed()) }, - [tokenMax, stakeBank], + [tokenMax, depositBank], ) const amountToBorrow = useMemo(() => { + const stakeAmount = + isSwapMode && bestRoute && stakeBank ? uiOutAmount : inputAmount const borrowPrice = borrowBank?.uiPrice const stakePrice = stakeBank?.uiPrice - if (!borrowPrice || !stakePrice || !Number(inputAmount)) return 0 + if (!borrowPrice || !stakePrice || !Number(stakeAmount)) return 0 if (clientContext === 'jlp') { const borrowAmount = - stakeBank?.uiPrice * Number(inputAmount) * (leverage - 1) + stakeBank?.uiPrice * Number(stakeAmount) * (leverage - 1) return borrowAmount } else { const priceDifference = (stakePrice - borrowPrice) / borrowPrice const borrowAmount = - (1 + priceDifference) * Number(inputAmount) * (leverage - 1) + (1 + priceDifference) * Number(stakeAmount) * (leverage - 1) return borrowAmount } - }, [leverage, borrowBank, stakeBank, inputAmount]) + }, [ + isSwapMode, + bestRoute, + stakeBank, + inputAmount, + borrowBank?.uiPrice, + clientContext, + leverage, + ]) const availableVaultBalance = useMemo(() => { if (!borrowBank) return 0 @@ -235,17 +300,40 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { }) try { // const newAccountfNum = getNextAccountNumber(mangoAccounts) + let stakeAmount = parseFloat(inputAmount) + if (!bestRoute && isSwapMode) { + notify({ + title: 'No swap route found', + type: 'error', + }) + } notify({ title: 'Building transaction. This may take a moment.', type: 'info', }) + if (isSwapMode && bestRoute) { + const { txid, outAmount } = await walletSwap( + bestRoute, + connection, + 0.5, + wallet, + client, + ) + stakeAmount = toUiDecimals(outAmount, stakeBank.mintDecimals) + notify({ + title: 'Transaction confirmed', + type: 'success', + txid: txid, + }) + } + const { signature: tx, slot } = await stakeAndCreate( client, group, mangoAccount, amountToBorrow, stakeBank.mint, - parseFloat(inputAmount), + stakeAmount, accNumber ?? 0, ) notify({ @@ -281,9 +369,13 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { ipAllowed, stakeBank, publicKey, - amountToBorrow, - inputAmount, clientContext, + inputAmount, + bestRoute, + isSwapMode, + amountToBorrow, + connection, + wallet, ]) const showInsufficientBalance = @@ -318,8 +410,57 @@ function StakeForm({ token: selectedToken, clientContext }: StakeFormProps) { }) }, [selectedToken, clientContext]) + const handleDepositTokenChange = (token: string) => { + setDepositToken(token) + setInputAmount('') + setSizePercentage('') + } + return ( <> +

Token to deposit

+
+ + +
)} -
+
-
) : null}
-
+
) : ipAllowed ? ( - `Boost! ${inputAmount} ${formatTokenSymbol(selectedToken)}` + `Boost! ${ + isSwapMode && uiOutAmount ? uiOutAmount : inputAmount + } ${formatTokenSymbol(selectedToken)}` ) : ( 'Country not allowed' )} diff --git a/components/notifications/TransactionNotification.tsx b/components/notifications/TransactionNotification.tsx index 41fca7f..5ce2ab6 100644 --- a/components/notifications/TransactionNotification.tsx +++ b/components/notifications/TransactionNotification.tsx @@ -112,13 +112,13 @@ const TransactionNotificationList = () => { ) : null} {reversedNotifications.map((n) => ( - + ))}
) } -const TransactionNotification = ({ +const TransactionNotificationComponent = ({ notification, }: { notification: TransactionNotification diff --git a/components/shared/TokenLogo.tsx b/components/shared/TokenLogo.tsx index c6682ac..7756aac 100644 --- a/components/shared/TokenLogo.tsx +++ b/components/shared/TokenLogo.tsx @@ -17,7 +17,6 @@ const TokenLogo = ({ if (!bank) return '' const tokenSymbol = bank.name.toLowerCase() const hasCustomIcon = CUSTOM_TOKEN_ICONS[tokenSymbol] - console.log(tokenSymbol) if (hasCustomIcon) return `/icons/${tokenSymbol}.svg` let jupiterLogoURI if (mangoTokens?.length) { diff --git a/hooks/useAccountHistory.ts b/hooks/useAccountHistory.ts index 633d871..a941b24 100644 --- a/hooks/useAccountHistory.ts +++ b/hooks/useAccountHistory.ts @@ -84,7 +84,7 @@ export default function useAccountHistory() { cacheTime: 1000 * 60 * 5, staleTime: 1000 * 60, retry: 3, - refetchOnWindowFocus: true, + refetchOnWindowFocus: false, enabled: !!stakeAccounts, }, ) diff --git a/hooks/useAnalytics.ts b/hooks/useAnalytics.ts new file mode 100644 index 0000000..33a2b0f --- /dev/null +++ b/hooks/useAnalytics.ts @@ -0,0 +1,60 @@ +import useMangoAccount from './useMangoAccount' +import { useWallet } from '@solana/wallet-adapter-react' +import { WHITE_LIST_API } from 'utils/constants' +import { useCallback } from 'react' + +export default function useAnalytics() { + // const { group } = useMangoGroup() + const { mangoAccountAddress } = useMangoAccount() + const { publicKey } = useWallet() + // eslint-disable-next-line react-hooks/exhaustive-deps + // const ignoredMints = [ + // 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', + // 'So11111111111111111111111111111111111111112', + // 'J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn', + // 'bSo13r4TkiE4KumL71LsHTPpL2euBYLFx6h9HP3piy1', + // '7Q2afV64in6N6SeZsAAB81TJzwDoD6zpqmHkzi9Dcavn', + // '7dHbWXmci3dT8UFYWYZweBLXgycu7Y3iL6trKn1Y7ARj', + // ] + // const banks = useMemo(() => { + // return group && mangoAccount + // ? [...group.banksMapByMint.values()].filter( + // (x) => + // x.length && + // x[0] && + // x[0].collateralFeePerDay > 0 && + // !ignoredMints.includes(x[0].mint.toBase58()) && + // mangoAccount.getTokenBalanceUi(x[0]) * x[0].uiPrice > 10000, + // ) + // : [] + // }, [group, ignoredMints, mangoAccount]) + + const sendAnalytics = useCallback( + async (data: object, tag: string) => { + if (publicKey?.toBase58() && tag && data && mangoAccountAddress) { + const enchantedData = JSON.stringify({ + mangoAccountAddress: mangoAccountAddress, + ...data, + }) + + await fetch(`${WHITE_LIST_API}analytics/add`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + wallet: publicKey.toBase58(), + data: enchantedData, + tag: tag, + }), + }) + } + }, + [mangoAccountAddress, publicKey], + ) + + return { + sendAnalytics, + } +} diff --git a/hooks/useJupiterSwapData.ts b/hooks/useJupiterSwapData.ts new file mode 100644 index 0000000..1955413 --- /dev/null +++ b/hooks/useJupiterSwapData.ts @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import useJupiterMints from 'hooks/useJupiterMints' +import mangoStore from '@store/mangoStore' + +const useJupiterSwapData = () => { + const inputBank = mangoStore((s) => s.swap.inputBank) + const outputBank = mangoStore((s) => s.swap.outputBank) + const { mangoTokens } = useJupiterMints() + + const [inputTokenInfo, outputTokenInfo] = useMemo(() => { + if (inputBank && outputBank) { + return [ + mangoTokens?.find( + (item) => item?.address === inputBank.mint.toString() || '', + ), + mangoTokens?.find( + (item) => item?.address === outputBank.mint.toString() || '', + ), + ] + } else { + return [] + } + }, [inputBank, outputBank, mangoTokens]) + + let inputCoingeckoId = inputTokenInfo?.extensions?.coingeckoId + let outputCoingeckoId = outputTokenInfo?.extensions?.coingeckoId + + if (inputBank?.name.toLocaleLowerCase() === 'dai') { + inputCoingeckoId = 'dai' + } + if (outputBank?.name.toLocaleLowerCase() === 'dai') { + outputCoingeckoId = 'dai' + } + + return { + inputTokenInfo, + inputCoingeckoId, + outputTokenInfo, + outputCoingeckoId, + } +} + +export default useJupiterSwapData diff --git a/hooks/useQuoteRoutes.ts b/hooks/useQuoteRoutes.ts new file mode 100644 index 0000000..7cb050d --- /dev/null +++ b/hooks/useQuoteRoutes.ts @@ -0,0 +1,665 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + AddressLookupTableAccount, + Connection, + PublicKey, + TransactionInstruction, + TransactionMessage, + VersionedTransaction, +} 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 { useMemo } from 'react' +import { JUPITER_V6_QUOTE_API_MAINNET } from 'utils/constants' +import { MangoAccount, toUiDecimals } from '@blockworks-foundation/mango-v4' +import { findRaydiumPoolInfo, getSwapTransaction } from 'utils/swap/raydium' +import mangoStore from '@store/mangoStore' +import useAnalytics from './useAnalytics' + +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: SwapModes + wallet: string | undefined + mangoAccount: MangoAccount | undefined + routingMode: RoutingMode + inDecimals: number | undefined + outDecimals: number | undefined + 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 deserializeJupiterIxAndAlt = async ( + connection: Connection, + swapTransaction: string, +): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => { + const parsedSwapTransaction = VersionedTransaction.deserialize( + Buffer.from(swapTransaction, 'base64'), + ) + const message = parsedSwapTransaction.message + // const lookups = message.addressTableLookups + const addressLookupTablesResponses = await Promise.all( + message.addressTableLookups.map((alt) => + connection.getAddressLookupTable(alt.accountKey), + ), + ) + const addressLookupTables: AddressLookupTableAccount[] = + addressLookupTablesResponses + .map((alt) => alt.value) + .filter((x): x is AddressLookupTableAccount => x !== null) + + const decompiledMessage = TransactionMessage.decompile(message, { + addressLookupTableAccounts: addressLookupTables, + }) + + return [decompiledMessage.instructions, addressLookupTables] +} + +const fetchJupiterTransaction = async ( + connection: Connection, + selectedRoute: JupiterV6RouteInfo, + userPublicKey: PublicKey, + slippage: number, + inputMint: PublicKey, + outputMint: PublicKey, + origin?: 'mango' | 'jupiter' | 'raydium', +): Promise<[TransactionInstruction[], AddressLookupTableAccount[]]> => { + // docs https://station.jup.ag/api-v6/post-swap + const transactions = await ( + await fetch( + `${ + origin === 'mango' ? MANGO_ROUTER_API_URL : JUPITER_V6_QUOTE_API_MAINNET + }/swap`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + // response from /quote api + quoteResponse: selectedRoute, + // user public key to be used for the swap + userPublicKey, + slippageBps: Math.ceil(slippage * 100), + wrapAndUnwrapSol: false, + }), + }, + ) + ).json() + + const { swapTransaction } = transactions + + const [ixs, alts] = await deserializeJupiterIxAndAlt( + connection, + swapTransaction, + ) + + const isSetupIx = (pk: PublicKey): boolean => + pk.toString() === 'ComputeBudget111111111111111111111111111111' || + pk.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + + const isDuplicateAta = (ix: TransactionInstruction): boolean => { + return ( + ix.programId.toString() === + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' && + (ix.keys[3].pubkey.toString() === inputMint.toString() || + ix.keys[3].pubkey.toString() === outputMint.toString()) + ) + } + + //remove ATA and compute setup from swaps in margin trades + const filtered_jup_ixs = ixs + .filter((ix) => !isSetupIx(ix.programId)) + .filter((ix) => !isDuplicateAta(ix)) + + return [filtered_jup_ixs, alts] +} + +const fetchJupiterRoute = async ( + inputMint: string | undefined, + outputMint: string | undefined, + amount = 0, + slippage = 50, + swapMode: SwapModes = 'ExactIn', + onlyDirectRoutes = true, + maxAccounts = 64, + connection: Connection, + wallet: string, + sendAnalytics?: (data: object, tag: string) => Promise, +) => { + 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}`, + ) + if (sendAnalytics) { + sendAnalytics( + { + url: `${JUPITER_V6_QUOTE_API_MAINNET}/quote?${paramsString}`, + }, + 'fetchJupiterRoute', + ) + } + + 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), + 'jupiter', + ) + + if ( + maxAccounts !== 64 && + [...ixes.flatMap((x) => x.keys.flatMap((k) => k.pubkey))].length > + maxAccounts + ) { + throw 'Max accounts exceeded' + } + resolve({ + bestRoute: res, + }) + } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'fetchJupiterRouteError', + ) + } + console.log('jupiter route error:', e) + reject(e) + } + }, + ) +} + +const fetchRaydiumRoute = async ( + inputMint: string | undefined, + outputMint: string | undefined, + amount = 0, + slippage = 50, + connection: Connection, + wallet: string, + isInWalletSwap: boolean, + sendAnalytics?: (data: object, tag: string) => Promise, +) => { + return new Promise<{ bestRoute: JupiterV6RouteInfo }>( + // eslint-disable-next-line no-async-promise-executor + async (resolve, reject) => { + try { + if (sendAnalytics) { + sendAnalytics( + { + inputMint, + outputMint, + amount, + slippage, + }, + 'fetchRaydiumRoute', + ) + } + + if (!inputMint || !outputMint) return + + 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) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'raydiumRouteError', + ) + } + console.log('raydium route error:', e) + reject(e) + } + }, + ) +} + +const fetchMangoRoute = async ( + inputMint = 'So11111111111111111111111111111111111111112', + outputMint = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', + amount = 0, + slippage = 50, + swapMode = 'ExactIn', + sendAnalytics?: (data: object, tag: string) => Promise, +) => { + return new Promise<{ bestRoute: JupiterV6RouteInfo }>( + // eslint-disable-next-line no-async-promise-executor + async (resolve, reject) => { + const timeout = setTimeout(() => { + reject('Request timed out') + }, 5000) + + try { + const paramsString = new URLSearchParams({ + inputMint: inputMint.toString(), + outputMint: outputMint.toString(), + amount: amount.toString(), + slippageBps: Math.ceil(slippage * 100).toString(), + mode: swapMode, + }).toString() + + const response = await fetch( + `${MANGO_ROUTER_API_URL}/quote?${paramsString}`, + ) + clearTimeout(timeout) + if (sendAnalytics) { + sendAnalytics( + { + url: `${MANGO_ROUTER_API_URL}/quote?${paramsString}`, + }, + 'fetchMangoRoute', + ) + } + + if (response.status === 500) { + throw 'No route found' + } + + const res = await response.json() + + if (res.outAmount) { + resolve({ + bestRoute: { ...res, origin: 'mango' }, + }) + } else { + reject('No route found') + } + } catch (e) { + clearTimeout(timeout) + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'mangoRouteError', + ) + } + console.log('mango router error:', e) + reject(e) + } + }, + ) +} + +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, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, + inputTokenDecimals: number, +): Promise<{ bestRoute: JupiterV6RouteInfo }> + +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, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, +): 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, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, + inputTokenDecimals: number, +): Promise<{ bestRoute: JupiterV6RouteInfo }> + +export async function handleGetRoutes( + inputMint: string | undefined, + outputMint: string | undefined, + amount = 0, + slippage = 50, + swapMode: SwapModes, + wallet: string | undefined, + mangoAccount: MangoAccount | undefined, + routingMode: RoutingMode = 'ALL', + connection: Connection, + sendAnalytics: ((data: object, tag: string) => Promise) | undefined, + inputTokenDecimals?: number, +) { + try { + if (sendAnalytics) { + sendAnalytics( + { + inputMint, + outputMint, + amount, + slippage, + swapMode, + wallet, + routingMode, + }, + 'handleGetRoutes', + ) + } + + wallet ||= PublicKey.default.toBase58() + + let maxAccounts: number + if (!mangoAccount) { + maxAccounts = 64 + } else { + // TODO: replace with client method + const totalSlots = + 2 * mangoAccount.tokensActive().length + + mangoAccount.serum3Active().length + + 2 * mangoAccount.perpActive().length + maxAccounts = 54 - totalSlots + } + + const routes = [] + + if ( + swapMode === 'ExactIn' && + (isMultiRoutingMode(routingMode) || isRaydiumRoutingMode(routingMode)) + ) { + const raydiumRoute = fetchRaydiumRoute( + inputMint, + outputMint, + toUiDecimals(amount, inputTokenDecimals!), + slippage, + connection, + wallet, + !mangoAccount, + sendAnalytics, + ) + routes.push(raydiumRoute) + } + + if ( + routingMode === 'ALL_AND_JUPITER_DIRECT' || + routingMode === 'JUPITER_DIRECT' + ) { + const jupiterDirectRoute = fetchJupiterRoute( + inputMint, + outputMint, + amount, + slippage, + swapMode, + true, + maxAccounts, + connection, + wallet, + sendAnalytics, + ) + routes.push(jupiterDirectRoute) + } + + if (isMultiRoutingMode(routingMode) || routingMode === 'JUPITER') { + const jupiterRoute = fetchJupiterRoute( + inputMint, + outputMint, + amount, + slippage, + swapMode, + false, + maxAccounts, + connection, + wallet, + sendAnalytics, + ) + routes.push(jupiterRoute) + } + + if (isMultiRoutingMode(routingMode) || routingMode === 'MANGO') { + const mangoRoute = fetchMangoRoute( + inputMint, + outputMint, + amount, + slippage, + swapMode, + sendAnalytics, + ) + 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 + }[] + ).sort((a, b) => + swapMode === 'ExactIn' + ? Number(b.bestRoute.outAmount) - Number(a.bestRoute.outAmount) + : Number(a.bestRoute.inAmount) - Number(b.bestRoute.inAmount), + ) + return { + bestRoute: sortedByBiggestOutAmount.length + ? sortedByBiggestOutAmount[0]?.bestRoute + : null, + } + } catch (e) { + if (sendAnalytics) { + sendAnalytics( + { + error: `${e}`, + }, + 'noRouteFoundError', + ) + } + return { + bestRoute: null, + } + } +} + +const useQuoteRoutes = ({ + inputMint, + outputMint, + amount, + slippage, + swapMode, + wallet, + mangoAccount, + routingMode = 'ALL', + inDecimals, + outDecimals, + enabled, +}: useQuoteRoutesPropTypes) => { + const connection = mangoStore((s) => s.connection) + const { sendAnalytics } = useAnalytics() + + const decimals = useMemo(() => { + return swapMode === 'ExactIn' ? inDecimals || 6 : outDecimals || 6 + }, [swapMode, inDecimals, outDecimals]) + + const nativeAmount = useMemo(() => { + return amount && !Number.isNaN(+amount) + ? new Decimal(amount).mul(10 ** decimals) + : new Decimal(0) + }, [amount, decimals]) + + const res = useQuery<{ bestRoute: JupiterV6RouteInfo | null }, Error>( + [ + [ + 'swap-routes', + nativeAmount.toString(), + inputMint, + outputMint, + swapMode, + wallet, + routingMode, + ], + inputMint, + outputMint, + amount, + slippage, + swapMode, + wallet, + routingMode, + ], + async () => { + if ( + isMultiRoutingMode(routingMode) || + isRaydiumRoutingMode(routingMode) + ) { + return handleGetRoutes( + inputMint, + outputMint, + nativeAmount.toNumber(), + slippage, + swapMode, + wallet, + mangoAccount, + routingMode, + connection, + sendAnalytics, + decimals, + ) + } else { + return handleGetRoutes( + inputMint, + outputMint, + nativeAmount.toNumber(), + slippage, + swapMode, + wallet, + mangoAccount, + routingMode, + connection, + sendAnalytics, + ) + } + }, + { + cacheTime: 1000 * 60, + staleTime: 1000 * 3, + enabled: enabled + ? enabled() + : nativeAmount.toNumber() && inputMint && outputMint + ? true + : false, + refetchInterval: 20000, + retry: 3, + }, + ) + + return amount + ? { + ...(res.data ?? { + routes: [], + bestRoute: undefined, + }), + isFetching: res.isFetching, + isLoading: res.isLoading, + isInitialLoading: res.isInitialLoading, + refetch: res.refetch, + } + : { + routes: [], + bestRoute: undefined, + isFetching: false, + isLoading: false, + isInitialLoading: false, + refetch: undefined, + } +} + +export default useQuoteRoutes diff --git a/hooks/useStakeRates.ts b/hooks/useStakeRates.ts index 7375f2d..0d3e5fa 100644 --- a/hooks/useStakeRates.ts +++ b/hooks/useStakeRates.ts @@ -95,7 +95,7 @@ export default function useStakeRates() { cacheTime: 1000 * 60 * 5, staleTime: 1000 * 60, retry: 3, - refetchOnWindowFocus: true, + refetchOnWindowFocus: false, }) return { diff --git a/package.json b/package.json index 2548cea..e0cb0e7 100644 --- a/package.json +++ b/package.json @@ -38,11 +38,13 @@ "@blockworks-foundation/mango-feeds": "0.1.7", "@blockworks-foundation/mango-v4": "0.31.3", "@blockworks-foundation/mango-v4-settings": "0.14.24", + "@blockworks-foundation/mangolana": "0.0.17", "@glitchful-dev/sol-apy-sdk": "3.0.2", "@headlessui/react": "1.6.6", "@heroicons/react": "2.0.10", - "@project-serum/anchor": "0.25.0", + "@project-serum/anchor": "0.26.0", "@pythnetwork/client": "2.15.0", + "@raydium-io/raydium-sdk": "1.3.1-beta.57", "@solana/spl-governance": "0.3.27", "@solana/spl-token": "0.3.7", "@solana/wallet-adapter-base": "0.9.23", @@ -90,7 +92,7 @@ "@types/react-grid-layout": "1.3.2", "@types/react-window": "1.8.5", "@types/recharts": "1.8.24", - "@typescript-eslint/eslint-plugin": "5.43.0", + "@typescript-eslint/eslint-plugin": "7.15.0", "autoprefixer": "10.4.13", "eslint": "8.13.0", "eslint-config-next": "13.4.17", @@ -104,7 +106,7 @@ "prettier": "3.0.2", "prettier-plugin-tailwindcss": "0.5.3", "tailwindcss": "3.3.3", - "typescript": "4.9.4" + "typescript": "5.5.3" }, "resolutions": { "@coral-xyz/anchor": "^0.27.0", diff --git a/types/jupiter.ts b/types/jupiter.ts index 88bb9b6..2086d03 100644 --- a/types/jupiter.ts +++ b/types/jupiter.ts @@ -149,3 +149,38 @@ export type Token = { } tags: string[] } + +export interface JupiterV6RoutePlan { + swapInfo: { + ammKey: string + label?: string + inputMint: string + outputMint: string + inAmount: number + outAmount: number + feeAmount: number + feeMint: string + } + percent: number +} + +export interface JupiterV6RouteInfo { + inputMint: string + inAmount: number + outputMint: string + outAmount: number + otherAmountThreshold: number + swapMode: SwapMode + slippageBps: number + platformFee?: { + amount: string + feeBps: number + } + priceImpactPct: number + routePlan: JupiterV6RoutePlan[] | undefined + contextSlot?: number + timeTaken?: number + error?: string + instructions?: TransactionInstruction[] + origin?: 'jupiter' | 'mango' | 'raydium' +} diff --git a/utils/constants.ts b/utils/constants.ts index d2389dc..1a29beb 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -284,6 +284,8 @@ export const LAST_WALLET_NAME = 'lastWalletName' export const PRIVACY_MODE = 'privacy-mode-0.1' +export const JUPITER_V6_QUOTE_API_MAINNET = 'https://quote-api.jup.ag/v6' + // Unused export const PROFILE_CATEGORIES = [ 'borrower', diff --git a/utils/swap/raydium.ts b/utils/swap/raydium.ts new file mode 100644 index 0000000..06f7671 --- /dev/null +++ b/utils/swap/raydium.ts @@ -0,0 +1,324 @@ +import { + LIQUIDITY_STATE_LAYOUT_V4, + LiquidityPoolKeys, + Liquidity, + Token, + TokenAmount, + Percent, + MARKET_STATE_LAYOUT_V3, + Market, + CurrencyAmount, + Price, +} from '@raydium-io/raydium-sdk' +import { TOKEN_PROGRAM_ID } from '@solana/spl-governance' +import { getAssociatedTokenAddressSync } from '@solana/spl-token' +import { + Connection, + GetProgramAccountsResponse, + PublicKey, + TransactionInstruction, +} from '@solana/web3.js' +import BN from 'bn.js' + +const RAYDIUM_V4_PROGRAM_ID = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8' + +const _getProgramAccounts = ( + connection: Connection, + baseMint: string, + quoteMint: string, +): Promise => { + const layout = LIQUIDITY_STATE_LAYOUT_V4 + + return connection.getProgramAccounts(new PublicKey(RAYDIUM_V4_PROGRAM_ID), { + filters: [ + { dataSize: layout.span }, + { + memcmp: { + offset: layout.offsetOf('baseMint'), + bytes: new PublicKey(baseMint).toBase58(), + }, + }, + { + memcmp: { + offset: layout.offsetOf('quoteMint'), + bytes: new PublicKey(quoteMint).toBase58(), + }, + }, + ], + }) +} + +const getProgramAccounts = async ( + connection: Connection, + baseMint: string, + quoteMint: string, +) => { + const response = await Promise.all([ + _getProgramAccounts(connection, baseMint, quoteMint), + _getProgramAccounts(connection, quoteMint, baseMint), + ]) + + return response.filter((r) => r.length > 0).flatMap((x) => x) +} + +export const findRaydiumPoolInfo = async ( + connection: Connection, + baseMint: string, + quoteMint: string, +): Promise => { + const layout = LIQUIDITY_STATE_LAYOUT_V4 + + const programData = await getProgramAccounts(connection, baseMint, quoteMint) + + const collectedPoolResults = programData + .map((info) => ({ + id: new PublicKey(info.pubkey), + version: 4, + programId: new PublicKey(RAYDIUM_V4_PROGRAM_ID), + ...layout.decode(info.account.data), + })) + .flat() + + const pools = await Promise.all([ + fetch(`https://api.dexscreener.com/latest/dex/search?q=${baseMint}`), + fetch(`https://api.dexscreener.com/latest/dex/search?q=${quoteMint}`), + ]) + const resp = await Promise.all([...pools.map((x) => x.json())]) + + const bestDexScannerPoolId = resp + .flatMap((x) => x.pairs) + .find( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (x: any) => + x.dexId === 'raydium' && + ((x.baseToken.address === baseMint && + x.quoteToken.address === quoteMint) || + (x.baseToken.address === quoteMint && + x.quoteToken.address === baseMint)), + )?.pairAddress + + const pool = collectedPoolResults.find( + (x) => x.id.toBase58() === bestDexScannerPoolId, + ) + + if (!pool) return undefined + + const market = await connection + .getAccountInfo(pool.marketId) + .then((item) => ({ + programId: item!.owner, + ...MARKET_STATE_LAYOUT_V3.decode(item!.data), + })) + + const authority = Liquidity.getAssociatedAuthority({ + programId: new PublicKey(RAYDIUM_V4_PROGRAM_ID), + }).publicKey + + const marketProgramId = market.programId + + const poolKeys = { + id: pool.id, + baseMint: pool.baseMint, + quoteMint: pool.quoteMint, + lpMint: pool.lpMint, + baseDecimals: Number.parseInt(pool.baseDecimal.toString()), + quoteDecimals: Number.parseInt(pool.quoteDecimal.toString()), + lpDecimals: Number.parseInt(pool.baseDecimal.toString()), + version: pool.version, + programId: pool.programId, + openOrders: pool.openOrders, + targetOrders: pool.targetOrders, + baseVault: pool.baseVault, + quoteVault: pool.quoteVault, + marketVersion: 3, + authority: authority, + marketProgramId, + marketId: market.ownAddress, + marketAuthority: Market.getAssociatedAuthority({ + programId: marketProgramId, + marketId: market.ownAddress, + }).publicKey, + marketBaseVault: market.baseVault, + marketQuoteVault: market.quoteVault, + marketBids: market.bids, + marketAsks: market.asks, + marketEventQueue: market.eventQueue, + withdrawQueue: pool.withdrawQueue, + lpVault: pool.lpVault, + lookupTableAccount: PublicKey.default, + } as LiquidityPoolKeys + + return poolKeys +} + +const calcAmountOut = async ( + connection: Connection, + poolKeys: LiquidityPoolKeys, + rawAmountIn: number, + slippage = 5, + swapInDirection: boolean, +) => { + const poolInfo = await Liquidity.fetchInfo({ + connection: connection, + poolKeys, + }) + + let currencyInMint = poolKeys.baseMint + let currencyInDecimals = poolInfo.baseDecimals + let currencyOutMint = poolKeys.quoteMint + let currencyOutDecimals = poolInfo.quoteDecimals + + if (!swapInDirection) { + currencyInMint = poolKeys.quoteMint + currencyInDecimals = poolInfo.quoteDecimals + currencyOutMint = poolKeys.baseMint + currencyOutDecimals = poolInfo.baseDecimals + } + + const currencyIn = new Token( + TOKEN_PROGRAM_ID, + currencyInMint, + currencyInDecimals, + ) + const amountIn = new TokenAmount( + currencyIn, + rawAmountIn.toFixed(currencyInDecimals), + false, + ) + const currencyOut = new Token( + TOKEN_PROGRAM_ID, + currencyOutMint, + currencyOutDecimals, + ) + const slippageX = new Percent(Math.ceil(slippage * 10), 1000) + + const { + amountOut, + minAmountOut, + currentPrice, + executionPrice, + priceImpact, + fee, + } = Liquidity.computeAmountOut({ + poolKeys, + poolInfo, + amountIn, + currencyOut, + slippage: slippageX, + }) + + return { + amountIn: amountIn, + amountOut: amountOut, + inAmount: amountIn.raw.toNumber(), + outAmount: amountOut.raw.toNumber(), + otherAmountThreshold: minAmountOut.raw.toNumber(), + minAmountOut: minAmountOut, + currentPrice, + executionPrice, + priceImpactPct: Number(priceImpact.toSignificant()) / 100, + fee, + inputMint: poolKeys.quoteMint.toBase58(), + outputMint: poolKeys.baseMint.toBase58(), + routePlan: [ + { + swapInfo: { + ammKey: poolKeys.id.toBase58(), + label: 'Raydium', + inputMint: poolKeys.quoteMint.toBase58(), + outputMint: poolKeys.baseMint.toBase58(), + inAmount: amountIn.raw.toNumber(), + outAmount: amountOut.raw.toNumber(), + feeAmount: 0, + feeMint: poolKeys.lpMint.toBase58(), + }, + percent: 100, + }, + ], + } +} + +export const getSwapTransaction = async ( + connection: Connection, + toToken: string, + amount: number, + poolKeys: LiquidityPoolKeys, + slippage = 5, + wallet: PublicKey, + mangoAccountSwap: boolean, +): Promise<{ + bestRoute: { + amountIn: TokenAmount + amountOut: TokenAmount | CurrencyAmount + inAmount: number + outAmount: number + otherAmountThreshold: number + currentPrice: Price + executionPrice: Price | null + priceImpactPct: number + fee: CurrencyAmount + instructions: TransactionInstruction[] + } +}> => { + const directionIn = poolKeys.quoteMint.toString() == toToken + + const bestRoute = await calcAmountOut( + connection, + poolKeys, + amount, + slippage, + directionIn, + ) + + const tokenInAta = getAssociatedTokenAddressSync( + new PublicKey(directionIn ? bestRoute.outputMint : bestRoute.inputMint), + wallet, + ) + const tokenOutAta = getAssociatedTokenAddressSync( + new PublicKey(directionIn ? bestRoute.inputMint : bestRoute.outputMint), + wallet, + ) + const swapTransaction = Liquidity.makeSwapInstruction({ + poolKeys: { + ...poolKeys, + }, + userKeys: { + tokenAccountIn: tokenInAta, + tokenAccountOut: tokenOutAta, + owner: wallet, + }, + amountIn: bestRoute.amountIn.raw, + amountOut: bestRoute.minAmountOut.raw.sub(bestRoute.fee?.raw ?? new BN(0)), + fixedSide: !directionIn ? 'in' : 'out', + }) + + const instructions = + swapTransaction.innerTransaction.instructions.filter(Boolean) + + const filtered_instructions = mangoAccountSwap + ? instructions + .filter((ix) => !isSetupIx(ix.programId)) + .filter( + (ix) => !isDuplicateAta(ix, poolKeys.baseMint, poolKeys.quoteMint), + ) + : instructions + + return { bestRoute: { ...bestRoute, instructions: filtered_instructions } } +} + +const isSetupIx = (pk: PublicKey): boolean => + pk.toString() === 'ComputeBudget111111111111111111111111111111' || + pk.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' + +const isDuplicateAta = ( + ix: TransactionInstruction, + inputMint: PublicKey, + outputMint: PublicKey, +): boolean => { + return ( + ix.programId.toString() === + 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' && + (ix.keys[3].pubkey.toString() === inputMint.toString() || + ix.keys[3].pubkey.toString() === outputMint.toString()) + ) +} diff --git a/utils/transactions.ts b/utils/transactions.ts index f707880..413a450 100644 --- a/utils/transactions.ts +++ b/utils/transactions.ts @@ -3,8 +3,10 @@ import { Bank, FlashLoanType, Group, + MANGO_ROUTER_API_URL, MangoAccount, MangoClient, + MangoError, MangoSignatureStatus, RouteInfo, TokenIndex, @@ -15,6 +17,7 @@ import { toNative, toNativeI80F48, toUiDecimals, + tryStringify, } from '@blockworks-foundation/mango-v4' import { TOKEN_PROGRAM_ID } from '@solana/spl-governance' import { @@ -28,16 +31,22 @@ import { Connection, Keypair, PublicKey, + RpcResponseAndContext, SYSVAR_INSTRUCTIONS_PUBKEY, + SignatureResult, SystemProgram, + Transaction, TransactionInstruction, TransactionMessage, VersionedTransaction, } from '@solana/web3.js' import { floorToDecimal } from './numbers' -import { BOOST_ACCOUNT_PREFIX } from './constants' +import { BOOST_ACCOUNT_PREFIX, JUPITER_V6_QUOTE_API_MAINNET } from './constants' import { notify } from './notifications' import { getStakableTokensDataForMint } from './tokens' +import { JupiterV6RouteInfo } from 'types/jupiter' +import { WalletContextState } from '@solana/wallet-adapter-react' +import { awaitTransactionSignatureConfirmation } from '@blockworks-foundation/mangolana/lib/transactions' export const withdrawAndClose = async ( client: MangoClient, @@ -962,3 +971,155 @@ export const getNextAccountNumber = (accounts: MangoAccount[]): number => { } return 0 } + +export const walletSwap = async ( + selectedRoute: JupiterV6RouteInfo, + connection: Connection, + slippage: number, + wallet: WalletContextState, + client: MangoClient, +) => { + const vtx = await fetchJupiterWalletSwapTransaction( + selectedRoute, + wallet!.publicKey!, + slippage, + selectedRoute.origin, + ) + + const latestBlockhash = await connection.getLatestBlockhash() + const sign = wallet.signTransaction! + const signed = await sign(vtx) + + const txid = await sendTxAndConfirm( + client.opts.multipleConnections, + connection, + signed, + latestBlockhash, + ) + return { txid, outAmount: selectedRoute.outAmount } +} + +/** Given a Jupiter route, fetch the transaction for the user to sign. + **This function should ONLY be used for wallet swaps* */ +export const fetchJupiterWalletSwapTransaction = async ( + selectedRoute: JupiterV6RouteInfo, + userPublicKey: PublicKey, + slippage: number, + origin?: 'mango' | 'jupiter' | 'raydium', +): Promise => { + // docs https://station.jup.ag/api-v6/post-swap + const params: { + quoteResponse: JupiterV6RouteInfo + userPublicKey: PublicKey + slippageBps: number + autoCreateOutAta?: boolean + wrapAndUnwrapSol?: boolean + } = { + // response from /quote api + quoteResponse: selectedRoute, + // user public key to be used for the swap + userPublicKey, + slippageBps: Math.ceil(slippage * 100), + } + + if (origin === 'mango') { + params.autoCreateOutAta = true + params.wrapAndUnwrapSol = true + } + + const transactions = await ( + await fetch( + `${ + origin === 'mango' ? MANGO_ROUTER_API_URL : JUPITER_V6_QUOTE_API_MAINNET + }/swap`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + }, + ) + ).json() + + const { swapTransaction } = transactions + const parsedSwapTransaction = VersionedTransaction.deserialize( + Buffer.from(swapTransaction, 'base64'), + ) + return parsedSwapTransaction +} + +export const sendTxAndConfirm = async ( + multipleConnections: Connection[] = [], + connection: Connection, + tx: Transaction | VersionedTransaction, + latestBlockhash: { + lastValidBlockHeight: number + blockhash: string + }, +) => { + let signature = '' + const abortController = new AbortController() + try { + const allConnections = [connection, ...multipleConnections] + const rawTransaction = tx.serialize() + signature = await Promise.any( + allConnections.map((c) => { + return c.sendRawTransaction(rawTransaction, { + skipPreflight: true, + }) + }), + ) + await Promise.any( + allConnections.map((c) => + awaitTransactionSignatureConfirmation({ + txid: signature, + confirmLevel: 'processed', + connection: c, + timeoutStrategy: { + block: latestBlockhash, + }, + abortSignal: abortController.signal, + }), + ), + ) + abortController.abort() + return signature + } catch (e) { + abortController.abort() + if (e instanceof AggregateError) { + for (const individualError of e.errors) { + const stringifiedError = tryStringify(individualError) + throw new MangoError({ + txid: signature, + message: `${ + stringifiedError + ? stringifiedError + : individualError + ? individualError + : 'Unknown error' + }`, + }) + } + } + if (isErrorWithSignatureResult(e)) { + const stringifiedError = tryStringify(e?.value?.err) + throw new MangoError({ + txid: signature, + message: `${stringifiedError ? stringifiedError : e?.value?.err}`, + }) + } + const stringifiedError = tryStringify(e) + throw new MangoError({ + txid: signature, + message: `${stringifiedError ? stringifiedError : e}`, + }) + } +} + +function isErrorWithSignatureResult( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + err: any, +): err is RpcResponseAndContext { + return err && typeof err.value !== 'undefined' +} diff --git a/yarn.lock b/yarn.lock index 28bdb8b..2af21ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -201,6 +201,14 @@ superstruct "^0.15.4" toml "^3.0.0" +"@coral-xyz/borsh@^0.26.0": + version "0.26.0" + resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.26.0.tgz#d054f64536d824634969e74138f9f7c52bbbc0d5" + integrity sha512-uCZ0xus0CszQPHYfWAqKS5swS1UxvePu83oOF+TWpUkedsNlg6p2p4azxZNSSqwXb9uXMFgxhuMBX9r3Xoi0vQ== + dependencies: + bn.js "^5.1.2" + buffer-layout "^1.2.0" + "@coral-xyz/borsh@^0.27.0": version "0.27.0" resolved "https://registry.yarnpkg.com/@coral-xyz/borsh/-/borsh-0.27.0.tgz#700c647ea5262b1488957ac7fb4e8acf72c72b63" @@ -217,6 +225,18 @@ bn.js "^5.1.2" buffer-layout "^1.2.0" +"@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== + "@eslint/eslintrc@^1.2.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.4.1.tgz#af58772019a2d271b7e2d4c23ff4ddcba3ccfb3e" @@ -1375,18 +1395,18 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.6.tgz#cee20bd55e68a1720bdab363ecf0c821ded4cd45" integrity sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw== -"@project-serum/anchor@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.25.0.tgz#88ee4843336005cf5a64c80636ce626f0996f503" - integrity sha512-E6A5Y/ijqpfMJ5psJvbw0kVTzLZFUcOFgs6eSM2M2iWE1lVRF18T6hWZVNl6zqZsoz98jgnNHtVGJMs+ds9A7A== +"@project-serum/anchor@0.26.0": + version "0.26.0" + resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.26.0.tgz#99e15a3923a5d10514f8185b2d3909e5699d60d5" + integrity sha512-Nq+COIjE1135T7qfnOHEn7E0q39bQTgXLFk837/rgFe6Hkew9WML7eHsS+lSYD2p3OJaTiUOHTAq1lHy36oIqQ== dependencies: - "@project-serum/borsh" "^0.2.5" - "@solana/web3.js" "^1.36.0" + "@coral-xyz/borsh" "^0.26.0" + "@solana/web3.js" "^1.68.0" base64-js "^1.5.1" bn.js "^5.1.2" bs58 "^4.0.1" buffer-layout "^1.2.2" - camelcase "^5.3.1" + camelcase "^6.3.0" cross-fetch "^3.1.5" crypto-hash "^1.3.0" eventemitter3 "^4.0.7" @@ -1532,6 +1552,22 @@ "@coral-xyz/anchor" "^0.26.0" buffer "^6.0.1" +"@raydium-io/raydium-sdk@1.3.1-beta.57": + version "1.3.1-beta.57" + resolved "https://registry.yarnpkg.com/@raydium-io/raydium-sdk/-/raydium-sdk-1.3.1-beta.57.tgz#ca498a2b0c54454cc444e9a55d7ac690f9ba094a" + integrity sha512-oHp9/N4CUEUmxxupaYr+gbrgGiZci9QJ/UX146G2jd0tSkcXu046EV1SE2HSJRnQfMFiJUoxwZi7qzENWH6dDQ== + dependencies: + "@solana/buffer-layout" "^4.0.1" + "@solana/spl-token" "^0.3.9" + axios "^1.6.2" + big.js "^6.2.1" + bn.js "^5.2.1" + decimal.js "^10.4.3" + decimal.js-light "^2.5.1" + fecha "^4.2.3" + lodash "^4.17.21" + toformat "^2.0.0" + "@react-native-async-storage/async-storage@^1.17.7": version "1.17.11" resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.11.tgz#7ec329c1b9f610e344602e806b04d7c928a2341d" @@ -1617,6 +1653,66 @@ dependencies: buffer "~6.0.3" +"@solana/codecs-core@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.2.tgz#689784d032fbc1fedbde40bb25d76cdcecf6553b" + integrity sha512-gLhCJXieSCrAU7acUJjbXl+IbGnqovvxQLlimztPoGgfLQ1wFYu+XJswrEVQqknZYK1pgxpxH3rZ+OKFs0ndQg== + dependencies: + "@solana/errors" "2.0.0-preview.2" + +"@solana/codecs-data-structures@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.2.tgz#e82cb1b6d154fa636cd5c8953ff3f32959cc0370" + integrity sha512-Xf5vIfromOZo94Q8HbR04TbgTwzigqrKII0GjYr21K7rb3nba4hUW2ir8kguY7HWFBcjHGlU5x3MevKBOLp3Zg== + dependencies: + "@solana/codecs-core" "2.0.0-preview.2" + "@solana/codecs-numbers" "2.0.0-preview.2" + "@solana/errors" "2.0.0-preview.2" + +"@solana/codecs-numbers@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.2.tgz#56995c27396cd8ee3bae8bd055363891b630bbd0" + integrity sha512-aLZnDTf43z4qOnpTcDsUVy1Ci9im1Md8thWipSWbE+WM9ojZAx528oAql+Cv8M8N+6ALKwgVRhPZkto6E59ARw== + dependencies: + "@solana/codecs-core" "2.0.0-preview.2" + "@solana/errors" "2.0.0-preview.2" + +"@solana/codecs-strings@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.2.tgz#8bd01a4e48614d5289d72d743c3e81305d445c46" + integrity sha512-EgBwY+lIaHHgMJIqVOGHfIfpdmmUDNoNO/GAUGeFPf+q0dF+DtwhJPEMShhzh64X2MeCZcmSO6Kinx0Bvmmz2g== + dependencies: + "@solana/codecs-core" "2.0.0-preview.2" + "@solana/codecs-numbers" "2.0.0-preview.2" + "@solana/errors" "2.0.0-preview.2" + +"@solana/codecs@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.2.tgz#d6615fec98f423166fb89409f9a4ad5b74c10935" + integrity sha512-4HHzCD5+pOSmSB71X6w9ptweV48Zj1Vqhe732+pcAQ2cMNnN0gMPMdDq7j3YwaZDZ7yrILVV/3+HTnfT77t2yA== + dependencies: + "@solana/codecs-core" "2.0.0-preview.2" + "@solana/codecs-data-structures" "2.0.0-preview.2" + "@solana/codecs-numbers" "2.0.0-preview.2" + "@solana/codecs-strings" "2.0.0-preview.2" + "@solana/options" "2.0.0-preview.2" + +"@solana/errors@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.2.tgz#e0ea8b008c5c02528d5855bc1903e5e9bbec322e" + integrity sha512-H2DZ1l3iYF5Rp5pPbJpmmtCauWeQXRJapkDg8epQ8BJ7cA2Ut/QEtC3CMmw/iMTcuS6uemFNLcWvlOfoQhvQuA== + dependencies: + chalk "^5.3.0" + commander "^12.0.0" + +"@solana/options@2.0.0-preview.2": + version "2.0.0-preview.2" + resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.2.tgz#13ff008bf43a5056ef9a091dc7bb3f39321e867e" + integrity sha512-FAHqEeH0cVsUOTzjl5OfUBw2cyT8d5Oekx4xcn5hn+NyPAfQJgM3CEThzgRD6Q/4mM5pVUnND3oK/Mt1RzSE/w== + dependencies: + "@solana/codecs-core" "2.0.0-preview.2" + "@solana/codecs-numbers" "2.0.0-preview.2" + "@solana/spl-governance@0.3.27": version "0.3.27" resolved "https://registry.yarnpkg.com/@solana/spl-governance/-/spl-governance-0.3.27.tgz#54ab8310a142b3d581d8abc3df37e3511f02619c" @@ -1630,6 +1726,14 @@ bs58 "^4.0.1" superstruct "^0.15.2" +"@solana/spl-token-metadata@^0.1.2": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.4.tgz#5cdc3b857a8c4a6877df24e24a8648c4132d22ba" + integrity sha512-N3gZ8DlW6NWDV28+vCCDJoTqaCZiF/jDUnk3o8GRkAFzHObiR60Bs1gXHBa8zCPdvOwiG6Z3dg5pg7+RW6XNsQ== + dependencies: + "@solana/codecs" "2.0.0-preview.2" + "@solana/spl-type-length-value" "0.1.0" + "@solana/spl-token@0.3.7": version "0.3.7" resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.7.tgz#6f027f9ad8e841f792c32e50920d9d2e714fc8da" @@ -1651,13 +1755,21 @@ buffer-layout "^1.2.0" dotenv "10.0.0" -"@solana/spl-token@^0.3.8": - version "0.3.8" - resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.8.tgz#8e9515ea876e40a4cc1040af865f61fc51d27edf" - integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg== +"@solana/spl-token@^0.3.8", "@solana/spl-token@^0.3.9": + version "0.3.11" + resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.3.11.tgz#cdc10f9472b29b39c8983c92592cadd06627fb9a" + integrity sha512-bvohO3rIMSVL24Pb+I4EYTJ6cL82eFpInEXD/I8K8upOGjpqHsKUoAempR/RnUlI1qSFNyFlWJfu6MNUgfbCQQ== dependencies: "@solana/buffer-layout" "^4.0.0" "@solana/buffer-layout-utils" "^0.2.0" + "@solana/spl-token-metadata" "^0.1.2" + buffer "^6.0.3" + +"@solana/spl-type-length-value@0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b" + integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA== + dependencies: buffer "^6.0.3" "@solana/wallet-adapter-alpha@^0.1.10": @@ -2602,11 +2714,6 @@ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.3.tgz#d6bfbbdd0c187354ca555213d1962f6d0691ff4e" integrity sha512-Xe7IImK09HP1sv2M/aI+48a20VX+TdRJucfq4vfRVy6nWN8PYPOEnlMRSgxJAgYQIXJVL8dZ4/ilAM7dWNaOww== -"@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -2712,11 +2819,6 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== -"@types/semver@^7.3.12": - version "7.3.13" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91" - integrity sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw== - "@types/sinon@^17.0.3": version "17.0.3" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-17.0.3.tgz#9aa7e62f0a323b9ead177ed23a36ea757141a5fa" @@ -2743,20 +2845,20 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.43.0.tgz#4a5248eb31b454715ddfbf8cfbf497529a0a78bc" - integrity sha512-wNPzG+eDR6+hhW4yobEmpR36jrqqQv1vxBq5LJO3fBAktjkvekfr4BRl+3Fn1CM/A+s8/EiGUbOMDoYqWdbtXA== - dependencies: - "@typescript-eslint/scope-manager" "5.43.0" - "@typescript-eslint/type-utils" "5.43.0" - "@typescript-eslint/utils" "5.43.0" - debug "^4.3.4" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" +"@typescript-eslint/eslint-plugin@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz#8eaf396ac2992d2b8f874b68eb3fcd6b179cb7f3" + integrity sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/type-utils" "7.15.0" + "@typescript-eslint/utils" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" "@typescript-eslint/parser@^5.4.2 || ^6.0.0": version "6.4.0" @@ -2769,14 +2871,6 @@ "@typescript-eslint/visitor-keys" "6.4.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.43.0.tgz#566e46303392014d5d163704724872e1f2dd3c15" - integrity sha512-XNWnGaqAtTJsUiZaoiGIrdJYHsUOd3BZ3Qj5zKp9w6km6HsrjPk/TGZv0qMTWyWj0+1QOqpHQ2gZOLXaGA9Ekw== - dependencies: - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/visitor-keys" "5.43.0" - "@typescript-eslint/scope-manager@6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.4.0.tgz#3048e4262ba3eafa4e2e69b08912d9037ec646ae" @@ -2785,43 +2879,38 @@ "@typescript-eslint/types" "6.4.0" "@typescript-eslint/visitor-keys" "6.4.0" -"@typescript-eslint/type-utils@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.43.0.tgz#91110fb827df5161209ecca06f70d19a96030be6" - integrity sha512-K21f+KY2/VvYggLf5Pk4tgBOPs2otTaIHy2zjclo7UZGLyFH86VfUOm5iq+OtDtxq/Zwu2I3ujDBykVW4Xtmtg== +"@typescript-eslint/scope-manager@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2" + integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw== dependencies: - "@typescript-eslint/typescript-estree" "5.43.0" - "@typescript-eslint/utils" "5.43.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + +"@typescript-eslint/type-utils@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz#5b83c904c6de91802fb399305a50a56d10472c39" + integrity sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg== + dependencies: + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/utils" "7.15.0" debug "^4.3.4" - tsutils "^3.21.0" + ts-api-utils "^1.3.0" "@typescript-eslint/types@4.33.0": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== -"@typescript-eslint/types@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.43.0.tgz#e4ddd7846fcbc074325293515fa98e844d8d2578" - integrity sha512-jpsbcD0x6AUvV7tyOlyvon0aUsQpF8W+7TpJntfCUWU1qaIKu2K34pMwQKSzQH8ORgUrGYY6pVIh1Pi8TNeteg== - "@typescript-eslint/types@6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.4.0.tgz#5b109a59a805f0d8d375895e42d9e5f0037f66ee" integrity sha512-+FV9kVFrS7w78YtzkIsNSoYsnOtrYVnKWSTVXoL1761CsCRv5wpDOINgsXpxD67YCLZtVQekDDyaxfjVWUJmmg== -"@typescript-eslint/typescript-estree@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.43.0.tgz#b6883e58ba236a602c334be116bfc00b58b3b9f2" - integrity sha512-BZ1WVe+QQ+igWal2tDbNg1j2HWUkAa+CVqdU79L4HP9izQY6CNhXfkNwd1SS4+sSZAP/EthI1uiCSY/+H0pROg== - dependencies: - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/visitor-keys" "5.43.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" +"@typescript-eslint/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" + integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== "@typescript-eslint/typescript-estree@6.4.0": version "6.4.0" @@ -2836,6 +2925,20 @@ semver "^7.5.4" ts-api-utils "^1.0.1" +"@typescript-eslint/typescript-estree@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" + integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== + dependencies: + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + "@typescript-eslint/typescript-estree@^4.33.0", "@typescript-eslint/typescript-estree@^4.8.2": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" @@ -2849,19 +2952,15 @@ semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/utils@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.43.0.tgz#00fdeea07811dbdf68774a6f6eacfee17fcc669f" - integrity sha512-8nVpA6yX0sCjf7v/NDfeaOlyaIIqL7OaIGOWSPFqUKK59Gnumd3Wa+2l8oAaYO2lk0sO+SbWFWRSvhu8gLGv4A== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.43.0" - "@typescript-eslint/types" "5.43.0" - "@typescript-eslint/typescript-estree" "5.43.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - semver "^7.3.7" +"@typescript-eslint/utils@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960" + integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.0" "@typescript-eslint/visitor-keys@4.33.0": version "4.33.0" @@ -2871,14 +2970,6 @@ "@typescript-eslint/types" "4.33.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@5.43.0": - version "5.43.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.43.0.tgz#cbbdadfdfea385310a20a962afda728ea106befa" - integrity sha512-icl1jNH/d18OVHLfcwdL3bWUKsBeIiKYTGxMJCoGe7xFht+E4QgzOqoWYrU8XSLJWhVw8nTacbm03v23J/hFTg== - dependencies: - "@typescript-eslint/types" "5.43.0" - eslint-visitor-keys "^3.3.0" - "@typescript-eslint/visitor-keys@6.4.0": version "6.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.0.tgz#96a426cdb1add28274abd7a34aefe27f8b7d51ef" @@ -2887,6 +2978,14 @@ "@typescript-eslint/types" "6.4.0" eslint-visitor-keys "^3.4.1" +"@typescript-eslint/visitor-keys@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66" + integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw== + dependencies: + "@typescript-eslint/types" "7.15.0" + eslint-visitor-keys "^3.4.3" + "@wallet-standard/app@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@wallet-standard/app/-/app-1.0.1.tgz#f83c3ae887f7fb52497a7b259bba734ae10a2994" @@ -3463,7 +3562,7 @@ axios@^0.21.0: dependencies: follow-redirects "^1.14.0" -axios@^1.1.3, axios@^1.2.0: +axios@^1.1.3, axios@^1.2.0, axios@^1.6.2: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== @@ -3645,6 +3744,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -3877,6 +3983,11 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -4008,6 +4119,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@^12.0.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^2.16.0, commander@^2.20.3, commander@^2.8.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4316,7 +4432,7 @@ decamelize@^1.2.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -decimal.js-light@^2.4.1: +decimal.js-light@^2.4.1, decimal.js-light@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== @@ -5012,14 +5128,6 @@ eslint-plugin-tailwindcss@3.13.0: fast-glob "^3.2.5" postcss "^8.4.4" -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" @@ -5040,7 +5148,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -5114,11 +5222,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -5287,6 +5390,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fecha@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -5659,6 +5767,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphviz@0.0.9: version "0.0.9" resolved "https://registry.yarnpkg.com/graphviz/-/graphviz-0.0.9.tgz#0bbf1df588c6a92259282da35323622528c4bbc4" @@ -5872,10 +5985,10 @@ ieee754@^1.1.13, ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^5.2.0: - version "5.2.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== immer@9.0.12: version "9.0.12" @@ -6829,13 +6942,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - madge@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/madge/-/madge-4.0.2.tgz#56a3aff8021a5844f8713e0789f6ee94095f2f41" @@ -6963,6 +7069,13 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -7094,11 +7207,6 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8513,12 +8621,10 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" +semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.4, semver@^7.6.0: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -9025,6 +9131,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toformat@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/toformat/-/toformat-2.0.0.tgz#7a043fd2dfbe9021a4e36e508835ba32056739d8" + integrity sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ== + toggle-selection@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" @@ -9060,10 +9171,10 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" -ts-api-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.1.tgz#8144e811d44c749cd65b2da305a032510774452d" - integrity sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A== +ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== ts-ev@^0.4.0: version "0.4.0" @@ -9151,10 +9262,10 @@ typed-array-length@^1.0.4: for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@4.9.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== +typescript@5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== typescript@^3.9.10, typescript@^3.9.5, typescript@^3.9.7: version "3.9.10"