feat: swap functionality (#319)

This commit is contained in:
Yusuf Seyrek 2023-07-26 09:23:30 +03:00 committed by GitHub
parent fef9227a0d
commit e09c2f9d53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 654 additions and 76 deletions

View File

@ -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",

View File

@ -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,
}

View File

@ -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
}
}

View File

@ -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<Route[]> {
try {
const swapperClient = await getSwapperQueryClient()
const routes = await swapperClient.route({
denomIn,
denomOut,
})
return routes.route as unknown as Route[]
} catch (ex) {
return []
}
}

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<g clip-path="url(#clip0_2324_198820)">
<path d="M7.99967 10.6673V8.00065M7.99967 5.33398H8.00634M14.6663 8.00065C14.6663 11.6826 11.6816 14.6673 7.99967 14.6673C4.31778 14.6673 1.33301 11.6826 1.33301 8.00065C1.33301 4.31875 4.31778 1.33398 7.99967 1.33398C11.6816 1.33398 14.6663 4.31875 14.6663 8.00065Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_2324_198820">
<rect width="16" height="16" fill="currentColor"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 588 B

View File

@ -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

View File

@ -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<HTMLInputElement>) => {
onChange(parseInt(event.target.value))
onChange(parseFloat(event.target.value))
},
[onChange],
)
@ -32,15 +32,20 @@ function RangeInput(props: Props) {
<input
className={className.input}
type='range'
value={value}
value={value.toFixed(2)}
step={max / 100}
max={max}
onChange={handleOnChange}
/>
<InputOverlay max={max} marginThreshold={marginThreshold} value={value} />
<InputOverlay
max={max}
marginThreshold={marginThreshold}
value={parseFloat(value.toFixed(2))}
/>
</div>
<div className={className.legendWrapper}>
<span>0</span>
<span>{max}</span>
<span>{max.toFixed(2)}</span>
</div>
</div>
)

View File

@ -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 (
<div className={classNames(className.container, containerClassName)}>
<label>
{label}
<div className={className.inputWrapper}>
<NumberInput
asset={asset}
amount={amount}
className={className.input}
maxDecimals={asset.decimals}
max={max}
onChange={setAmount}
onFocus={onFocus}
/>
<span>{asset.symbol}</span>
</div>
<div className={className.footer}>
<div className={className.maxButtonWrapper}>
<span className={className.maxButtonLabel}>{maxButtonLabel}</span>
<span className={className.maxValue}>{maxValue}</span>
<div className={className.maxButton} onClick={handleMaxClick}>
MAX
</div>
</div>
<div className={className.assetValue}>
{formatValue(assetUSDValue.toString(), { prefix: '~ $', minDecimals: 2 })}
</div>
</div>
</label>
</div>
)
}
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',
}

View File

@ -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 (
<div className='flex flex-1 flex-row justify-between bg-white/5 px-4 py-2'>
<Text size='sm'>Margin</Text>
<ConditionalWrapper
condition={!!props.disabled}
wrapper={(children) => (
<Tooltip type='info' content={<Text size='sm'>Margin is not supported yet.</Text>}>
{children}
</Tooltip>
)}
>
<div className='flex flex-row'>
<Switch {...props} name='margin' />
<InfoCircle width={16} height={16} className='ml-2 mt-0.5 opacity-20' />
</div>
</ConditionalWrapper>
</div>
)
}

View File

@ -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,
},
]

View File

@ -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 (
<div className={className.wrapper}>
{ORDER_TYPE_TABS.map((tab) => {
const isSelected = tab.type === selected
const classes = classNames(className.tab, {
[className.selectedTab]: isSelected,
[className.disabledTab]: tab.isDisabled,
})
return (
<ConditionalWrapper
key={tab.type}
condition={tab.isDisabled && !!tab.tooltipText}
wrapper={(children) => (
<Tooltip type='info' content={<Text size='sm'>{tab.tooltipText}</Text>}>
{children}
</Tooltip>
)}
>
<div onClick={() => onChange(tab.type)} className={classes}>
{tab.type}
{tab.isDisabled && <InfoCircle className={className.infoCircle} />}
</div>
</ConditionalWrapper>
)
})}
</div>
)
}
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',
}

