diff --git a/jest.config.js b/jest.config.js index e15911959..36a31e155 100644 --- a/jest.config.js +++ b/jest.config.js @@ -7,8 +7,8 @@ module.exports = { // Jest does not always resolve src/test (probably because of babel's TypeScript transpilation): '^test/*': '/src/test', - '@uniswap/conedison/format': '@uniswap/conedison/dist/format.js', - '@uniswap/conedison/provider': '@uniswap/conedison/dist/provider/index.js', + 'utils/conedison/format': 'utils/conedison/dist/format.js', + 'utils/conedison/provider': 'utils/conedison/dist/provider/index.js', }, setupFiles: ['/test/setup.ts'], setupFilesAfterEnv: ['jest-extended/all', '/test/setup-jest.ts'], diff --git a/package.json b/package.json index f07d687e8..3493717b2 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "@fontsource/inter": "^4.5.1", "@popperjs/core": "^2.4.4", "@reduxjs/toolkit": "^1.6.1", - "@uniswap/conedison": "^1.5.3", "@uniswap/permit2-sdk": "^1.2.0", "@uniswap/redux-multicall": "^1.1.8", "@uniswap/router-sdk": "^1.3.0", @@ -72,6 +71,8 @@ "@uniswap/universal-router-sdk": "^1.3.8", "@uniswap/v2-sdk": "^3.0.1", "@uniswap/v3-sdk": "^3.8.2", + "@walletconnect/ethereum-provider-v1": "npm:@walletconnect/ethereum-provider@^1.8.0", + "@walletconnect/ethereum-provider-v2": "npm:@walletconnect/ethereum-provider@^2.4.6", "@web3-react/core": "^8.2.0", "@web3-react/eip1193": "^8.2.0", "@web3-react/empty": "^8.2.0", diff --git a/src/components/Swap/Input.tsx b/src/components/Swap/Input.tsx index b55ab199a..ecd3092cd 100644 --- a/src/components/Swap/Input.tsx +++ b/src/components/Swap/Input.tsx @@ -1,5 +1,4 @@ import { t, Trans } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { TextButton } from 'components/Button' import { loadingTransitionCss } from 'css/loading' @@ -14,6 +13,7 @@ import { TradeState } from 'state/routing/types' import { Field } from 'state/swap' import styled from 'styled-components/macro' import { AnimationSpeed, ThemedText } from 'theme' +import { formatCurrencyAmount, NumberType } from 'utils/conedison/format' import { maxAmountSpend } from 'utils/maxAmountSpend' import Column from '../Column' diff --git a/src/components/Swap/Price.tsx b/src/components/Swap/Price.tsx index 1e57aacc5..10ef2f1b0 100644 --- a/src/components/Swap/Price.tsx +++ b/src/components/Swap/Price.tsx @@ -1,8 +1,8 @@ -import { formatCurrencyAmount, formatPrice, NumberType } from '@uniswap/conedison/format' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import Row from 'components/Row' import { useCallback, useMemo, useState } from 'react' import { InterfaceTrade } from 'state/routing/types' +import { formatCurrencyAmount, formatPrice, NumberType } from 'utils/conedison/format' import { ThemedText } from 'theme' import { TextButton } from '../Button' diff --git a/src/components/Swap/PriceImpactRow.tsx b/src/components/Swap/PriceImpactRow.tsx index 8b8ff7461..823db885b 100644 --- a/src/components/Swap/PriceImpactRow.tsx +++ b/src/components/Swap/PriceImpactRow.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro' -import { formatPriceImpact } from '@uniswap/conedison/format' +import { formatPriceImpact } from 'utils/conedison/format' import Row from 'components/Row' import Tooltip, { SmallToolTipBody, TooltipText } from 'components/Tooltip' import { PriceImpact } from 'hooks/usePriceImpact' diff --git a/src/components/Swap/RoutingDiagram/index.tsx b/src/components/Swap/RoutingDiagram/index.tsx index 05fa681ac..afb67d450 100644 --- a/src/components/Swap/RoutingDiagram/index.tsx +++ b/src/components/Swap/RoutingDiagram/index.tsx @@ -1,5 +1,5 @@ import { Trans } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' +import { formatCurrencyAmount, NumberType } from 'utils/conedison/format' import { Protocol } from '@uniswap/router-sdk' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import { FeeAmount } from '@uniswap/v3-sdk' diff --git a/src/components/Swap/Summary/Details.tsx b/src/components/Swap/Summary/Details.tsx index a0bb7a54e..1de4101f3 100644 --- a/src/components/Swap/Summary/Details.tsx +++ b/src/components/Swap/Summary/Details.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro' -import { formatCurrencyAmount, formatPriceImpact, NumberType } from '@uniswap/conedison/format' +import { formatCurrencyAmount, formatPriceImpact, NumberType } from 'utils/conedison/format' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import Column from 'components/Column' import { useIsDialogPageCentered } from 'components/Dialog' diff --git a/src/components/Swap/Summary/Estimate.tsx b/src/components/Swap/Summary/Estimate.tsx index 44d7c4037..eebb3d490 100644 --- a/src/components/Swap/Summary/Estimate.tsx +++ b/src/components/Swap/Summary/Estimate.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/macro' -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' +import { formatCurrencyAmount, NumberType } from 'utils/conedison/format' import { formatSlippage, Slippage } from 'hooks/useSlippage' import { ReactNode, useMemo } from 'react' import { InterfaceTrade } from 'state/routing/types' diff --git a/src/components/Swap/Summary/Summary.tsx b/src/components/Swap/Summary/Summary.tsx index 9b9b98f5a..6968cff50 100644 --- a/src/components/Swap/Summary/Summary.tsx +++ b/src/components/Swap/Summary/Summary.tsx @@ -1,4 +1,4 @@ -import { formatPriceImpact } from '@uniswap/conedison/format' +import { formatPriceImpact } from 'utils/conedison/format' import { Currency, CurrencyAmount } from '@uniswap/sdk-core' import { PriceImpact } from 'hooks/usePriceImpact' import { ArrowDown, ArrowRight } from 'icons' diff --git a/src/components/Swap/Summary/index.tsx b/src/components/Swap/Summary/index.tsx index 238b2b5ef..43f4122c3 100644 --- a/src/components/Swap/Summary/index.tsx +++ b/src/components/Swap/Summary/index.tsx @@ -1,5 +1,5 @@ import { t, Trans } from '@lingui/macro' -import { formatPriceImpact } from '@uniswap/conedison/format' +import { formatPriceImpact } from 'utils/conedison/format' import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' import ActionButton, { Action, ActionButtonColor } from 'components/ActionButton' import Column from 'components/Column' diff --git a/src/components/Swap/Toolbar/GasEstimateTooltip.tsx b/src/components/Swap/Toolbar/GasEstimateTooltip.tsx index dd440ff09..6952dc046 100644 --- a/src/components/Swap/Toolbar/GasEstimateTooltip.tsx +++ b/src/components/Swap/Toolbar/GasEstimateTooltip.tsx @@ -1,4 +1,4 @@ -import { formatCurrencyAmount, NumberType } from '@uniswap/conedison/format' +import { formatCurrencyAmount, NumberType } from 'utils/conedison/format' import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { BottomSheetModal } from 'components/BottomSheetModal' import Column from 'components/Column' diff --git a/src/components/Swap/Toolbar/index.tsx b/src/components/Swap/Toolbar/index.tsx index 57d8cb23d..1c61d16c8 100644 --- a/src/components/Swap/Toolbar/index.tsx +++ b/src/components/Swap/Toolbar/index.tsx @@ -1,5 +1,5 @@ import { t, Trans } from '@lingui/macro' -import { formatCurrencyAmount, formatPriceImpact, NumberType } from '@uniswap/conedison/format' +import { formatCurrencyAmount, formatPriceImpact, NumberType } from 'utils/conedison/format' import ActionButton from 'components/ActionButton' import Column from 'components/Column' import Expando from 'components/Expando' diff --git a/src/hooks/usePermitAllowance.ts b/src/hooks/usePermitAllowance.ts index 39337aa59..b8cea3bbb 100644 --- a/src/hooks/usePermitAllowance.ts +++ b/src/hooks/usePermitAllowance.ts @@ -1,5 +1,4 @@ import { t } from '@lingui/macro' -import { signTypedData } from '@uniswap/conedison/provider/signing' import { AllowanceTransfer, MaxAllowanceTransferAmount, PERMIT2_ADDRESS, PermitSingle } from '@uniswap/permit2-sdk' import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { useWeb3React } from '@web3-react/core' @@ -10,6 +9,7 @@ import { useSingleCallResult } from 'hooks/multicall' import { useContract } from 'hooks/useContract' import ms from 'ms.macro' import { useCallback, useEffect, useMemo, useState } from 'react' +import { signTypedData } from 'utils/conedison/provider/signing' import { isUserRejection } from 'utils/jsonRpcError' import { usePerfEventHandler } from './usePerfEventHandler' diff --git a/src/hooks/usePriceImpact.test.tsx b/src/hooks/usePriceImpact.test.tsx index b8486282a..83b72bdb2 100644 --- a/src/hooks/usePriceImpact.test.tsx +++ b/src/hooks/usePriceImpact.test.tsx @@ -1,4 +1,4 @@ -import { formatPriceImpact } from '@uniswap/conedison/format' +import { formatPriceImpact } from 'utils/conedison/format' import { CurrencyAmount, TradeType } from '@uniswap/sdk-core' import { InterfaceTrade } from 'state/routing/types' import { renderHook } from 'test' diff --git a/src/hooks/useSlippage.ts b/src/hooks/useSlippage.ts index 93fcb9689..9e8e80c92 100644 --- a/src/hooks/useSlippage.ts +++ b/src/hooks/useSlippage.ts @@ -1,4 +1,4 @@ -import { formatPriceImpact } from '@uniswap/conedison/format' +import { formatPriceImpact } from 'utils/conedison/format' import { CurrencyAmount, Percent, Token } from '@uniswap/sdk-core' import useAutoSlippageTolerance, { DEFAULT_AUTO_SLIPPAGE } from 'hooks/useAutoSlippageTolerance' import { useAtomValue } from 'jotai/utils' diff --git a/src/hooks/useUniversalRouter.test.ts b/src/hooks/useUniversalRouter.test.ts index a285ad5fd..b0a30c547 100644 --- a/src/hooks/useUniversalRouter.test.ts +++ b/src/hooks/useUniversalRouter.test.ts @@ -1,5 +1,5 @@ import { TransactionResponse } from '@ethersproject/abstract-provider' -import { sendTransaction } from '@uniswap/conedison/provider/index' +import { sendTransaction } from 'utils/conedison/provider/index' import { TradeType } from '@uniswap/sdk-core' import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { SupportedChainId } from 'constants/chains' @@ -22,7 +22,7 @@ const SWAP_TRANSACTION_INFO = { slippageTolerance: OPTIONS.slippageTolerance, } -jest.mock('@uniswap/conedison/provider/index', () => ({ +jest.mock('utils/conedison/provider/index', () => ({ sendTransaction: jest.fn().mockResolvedValue(RESPONSE), })) diff --git a/src/hooks/useUniversalRouter.ts b/src/hooks/useUniversalRouter.ts index 946bb3555..8bae9b019 100644 --- a/src/hooks/useUniversalRouter.ts +++ b/src/hooks/useUniversalRouter.ts @@ -1,6 +1,6 @@ import { BigNumber } from '@ethersproject/bignumber' import { t } from '@lingui/macro' -import { sendTransaction } from '@uniswap/conedison/provider/index' +import { sendTransaction } from 'utils/conedison/provider/index' import { Percent } from '@uniswap/sdk-core' import { SwapRouter, UNIVERSAL_ROUTER_ADDRESS } from '@uniswap/universal-router-sdk' import { FeeOptions, toHex } from '@uniswap/v3-sdk' diff --git a/src/utils/conedison/format.test.ts b/src/utils/conedison/format.test.ts new file mode 100644 index 000000000..a40b363b3 --- /dev/null +++ b/src/utils/conedison/format.test.ts @@ -0,0 +1,184 @@ +import { Percent } from '@uniswap/sdk-core' + +import { formatNumber, formatPriceImpact, formatSlippage, formatUSDPrice, NumberType } from './format' + +it('formats token reference numbers correctly', () => { + expect(formatNumber(1234567000000000, NumberType.TokenNonTx)).toBe('>999T') + expect(formatNumber(1002345, NumberType.TokenNonTx)).toBe('1.00M') + expect(formatNumber(1234, NumberType.TokenNonTx)).toBe('1,234.00') + expect(formatNumber(0.00909, NumberType.TokenNonTx)).toBe('0.009') + expect(formatNumber(0.09001, NumberType.TokenNonTx)).toBe('0.090') + expect(formatNumber(0.00099, NumberType.TokenNonTx)).toBe('<0.001') + expect(formatNumber(0, NumberType.TokenNonTx)).toBe('0') +}) + +it('formats token transaction numbers correctly', () => { + expect(formatNumber(1234567.8901, NumberType.TokenTx)).toBe('1,234,567.89') + expect(formatNumber(765432.1, NumberType.TokenTx)).toBe('765,432.10') + + expect(formatNumber(7654.321, NumberType.TokenTx)).toBe('7,654.32') + expect(formatNumber(765.4321, NumberType.TokenTx)).toBe('765.432') + expect(formatNumber(76.54321, NumberType.TokenTx)).toBe('76.5432') + expect(formatNumber(7.654321, NumberType.TokenTx)).toBe('7.65432') + expect(formatNumber(7.60000054321, NumberType.TokenTx)).toBe('7.60') + expect(formatNumber(7.6, NumberType.TokenTx)).toBe('7.60') + expect(formatNumber(7, NumberType.TokenTx)).toBe('7.00') + + expect(formatNumber(0.987654321, NumberType.TokenTx)).toBe('0.98765') + expect(formatNumber(0.9, NumberType.TokenTx)).toBe('0.90') + expect(formatNumber(0.901000123, NumberType.TokenTx)).toBe('0.901') + expect(formatNumber(0.000000001, NumberType.TokenTx)).toBe('<0.00001') + expect(formatNumber(0, NumberType.TokenTx)).toBe('0') +}) + +it('formats fiat estimates on token details pages correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenDetails)).toBe('$1.23M') + expect(formatNumber(1234.5678, NumberType.FiatTokenDetails)).toBe('$1,234.57') + expect(formatNumber(1.048942, NumberType.FiatTokenDetails)).toBe('$1.049') + + expect(formatNumber(0.001231, NumberType.FiatTokenDetails)).toBe('$0.00123') + expect(formatNumber(0.00001231, NumberType.FiatTokenDetails)).toBe('$0.0000123') + + expect(formatNumber(0.0000001234, NumberType.FiatTokenDetails)).toBe('$0.000000123') + expect(formatNumber(0.000000009876, NumberType.FiatTokenDetails)).toBe('<$0.00000001') +}) + +it('formats fiat estimates for tokens correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenPrice)).toBe('$1.23M') + expect(formatNumber(1234.5678, NumberType.FiatTokenPrice)).toBe('$1,234.57') + + expect(formatNumber(0.010235, NumberType.FiatTokenPrice)).toBe('$0.0102') + expect(formatNumber(0.001231, NumberType.FiatTokenPrice)).toBe('$0.00123') + expect(formatNumber(0.00001231, NumberType.FiatTokenPrice)).toBe('$0.0000123') + + expect(formatNumber(0.0000001234, NumberType.FiatTokenPrice)).toBe('$0.000000123') + expect(formatNumber(0.000000009876, NumberType.FiatTokenPrice)).toBe('<$0.00000001') +}) + +it('formats fiat estimates for token stats correctly', () => { + expect(formatNumber(1234576, NumberType.FiatTokenStats)).toBe('$1.2M') + expect(formatNumber(234567, NumberType.FiatTokenStats)).toBe('$234.6K') + expect(formatNumber(123.456, NumberType.FiatTokenStats)).toBe('$123.46') + expect(formatNumber(1.23, NumberType.FiatTokenStats)).toBe('$1.23') + expect(formatNumber(0.123, NumberType.FiatTokenStats)).toBe('$0.12') + expect(formatNumber(0.00123, NumberType.FiatTokenStats)).toBe('<$0.01') + expect(formatNumber(0, NumberType.FiatTokenStats)).toBe('-') +}) + +it('formats gas USD prices correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatGasPrice)).toBe('$1.23M') + expect(formatNumber(18.448, NumberType.FiatGasPrice)).toBe('$18.45') + expect(formatNumber(0.0099, NumberType.FiatGasPrice)).toBe('<$0.01') +}) + +it('formats USD token quantities prices correctly', () => { + expect(formatNumber(1234567.891, NumberType.FiatTokenQuantity)).toBe('$1.23M') + expect(formatNumber(18.448, NumberType.FiatTokenQuantity)).toBe('$18.45') + expect(formatNumber(0.0099, NumberType.FiatTokenQuantity)).toBe('<$0.01') + expect(formatNumber(0, NumberType.FiatTokenQuantity)).toBe('$0.00') +}) + +it('formats Swap text input/output numbers correctly', () => { + expect(formatNumber(1234567.8901, NumberType.SwapTradeAmount)).toBe('1234570') + expect(formatNumber(765432.1, NumberType.SwapTradeAmount)).toBe('765432') + + expect(formatNumber(7654.321, NumberType.SwapTradeAmount)).toBe('7654.32') + expect(formatNumber(765.4321, NumberType.SwapTradeAmount)).toBe('765.432') + expect(formatNumber(76.54321, NumberType.SwapTradeAmount)).toBe('76.5432') + expect(formatNumber(7.654321, NumberType.SwapTradeAmount)).toBe('7.65432') + expect(formatNumber(7.60000054321, NumberType.SwapTradeAmount)).toBe('7.60') + expect(formatNumber(7.6, NumberType.SwapTradeAmount)).toBe('7.60') + expect(formatNumber(7, NumberType.SwapTradeAmount)).toBe('7.00') + + expect(formatNumber(0.987654321, NumberType.SwapTradeAmount)).toBe('0.98765') + expect(formatNumber(0.9, NumberType.SwapTradeAmount)).toBe('0.90') + expect(formatNumber(0.901000123, NumberType.SwapTradeAmount)).toBe('0.901') + expect(formatNumber(0.000000001, NumberType.SwapTradeAmount)).toBe('0.000000001') + expect(formatNumber(0, NumberType.SwapTradeAmount)).toBe('0') +}) + +it('formats NFT numbers correctly', () => { + expect(formatNumber(1234567000000000, NumberType.NFTTokenFloorPrice)).toBe('>999T') + expect(formatNumber(1002345, NumberType.NFTTokenFloorPrice)).toBe('1M') + expect(formatNumber(1234, NumberType.NFTTokenFloorPrice)).toBe('1.23K') + expect(formatNumber(12.34467, NumberType.NFTTokenFloorPrice)).toBe('12.34') + expect(formatNumber(12.1, NumberType.NFTTokenFloorPrice)).toBe('12.1') + expect(formatNumber(0.00909, NumberType.NFTTokenFloorPrice)).toBe('0.009') + expect(formatNumber(0.09001, NumberType.NFTTokenFloorPrice)).toBe('0.09') + expect(formatNumber(0.00099, NumberType.NFTTokenFloorPrice)).toBe('<0.001') + expect(formatNumber(0, NumberType.NFTTokenFloorPrice)).toBe('0') + + expect(formatNumber(12.1, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('12.10') + expect(formatNumber(0.09001, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('0.090') + + expect(formatNumber(0.987654321, NumberType.NFTCollectionStats)).toBe('0.98765') + expect(formatNumber(0.9, NumberType.NFTCollectionStats)).toBe('0.9') + expect(formatNumber(76543.21, NumberType.NFTCollectionStats)).toBe('76543.2') + expect(formatNumber(7.60000054321, NumberType.NFTCollectionStats)).toBe('7.6') + expect(formatNumber(1234567890, NumberType.NFTCollectionStats)).toBe('1.23457B') + expect(formatNumber(1234567000000000, NumberType.NFTCollectionStats)).toBe('>999T') +}) + +describe('formatUSDPrice', () => { + it('should correctly format 0.000000009876', () => { + expect(formatUSDPrice(0.000000009876)).toBe('<$0.00000001') + }) + it('should correctly format 0.00001231', () => { + expect(formatUSDPrice(0.00001231)).toBe('$0.0000123') + }) + it('should correctly format 0.001231', () => { + expect(formatUSDPrice(0.001231)).toBe('$0.00123') + }) + it('should correctly format 0.0', () => { + expect(formatUSDPrice(0.0)).toBe('$0.00') + }) + it('should correctly format 0', () => { + expect(formatUSDPrice(0)).toBe('$0.00') + }) + it('should correctly format 1.048942', () => { + expect(formatUSDPrice(1.048942)).toBe('$1.049') + }) + it('should correctly format 0.10235', () => { + expect(formatUSDPrice(0.10235)).toBe('$0.102') + }) + it('should correctly format 1234.5678', () => { + expect(formatUSDPrice(1_234.5678)).toBe('$1,234.57') + }) + it('should correctly format 1234567.8910', () => { + expect(formatUSDPrice(1_234_567.891)).toBe('$1.23M') + }) + it('should correctly format 1000000000000', () => { + expect(formatUSDPrice(1_000_000_000_000)).toBe('$1.00T') + }) + it('should correctly format 1000000000000000', () => { + expect(formatUSDPrice(1_000_000_000_000_000)).toBe('>$999T') + }) +}) + +describe('formatPriceImpact', () => { + it('should correctly format undefined', () => { + expect(formatPriceImpact(undefined)).toBe('-') + }) + + it('correctly formats a percent with 3 significant digits', () => { + expect(formatPriceImpact(new Percent(-1, 100000))).toBe('0.001%') + expect(formatPriceImpact(new Percent(-1, 1000))).toBe('0.100%') + expect(formatPriceImpact(new Percent(-1, 100))).toBe('1.000%') + expect(formatPriceImpact(new Percent(-1, 10))).toBe('10.000%') + expect(formatPriceImpact(new Percent(-1, 1))).toBe('100.000%') + }) +}) + +describe('formatSlippage', () => { + it('should correctly format undefined', () => { + expect(formatSlippage(undefined)).toBe('-') + }) + + it('correctly formats a percent with 3 significant digits', () => { + expect(formatSlippage(new Percent(1, 100000))).toBe('0.001%') + expect(formatSlippage(new Percent(1, 1000))).toBe('0.100%') + expect(formatSlippage(new Percent(1, 100))).toBe('1.000%') + expect(formatSlippage(new Percent(1, 10))).toBe('10.000%') + expect(formatSlippage(new Percent(1, 1))).toBe('100.000%') + }) +}) diff --git a/src/utils/conedison/format.ts b/src/utils/conedison/format.ts new file mode 100644 index 000000000..5a13135c0 --- /dev/null +++ b/src/utils/conedison/format.ts @@ -0,0 +1,360 @@ +import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core' + +import { Nullish } from './types' + +// Number formatting follows the standards laid out in this spec: +// https://www.notion.so/uniswaplabs/Number-standards-fbb9f533f10e4e22820722c2f66d23c0 + +const FIVE_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 5, +}) + +const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 5, + minimumFractionDigits: 2, +}) + +const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 5, + minimumFractionDigits: 2, + useGrouping: false, +}) + +const THREE_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 0, +}) + +const THREE_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 3, +}) + +const THREE_DECIMALS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 3, + minimumFractionDigits: 3, + currency: 'USD', + style: 'currency', +}) + +const TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, +}) + +const TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, + minimumFractionDigits: 2, +}) + +const TWO_DECIMALS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumFractionDigits: 2, + minimumFractionDigits: 2, + currency: 'USD', + style: 'currency', +}) + +const SHORTHAND_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 2, + maximumFractionDigits: 2, +}) + +const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 2, +}) + +const SHORTHAND_FIVE_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', { + notation: 'compact', + maximumFractionDigits: 5, +}) + +const SHORTHAND_USD_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + currency: 'USD', + style: 'currency', +}) + +const SHORTHAND_USD_ONE_DECIMAL = new Intl.NumberFormat('en-US', { + notation: 'compact', + minimumFractionDigits: 1, + maximumFractionDigits: 1, + currency: 'USD', + style: 'currency', +}) + +const SIX_SIG_FIGS_TWO_DECIMALS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + minimumSignificantDigits: 3, + maximumFractionDigits: 2, + minimumFractionDigits: 2, +}) + +const SIX_SIG_FIGS_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + useGrouping: false, +}) + +const SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS = new Intl.NumberFormat('en-US', { + notation: 'standard', + maximumSignificantDigits: 6, + minimumSignificantDigits: 3, + maximumFractionDigits: 2, + minimumFractionDigits: 2, + useGrouping: false, +}) + +const THREE_SIG_FIGS_USD = new Intl.NumberFormat('en-US', { + notation: 'standard', + minimumSignificantDigits: 3, + maximumSignificantDigits: 3, + currency: 'USD', + style: 'currency', +}) + +type Format = Intl.NumberFormat | string + +// each rule must contain either an `upperBound` or an `exact` value. +// upperBound => number will use that formatter as long as it is < upperBound +// exact => number will use that formatter if it is === exact +type FormatterRule = + | { upperBound?: undefined; exact: number; formatter: Format } + | { upperBound: number; exact?: undefined; formatter: Format } + +// these formatter objects dictate which formatter rule to use based on the interval that +// the number falls into. for example, based on the rule set below, if your number +// falls between 1 and 1e6, you'd use TWO_DECIMALS as the formatter. +const tokenNonTxFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS }, + { upperBound: 1e6, formatter: TWO_DECIMALS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const tokenTxFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.00001, formatter: '<0.00001' }, + { upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN }, + { upperBound: 10000, formatter: SIX_SIG_FIGS_TWO_DECIMALS }, + { upperBound: Infinity, formatter: TWO_DECIMALS }, +] + +const swapTradeAmountFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.1, formatter: SIX_SIG_FIGS_NO_COMMAS }, + { upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS }, + { upperBound: Infinity, formatter: SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS }, +] + +const fiatTokenDetailsFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: 0.00000001, formatter: '<$0.00000001' }, + { upperBound: 0.1, formatter: THREE_SIG_FIGS_USD }, + { upperBound: 1.05, formatter: THREE_DECIMALS_USD }, + { upperBound: 1e6, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS }, +] + +const fiatTokenPricesFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: 0.00000001, formatter: '<$0.00000001' }, + { upperBound: 0.1, formatter: THREE_SIG_FIGS_USD }, // Round to 3 significant figures, show significant trailing zeros + { upperBound: 1.05, formatter: THREE_DECIMALS_USD }, // Round to 3 decimal places, show significant trailing zeros + { upperBound: 1_000_000, formatter: TWO_DECIMALS_USD }, // Round to 2 decimal places + { upperBound: 1_000_000_000_000_000, formatter: SHORTHAND_USD_TWO_DECIMALS }, // Use M/B/T abbreviations + { upperBound: Infinity, formatter: '>$999T' }, // Use M/B/T abbreviations +] + +const fiatTokenStatsFormatter: FormatterRule[] = [ + // if token stat value is 0, we probably don't have the data for it, so show '-' as a placeholder + { exact: 0, formatter: '-' }, + { upperBound: 0.01, formatter: '<$0.01' }, + { upperBound: 1000, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_ONE_DECIMAL }, +] + +const fiatGasPriceFormatter: FormatterRule[] = [ + { upperBound: 0.01, formatter: '<$0.01' }, + { upperBound: 1e6, formatter: TWO_DECIMALS_USD }, + { upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS }, +] + +const fiatTokenQuantityFormatter = [{ exact: 0, formatter: '$0.00' }, ...fiatGasPriceFormatter] + +const portfolioBalanceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '$0.00' }, + { upperBound: Infinity, formatter: TWO_DECIMALS_USD }, +] + +const ntfTokenFloorPriceFormatterTrailingZeros: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS }, + { upperBound: 1000, formatter: TWO_DECIMALS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const ntfTokenFloorPriceFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.001, formatter: '<0.001' }, + { upperBound: 1, formatter: THREE_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: 1000, formatter: TWO_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +const ntfCollectionStatsFormatter: FormatterRule[] = [ + { exact: 0, formatter: '0' }, + { upperBound: 0.00001, formatter: '<0.00001' }, + { upperBound: 1, formatter: FIVE_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: 1e6, formatter: SIX_SIG_FIGS_NO_COMMAS }, + { upperBound: 1e15, formatter: SHORTHAND_FIVE_DECIMALS_NO_TRAILING_ZEROS }, + { upperBound: Infinity, formatter: '>999T' }, +] + +export enum NumberType { + // used for token quantities in non-transaction contexts (e.g. portfolio balances) + TokenNonTx = 'token-non-tx', + + // used for token quantities in transaction contexts (e.g. swap, send) + TokenTx = 'token-tx', + + // this formatter is only used for displaying the swap trade output amount + // in the text input boxes. Output amounts on review screen should use the above TokenTx formatter + SwapTradeAmount = 'swap-trade-amount', + + // fiat prices in any component that belongs in the Token Details flow (except for token stats) + FiatTokenDetails = 'fiat-token-details', + + // fiat prices everywhere except Token Details flow + FiatTokenPrice = 'fiat-token-price', + + // fiat values for market cap, TVL, volume in the Token Details screen + FiatTokenStats = 'fiat-token-stats', + + // fiat price of token balances + FiatTokenQuantity = 'fiat-token-quantity', + + // fiat gas prices + FiatGasPrice = 'fiat-gas-price', + + // portfolio balance + PortfolioBalance = 'portfolio-balance', + + // nft floor price denominated in a token (e.g, ETH) + NFTTokenFloorPrice = 'nft-token-floor-price', + + // nft collection stats like number of items, holder, and sales + NFTCollectionStats = 'nft-collection-stats', + + // nft floor price with trailing zeros + NFTTokenFloorPriceTrailingZeros = 'nft-token-floor-price-trailing-zeros', +} + +const TYPE_TO_FORMATTER_RULES = { + [NumberType.TokenNonTx]: tokenNonTxFormatter, + [NumberType.TokenTx]: tokenTxFormatter, + [NumberType.SwapTradeAmount]: swapTradeAmountFormatter, + [NumberType.FiatTokenQuantity]: fiatTokenQuantityFormatter, + [NumberType.FiatTokenDetails]: fiatTokenDetailsFormatter, + [NumberType.FiatTokenPrice]: fiatTokenPricesFormatter, + [NumberType.FiatTokenStats]: fiatTokenStatsFormatter, + [NumberType.FiatGasPrice]: fiatGasPriceFormatter, + [NumberType.PortfolioBalance]: portfolioBalanceFormatter, + [NumberType.NFTTokenFloorPrice]: ntfTokenFloorPriceFormatter, + [NumberType.NFTTokenFloorPriceTrailingZeros]: ntfTokenFloorPriceFormatterTrailingZeros, + [NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter, +} + +function getFormatterRule(input: number, type: NumberType) { + const rules = TYPE_TO_FORMATTER_RULES[type] + for (let i = 0; i < rules.length; i++) { + const rule = rules[i] + if ( + (rule.exact !== undefined && input === rule.exact) || + (rule.upperBound !== undefined && input < rule.upperBound) + ) { + return rule.formatter + } + } + + throw new Error(`formatter for type ${type} not configured correctly`) +} + +export function formatNumber(input: Nullish, type: NumberType = NumberType.TokenNonTx, placeholder = '-') { + if (input === null || input === undefined) { + return placeholder + } + + const formatter = getFormatterRule(input, type) + if (typeof formatter === 'string') return formatter + return formatter.format(input) +} + +export function formatCurrencyAmount( + amount: Nullish>, + type: NumberType = NumberType.TokenNonTx, + placeholder?: string +) { + return formatNumber(amount ? parseFloat(amount.toSignificant()) : undefined, type, placeholder) +} + +export function formatPriceImpact(priceImpact: Percent | undefined) { + if (!priceImpact) return '-' + + return `${priceImpact.multiply(-1).toFixed(3)}%` +} + +export function formatSlippage(slippage: Percent | undefined) { + if (!slippage) return '-' + + return `${slippage.toFixed(3)}%` +} + +export function formatPrice(price: Nullish>, type: NumberType = NumberType.FiatTokenPrice) { + if (price === null || price === undefined) { + return '-' + } + + return formatNumber(parseFloat(price.toSignificant()), type) +} + +/** + * Very simple date formatter + * Feel free to add more options / adapt to your needs. + */ +export function formatDate(date: Date) { + return date.toLocaleString('en-US', { + day: 'numeric', // numeric, 2-digit + year: 'numeric', // numeric, 2-digit + month: 'short', // numeric, 2-digit, long, short, narrow + hour: 'numeric', // numeric, 2-digit + minute: 'numeric', // numeric, 2-digit + }) +} + +export function formatNumberOrString(price: Nullish, type: NumberType) { + if (price === null || price === undefined) return '-' + if (typeof price === 'string') return formatNumber(parseFloat(price), type) + return formatNumber(price, type) +} + +export function formatUSDPrice(price: Nullish, type: NumberType = NumberType.FiatTokenPrice) { + return formatNumberOrString(price, type) +} diff --git a/src/utils/conedison/provider/index.test.ts b/src/utils/conedison/provider/index.test.ts new file mode 100644 index 000000000..9f56d3898 --- /dev/null +++ b/src/utils/conedison/provider/index.test.ts @@ -0,0 +1,67 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { JsonRpcProvider, JsonRpcSigner, TransactionRequest, TransactionResponse } from '@ethersproject/providers' + +import { sendTransaction } from '.' +import * as Meta from './meta' + +jest.mock('./meta') + +describe('sendTransaction', () => { + const getTransaction = jest.fn() + const sendUncheckedTransaction = jest.fn() + const request = { calldata: 'test' } as TransactionRequest + const response = {} as TransactionResponse + const signer = { + estimateGas: jest.fn().mockResolvedValue(BigNumber.from(10)), + sendUncheckedTransaction, + } as unknown as JsonRpcSigner + const provider = { + once: jest.fn(), + getSigner: jest.fn().mockReturnValue(signer), + getTransaction, + _wrapTransaction: jest.fn().mockImplementation((tx) => tx), + } as unknown as JsonRpcProvider + + beforeEach(() => { + getTransaction.mockReturnValueOnce(response) + sendUncheckedTransaction.mockReset() + }) + + it('sends a transaction with no gas margin', async () => { + await expect(sendTransaction(provider, request)).resolves.toBe(response) + expect(signer.sendUncheckedTransaction).toHaveBeenCalledWith({ ...request, gasLimit: BigNumber.from(10) }) + }) + + it('polls for a response', async () => { + jest.spyOn(provider, 'once').mockImplementation((name, listener) => { + listener() + return provider + }) + getTransaction.mockReset().mockReturnValueOnce(undefined).mockReturnValueOnce(response) + await expect(sendTransaction(provider, request)).resolves.toBe(response) + expect(getTransaction).toHaveBeenCalledTimes(2) + }) + + it('sends a transaction with configured gas margin', async () => { + await expect(sendTransaction(provider, request, 0.1)).resolves.toBe(response) + expect(signer.sendUncheckedTransaction).toHaveBeenCalledWith({ ...request, gasLimit: BigNumber.from(11) }) + }) + + it('sends a transaction with no gas limit', async () => { + await expect(sendTransaction(provider, request, 0.1, true)).resolves.toBe(response) + expect(signer.sendUncheckedTransaction).toHaveBeenCalledWith({ ...request }) + }) + + describe('with Uniswap Wallet', () => { + beforeEach(() => { + const getWalletMeta = Meta.getWalletMeta as jest.Mock + getWalletMeta.mockReturnValueOnce({ name: 'Uniswap Wallet' }) + getTransaction.mockReturnValueOnce(response) + }) + + it('sends a transaction with no gas limit', async () => { + await expect(sendTransaction(provider, request)).resolves.toBe(response) + expect(signer.sendUncheckedTransaction).toHaveBeenCalledWith({ ...request }) + }) + }) +}) diff --git a/src/utils/conedison/provider/index.ts b/src/utils/conedison/provider/index.ts new file mode 100644 index 000000000..bc4a282d5 --- /dev/null +++ b/src/utils/conedison/provider/index.ts @@ -0,0 +1,58 @@ +import { TransactionRequest, TransactionResponse } from '@ethersproject/abstract-provider' +import { BigNumber } from '@ethersproject/bignumber' +import { Deferrable } from '@ethersproject/properties' +import { JsonRpcProvider } from '@ethersproject/providers' +import { poll } from '@ethersproject/web' + +import { getWalletMeta } from './meta' + +export * from './meta' +export * from './signing' + +function isUniswapWallet(provider: JsonRpcProvider): boolean { + return getWalletMeta(provider)?.name === 'Uniswap Wallet' +} + +/** + * Sends a transaction, optionally including a gas limit. + * + * The ethers sendTransaction implementation first checks the blockNumber, which causes a delay and may inhibit + * deep-linking behavior on iOS. This wrapper works around that by optionally estimating gas (another source of delay), + * and by sending the transaction as an unchecked transaction. + * @see https://docs.walletconnect.com/2.0/swift/guides/mobile-linking#ios-app-link-constraints. + */ +export async function sendTransaction( + provider: JsonRpcProvider, + transaction: Deferrable, + gasMargin = 0, + skipGasLimit = isUniswapWallet(provider) +): Promise { + const signer = provider.getSigner() + + let gasLimit: BigNumber | undefined + if (!skipGasLimit) { + gasLimit = await signer.estimateGas(transaction) + if (gasMargin) { + gasLimit = gasLimit.add(gasLimit.mul(Math.floor(gasMargin * 100)).div(100)) + } + } + + const hash = await signer.sendUncheckedTransaction({ ...transaction, gasLimit }) + + try { + // JSON-RPC only provides an opaque transaction hash, so we poll for the actual transaction. + // Polling continues until a defined value is returned (see https://docs.ethers.org/v5/api/utils/web/#utils-poll). + // NB: sendTransaction is a modified version of JsonRpcProvider.sendTransaction - see the original implementation. + return await poll( + (async () => { + const tx = await provider.getTransaction(hash) + if (tx === null) return undefined + return provider._wrapTransaction(tx, hash) + }) as () => Promise, + { oncePoll: provider } + ) + } catch (error) { + error.transactionHash = hash + throw error + } +} diff --git a/src/utils/conedison/provider/meta.test.ts b/src/utils/conedison/provider/meta.test.ts new file mode 100644 index 000000000..2b250946a --- /dev/null +++ b/src/utils/conedison/provider/meta.test.ts @@ -0,0 +1,104 @@ +import type { ExternalProvider } from '@ethersproject/providers' +import { JsonRpcProvider } from '@ethersproject/providers' +import type WalletConnectProviderV1 from '@walletconnect/ethereum-provider-v1' +import type WalletConnectProviderV2 from '@walletconnect/ethereum-provider-v2' + +import { getWalletMeta, WalletMeta, WalletType } from './meta' + +class MockJsonRpcProvider extends JsonRpcProvider { + name = 'JsonRpcProvider' + arg: string + + constructor(arg?: unknown) { + super() + this.arg = JSON.stringify(arg) + } +} + +const WC_META = { name: 'name', description: 'description', url: 'url', icons: [] } + +class MockWalletConnectProviderV1 extends MockJsonRpcProvider { + name = WalletType.WALLET_CONNECT + provider: WalletConnectProviderV1 + + constructor(peerMeta: typeof WC_META | null) { + super(peerMeta) + this.provider = { isWalletConnect: true, connector: { peerMeta } } as unknown as WalletConnectProviderV1 + } +} + +class MockWalletConnectProviderV2 extends MockJsonRpcProvider { + name = WalletType.WALLET_CONNECT + provider: WalletConnectProviderV2 + + constructor(metadata: typeof WC_META | null) { + super(metadata) + this.provider = { isWalletConnect: true, session: { peer: { metadata } } } as unknown as WalletConnectProviderV2 + } +} + +class MockInjectedProvider extends MockJsonRpcProvider { + name = WalletType.INJECTED + provider: ExternalProvider + + constructor(provider: Record) { + super(provider) + this.provider = { + isConnected() { + return true + }, + ...provider, + } as ExternalProvider + } +} + +const testCases: [MockJsonRpcProvider, WalletMeta | undefined][] = [ + [new MockJsonRpcProvider(), undefined], + [new MockWalletConnectProviderV1(null), { type: WalletType.WALLET_CONNECT, agent: '(WalletConnect)' }], + [ + new MockWalletConnectProviderV1(WC_META), + { type: WalletType.WALLET_CONNECT, agent: 'name (WalletConnect)', ...WC_META }, + ], + [new MockWalletConnectProviderV2(null), { type: WalletType.WALLET_CONNECT, agent: '(WalletConnect)' }], + [ + new MockWalletConnectProviderV2(WC_META), + { type: WalletType.WALLET_CONNECT, agent: 'name (WalletConnect)', ...WC_META }, + ], + [new MockInjectedProvider({}), { type: WalletType.INJECTED, agent: '(Injected)', name: undefined }], + [ + new MockInjectedProvider({ isMetaMask: false }), + { type: WalletType.INJECTED, agent: '(Injected)', name: undefined }, + ], + [ + new MockInjectedProvider({ isMetaMask: true }), + { type: WalletType.INJECTED, agent: 'MetaMask (Injected)', name: 'MetaMask' }, + ], + [ + new MockInjectedProvider({ isTest: true, isMetaMask: true }), + { type: WalletType.INJECTED, agent: 'Test MetaMask (Injected)', name: 'Test' }, + ], + [ + new MockInjectedProvider({ isCoinbaseWallet: true, qrUrl: undefined }), + { type: WalletType.INJECTED, agent: 'CoinbaseWallet (Injected)', name: 'CoinbaseWallet' }, + ], + [ + new MockInjectedProvider({ isCoinbaseWallet: true, qrUrl: true }), + { type: WalletType.INJECTED, agent: 'CoinbaseWallet qrUrl (Injected)', name: 'CoinbaseWallet' }, + ], + [ + new MockInjectedProvider({ isA: true, isB: false }), + { type: WalletType.INJECTED, agent: 'A (Injected)', name: 'A' }, + ], + [ + new MockInjectedProvider({ isA: true, isB: true }), + { type: WalletType.INJECTED, agent: 'A B (Injected)', name: 'A' }, + ], +] + +describe('meta', () => { + describe.each(testCases)('getWalletMeta/getWalletName returns the project meta/name', (provider, meta) => { + it(`${provider?.name} ${provider.arg}`, () => { + expect(getWalletMeta(provider)).toEqual(meta) + }) + }) +}) diff --git a/src/utils/conedison/provider/meta.ts b/src/utils/conedison/provider/meta.ts new file mode 100644 index 000000000..5902f19f9 --- /dev/null +++ b/src/utils/conedison/provider/meta.ts @@ -0,0 +1,102 @@ +import type { ExternalProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' +import type WalletConnectProviderV1 from '@walletconnect/ethereum-provider-v1' +import type WalletConnectProviderV2 from '@walletconnect/ethereum-provider-v2' + +function isWeb3Provider(provider: JsonRpcProvider): provider is Web3Provider { + return 'provider' in provider +} + +type WalletConnectProvider = WalletConnectProviderV1 | WalletConnectProviderV2 + +function isWalletConnectProvider(provider: ExternalProvider): provider is WalletConnectProvider { + return (provider as WalletConnectProvider).isWalletConnect +} + +export enum WalletType { + WALLET_CONNECT = 'WalletConnect', + INJECTED = 'Injected', +} + +/** + * WalletMeta for WalletConnect or Injected wallets. + * + * For WalletConnect wallets, name, description, url, and icons are taken from WalletConnect's peerMeta + * v1: @see https://docs.walletconnect.com/1.0/specs#session-request + * v2: @see https://docs.walletconnect.com/2.0/specs/clients/core/pairing/data-structures#metadata + * + * For Injected wallets, the name is derived from the `is*` properties on the provider (eg `isCoinbaseWallet`). + */ +export interface WalletMeta { + type: WalletType + /** + * The agent string of the wallet, for use with analytics/debugging. + * Denotes the wallet's provenance - analagous to a User String - including all `is*` properties and the type. + * + * Some injected wallets are used different ways (eg with/without spoofing MetaMask). + * The agent will capture these differences, while the name will not. + * + * @example 'CoinbaseWallet qUrl (Injected)' + */ + agent: string + /** + * The name of the wallet, for use with UI. + * + * @example 'CoinbaseWallet' + */ + name?: string + description?: string + url?: string + icons?: string[] +} + +function getWalletConnectMeta(provider: WalletConnectProvider): WalletMeta { + let metadata: + | WalletConnectProviderV1['connector']['peerMeta'] + | NonNullable['peer']['metadata'] + | undefined + if ('session' in provider) { + metadata = provider.session?.peer.metadata + } else { + metadata = provider.connector.peerMeta + } + return { + type: WalletType.WALLET_CONNECT, + agent: metadata ? `${metadata.name} (WalletConnect)` : '(WalletConnect)', + ...metadata, + } +} + +function getInjectedMeta(provider: ExternalProvider & Record): WalletMeta { + const properties = Object.getOwnPropertyNames(provider) + const names = + properties + .filter((name) => name.match(/^is.*$/) && (provider as Record)[name] === true) + .map((name) => name.slice(2)) ?? [] + + // Many wallets spoof MetaMask by setting `isMetaMask` along with their own identifier, + // so we sort MetaMask last so that these wallets' names come first. + names.sort((a, b) => (a === 'MetaMask' ? 1 : b === 'MetaMask' ? -1 : 0)) + + // Coinbase Wallet can be connected through an extension or a QR code, with `qrUrl` as the only differentiator, + // so we capture `qrUrl` in the agent string. + if (properties.includes('qrUrl') && provider['qrUrl']) { + names.push('qrUrl') + } + + return { + type: WalletType.INJECTED, + agent: [...names, '(Injected)'].join(' '), + name: names[0], + // TODO(WEB-2914): Populate description, url, and icons for known wallets. + } +} + +export function getWalletMeta(provider: JsonRpcProvider): WalletMeta | undefined { + if (!isWeb3Provider(provider)) return undefined + + if (isWalletConnectProvider(provider.provider)) { + return getWalletConnectMeta(provider.provider) + } else { + return getInjectedMeta(provider.provider) + } +} diff --git a/src/utils/conedison/provider/signing.test.ts b/src/utils/conedison/provider/signing.test.ts new file mode 100644 index 000000000..905d40c07 --- /dev/null +++ b/src/utils/conedison/provider/signing.test.ts @@ -0,0 +1,129 @@ +import { ExternalProvider, JsonRpcProvider, JsonRpcSigner, Web3Provider } from '@ethersproject/providers' +import { Mutable } from 'types' + +import { signTypedData } from '../provider' + +describe('signing', () => { + describe('signTypedData', () => { + const wallet = '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826' + const domain = { + name: 'Ether Mail', + version: '1', + chainId: '1', + verifyingContract: '0xcccccccccccccccccccccccccccccccccccccccc', + } + + const types = { + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + } + + const value = { + from: { + name: 'Cow', + wallet, + }, + to: { + name: 'Bob', + wallet: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + }, + contents: 'Hello, Bob!', + } + + let signer: JsonRpcSigner + beforeEach(() => { + signer = new JsonRpcProvider().getSigner() + jest.spyOn(signer, 'getAddress').mockResolvedValue(wallet) + }) + + function itFallsBackToEthSignIfUnimplemented(signingMethod: string) { + it.each(['not found', 'not implemented'])(`falls back to eth_sign if ${signingMethod} is %s`, async (message) => { + const send = jest + .spyOn(signer.provider, 'send') + .mockImplementationOnce((method) => { + if (method === signingMethod) return Promise.reject({ message: `method ${message}` }) + throw new Error('Unimplemented') + }) + .mockImplementationOnce((method, params) => { + if (method === 'eth_sign') return Promise.resolve() + throw new Error('Unimplemented') + }) + jest.spyOn(console, 'warn').mockImplementation(() => undefined) + + await signTypedData(signer, domain, types, value) + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining('signTypedData: wallet does not implement EIP-712, falling back to eth_sign'), + expect.anything() + ) + expect(send).toHaveBeenCalledTimes(2) + expect(send).toHaveBeenCalledWith(signingMethod, [wallet, expect.anything()]) + expect(send).toHaveBeenCalledWith('eth_sign', [wallet, expect.anything()]) + const hash = send.mock.lastCall[1]?.[1] + expect(hash).toBe('0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2') + }) + } + + function itFailsIfRejected(signingMethod: string) { + it('fails if rejected', async () => { + const send = jest.spyOn(signer.provider, 'send').mockImplementationOnce((method) => { + if (method === signingMethod) return Promise.reject(new Error('User rejected')) + throw new Error('Unimplemented') + }) + + await expect(async () => await signTypedData(signer, domain, types, value)).rejects.toThrow('User rejected') + expect(send).toHaveBeenCalledTimes(1) + expect(send).toHaveBeenCalledWith(signingMethod, [wallet, expect.anything()]) + const data = send.mock.lastCall[1]?.[1] + expect(JSON.parse(data)).toEqual(expect.objectContaining({ domain, message: value })) + }) + } + + it('signs using eth_signTypedData_v4', async () => { + const send = jest.spyOn(signer.provider, 'send').mockImplementationOnce((method, params) => { + if (method === 'eth_signTypedData_v4') return Promise.resolve() + throw new Error('Unimplemented') + }) + + await signTypedData(signer, domain, types, value) + expect(send).toHaveBeenCalledTimes(1) + expect(send).toHaveBeenCalledWith('eth_signTypedData_v4', [wallet, expect.anything()]) + const data = send.mock.lastCall[1]?.[1] + expect(JSON.parse(data)).toEqual(expect.objectContaining({ domain, message: value })) + }) + + itFallsBackToEthSignIfUnimplemented('eth_signTypedData_v4') + itFailsIfRejected('eth_signTypedData_v4') + + describe('wallets which do not support eth_signTypedData_v4', () => { + describe.each(['SafePal Wallet', 'Ledger Wallet Connect'])('%s', (name) => { + beforeEach(() => { + const web3Provider = signer.provider as Mutable + web3Provider.provider = { isWalletConnect: true, connector: { peerMeta: { name } } } as ExternalProvider + }) + + it('signs using eth_signTypedData', async () => { + const send = jest.spyOn(signer.provider, 'send').mockImplementationOnce((method, params) => { + if (method === 'eth_signTypedData') return Promise.resolve() + throw new Error('Unimplemented') + }) + + await signTypedData(signer, domain, types, value) + expect(send).toHaveBeenCalledTimes(1) + expect(send).toHaveBeenCalledWith('eth_signTypedData', [wallet, expect.anything()]) + const data = send.mock.lastCall[1]?.[1] + expect(JSON.parse(data)).toEqual(expect.objectContaining({ domain, message: value })) + }) + + itFallsBackToEthSignIfUnimplemented('eth_signTypedData') + itFailsIfRejected('eth_signTypedData') + }) + }) + }) +}) diff --git a/src/utils/conedison/provider/signing.ts b/src/utils/conedison/provider/signing.ts new file mode 100644 index 000000000..92f2f9845 --- /dev/null +++ b/src/utils/conedison/provider/signing.ts @@ -0,0 +1,60 @@ +import type { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer' +import { _TypedDataEncoder } from '@ethersproject/hash' +import type { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers' + +import { getWalletMeta, WalletType } from './meta' + +// These are WalletConnect peers which do not implement eth_signTypedData_v4, but *do* implement eth_signTypedData. +// They are special-cased so that signing will still use EIP-712 (which is safer for the user). +const WC_PEERS_LACKING_V4_SUPPORT = ['SafePal Wallet', 'Ledger Wallet Connect'] + +// Assumes v4 support by default, except for known wallets. +function supportsV4(provider: JsonRpcProvider): boolean { + const meta = getWalletMeta(provider) + if (meta) { + const { type, name } = meta + if (name) { + if (type === WalletType.WALLET_CONNECT && name && WC_PEERS_LACKING_V4_SUPPORT.includes(name)) { + return false + } + } + } + + return true +} + +/** + * Signs TypedData with EIP-712, if available, or else by falling back to eth_sign. + * Calls eth_signTypedData_v4, or eth_signTypedData for wallets with incomplete EIP-712 support. + * + * @see https://github.com/ethers-io/ethers.js/blob/c80fcddf50a9023486e9f9acb1848aba4c19f7b6/packages/providers/src.ts/json-rpc-provider.ts#L334 + */ +export async function signTypedData( + signer: JsonRpcSigner, + domain: TypedDataDomain, + types: Record, + // Use Record for the value to match the JsonRpcSigner._signTypedData signature. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: Record +) { + // Populate any ENS names (in-place) + const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => { + return signer.provider.resolveName(name) as Promise + }) + + const method = supportsV4(signer.provider) ? 'eth_signTypedData_v4' : 'eth_signTypedData' + const address = (await signer.getAddress()).toLowerCase() + const message = JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value)) + + try { + return await signer.provider.send(method, [address, message]) + } catch (error) { + // If eth_signTypedData is unimplemented, fall back to eth_sign. + if (typeof error.message === 'string' && error.message.match(/not (found|implemented)/i)) { + console.warn('signTypedData: wallet does not implement EIP-712, falling back to eth_sign', error.message) + const hash = _TypedDataEncoder.hash(populated.domain, types, populated.value) + return await signer.provider.send('eth_sign', [address, hash]) + } + throw error + } +} diff --git a/src/utils/conedison/types.ts b/src/utils/conedison/types.ts new file mode 100644 index 000000000..ba725b6ed --- /dev/null +++ b/src/utils/conedison/types.ts @@ -0,0 +1,6 @@ +export type Address = string +export type AddressTo = Record +export type Mutable = { -readonly [P in keyof T]: T[P] } +export type Nullable = T | null +export type Nullish = Nullable | undefined +export type Primitive = number | string | boolean | bigint | symbol | null | undefined diff --git a/test/setup-jest.ts b/test/setup-jest.ts index ada70cc4b..a1158a3f7 100644 --- a/test/setup-jest.ts +++ b/test/setup-jest.ts @@ -7,7 +7,7 @@ import fetch from 'jest-fetch-mock' fetch.enableMocks() -jest.mock('@uniswap/conedison/format', () => ({ +jest.mock('utils/conedison/format', () => ({ formatCurrencyAmount: jest.fn((amount: CurrencyAmount) => amount.toFixed(2)), formatPriceImpact: jest.fn((percent: Percent) => percent.toFixed(2) + '%'), NumberType: { @@ -18,7 +18,7 @@ jest.mock('@uniswap/conedison/format', () => ({ const MOCK_TYPED_DATA_SIG = '0x1befd08fcc4085dc484346d69fd15659616522454a33e66e7b0f6917379ab888236304ebed307813208bf004da04d998dcd15a8f83241d033e4040adc4b0b5311b' -jest.mock('@uniswap/conedison/provider/signing', () => ({ +jest.mock('utils/conedison/provider/signing', () => ({ signTypedData: () => Promise.resolve(MOCK_TYPED_DATA_SIG), })) diff --git a/yarn.lock b/yarn.lock index d9e7e7b94..9fe39ab4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2769,6 +2769,140 @@ dependencies: antlr4ts "^0.5.0-alpha.4" +"@stablelib/aead@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" + integrity sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg== + +"@stablelib/binary@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" + integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== + dependencies: + "@stablelib/int" "^1.0.1" + +"@stablelib/bytes@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/bytes/-/bytes-1.0.1.tgz#0f4aa7b03df3080b878c7dea927d01f42d6a20d8" + integrity sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ== + +"@stablelib/chacha20poly1305@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz#de6b18e283a9cb9b7530d8767f99cde1fec4c2ee" + integrity sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA== + dependencies: + "@stablelib/aead" "^1.0.1" + "@stablelib/binary" "^1.0.1" + "@stablelib/chacha" "^1.0.1" + "@stablelib/constant-time" "^1.0.1" + "@stablelib/poly1305" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/chacha@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha/-/chacha-1.0.1.tgz#deccfac95083e30600c3f92803a3a1a4fa761371" + integrity sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/constant-time@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/constant-time/-/constant-time-1.0.1.tgz#bde361465e1cf7b9753061b77e376b0ca4c77e35" + integrity sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg== + +"@stablelib/ed25519@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.3.tgz#f8fdeb6f77114897c887bb6a3138d659d3f35996" + integrity sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg== + dependencies: + "@stablelib/random" "^1.0.2" + "@stablelib/sha512" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hash@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hash/-/hash-1.0.1.tgz#3c944403ff2239fad8ebb9015e33e98444058bc5" + integrity sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg== + +"@stablelib/hkdf@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hkdf/-/hkdf-1.0.1.tgz#b4efd47fd56fb43c6a13e8775a54b354f028d98d" + integrity sha512-SBEHYE16ZXlHuaW5RcGk533YlBj4grMeg5TooN80W3NpcHRtLZLLXvKyX0qcRFxf+BGDobJLnwkvgEwHIDBR6g== + dependencies: + "@stablelib/hash" "^1.0.1" + "@stablelib/hmac" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hmac@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hmac/-/hmac-1.0.1.tgz#3d4c1b8cf194cb05d28155f0eed8a299620a07ec" + integrity sha512-V2APD9NSnhVpV/QMYgCVMIYKiYG6LSqw1S65wxVoirhU/51ACio6D4yDVSwMzuTJXWZoVHbDdINioBwKy5kVmA== + dependencies: + "@stablelib/constant-time" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/int@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" + integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== + +"@stablelib/keyagreement@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz#4612efb0a30989deb437cd352cee637ca41fc50f" + integrity sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg== + dependencies: + "@stablelib/bytes" "^1.0.1" + +"@stablelib/poly1305@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/poly1305/-/poly1305-1.0.1.tgz#93bfb836c9384685d33d70080718deae4ddef1dc" + integrity sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA== + dependencies: + "@stablelib/constant-time" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/random@^1.0.1", "@stablelib/random@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.2.tgz#2dece393636489bf7e19c51229dd7900eddf742c" + integrity sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha256@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha256/-/sha256-1.0.1.tgz#77b6675b67f9b0ea081d2e31bda4866297a3ae4f" + integrity sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha512@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha512/-/sha512-1.0.1.tgz#6da700c901c2c0ceacbd3ae122a38ac57c72145f" + integrity sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/wipe@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" + integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== + +"@stablelib/x25519@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/x25519/-/x25519-1.0.3.tgz#13c8174f774ea9f3e5e42213cbf9fc68a3c7b7fd" + integrity sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw== + dependencies: + "@stablelib/keyagreement" "^1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/wipe" "^1.0.1" + "@styled-system/background@^5.1.2": version "5.1.2" resolved "https://registry.npmjs.org/@styled-system/background/-/background-5.1.2.tgz" @@ -3530,11 +3664,6 @@ resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@uniswap/conedison@^1.5.3": - version "1.5.3" - resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.5.3.tgz#2a2fc9ca848644f21944a2d087de54032a6acf93" - integrity sha512-b8j2/0FzqLU4Qq+M+QEPGzacnZxNrzAHp7yoAWRvNJiFyLjBvcgfaT9ORS8rw17M8XBLWjh83faj5Kymc+62qw== - "@uniswap/default-token-list@^2.0.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.2.0.tgz#d85a5c2520f57f4920bd989dfc9f01e1b701a567" @@ -3846,6 +3975,28 @@ "@walletconnect/types" "^1.8.0" "@walletconnect/utils" "^1.8.0" +"@walletconnect/core@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.7.7.tgz#49ddaa9d8aff365cd347b951d9b4c1c39a949e83" + integrity sha512-/Tmrjx9XDG8qylsUFU2fWvMoxlDwW+zzUcCgTaebMAmssCZ8NSknbBdjAdAKiey1TaLEgFkaCxXgXfioinWNYg== + dependencies: + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-provider" "1.0.13" + "@walletconnect/jsonrpc-types" "1.0.3" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/jsonrpc-ws-connection" "^1.0.11" + "@walletconnect/keyvaluestorage" "^1.0.2" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/relay-api" "^1.0.9" + "@walletconnect/relay-auth" "^1.0.4" + "@walletconnect/safe-json" "^1.0.2" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.7.7" + "@walletconnect/utils" "2.7.7" + events "^3.3.0" + lodash.isequal "4.5.0" + uint8arrays "^3.1.0" + "@walletconnect/core@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.8.0.tgz#6b2748b90c999d9d6a70e52e26a8d5e8bfeaa81e" @@ -3879,7 +4030,15 @@ resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.0.tgz#c4545869fa9c389ec88c364e1a5f8178e8ab5034" integrity sha512-4BwqyWy6KpSvkocSaV7WR3BlZfrxLbJSLkg+j7Gl6pTDE+U55lLhJvQaMuDVazXYxcjBsG09k7UlH7cGiUI5vQ== -"@walletconnect/ethereum-provider@^1.7.8": +"@walletconnect/environment@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.1.tgz#1d7f82f0009ab821a2ba5ad5e5a7b8ae3b214cd7" + integrity sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg== + dependencies: + tslib "1.14.1" + +"@walletconnect/ethereum-provider-v1@npm:@walletconnect/ethereum-provider@^1.8.0", "@walletconnect/ethereum-provider@^1.7.8": + name "@walletconnect/ethereum-provider-v1" version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-1.8.0.tgz#ed1dbf9cecc3b818758a060d2f9017c50bde1d32" integrity sha512-Nq9m+oo5P0F+njsROHw9KMWdoc/8iGHYzQdkjJN/1C7DtsqFRg5k5a3hd9rzCLpbPsOC1q8Z5lRs6JQgDvPm6Q== @@ -3893,6 +4052,38 @@ eip1193-provider "1.0.1" eventemitter3 "4.0.7" +"@walletconnect/ethereum-provider-v2@npm:@walletconnect/ethereum-provider@^2.4.6": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/ethereum-provider/-/ethereum-provider-2.7.7.tgz#1b7b934c0a4f7a504174d28006c964f48523ee01" + integrity sha512-wVVMgpMMcPySBKHAPu7QfL18TMrjAgOePz/mfuOjWal+vT9yVSPA34oFyHlzJKvcQ/abP7Zj3AzDtZbyXWRxwQ== + dependencies: + "@walletconnect/jsonrpc-http-connection" "^1.0.7" + "@walletconnect/jsonrpc-provider" "^1.0.13" + "@walletconnect/jsonrpc-types" "^1.0.3" + "@walletconnect/jsonrpc-utils" "^1.0.8" + "@walletconnect/sign-client" "2.7.7" + "@walletconnect/types" "2.7.7" + "@walletconnect/universal-provider" "2.7.7" + "@walletconnect/utils" "2.7.7" + events "^3.3.0" + +"@walletconnect/events@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/events/-/events-1.0.1.tgz#2b5f9c7202019e229d7ccae1369a9e86bda7816c" + integrity sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ== + dependencies: + keyvaluestorage-interface "^1.0.0" + tslib "1.14.1" + +"@walletconnect/heartbeat@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@walletconnect/heartbeat/-/heartbeat-1.2.1.tgz#afaa3a53232ae182d7c9cff41c1084472d8f32e9" + integrity sha512-yVzws616xsDLJxuG/28FqtZ5rzrTA4gUjdEMTbWB5Y8V1XHRmqq4efAxCw5ie7WjbXFSUyBHaWlMR+2/CpQC5Q== + dependencies: + "@walletconnect/events" "^1.0.1" + "@walletconnect/time" "^1.0.2" + tslib "1.14.1" + "@walletconnect/iso-crypto@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.8.0.tgz#44ddf337c4f02837c062dbe33fa7ab36789df451" @@ -3911,6 +4102,25 @@ "@walletconnect/safe-json" "^1.0.0" cross-fetch "^3.1.4" +"@walletconnect/jsonrpc-http-connection@^1.0.7": + version "1.0.7" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.7.tgz#a6973569b8854c22da707a759d241e4f5c2d5a98" + integrity sha512-qlfh8fCfu8LOM9JRR9KE0s0wxP6ZG9/Jom8M0qsoIQeKF3Ni0FyV4V1qy/cc7nfI46SLQLSl4tgWSfLiE1swyQ== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.6" + "@walletconnect/safe-json" "^1.0.1" + cross-fetch "^3.1.4" + tslib "1.14.1" + +"@walletconnect/jsonrpc-provider@1.0.13", "@walletconnect/jsonrpc-provider@^1.0.13": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.13.tgz#9a74da648d015e1fffc745f0c7d629457f53648b" + integrity sha512-K73EpThqHnSR26gOyNEL+acEex3P7VWZe6KE12ZwKzAt2H4e5gldZHbjsu2QR9cLeJ8AXuO7kEMOIcRv1QEc7g== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.8" + "@walletconnect/safe-json" "^1.0.2" + tslib "1.14.1" + "@walletconnect/jsonrpc-provider@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.5.tgz#1a66053b6f083a9885a32b7c2c8f6a376f1a4458" @@ -3919,6 +4129,14 @@ "@walletconnect/jsonrpc-utils" "^1.0.3" "@walletconnect/safe-json" "^1.0.0" +"@walletconnect/jsonrpc-types@1.0.3", "@walletconnect/jsonrpc-types@^1.0.2", "@walletconnect/jsonrpc-types@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz#65e3b77046f1a7fa8347ae02bc1b841abe6f290c" + integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw== + dependencies: + keyvaluestorage-interface "^1.0.0" + tslib "1.14.1" + "@walletconnect/jsonrpc-types@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.1.tgz#a96b4bb2bcc8838a70e06f15c1b5ab11c47d8e95" @@ -3926,6 +4144,15 @@ dependencies: keyvaluestorage-interface "^1.0.0" +"@walletconnect/jsonrpc-utils@1.0.8", "@walletconnect/jsonrpc-utils@^1.0.6", "@walletconnect/jsonrpc-utils@^1.0.7", "@walletconnect/jsonrpc-utils@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz#82d0cc6a5d6ff0ecc277cb35f71402c91ad48d72" + integrity sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw== + dependencies: + "@walletconnect/environment" "^1.0.1" + "@walletconnect/jsonrpc-types" "^1.0.3" + tslib "1.14.1" + "@walletconnect/jsonrpc-utils@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.3.tgz#5bd49865eef0eae48e8b45a06731dc18691cf8c7" @@ -3934,6 +4161,33 @@ "@walletconnect/environment" "^1.0.0" "@walletconnect/jsonrpc-types" "^1.0.1" +"@walletconnect/jsonrpc-ws-connection@^1.0.11": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.11.tgz#1ce59d86f273d576ca73385961303ebd44dd923f" + integrity sha512-TiFJ6saasKXD+PwGkm5ZGSw0837nc6EeFmurSPgIT/NofnOV4Tv7CVJqGQN0rQYoJUSYu21cwHNYaFkzNpUN+w== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.6" + "@walletconnect/safe-json" "^1.0.2" + events "^3.3.0" + tslib "1.14.1" + ws "^7.5.1" + +"@walletconnect/keyvaluestorage@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.0.2.tgz#92f5ca0f54c1a88a093778842ce0c874d86369c8" + integrity sha512-U/nNG+VLWoPFdwwKx0oliT4ziKQCEoQ27L5Hhw8YOFGA2Po9A9pULUYNWhDgHkrb0gYDNt//X7wABcEWWBd3FQ== + dependencies: + safe-json-utils "^1.1.1" + tslib "1.14.1" + +"@walletconnect/logger@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.0.1.tgz#7f489b96e9a1ff6bf3e58f0fbd6d69718bf844a8" + integrity sha512-SsTKdsgWm+oDTBeNE/zHxxr5eJfZmE9/5yp/Ku+zJtcTAjELb3DXueWkDXmE9h8uHIbJzIb5wj5lPdzyrjT6hQ== + dependencies: + pino "7.11.0" + tslib "1.14.1" + "@walletconnect/mobile-registry@^1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.4.0.tgz#502cf8ab87330841d794819081e748ebdef7aee5" @@ -3960,11 +4214,53 @@ "@walletconnect/environment" "^1.0.0" randombytes "^2.1.0" +"@walletconnect/relay-api@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.9.tgz#f8c2c3993dddaa9f33ed42197fc9bfebd790ecaf" + integrity sha512-Q3+rylJOqRkO1D9Su0DPE3mmznbAalYapJ9qmzDgK28mYF9alcP3UwG/og5V7l7CFOqzCLi7B8BvcBUrpDj0Rg== + dependencies: + "@walletconnect/jsonrpc-types" "^1.0.2" + tslib "1.14.1" + +"@walletconnect/relay-auth@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz#0b5c55c9aa3b0ef61f526ce679f3ff8a5c4c2c7c" + integrity sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ== + dependencies: + "@stablelib/ed25519" "^1.0.2" + "@stablelib/random" "^1.0.1" + "@walletconnect/safe-json" "^1.0.1" + "@walletconnect/time" "^1.0.2" + tslib "1.14.1" + uint8arrays "^3.0.0" + "@walletconnect/safe-json@1.0.0", "@walletconnect/safe-json@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@walletconnect/safe-json/-/safe-json-1.0.0.tgz#12eeb11d43795199c045fafde97e3c91646683b2" integrity sha512-QJzp/S/86sUAgWY6eh5MKYmSfZaRpIlmCJdi5uG4DJlKkZrHEF7ye7gA+VtbVzvTtpM/gRwO2plQuiooIeXjfg== +"@walletconnect/safe-json@^1.0.1", "@walletconnect/safe-json@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/safe-json/-/safe-json-1.0.2.tgz#7237e5ca48046e4476154e503c6d3c914126fa77" + integrity sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA== + dependencies: + tslib "1.14.1" + +"@walletconnect/sign-client@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.7.7.tgz#a2be064eaff37ab036919bd33f1cf9ddf4681fdd" + integrity sha512-lTyF8ZEp+HwPNBW/Fw5iWnMm9O5tC1qwf5YfhNczZ7+q6+UUopOoRrsAvwqftJIkgKmfC8lHT52G/XM2JGVjbQ== + dependencies: + "@walletconnect/core" "2.7.7" + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.7.7" + "@walletconnect/utils" "2.7.7" + events "^3.3.0" + "@walletconnect/signer-connection@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/signer-connection/-/signer-connection-1.8.0.tgz#6cdf490df770e504cc1a550bdb5bac7696b130bc" @@ -3986,11 +4282,66 @@ "@walletconnect/utils" "^1.8.0" ws "7.5.3" +"@walletconnect/time@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/time/-/time-1.0.2.tgz#6c5888b835750ecb4299d28eecc5e72c6d336523" + integrity sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g== + dependencies: + tslib "1.14.1" + +"@walletconnect/types@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.7.7.tgz#c02831a17b6162d8594c45e3cc4668015e022f51" + integrity sha512-Z4Y+BKPX7X1UBCf7QV35mVy2QU9CS+5G+EthCaJwpieirZNHamHEwNXUjuUUb3PrYOLwlfRYUT5edeFW9wvoeQ== + dependencies: + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-types" "1.0.3" + "@walletconnect/keyvaluestorage" "^1.0.2" + "@walletconnect/logger" "^2.0.1" + events "^3.3.0" + "@walletconnect/types@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.8.0.tgz#3f5e85b2d6b149337f727ab8a71b8471d8d9a195" integrity sha512-Cn+3I0V0vT9ghMuzh1KzZvCkiAxTq+1TR2eSqw5E5AVWfmCtECFkVZBP6uUJZ8YjwLqXheI+rnjqPy7sVM4Fyg== +"@walletconnect/universal-provider@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/universal-provider/-/universal-provider-2.7.7.tgz#5c9017672b5d1255442533395eda67b19ffb2f2f" + integrity sha512-MY+R1sLmIKjFYjanWUM6bOM077+SnShSUfSjCTrsoZE2RDddcSz9EtcATovBSPfzPwUTS20mgcgrkRT4zrFRyQ== + dependencies: + "@walletconnect/jsonrpc-http-connection" "^1.0.7" + "@walletconnect/jsonrpc-provider" "1.0.13" + "@walletconnect/jsonrpc-types" "^1.0.2" + "@walletconnect/jsonrpc-utils" "^1.0.7" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/sign-client" "2.7.7" + "@walletconnect/types" "2.7.7" + "@walletconnect/utils" "2.7.7" + eip1193-provider "1.0.1" + events "^3.3.0" + +"@walletconnect/utils@2.7.7": + version "2.7.7" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.7.7.tgz#e2d8732f8ac3ffbc1de13e923891b256eb3bbefb" + integrity sha512-ozh9gvRAdXkiu+6nOAkoDCokDVPXK/tNATrrYuOhhR+EmGDjlZU2d27HT+HiGREdza0b1HdZN4XneGm0gERV5w== + dependencies: + "@stablelib/chacha20poly1305" "1.0.1" + "@stablelib/hkdf" "1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/sha256" "1.0.1" + "@stablelib/x25519" "^1.0.3" + "@walletconnect/relay-api" "^1.0.9" + "@walletconnect/safe-json" "^1.0.2" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.7.7" + "@walletconnect/window-getters" "^1.0.1" + "@walletconnect/window-metadata" "^1.0.1" + detect-browser "5.3.0" + query-string "7.1.3" + uint8arrays "^3.1.0" + "@walletconnect/utils@^1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.8.0.tgz#2591a197c1fa7429941fe428876088fda6632060" @@ -4009,6 +4360,13 @@ resolved "https://registry.yarnpkg.com/@walletconnect/window-getters/-/window-getters-1.0.0.tgz#1053224f77e725dfd611c83931b5f6c98c32bfc8" integrity sha512-xB0SQsLaleIYIkSsl43vm8EwETpBzJ2gnzk7e0wMF3ktqiTGS6TFHxcprMl5R44KKh4tCcHCJwolMCaDSwtAaA== +"@walletconnect/window-getters@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/window-getters/-/window-getters-1.0.1.tgz#f36d1c72558a7f6b87ecc4451fc8bd44f63cbbdc" + integrity sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q== + dependencies: + tslib "1.14.1" + "@walletconnect/window-metadata@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@walletconnect/window-metadata/-/window-metadata-1.0.0.tgz#93b1cc685e6b9b202f29c26be550fde97800c4e5" @@ -4016,6 +4374,14 @@ dependencies: "@walletconnect/window-getters" "^1.0.0" +"@walletconnect/window-metadata@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz#2124f75447b7e989e4e4e1581d55d25bc75f7be5" + integrity sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA== + dependencies: + "@walletconnect/window-getters" "^1.0.1" + tslib "1.14.1" + "@web3-react/core@^8.2.0": version "8.2.0" resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.2.0.tgz#95fb615bb283be520e6f61b5e48cfb0047943808" @@ -4656,6 +5022,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + await-timeout@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/await-timeout/-/await-timeout-1.1.1.tgz#d42062ee6bc4eb271fe4d4f851eb658dae7e3906" @@ -5282,9 +5653,9 @@ camelize@^1.0.0: integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= caniuse-lite@^1.0.30001332: - version "1.0.30001418" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001418.tgz" - integrity sha512-oIs7+JL3K9JRQ3jPZjlH6qyYDp+nBTCais7hjh0s+fuBwufc7uZ7hPYMXrDOJhV360KGMTcczMRObk0/iMqZRg== + version "1.0.30001489" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001489.tgz" + integrity sha512-x1mgZEXK8jHIfAxm+xgdpHpk50IN3z3q3zP261/WS+uvePxW8izXuCu6AHz0lkuYTlATDehiZ/tNyYBdSQsOUQ== cardinal@^2.1.1: version "2.1.1" @@ -6162,6 +6533,11 @@ detect-browser@5.2.0: resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.2.0.tgz#c9cd5afa96a6a19fda0bbe9e9be48a6b6e1e9c97" integrity sha512-tr7XntDAu50BVENgQfajMLzacmSe34D+qZc4zjnniz0ZVuw/TZcLcyxHQjYpJTM36sGEkZZlYLnIM1hH7alTMA== +detect-browser@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" + integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -6319,6 +6695,16 @@ duplexer2@~0.1.0: dependencies: readable-stream "^2.0.2" +duplexify@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -6401,6 +6787,13 @@ encoding@^0.1.13: dependencies: iconv-lite "^0.6.2" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + engine.io-client@~3.3.1: version "3.3.3" resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.3.3.tgz#aeb45695ced81b787a8a10c92b0bc226b1cb3c53" @@ -7015,7 +7408,7 @@ eventemitter3@4.0.7, eventemitter3@^4.0.0, eventemitter3@^4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -7144,6 +7537,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-redact@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.2.0.tgz#b1e2d39bc731376d28bde844454fa23e26919987" + integrity sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw== + fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" @@ -7196,6 +7594,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -9552,6 +9955,11 @@ lodash.get@^4.4.2: resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.isequal@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.ismatch@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz#756cb5150ca3ba6f11085a78849645f188f85f37" @@ -10153,6 +10561,11 @@ multicodec@^3.0.1: uint8arrays "^2.1.3" varint "^5.0.2" +multiformats@^9.4.2: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + multihashes@^4.0.1, multihashes@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/multihashes/-/multihashes-4.0.2.tgz" @@ -10618,6 +11031,11 @@ omggif@^1.0.10, omggif@^1.0.9: resolved "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz" integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== +on-exit-leak-free@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" + integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -11032,6 +11450,36 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= +pino-abstract-transport@v0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" + integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== + dependencies: + duplexify "^4.1.2" + split2 "^4.0.0" + +pino-std-serializers@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" + integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== + +pino@7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" + integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.0.0" + on-exit-leak-free "^0.2.0" + pino-abstract-transport v0.5.0 + pino-std-serializers "^4.0.0" + process-warning "^1.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.1.0" + safe-stable-stringify "^2.1.0" + sonic-boom "^2.2.1" + thread-stream "^0.15.1" + pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -11195,6 +11643,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-warning@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" + integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -11355,6 +11808,16 @@ query-string@6.13.5: split-on-first "^1.0.0" strict-uri-encode "^2.0.0" +query-string@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" @@ -11375,6 +11838,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + quick-lru@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" @@ -11480,7 +11948,7 @@ react-cosmos@^5.6.6: webpack-dev-middleware "^4.3.0" yargs "^16.2.0" -react-dom@>=17.0.1: +react-dom@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== @@ -11578,7 +12046,7 @@ react-window@^1.8.5: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@>=17.0.1: +react@18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -11656,6 +12124,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.1.1: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdir-scoped-modules@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" @@ -11680,6 +12157,11 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +real-require@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" + integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== + rebass@^4.0.7: version "4.0.7" resolved "https://registry.npmjs.org/rebass/-/rebass-4.0.7.tgz" @@ -12052,6 +12534,11 @@ safe-json-utils@^1.1.1: resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1" integrity sha512-SAJWGKDs50tAbiDXLf89PDwt9XYkWyANFWVzn4dTXl5QyI8t2o/bW5/OJl3lvc2WVU4MEpTo9Yz5NVFNsp+OJQ== +safe-stable-stringify@^2.1.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -12408,6 +12895,13 @@ solc@0.7.3: semver "^5.5.0" tmp "0.0.33" +sonic-boom@^2.2.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" + integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== + dependencies: + atomic-sleep "^1.0.0" + "source-map-js@>=0.6.2 <2.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" @@ -12499,6 +12993,11 @@ split2@^3.0.0: dependencies: readable-stream "^3.0.0" +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + split2@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/split2/-/split2-1.0.0.tgz#52e2e221d88c75f9a73f90556e263ff96772b314" @@ -12574,6 +13073,11 @@ stream-combiner2@~1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -12919,6 +13423,13 @@ text-table@^0.2.0, text-table@~0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +thread-stream@^0.15.1: + version "0.15.2" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" + integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== + dependencies: + real-require "^0.1.0" + throat@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -13100,7 +13611,7 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -13244,6 +13755,13 @@ uint8arrays@^2.1.3: dependencies: multibase "^4.0.1" +uint8arrays@^3.0.0, uint8arrays@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -13720,6 +14238,11 @@ ws@^7.4.0, ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== +ws@^7.5.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + ws@~6.1.0: version "6.1.4" resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.4.tgz#5b5c8800afab925e94ccb29d153c8d02c1776ef9"