chore: moving conedison/provider to interface (#7119)

* chore: moving provider from coned to interface

* moving signing over to interface

* updating lockfile

* dedup

* use d3-array build for jest

* downgrading jest/types

* Revert "downgrading jest/types"

This reverts commit 88d3746c00dcd98275be18a4b8af0314ae4329ad.
This commit is contained in:
Jack Short 2023-08-18 16:26:08 -04:00 committed by GitHub
parent 31d0c3c9b3
commit 6ec9bb7362
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 411 additions and 70 deletions

@ -56,10 +56,10 @@ module.exports = {
'\\.css\\.ts$': '@vanilla-extract/jest-transform',
'\\.(t|j)sx?$': '@swc/jest',
},
// Use @uniswap/conedison's build directly, as jest does not support its exports.
transformIgnorePatterns: ['@uniswap/conedison/provider'],
// Use d3-arrays's build directly, as jest does not support its exports.
transformIgnorePatterns: ['d3-array'],
moduleNameMapper: {
'@uniswap/conedison/provider': '@uniswap/conedison/dist/provider',
'd3-array': 'd3-array/dist/d3-array.min.js',
},
})
},

@ -94,7 +94,7 @@
"@types/array.prototype.flat": "^1.2.1",
"@types/array.prototype.flatmap": "^1.2.2",
"@types/d3": "^6.7.1",
"@types/jest": "^25.2.1",
"@types/jest": "^27.0.1",
"@types/lingui__core": "^2.7.1",
"@types/lingui__macro": "^2.7.4",
"@types/lingui__react": "^2.8.3",
@ -192,7 +192,6 @@
"@types/react-window-infinite-loader": "^1.0.6",
"@uniswap/analytics": "^1.4.0",
"@uniswap/analytics-events": "^2.17.0",
"@uniswap/conedison": "^1.8.0",
"@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "^1.0.1",

@ -1,5 +1,4 @@
import { CustomUserProperties, InterfaceEventName, WalletConnectionResult } from '@uniswap/analytics-events'
import { getWalletMeta } from '@uniswap/conedison/provider/meta'
import { useWeb3React, Web3ReactHooks, Web3ReactProvider } from '@web3-react/core'
import { Connector } from '@web3-react/types'
import { sendAnalyticsEvent, user } from 'analytics'
@ -13,6 +12,7 @@ import { ReactNode, useEffect } from 'react'
import { useLocation } from 'react-router-dom'
import { useConnectedWallets } from 'state/wallets/hooks'
import { getCurrentPageFromLocation } from 'utils/urlRoutes'
import { getWalletMeta } from 'utils/walletMeta'
export default function Web3Provider({ children }: { children: ReactNode }) {
useEagerlyConnect()

@ -1,4 +1,3 @@
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'
@ -9,6 +8,7 @@ import { useSingleCallResult } from 'lib/hooks/multicall'
import ms from 'ms'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { toReadableError, UserRejectedRequestError } from 'utils/errors'
import { signTypedData } from 'utils/signing'
import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
const PERMIT_EXPIRATION = ms(`30d`)
@ -77,7 +77,6 @@ export function useUpdatePermitAllowance(
}
const { domain, types, values } = AllowanceTransfer.getPermitData(permit, PERMIT2_ADDRESS, chainId)
// Use conedison's signTypedData for better x-wallet compatibility.
const signature = await signTypedData(provider.getSigner(account), domain, types, values)
onPermitSignature?.({ ...permit, signature })
return

@ -1,7 +1,6 @@
import { BigNumber } from '@ethersproject/bignumber'
import * as Sentry from '@sentry/react'
import { SwapEventName } from '@uniswap/analytics-events'
import { signTypedData } from '@uniswap/conedison/provider/signing'
import { Percent } from '@uniswap/sdk-core'
import { DutchOrder, DutchOrderBuilder } from '@uniswap/uniswapx-sdk'
import { useWeb3React } from '@web3-react/core'
@ -11,6 +10,7 @@ import { useCallback } from 'react'
import { DutchOrderTrade, TradeFillType } from 'state/routing/types'
import { trace } from 'tracing/trace'
import { UserRejectedRequestError } from 'utils/errors'
import { signTypedData } from 'utils/signing'
import { didUserReject, swapErrorToUserReadableMessage } from 'utils/swapErrorToUserReadableMessage'
const DEFAULT_START_TIME_PADDING_SECONDS = 30

154
src/utils/signing.test.ts Normal file

@ -0,0 +1,154 @@
import { ExternalProvider, JsonRpcProvider, JsonRpcSigner, Web3Provider } from '@ethersproject/providers'
import { signTypedData } from 'utils/signing'
type Mutable<T> = { -readonly [P in keyof T]: T[P] }
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) => {
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) => {
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>
web3Provider.provider = {
isWalletConnect: true,
session: { peer: { metadata: { name } } },
} as ExternalProvider
})
it('signs using eth_signTypedData', async () => {
const send = jest.spyOn(signer.provider, 'send').mockImplementationOnce((method) => {
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')
})
})
describe('TrustWallet fallback for eth_signTypedData_v4', () => {
beforeEach(() => {
const web3Provider = signer.provider as Mutable<Web3Provider>
web3Provider.provider = {
isWalletConnect: true,
session: { peer: { metadata: { name: 'Trust Wallet' } } },
} as ExternalProvider
})
it('signs using eth_sign', async () => {
jest.spyOn(console, 'warn').mockReturnValue()
const send = jest.spyOn(signer.provider, 'send').mockImplementation((method) => {
if (method === 'eth_sign') return Promise.resolve()
throw new Error('TrustWalletConnect.WCError error 1')
})
await signTypedData(signer, domain, types, value)
expect(send).toHaveBeenCalledTimes(2)
expect(send).toHaveBeenCalledWith('eth_sign', [wallet, expect.anything()])
})
})
})
})