View File

@ -0,0 +1,6 @@
export type AvailableOrderType = 'Market' | 'Limit' | 'Stop'
export interface OrderTab {
type: AvailableOrderType
isDisabled: boolean
tooltipText: string
}

View File

@ -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 (
<div className={classNames(containerClassName, className.container)}>
<div className={className.summaryWrapper}>
<span className={className.title}>Summary</span>
<div className={className.infoLine}>
<span className={className.infoLineLabel}>Fees</span>
<span>{formatAmountWithSymbol(hardcodedFee.amount[0])}</span>
</div>
<div className={className.infoLine}>
<span className={className.infoLineLabel}>Route</span>
<span>{parsedRoutes}</span>
</div>
</div>
<Button
disabled={routes.length === 0 || buyButtonDisabled}
showProgressIndicator={isButtonBusy || isRouteLoading}
text={buttonText}
onClick={handleBuyClick}
size='md'
/>
</div>
)
}
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',
}

View File

@ -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<AvailableOrderType>('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 (
<>
<Divider />
<MarginToggle checked={isMarginChecked} onChange={setMarginChecked} disabled />
<Divider />
<OrderTypeSelector selected={selectedOrderType} onChange={setSelectedOrderType} />
<AssetAmountInput
label='Buy'
max={maxBuyableAmountEstimation}
amount={buyAssetAmount}
setAmount={setBuyAssetAmount}
asset={buyAsset}
assetUSDValue={buyAssetValue}
maxButtonLabel='Max Amount:'
containerClassName='mx-3 my-6'
onFocus={() => setFocusedInput('buy')}
/>
<RangeInput
max={accountSellAssetDeposit.shiftedBy(-sellAsset.decimals).toNumber()}
value={sellAssetAmount.shiftedBy(-sellAsset.decimals).toNumber()}
onChange={(value) => {
setFocusedInput('sell')
setSellAssetAmount(BN(value).shiftedBy(sellAsset.decimals).integerValue())
}}
wrapperClassName='p-4'
/>
<AssetAmountInput
label='Sell'
max={accountSellAssetDeposit}
amount={sellAssetAmount}
setAmount={setSellAssetAmount}
assetUSDValue={sellAssetValue}
asset={sellAsset}
maxButtonLabel='Balance:'
containerClassName='mx-3'
onFocus={() => setFocusedInput('sell')}
/>
<TradeSummary
containerClassName='m-3 mt-10'
buyAsset={buyAsset}
sellAsset={sellAsset}
buyAction={handleBuyClick}
buyButtonDisabled={sellAssetAmount.isZero()}
/>
</>
)
}

View File

@ -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 (
<div
@ -24,19 +22,13 @@ export default function TradeModule(props: Props) {
)}
>
<AssetSelector
buyAsset={props.buyAsset}
sellAsset={props.sellAsset}
onChangeBuyAsset={props.onChangeBuyAsset}
onChangeSellAsset={props.onChangeSellAsset}
/>
<Divider />
<RangeInput
max={4000}
marginThreshold={2222}
value={value}
onChange={setValue}
wrapperClassName='p-4'
buyAsset={buyAsset}
sellAsset={sellAsset}
onChangeBuyAsset={onChangeBuyAsset}
onChangeSellAsset={onChangeSellAsset}
/>
<SwapForm buyAsset={buyAsset} sellAsset={sellAsset} />
</div>
)
}

3
src/constants/math.ts Normal file
View File

@ -0,0 +1,3 @@
import { BN } from 'utils/helpers'
export const ZERO = BN(0)

View File

@ -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: [],
})
}

View File

@ -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<Store>,
@ -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
},
}
}

View File

@ -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;
}
}

View File

@ -56,4 +56,11 @@ interface BroadcastSlice {
coin: BNCoin
accountBalance?: boolean
}) => Promise<boolean>
swap: (options: {
fee: StdFee
accountId: string
coinIn: BNCoin
denomOut: string
slippage: number
}) => Promise<boolean>
}

View File

@ -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: '' }
}

108
yarn.lock
View File

@ -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"