diff --git a/package.json b/package.json index 23d41d1c..e75eaba8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "classnames": "^2.3.2", "debounce-promise": "^3.1.2", "moment": "^2.29.4", - "next": "^13.4.10", + "next": "13.4.9", "react": "^18.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", diff --git a/src/api/cosmwasm-client.ts b/src/api/cosmwasm-client.ts index 1880dac5..015ff2d8 100644 --- a/src/api/cosmwasm-client.ts +++ b/src/api/cosmwasm-client.ts @@ -8,6 +8,7 @@ import { MarsOracleOsmosisQueryClient } from 'types/generated/mars-oracle-osmosi import { MarsMockRedBankQueryClient } from 'types/generated/mars-mock-red-bank/MarsMockRedBank.client' import { MarsMockVaultQueryClient } from 'types/generated/mars-mock-vault/MarsMockVault.client' import { MarsParamsQueryClient } from 'types/generated/mars-params/MarsParams.client' +import { MarsSwapperOsmosisQueryClient } from 'types/generated/mars-swapper-osmosis/MarsSwapperOsmosis.client' let _cosmWasmClient: CosmWasmClient let _accountNftQueryClient: MarsAccountNftQueryClient @@ -16,6 +17,7 @@ let _oracleQueryClient: MarsOracleOsmosisQueryClient let _redBankQueryClient: MarsMockRedBankQueryClient let _paramsQueryClient: MarsParamsQueryClient let _incentivesQueryClient: MarsIncentivesQueryClient +let _swapperOsmosisClient: MarsSwapperOsmosisQueryClient const getClient = async () => { try { @@ -119,6 +121,19 @@ const getIncentivesQueryClient = async () => { } } +const getSwapperQueryClient = async () => { + try { + if (!_swapperOsmosisClient) { + const client = await getClient() + _swapperOsmosisClient = new MarsSwapperOsmosisQueryClient(client, ENV.ADDRESS_SWAPPER) + } + + return _swapperOsmosisClient + } catch (error) { + throw error + } +} + export { getClient, getAccountNftQueryClient, @@ -128,4 +143,5 @@ export { getRedBankQueryClient, getVaultQueryClient, getIncentivesQueryClient, + getSwapperQueryClient, } diff --git a/src/api/swap/estimateExactIn.ts b/src/api/swap/estimateExactIn.ts new file mode 100644 index 00000000..c4252678 --- /dev/null +++ b/src/api/swap/estimateExactIn.ts @@ -0,0 +1,14 @@ +import { getSwapperQueryClient } from 'api/cosmwasm-client' +import { ZERO } from 'constants/math' +import { BN } from 'utils/helpers' + +export default async function estimateExactIn(coinIn: Coin, denomOut: string) { + try { + const swapperClient = await getSwapperQueryClient() + const estimatedAmount = (await swapperClient.estimateExactInSwap({ coinIn, denomOut })).amount + + return BN(estimatedAmount) + } catch (ex) { + return ZERO + } +} diff --git a/src/api/swap/getSwapRoute.ts b/src/api/swap/getSwapRoute.ts new file mode 100644 index 00000000..82c87f77 --- /dev/null +++ b/src/api/swap/getSwapRoute.ts @@ -0,0 +1,20 @@ +import { getSwapperQueryClient } from 'api/cosmwasm-client' + +interface Route { + pool_id: string + token_out_denom: string +} + +export default async function getSwapRoute(denomIn: string, denomOut: string): Promise { + try { + const swapperClient = await getSwapperQueryClient() + const routes = await swapperClient.route({ + denomIn, + denomOut, + }) + + return routes.route as unknown as Route[] + } catch (ex) { + return [] + } +} diff --git a/src/components/Icons/InfoCircle.svg b/src/components/Icons/InfoCircle.svg new file mode 100644 index 00000000..e2fa7a3e --- /dev/null +++ b/src/components/Icons/InfoCircle.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index 0e763712..735a9164 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -44,4 +44,5 @@ export { default as SwapIcon } from 'components/Icons/SwapIcon.svg' export { default as TrashBin } from 'components/Icons/TrashBin.svg' export { default as VerticalThreeLine } from 'components/Icons/VerticalThreeLine.svg' export { default as Wallet } from 'components/Icons/Wallet.svg' +export { default as InfoCircle } from 'components/Icons/InfoCircle.svg' // @endindex diff --git a/src/components/RangeInput/index.tsx b/src/components/RangeInput/index.tsx index 865ec794..ad478761 100644 --- a/src/components/RangeInput/index.tsx +++ b/src/components/RangeInput/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, useCallback } from 'react' +import { ChangeEvent, useCallback, useMemo } from 'react' import classNames from 'classnames' import InputOverlay from 'components/RangeInput/InputOverlay' @@ -17,7 +17,7 @@ function RangeInput(props: Props) { const handleOnChange = useCallback( (event: ChangeEvent) => { - onChange(parseInt(event.target.value)) + onChange(parseFloat(event.target.value)) }, [onChange], ) @@ -32,15 +32,20 @@ function RangeInput(props: Props) { - +
0 - {max} + {max.toFixed(2)}
) diff --git a/src/components/Trade/TradeModule/SwapForm/AssetAmountInput.tsx b/src/components/Trade/TradeModule/SwapForm/AssetAmountInput.tsx new file mode 100644 index 00000000..13ffe300 --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/AssetAmountInput.tsx @@ -0,0 +1,87 @@ +import classNames from 'classnames' +import { useCallback, useMemo } from 'react' + +import NumberInput from 'components/NumberInput' +import { formatValue } from 'utils/formatters' + +interface Props { + label?: string + max: BigNumber + asset: Asset + amount: BigNumber + maxButtonLabel: string + assetUSDValue: BigNumber + amountValueText?: string + containerClassName?: string + setAmount: (amount: BigNumber) => void + onFocus?: () => void +} + +export default function AssetAmountInput(props: Props) { + const { + label, + amount, + setAmount, + asset, + containerClassName, + max, + maxButtonLabel, + onFocus, + assetUSDValue, + } = props + + const handleMaxClick = useCallback(() => { + setAmount(max) + }, [max, setAmount]) + + const maxValue = useMemo(() => { + const val = max.shiftedBy(-asset.decimals) + return val.isGreaterThan(1) ? val.toFixed(2) : val.toPrecision(2) + }, [asset.decimals, max]) + + return ( +
+ +
+ ) +} + +const className = { + container: '', + inputWrapper: + 'flex flex-1 flex-row py-3 border-[1px] border-white border-opacity-20 rounded bg-white bg-opacity-5 pl-3 pr-2 mt-2', + input: 'border-none bg-transparent outline-none flex-1 !text-left', + footer: 'flex flex-1 flex-row', + maxButtonWrapper: 'flex flex-1 flex-row mt-2', + maxButtonLabel: 'font-bold text-xs', + maxValue: 'font-bold text-xs text-white text-opacity-60 mx-1', + maxButton: + 'cursor-pointer select-none bg-white bg-opacity-20 text-2xs !leading-3 font-bold py-0.5 px-1.5 rounded', + assetValue: 'text-xs text-white text-opacity-60 mt-2', +} diff --git a/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx b/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx new file mode 100644 index 00000000..c80c0d49 --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/MarginToggle.tsx @@ -0,0 +1,33 @@ +import { InfoCircle } from 'components/Icons' +import Switch from 'components/Switch' +import Text from 'components/Text' +import { Tooltip } from 'components/Tooltip' +import ConditionalWrapper from 'hocs/ConditionalWrapper' + +interface Props { + checked: boolean + onChange: (value: boolean) => void + disabled?: boolean +} + +export default function MarginToggle(props: Props) { + return ( +
+ Margin + + ( + Margin is not supported yet.}> + {children} + + )} + > +
+ + +
+
+
+ ) +} diff --git a/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants.ts b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants.ts new file mode 100644 index 00000000..c7f93f3f --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants.ts @@ -0,0 +1,18 @@ +import { OrderTab } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types' + +const ORDER_TYPE_UNAVAILABLE_MESSAGE = + 'This type of order is currently unavailable and is coming soon.' + +export const ORDER_TYPE_TABS: OrderTab[] = [ + { type: 'Market', isDisabled: false, tooltipText: '' }, + { + type: 'Limit', + isDisabled: true, + tooltipText: ORDER_TYPE_UNAVAILABLE_MESSAGE, + }, + { + type: 'Stop', + isDisabled: true, + tooltipText: ORDER_TYPE_UNAVAILABLE_MESSAGE, + }, +] diff --git a/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/index.tsx b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/index.tsx new file mode 100644 index 00000000..387c55bc --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/index.tsx @@ -0,0 +1,54 @@ +import classNames from 'classnames' + +import { InfoCircle } from 'components/Icons' +import Text from 'components/Text' +import { Tooltip } from 'components/Tooltip' +import { ORDER_TYPE_TABS } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/constants' +import ConditionalWrapper from 'hocs/ConditionalWrapper' +import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types' + +interface Props { + selected: AvailableOrderType + onChange: (value: AvailableOrderType) => void +} + +export default function OrderTypeSelector(props: Props) { + const { selected, onChange } = props + + return ( +
+ {ORDER_TYPE_TABS.map((tab) => { + const isSelected = tab.type === selected + const classes = classNames(className.tab, { + [className.selectedTab]: isSelected, + [className.disabledTab]: tab.isDisabled, + }) + + return ( + ( + {tab.tooltipText}}> + {children} + + )} + > +
onChange(tab.type)} className={classes}> + {tab.type} + {tab.isDisabled && } +
+
+ ) + })} +
+ ) +} + +const className = { + wrapper: 'flex flex-1 flex-row px-3 pt-3', + tab: 'mr-4 pb-2 cursor-pointer select-none flex flex-row', + selectedTab: 'border-b-2 border-pink border-solid', + disabledTab: 'opacity-50 pointer-events-none', + infoCircle: 'w-4 h-4 ml-2 mt-1', +} diff --git a/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/types.ts b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/types.ts new file mode 100644 index 00000000..af9e1cbd --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/OrderTypeSelector/types.ts @@ -0,0 +1,6 @@ +export type AvailableOrderType = 'Market' | 'Limit' | 'Stop' +export interface OrderTab { + type: AvailableOrderType + isDisabled: boolean + tooltipText: string +} diff --git a/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx b/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx new file mode 100644 index 00000000..7ebaa69b --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/TradeSummary.tsx @@ -0,0 +1,74 @@ +import classNames from 'classnames' +import { useCallback, useMemo, useState } from 'react' + +import Button from 'components/Button' +import { hardcodedFee } from 'utils/constants' +import { formatAmountWithSymbol } from 'utils/formatters' +import { getAssetByDenom } from 'utils/assets' +import useSwapRoute from 'hooks/useSwapRoute' + +interface Props { + buyAsset: Asset + sellAsset: Asset + containerClassName?: string + buyButtonDisabled: boolean + buyAction: () => void +} + +export default function TradeSummary(props: Props) { + const { containerClassName, buyAsset, sellAsset, buyAction, buyButtonDisabled } = props + const { data: routes, isLoading: isRouteLoading } = useSwapRoute(sellAsset.denom, buyAsset.denom) + const [isButtonBusy, setButtonBusy] = useState(false) + + const parsedRoutes = useMemo(() => { + if (!routes.length) return '-' + + const routeSymbols = routes.map((r) => getAssetByDenom(r.token_out_denom)?.symbol) + routeSymbols.unshift(sellAsset.symbol) + + return routeSymbols.join(' -> ') + }, [routes, sellAsset.symbol]) + + const handleBuyClick = useCallback(async () => { + setButtonBusy(true) + await buyAction() + setButtonBusy(false) + }, [buyAction]) + + const buttonText = useMemo( + () => (routes.length ? `Buy ${buyAsset.symbol}` : 'No route found'), + [buyAsset.symbol, routes], + ) + + return ( +
+
+ Summary +
+ Fees + {formatAmountWithSymbol(hardcodedFee.amount[0])} +
+
+ Route + {parsedRoutes} +
+
+
+ ) +} + +const className = { + container: + 'flex flex-1 flex-col bg-white bg-opacity-5 rounded border-[1px] border-white border-opacity-20 ', + title: 'text-xs font-bold mb-2', + summaryWrapper: 'flex flex-1 flex-col m-3', + infoLine: 'flex flex-1 flex-row text-xs text-white justify-between mb-1', + infoLineLabel: 'opacity-40', +} diff --git a/src/components/Trade/TradeModule/SwapForm/index.tsx b/src/components/Trade/TradeModule/SwapForm/index.tsx new file mode 100644 index 00000000..0ea8428d --- /dev/null +++ b/src/components/Trade/TradeModule/SwapForm/index.tsx @@ -0,0 +1,168 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' + +import Divider from 'components/Divider' +import { DEFAULT_SETTINGS } from 'constants/defaultSettings' +import { SLIPPAGE_KEY } from 'constants/localStore' +import { ZERO } from 'constants/math' +import useCurrentAccount from 'hooks/useCurrentAccount' +import useLocalStorage from 'hooks/useLocalStorage' +import usePrices from 'hooks/usePrices' +import useStore from 'store' +import { byDenom } from 'utils/array' +import { hardcodedFee } from 'utils/constants' +import RangeInput from 'components/RangeInput' +import { BN } from 'utils/helpers' +import AssetAmountInput from 'components/Trade/TradeModule/SwapForm/AssetAmountInput' +import MarginToggle from 'components/Trade/TradeModule/SwapForm/MarginToggle' +import OrderTypeSelector from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector' +import { AvailableOrderType } from 'components/Trade/TradeModule/SwapForm/OrderTypeSelector/types' +import TradeSummary from 'components/Trade/TradeModule/SwapForm/TradeSummary' +import { BNCoin } from 'types/classes/BNCoin' +import estimateExactIn from 'api/swap/estimateExactIn' + +interface Props { + buyAsset: Asset + sellAsset: Asset +} + +export default function SwapForm(props: Props) { + const { buyAsset, sellAsset } = props + const account = useCurrentAccount() + const { data: prices } = usePrices() + const swap = useStore((s) => s.swap) + const [isMarginChecked, setMarginChecked] = useState(false) + const [buyAssetAmount, setBuyAssetAmount] = useState(ZERO) + const [sellAssetAmount, setSellAssetAmount] = useState(ZERO) + const [slippage] = useLocalStorage(SLIPPAGE_KEY, DEFAULT_SETTINGS.slippage) + const [focusedInput, setFocusedInput] = useState<'buy' | 'sell' | null>(null) + const [maxBuyableAmountEstimation, setMaxBuyableAmountEstimation] = useState(ZERO) + const [selectedOrderType, setSelectedOrderType] = useState('Market') + + const accountSellAssetDeposit = useMemo( + () => account?.deposits.find(byDenom(sellAsset.denom))?.amount || ZERO, + [account, sellAsset.denom], + ) + + useEffect(() => { + estimateExactIn( + { denom: sellAsset.denom, amount: accountSellAssetDeposit }, + buyAsset.denom, + ).then(setMaxBuyableAmountEstimation) + }, [accountSellAssetDeposit, buyAsset.denom, sellAsset.denom]) + + const [buyAssetValue, sellAssetValue] = useMemo(() => { + const buyAssetPrice = prices.find(byDenom(buyAsset.denom))?.amount ?? ZERO + const sellAssetPrice = prices.find(byDenom(sellAsset.denom))?.amount ?? ZERO + + return [ + buyAssetPrice.multipliedBy(buyAssetAmount.shiftedBy(-buyAsset.decimals)), + sellAssetPrice.multipliedBy(sellAssetAmount.shiftedBy(-sellAsset.decimals)), + ] + }, [ + prices, + buyAsset.denom, + buyAsset.decimals, + sellAsset.denom, + sellAsset.decimals, + buyAssetAmount, + sellAssetAmount, + ]) + + useEffect(() => { + if (focusedInput === 'sell') { + estimateExactIn( + { denom: sellAsset.denom, amount: sellAssetAmount.toString() }, + buyAsset.denom, + ).then(setBuyAssetAmount) + } + }, [buyAsset.denom, focusedInput, sellAsset.denom, sellAssetAmount]) + + useEffect(() => { + if (focusedInput === 'buy') { + estimateExactIn( + { + denom: buyAsset.denom, + amount: buyAssetAmount.toString(), + }, + sellAsset.denom, + ).then(setSellAssetAmount) + } + }, [buyAsset.denom, buyAssetAmount, focusedInput, sellAsset.denom]) + + useEffect(() => { + setFocusedInput(null) + setBuyAssetAmount(ZERO) + setSellAssetAmount(ZERO) + }, [sellAsset.denom]) + + useEffect(() => { + setFocusedInput(null) + }, [buyAsset.denom]) + + const handleBuyClick = useCallback(async () => { + if (account?.id) { + const isSucceeded = await swap({ + fee: hardcodedFee, + accountId: account.id, + coinIn: BNCoin.fromDenomAndBigNumber(sellAsset.denom, sellAssetAmount.integerValue()), + denomOut: buyAsset.denom, + slippage, + }) + if (isSucceeded) { + setSellAssetAmount(ZERO) + } + } + }, [account?.id, buyAsset.denom, sellAsset.denom, sellAssetAmount, slippage, swap]) + + return ( + <> + + + + + + + setFocusedInput('buy')} + /> + + { + setFocusedInput('sell') + setSellAssetAmount(BN(value).shiftedBy(sellAsset.decimals).integerValue()) + }} + wrapperClassName='p-4' + /> + + setFocusedInput('sell')} + /> + + + + ) +} diff --git a/src/components/Trade/TradeModule/index.tsx b/src/components/Trade/TradeModule/index.tsx index 44e2cea8..8af62e08 100644 --- a/src/components/Trade/TradeModule/index.tsx +++ b/src/components/Trade/TradeModule/index.tsx @@ -1,9 +1,7 @@ import classNames from 'classnames' -import { useState } from 'react' -import Divider from 'components/Divider' -import RangeInput from 'components/RangeInput' import AssetSelector from 'components/Trade/TradeModule/AssetSelector' +import SwapForm from 'components/Trade/TradeModule/SwapForm' interface Props { buyAsset: Asset @@ -13,7 +11,7 @@ interface Props { } export default function TradeModule(props: Props) { - const [value, setValue] = useState(0) + const { buyAsset, sellAsset, onChangeBuyAsset, onChangeSellAsset } = props return (
- - + +
) } diff --git a/src/constants/math.ts b/src/constants/math.ts new file mode 100644 index 00000000..233797d8 --- /dev/null +++ b/src/constants/math.ts @@ -0,0 +1,3 @@ +import { BN } from 'utils/helpers' + +export const ZERO = BN(0) diff --git a/src/hooks/useSwapRoute.ts b/src/hooks/useSwapRoute.ts new file mode 100644 index 00000000..02ddf2bb --- /dev/null +++ b/src/hooks/useSwapRoute.ts @@ -0,0 +1,9 @@ +import useSWR from 'swr' + +import getSwapRoute from 'api/swap/getSwapRoute' + +export default function useSwapRoute(denomIn: string, denomOut: string) { + return useSWR(`swapRoute-${denomIn}-${denomOut}`, () => getSwapRoute(denomIn, denomOut), { + fallbackData: [], + }) +} diff --git a/src/store/slices/broadcast.ts b/src/store/slices/broadcast.ts index 17d57295..d5e81b82 100644 --- a/src/store/slices/broadcast.ts +++ b/src/store/slices/broadcast.ts @@ -13,6 +13,7 @@ import { } from 'types/generated/mars-credit-manager/MarsCreditManager.types' import { getSingleValueFromBroadcastResult } from 'utils/broadcast' import { formatAmountWithSymbol } from 'utils/formatters' +import getTokenOutFromSwapResponse from 'utils/getTokenOutFromSwapResponse' export default function createBroadcastSlice( set: SetState, @@ -324,5 +325,36 @@ export default function createBroadcastSlice( ) return !!response.result }, + swap: async (options: { + fee: StdFee + accountId: string + coinIn: BNCoin + denomOut: string + slippage: number + }) => { + const msg: CreditManagerExecuteMsg = { + update_credit_account: { + account_id: options.accountId, + actions: [ + { + swap_exact_in: { + coin_in: options.coinIn.toActionCoin(), + denom_out: options.denomOut, + slippage: options.slippage.toString(), + }, + }, + ], + }, + } + + const response = await get().executeMsg({ msg, fee: options.fee }) + const coinOut = getTokenOutFromSwapResponse(response, options.denomOut) + const successMessage = `Swapped ${formatAmountWithSymbol( + options.coinIn.toCoin(), + )} for ${formatAmountWithSymbol(coinOut)}` + + handleResponseMessages(response, successMessage) + return !!response.result + }, } } diff --git a/src/styles/globals.css b/src/styles/globals.css index beeb4269..a6115bb5 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -28,3 +28,14 @@ font-style: normal; font-display: swap; } + +@layer base { + input[type='number']::-webkit-outer-spin-button, + input[type='number']::-webkit-inner-spin-button, + input[type='number'] { + -webkit-appearance: none; + margin: 0; + -moz-appearance: textfield !important; + appearance: none; + } +} diff --git a/src/types/interfaces/store/broadcast.d.ts b/src/types/interfaces/store/broadcast.d.ts index 7b9574cc..6d25db72 100644 --- a/src/types/interfaces/store/broadcast.d.ts +++ b/src/types/interfaces/store/broadcast.d.ts @@ -56,4 +56,11 @@ interface BroadcastSlice { coin: BNCoin accountBalance?: boolean }) => Promise + swap: (options: { + fee: StdFee + accountId: string + coinIn: BNCoin + denomOut: string + slippage: number + }) => Promise } diff --git a/src/utils/getTokenOutFromSwapResponse.ts b/src/utils/getTokenOutFromSwapResponse.ts new file mode 100644 index 00000000..6feeb642 --- /dev/null +++ b/src/utils/getTokenOutFromSwapResponse.ts @@ -0,0 +1,18 @@ +export default function getTokenOutFromSwapResponse( + response: BroadcastResult, + denom: string, +): Coin { + if (response.result) { + const rawLogs = JSON.parse(response.result.rawLogs) + const events = rawLogs[0].events + const tokenSwappedEvent = events.find((e: { type: string }) => e.type === 'token_swapped') + const tokensOutValue = tokenSwappedEvent.attributes.find( + (a: { key: string }) => a.key === 'tokens_out', + ).value + const amount = tokensOutValue.split(denom)[0] + + return { denom, amount } + } + + return { denom: '', amount: '' } +} diff --git a/yarn.lock b/yarn.lock index 7975499a..e6d54b4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2640,10 +2640,10 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" -"@next/env@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.10.tgz#8b17783d2c09be126bbde9ff1164566517131bff" - integrity sha512-3G1yD/XKTSLdihyDSa8JEsaWOELY+OWe08o0LUYzfuHp1zHDA8SObQlzKt+v+wrkkPcnPweoLH1ImZeUa0A1NQ== +"@next/env@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/env/-/env-13.4.9.tgz#b77759514dd56bfa9791770755a2482f4d6ca93e" + integrity sha512-vuDRK05BOKfmoBYLNi2cujG2jrYbEod/ubSSyqgmEx9n/W3eZaJQdRNhTfumO+qmq/QTzLurW487n/PM/fHOkw== "@next/eslint-plugin-next@13.4.12": version "13.4.12" @@ -2652,50 +2652,50 @@ dependencies: glob "7.1.7" -"@next/swc-darwin-arm64@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.10.tgz#962ac55559970d1725163ff9d62d008bc1c33503" - integrity sha512-4bsdfKmmg7mgFGph0UorD1xWfZ5jZEw4kKRHYEeTK9bT1QnMbPVPlVXQRIiFPrhoDQnZUoa6duuPUJIEGLV1Jg== +"@next/swc-darwin-arm64@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.9.tgz#0ed408d444bbc6b0a20f3506a9b4222684585677" + integrity sha512-TVzGHpZoVBk3iDsTOQA/R6MGmFp0+17SWXMEWd6zG30AfuELmSSMe2SdPqxwXU0gbpWkJL1KgfLzy5ReN0crqQ== -"@next/swc-darwin-x64@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.10.tgz#90c01fdce5101953df0039eef48e4074055cc5aa" - integrity sha512-ngXhUBbcZIWZWqNbQSNxQrB9T1V+wgfCzAor2olYuo/YpaL6mUYNUEgeBMhr8qwV0ARSgKaOp35lRvB7EmCRBg== +"@next/swc-darwin-x64@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.9.tgz#a08fccdee68201522fe6618ec81f832084b222f8" + integrity sha512-aSfF1fhv28N2e7vrDZ6zOQ+IIthocfaxuMWGReB5GDriF0caTqtHttAvzOMgJgXQtQx6XhyaJMozLTSEXeNN+A== -"@next/swc-linux-arm64-gnu@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.10.tgz#8fc25052c345ffc8f6c51f61d1bb6c359b80ab2b" - integrity sha512-SjCZZCOmHD4uyM75MVArSAmF5Y+IJSGroPRj2v9/jnBT36SYFTORN8Ag/lhw81W9EeexKY/CUg2e9mdebZOwsg== +"@next/swc-linux-arm64-gnu@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.9.tgz#1798c2341bb841e96521433eed00892fb24abbd1" + integrity sha512-JhKoX5ECzYoTVyIy/7KykeO4Z2lVKq7HGQqvAH+Ip9UFn1MOJkOnkPRB7v4nmzqAoY+Je05Aj5wNABR1N18DMg== -"@next/swc-linux-arm64-musl@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.10.tgz#25e6b0dbb87c89c44c3e3680227172862bc7072c" - integrity sha512-F+VlcWijX5qteoYIOxNiBbNE8ruaWuRlcYyIRK10CugqI/BIeCDzEDyrHIHY8AWwbkTwe6GRHabMdE688Rqq4Q== +"@next/swc-linux-arm64-musl@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.9.tgz#cee04c51610eddd3638ce2499205083656531ea0" + integrity sha512-OOn6zZBIVkm/4j5gkPdGn4yqQt+gmXaLaSjRSO434WplV8vo2YaBNbSHaTM9wJpZTHVDYyjzuIYVEzy9/5RVZw== -"@next/swc-linux-x64-gnu@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.10.tgz#24fa8070ea0855c0aa020832ce7d1b84d3413fc1" - integrity sha512-WDv1YtAV07nhfy3i1visr5p/tjiH6CeXp4wX78lzP1jI07t4PnHHG1WEDFOduXh3WT4hG6yN82EQBQHDi7hBrQ== +"@next/swc-linux-x64-gnu@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.9.tgz#1932d0367916adbc6844b244cda1d4182bd11f7a" + integrity sha512-iA+fJXFPpW0SwGmx/pivVU+2t4zQHNOOAr5T378PfxPHY6JtjV6/0s1vlAJUdIHeVpX98CLp9k5VuKgxiRHUpg== -"@next/swc-linux-x64-musl@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.10.tgz#ae55914d50589a4f8b91c8eeebdd713f0c1b1675" - integrity sha512-zFkzqc737xr6qoBgDa3AwC7jPQzGLjDlkNmt/ljvQJ/Veri5ECdHjZCUuiTUfVjshNIIpki6FuP0RaQYK9iCRg== +"@next/swc-linux-x64-musl@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.9.tgz#a66aa8c1383b16299b72482f6360facd5cde3c7a" + integrity sha512-rlNf2WUtMM+GAQrZ9gMNdSapkVi3koSW3a+dmBVp42lfugWVvnyzca/xJlN48/7AGx8qu62WyO0ya1ikgOxh6A== -"@next/swc-win32-arm64-msvc@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.10.tgz#ab3098b2305f3c0e46dfb2e318a9988bff884047" - integrity sha512-IboRS8IWz5mWfnjAdCekkl8s0B7ijpWeDwK2O8CdgZkoCDY0ZQHBSGiJ2KViAG6+BJVfLvcP+a2fh6cdyBr9QQ== +"@next/swc-win32-arm64-msvc@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.9.tgz#39482ee856c867177a612a30b6861c75e0736a4a" + integrity sha512-5T9ybSugXP77nw03vlgKZxD99AFTHaX8eT1ayKYYnGO9nmYhJjRPxcjU5FyYI+TdkQgEpIcH7p/guPLPR0EbKA== -"@next/swc-win32-ia32-msvc@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.10.tgz#a1c5980538641ca656012c00d05b08882cf0ec9f" - integrity sha512-bSA+4j8jY4EEiwD/M2bol4uVEu1lBlgsGdvM+mmBm/BbqofNBfaZ2qwSbwE2OwbAmzNdVJRFRXQZ0dkjopTRaQ== +"@next/swc-win32-ia32-msvc@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.9.tgz#29db85e34b597ade1a918235d16a760a9213c190" + integrity sha512-ojZTCt1lP2ucgpoiFgrFj07uq4CZsq4crVXpLGgQfoFq00jPKRPgesuGPaz8lg1yLfvafkU3Jd1i8snKwYR3LA== -"@next/swc-win32-x64-msvc@13.4.10": - version "13.4.10" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.10.tgz#44dd9eea943ed14a1012edd5011b8e905f5e6fc4" - integrity sha512-g2+tU63yTWmcVQKDGY0MV1PjjqgZtwM4rB1oVVi/v0brdZAcrcTV+04agKzWtvWroyFz6IqtT0MoZJA7PNyLVw== +"@next/swc-win32-x64-msvc@13.4.9": + version "13.4.9" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.9.tgz#0c2758164cccd61bc5a1c6cd8284fe66173e4a2b" + integrity sha512-QbT03FXRNdpuL+e9pLnu+XajZdm/TtIXVYY4lA9t+9l0fLZbHXDYEKitAqxrOj37o3Vx5ufxiRAniaIebYDCgw== "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" @@ -7597,12 +7597,12 @@ natural-compare@^1.4.0: resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -next@^13.4.10: - version "13.4.10" - resolved "https://registry.yarnpkg.com/next/-/next-13.4.10.tgz#a5b50696759c61663d5a1dd726995fa0576a382e" - integrity sha512-4ep6aKxVTQ7rkUW2fBLhpBr/5oceCuf4KmlUpvG/aXuDTIf9mexNSpabUD6RWPspu6wiJJvozZREhXhueYO36A== +next@13.4.9: + version "13.4.9" + resolved "https://registry.yarnpkg.com/next/-/next-13.4.9.tgz#473de5997cb4c5d7a4fb195f566952a1cbffbeba" + integrity sha512-vtefFm/BWIi/eWOqf1GsmKG3cjKw1k3LjuefKRcL3iiLl3zWzFdPG3as6xtxrGO6gwTzzaO1ktL4oiHt/uvTjA== dependencies: - "@next/env" "13.4.10" + "@next/env" "13.4.9" "@swc/helpers" "0.5.1" busboy "1.6.0" caniuse-lite "^1.0.30001406" @@ -7611,15 +7611,15 @@ next@^13.4.10: watchpack "2.4.0" zod "3.21.4" optionalDependencies: - "@next/swc-darwin-arm64" "13.4.10" - "@next/swc-darwin-x64" "13.4.10" - "@next/swc-linux-arm64-gnu" "13.4.10" - "@next/swc-linux-arm64-musl" "13.4.10" - "@next/swc-linux-x64-gnu" "13.4.10" - "@next/swc-linux-x64-musl" "13.4.10" - "@next/swc-win32-arm64-msvc" "13.4.10" - "@next/swc-win32-ia32-msvc" "13.4.10" - "@next/swc-win32-x64-msvc" "13.4.10" + "@next/swc-darwin-arm64" "13.4.9" + "@next/swc-darwin-x64" "13.4.9" + "@next/swc-linux-arm64-gnu" "13.4.9" + "@next/swc-linux-arm64-musl" "13.4.9" + "@next/swc-linux-x64-gnu" "13.4.9" + "@next/swc-linux-x64-musl" "13.4.9" + "@next/swc-win32-arm64-msvc" "13.4.9" + "@next/swc-win32-ia32-msvc" "13.4.9" + "@next/swc-win32-x64-msvc" "13.4.9" no-case@^3.0.4: version "3.0.4"