64
src/utils/signing.ts Normal file

@ -0,0 +1,64 @@
import type { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer'
import { _TypedDataEncoder } from '@ethersproject/hash'
import type { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
import { getWalletMeta, WalletType } from 'utils/walletMeta'
// 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<string, TypedDataField[]>,
// Use Record<string, any> for the value to match the JsonRpcSigner._signTypedData signature.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: Record<string, any>
) {
// Populate any ENS names (in-place)
const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => {
return signer.provider.resolveName(name) as Promise<string>
})
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) ||
error.message.match(/TrustWalletConnect.WCError error 1/) ||
error.message.match(/Missing or invalid/))
) {
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
}
}

@ -0,0 +1,87 @@
import type { ExternalProvider } from '@ethersproject/providers'
import { JsonRpcProvider } from '@ethersproject/providers'
import type WalletConnectProvider from '@walletconnect/ethereum-provider'
import { getWalletMeta, WalletMeta, WalletType } from 'utils/walletMeta'
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 MockWalletConnectProviderV2 extends MockJsonRpcProvider {
name = WalletType.WALLET_CONNECT
provider: WalletConnectProvider
constructor(metadata: typeof WC_META | null) {
super(metadata)
this.provider = { isWalletConnect: true, session: { peer: { metadata } } } as unknown as WalletConnectProvider
}
}
class MockInjectedProvider extends MockJsonRpcProvider {
name = WalletType.INJECTED
provider: ExternalProvider
constructor(provider: Record<string, boolean | undefined>) {
super(provider)
this.provider = {
isConnected() {
return true
},
...provider,
} as ExternalProvider
}
}
const testCases: [MockJsonRpcProvider, WalletMeta | undefined][] = [
[new MockJsonRpcProvider(), undefined],
[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)
})
})
})

91
src/utils/walletMeta.ts Normal file

@ -0,0 +1,91 @@
import type { ExternalProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers'
import type WalletConnectProvider from '@walletconnect/ethereum-provider'
function isWeb3Provider(provider: JsonRpcProvider): provider is Web3Provider {
return 'provider' in provider
}
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 {
const metadata = provider.session?.peer.metadata
return {
type: WalletType.WALLET_CONNECT,
agent: metadata ? `${metadata.name} (WalletConnect)` : '(WalletConnect)',
...metadata,
}
}
function getInjectedMeta(provider: ExternalProvider & Record<string, unknown>): WalletMeta {
const properties = Object.getOwnPropertyNames(provider)
const names =
properties
.filter((name) => name.match(/^is.*$/) && (provider as Record<string, unknown>)[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)
}
}

@ -3408,16 +3408,6 @@
slash "^3.0.0"
write-file-atomic "^4.0.2"
"@jest/types@^25.5.0":
version "25.5.0"
resolved "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz"
integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
dependencies:
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/istanbul-reports" "^1.1.1"
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@jest/types@^26.6.2":
version "26.6.2"
resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
@ -5672,14 +5662,6 @@
dependencies:
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-reports@^1.1.1":
version "1.1.2"
resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz"
integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==
dependencies:
"@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*"
"@types/istanbul-reports@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff"
@ -5687,13 +5669,13 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jest@*", "@types/jest@^25.2.1":
version "25.2.3"
resolved "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz"
integrity sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==
"@types/jest@*", "@types/jest@^27.0.1":
version "27.5.2"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c"
integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==
dependencies:
jest-diff "^25.2.1"
pretty-format "^25.2.1"
jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0"
"@types/js-yaml@^4.0.0":
version "4.0.5"
@ -6209,11 +6191,6 @@
react "^18.2.0"
react-dom "^18.2.0"
"@uniswap/conedison@^1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@uniswap/conedison/-/conedison-1.8.0.tgz#60fcfa1475780350a719510ccee1f6a49f8c5cf8"
integrity sha512-EeC37bbrd4sJnqW3TxNhExdWUBvzyJDhbTXcbDzFdBh/bg5sOCu1NbYJEnrjwBHEfewc9nF2OjEzejJFyJUx9A==
"@uniswap/default-token-list@^11.2.0":
version "11.2.0"
resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-11.2.0.tgz#f7babd37b8f2f1d5c724bd4689be93c4aedd9205"
@ -10391,11 +10368,6 @@ didyoumean@^1.2.2:
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
diff-sequences@^25.2.6:
version "25.2.6"
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz"
integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
diff-sequences@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
@ -13905,16 +13877,6 @@ jest-dev-server@^9.0.0:
tree-kill "^1.2.2"
wait-on "^7.0.1"
jest-diff@^25.2.1:
version "25.5.0"
resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz"
integrity sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==
dependencies:
chalk "^3.0.0"
diff-sequences "^25.2.6"
jest-get-type "^25.2.6"
pretty-format "^25.5.0"
jest-diff@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def"
@ -14031,11 +13993,6 @@ jest-fetch-mock@^3.0.3:
cross-fetch "^3.0.4"
promise-polyfill "^8.1.3"
jest-get-type@^25.2.6:
version "25.2.6"
resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz"
integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
jest-get-type@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1"
@ -14124,7 +14081,7 @@ jest-leak-detector@^29.6.1:
jest-get-type "^29.4.3"
pretty-format "^29.6.1"
jest-matcher-utils@^27.5.1:
jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
@ -17333,16 +17290,6 @@ pretty-error@^4.0.0:
lodash "^4.17.20"
renderkid "^3.0.0"
pretty-format@^25.2.1, pretty-format@^25.5.0:
version "25.5.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz"
integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
dependencies:
"@jest/types" "^25.5.0"
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^16.12.0"
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
@ -17353,7 +17300,7 @@ pretty-format@^26.6.2:
ansi-styles "^4.0.0"
react-is "^17.0.1"
pretty-format@^27.0.2, pretty-format@^27.5.1:
pretty-format@^27.0.0, pretty-format@^27.0.2, pretty-format@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==