feat: redux migration (#6830)
* feat: start working on redux migrations * feat: fix migations and add tests * feat: fix persistence and improve tests * fix: tests * fix: rename test file so it doesnt run in jest * fix: tests * fix: lint * feat: indexedDB * fix: e2e tests * fix: address some comments * fix: update legacy migrations * fix: fix rehydrations * fix: remove PersistGate and fix e2e tests * fix: add comment to helper function
This commit is contained in:
parent
69ae42f285
commit
38cce46c7b
@ -3,8 +3,8 @@ import 'cypress-hardhat/lib/browser'
|
|||||||
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
import { Eip1193Bridge } from '@ethersproject/experimental/lib/eip1193-bridge'
|
||||||
|
|
||||||
import { FeatureFlag } from '../../src/featureFlags'
|
import { FeatureFlag } from '../../src/featureFlags'
|
||||||
import { UserState } from '../../src/state/user/reducer'
|
import { initialState, UserState } from '../../src/state/user/reducer'
|
||||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
@ -54,18 +54,12 @@ Cypress.Commands.overwrite(
|
|||||||
onBeforeLoad(win) {
|
onBeforeLoad(win) {
|
||||||
options?.onBeforeLoad?.(win)
|
options?.onBeforeLoad?.(win)
|
||||||
|
|
||||||
// We want to test from a clean state, so we clear the local storage (which clears redux).
|
setInitialUserState(win, {
|
||||||
win.localStorage.clear()
|
...initialState,
|
||||||
|
|
||||||
// Set initial user state.
|
|
||||||
win.localStorage.setItem(
|
|
||||||
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
|
|
||||||
JSON.stringify({
|
|
||||||
hideUniswapWalletBanner: true,
|
hideUniswapWalletBanner: true,
|
||||||
...CONNECTED_WALLET_USER_STATE,
|
...CONNECTED_WALLET_USER_STATE,
|
||||||
...(options?.userState ?? {}),
|
...(options?.userState ?? {}),
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Set feature flags, if configured.
|
// Set feature flags, if configured.
|
||||||
if (options?.featureFlags) {
|
if (options?.featureFlags) {
|
||||||
|
@ -4,3 +4,31 @@ import { UserState } from '../../src/state/user/reducer'
|
|||||||
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: ConnectionType.INJECTED }
|
export const CONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: ConnectionType.INJECTED }
|
||||||
|
|
||||||
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
|
export const DISCONNECTED_WALLET_USER_STATE: Partial<UserState> = { selectedWallet: undefined }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This sets the initial value of the "user" slice in IndexedDB.
|
||||||
|
* Other persisted slices are not set, so they will be filled with their respective initial values
|
||||||
|
* when the app runs.
|
||||||
|
*/
|
||||||
|
export function setInitialUserState(win: Cypress.AUTWindow, initialUserState: any) {
|
||||||
|
win.indexedDB.deleteDatabase('redux')
|
||||||
|
|
||||||
|
const dbRequest = win.indexedDB.open('redux')
|
||||||
|
|
||||||
|
dbRequest.onsuccess = function () {
|
||||||
|
const db = dbRequest.result
|
||||||
|
const transaction = db.transaction('keyvaluepairs', 'readwrite')
|
||||||
|
const store = transaction.objectStore('keyvaluepairs')
|
||||||
|
store.put(
|
||||||
|
{
|
||||||
|
user: initialUserState,
|
||||||
|
},
|
||||||
|
'persist:interface'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
dbRequest.onupgradeneeded = function () {
|
||||||
|
const db = dbRequest.result
|
||||||
|
db.createObjectStore('keyvaluepairs')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -143,6 +143,7 @@
|
|||||||
"terser-webpack-plugin": "^5.3.9",
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
"ts-transform-graphql-tag": "^0.2.1",
|
"ts-transform-graphql-tag": "^0.2.1",
|
||||||
|
"tsafe": "^1.6.4",
|
||||||
"typechain": "^5.0.0",
|
"typechain": "^5.0.0",
|
||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"webpack": "^5.88.2",
|
"webpack": "^5.88.2",
|
||||||
@ -235,6 +236,7 @@
|
|||||||
"inter-ui": "^3.13.1",
|
"inter-ui": "^3.13.1",
|
||||||
"jotai": "^1.3.7",
|
"jotai": "^1.3.7",
|
||||||
"jsbi": "^3.1.4",
|
"jsbi": "^3.1.4",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
"make-plural": "^7.0.0",
|
"make-plural": "^7.0.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"multicodec": "^3.0.1",
|
"multicodec": "^3.0.1",
|
||||||
@ -265,7 +267,7 @@
|
|||||||
"react-window-infinite-loader": "^1.0.8",
|
"react-window-infinite-loader": "^1.0.8",
|
||||||
"rebass": "^4.0.7",
|
"rebass": "^4.0.7",
|
||||||
"redux": "^4.1.2",
|
"redux": "^4.1.2",
|
||||||
"redux-localstorage-simple": "^2.3.1",
|
"redux-persist": "^6.0.0",
|
||||||
"statsig-react": "^1.22.0",
|
"statsig-react": "^1.22.0",
|
||||||
"styled-components": "^5.3.5",
|
"styled-components": "^5.3.5",
|
||||||
"tiny-invariant": "^1.2.0",
|
"tiny-invariant": "^1.2.0",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Connector } from '@web3-react/types'
|
import { Connector } from '@web3-react/types'
|
||||||
import { gnosisSafeConnection, networkConnection } from 'connection'
|
import { gnosisSafeConnection, networkConnection } from 'connection'
|
||||||
import { getConnection } from 'connection'
|
import { getConnection } from 'connection'
|
||||||
import { Connection } from 'connection/types'
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||||
import { updateSelectedWallet } from 'state/user/reducer'
|
import { updateSelectedWallet } from 'state/user/reducer'
|
||||||
@ -22,22 +21,24 @@ export default function useEagerlyConnect() {
|
|||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||||
|
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
|
||||||
let selectedConnection: Connection | undefined
|
|
||||||
if (selectedWallet) {
|
|
||||||
try {
|
|
||||||
selectedConnection = getConnection(selectedWallet)
|
|
||||||
} catch {
|
|
||||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!selectedWallet) return
|
||||||
|
try {
|
||||||
|
const selectedConnection = getConnection(selectedWallet)
|
||||||
connect(gnosisSafeConnection.connector)
|
connect(gnosisSafeConnection.connector)
|
||||||
connect(networkConnection.connector)
|
connect(networkConnection.connector)
|
||||||
|
|
||||||
if (selectedConnection) {
|
if (selectedConnection) {
|
||||||
connect(selectedConnection.connector)
|
connect(selectedConnection.connector)
|
||||||
} // The dependency list is empty so this is only run once on mount
|
}
|
||||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
} catch {
|
||||||
|
// only clear the persisted wallet type if it failed to connect.
|
||||||
|
if (rehydrated) {
|
||||||
|
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, [dispatch, rehydrated, selectedWallet])
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import { sendAnalyticsEvent } from 'analytics'
|
|||||||
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
|
import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||||
|
import { AppState } from 'state/reducer'
|
||||||
|
|
||||||
import { AppState } from '../types'
|
|
||||||
import {
|
import {
|
||||||
addPopup,
|
addPopup,
|
||||||
ApplicationModal,
|
ApplicationModal,
|
||||||
|
@ -47,7 +47,7 @@ export enum ApplicationModal {
|
|||||||
UNISWAP_NFT_AIRDROP_CLAIM,
|
UNISWAP_NFT_AIRDROP_CLAIM,
|
||||||
}
|
}
|
||||||
|
|
||||||
type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
|
export type PopupList = Array<{ key: string; show: boolean; content: PopupContent; removeAfterMs: number | null }>
|
||||||
|
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
readonly chainId: number | null
|
readonly chainId: number | null
|
||||||
|
@ -6,11 +6,11 @@ import JSBI from 'jsbi'
|
|||||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||||
import { ReactNode, useCallback } from 'react'
|
import { ReactNode, useCallback } from 'react'
|
||||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||||
|
import { AppState } from 'state/reducer'
|
||||||
|
|
||||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||||
import { useTokenBalances } from '../connection/hooks'
|
import { useTokenBalances } from '../connection/hooks'
|
||||||
import { AppState } from '../types'
|
|
||||||
import { Field, typeInput } from './actions'
|
import { Field, typeInput } from './actions'
|
||||||
|
|
||||||
export function useBurnState(): AppState['burn'] {
|
export function useBurnState(): AppState['burn'] {
|
||||||
|
@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
|
|||||||
|
|
||||||
import { Field, typeInput } from './actions'
|
import { Field, typeInput } from './actions'
|
||||||
|
|
||||||
interface BurnState {
|
export interface BurnState {
|
||||||
readonly independentField: Field
|
readonly independentField: Field
|
||||||
readonly typedValue: string
|
readonly typedValue: string
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
|||||||
import { PositionDetails } from 'types/position'
|
import { PositionDetails } from 'types/position'
|
||||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||||
|
|
||||||
import { AppState } from '../../types'
|
import { AppState } from '../../reducer'
|
||||||
import { selectPercent } from './actions'
|
import { selectPercent } from './actions'
|
||||||
|
|
||||||
export function useBurnV3State(): AppState['burnV3'] {
|
export function useBurnV3State(): AppState['burnV3'] {
|
||||||
|
@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
|
|||||||
|
|
||||||
import { selectPercent } from './actions'
|
import { selectPercent } from './actions'
|
||||||
|
|
||||||
interface BurnV3State {
|
export interface BurnV3State {
|
||||||
readonly percent: number
|
readonly percent: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
||||||
import { load, save } from 'redux-localstorage-simple'
|
import { persistStore } from 'redux-persist'
|
||||||
import { isTestEnv } from 'utils/env'
|
|
||||||
|
|
||||||
import { updateVersion } from './global/actions'
|
import { updateVersion } from './global/actions'
|
||||||
import { sentryEnhancer } from './logging'
|
import { sentryEnhancer } from './logging'
|
||||||
import reducer from './reducer'
|
import reducer from './reducer'
|
||||||
import { routingApi } from './routing/slice'
|
import { routingApi } from './routing/slice'
|
||||||
|
|
||||||
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'signatures', 'lists']
|
export function createDefaultStore() {
|
||||||
|
return configureStore({
|
||||||
const store = configureStore({
|
|
||||||
reducer,
|
reducer,
|
||||||
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
|
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
@ -21,15 +19,23 @@ const store = configureStore({
|
|||||||
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
|
// because we are not adding it into any persisted store that requires serialization (e.g. localStorage)
|
||||||
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
|
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
|
||||||
ignoredPaths: [routingApi.reducerPath],
|
ignoredPaths: [routingApi.reducerPath],
|
||||||
|
ignoredActions: [
|
||||||
|
// ignore the redux-persist actions
|
||||||
|
'persist/PERSIST',
|
||||||
|
'persist/REHYDRATE',
|
||||||
|
'persist/PURGE',
|
||||||
|
'persist/FLUSH',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
}).concat(routingApi.middleware),
|
||||||
})
|
})
|
||||||
.concat(routingApi.middleware)
|
}
|
||||||
.concat(save({ states: PERSISTED_KEYS, debounce: 1000 })),
|
|
||||||
preloadedState: load({ states: PERSISTED_KEYS, disableWarnings: isTestEnv() }),
|
|
||||||
})
|
|
||||||
|
|
||||||
store.dispatch(updateVersion())
|
const store = createDefaultStore()
|
||||||
|
export const persistor = persistStore(store)
|
||||||
|
|
||||||
setupListeners(store.dispatch)
|
setupListeners(store.dispatch)
|
||||||
|
|
||||||
|
store.dispatch(updateVersion())
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { TokenAddressMap, tokensToChainTokenMap } from 'lib/hooks/useTokenList/utils'
|
import { TokenAddressMap, tokensToChainTokenMap } from 'lib/hooks/useTokenList/utils'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { useAppSelector } from 'state/hooks'
|
import { useAppSelector } from 'state/hooks'
|
||||||
|
import { AppState } from 'state/reducer'
|
||||||
import sortByListPriority from 'utils/listSort'
|
import sortByListPriority from 'utils/listSort'
|
||||||
|
|
||||||
import BROKEN_LIST from '../../constants/tokenLists/broken.tokenlist.json'
|
import BROKEN_LIST from '../../constants/tokenLists/broken.tokenlist.json'
|
||||||
import { AppState } from '../types'
|
|
||||||
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
|
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
|
||||||
|
|
||||||
type Mutable<T> = {
|
type Mutable<T> = {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
import tokenSafetyLookup from 'constants/tokenSafetyLookup'
|
||||||
import { createStore, Store } from 'redux'
|
import { createStore, Store } from 'redux'
|
||||||
|
import { updateVersion } from 'state/global/actions'
|
||||||
|
|
||||||
import { DEFAULT_LIST_OF_LISTS } from '../../constants/lists'
|
import { DEFAULT_LIST_OF_LISTS } from '../../constants/lists'
|
||||||
import tokenSafetyLookup from '../../constants/tokenSafetyLookup'
|
|
||||||
import { updateVersion } from '../global/actions'
|
|
||||||
import { acceptListUpdate, addList, fetchTokenList, removeList } from './actions'
|
import { acceptListUpdate, addList, fetchTokenList, removeList } from './actions'
|
||||||
import reducer, { ListsState } from './reducer'
|
import reducer, { ListsState } from './reducer'
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ const NEW_LIST_STATE: ListState = {
|
|||||||
|
|
||||||
type Mutable<T> = { -readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U> ? U[] : T[P] }
|
type Mutable<T> = { -readonly [P in keyof T]: T[P] extends ReadonlyArray<infer U> ? U[] : T[P] }
|
||||||
|
|
||||||
const initialState: ListsState = {
|
export const initialState: ListsState = {
|
||||||
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
|
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
|
||||||
byUrl: {
|
byUrl: {
|
||||||
...DEFAULT_LIST_OF_LISTS.reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
|
...DEFAULT_LIST_OF_LISTS.reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import noop from 'utils/noop'
|
import noop from 'utils/noop'
|
||||||
|
|
||||||
import { AppState } from './types'
|
import { AppState } from './reducer'
|
||||||
|
|
||||||
/* Utility type to mark all properties of a type as optional */
|
/* Utility type to mark all properties of a type as optional */
|
||||||
type DeepPartial<T> = T extends object
|
type DeepPartial<T> = T extends object
|
||||||
|
@ -3,7 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
|||||||
|
|
||||||
import { filterToKey, Log } from './utils'
|
import { filterToKey, Log } from './utils'
|
||||||
|
|
||||||
interface LogsState {
|
export interface LogsState {
|
||||||
[chainId: number]: {
|
[chainId: number]: {
|
||||||
[filterKey: string]: {
|
[filterKey: string]: {
|
||||||
listeners: number
|
listeners: number
|
||||||
|
135
src/state/migrations.test.ts
Normal file
135
src/state/migrations.test.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { Store } from '@reduxjs/toolkit'
|
||||||
|
import { persistStore } from 'redux-persist'
|
||||||
|
import { createDefaultStore } from 'state'
|
||||||
|
|
||||||
|
import { initialState as initialListsState } from './lists/reducer'
|
||||||
|
import { initialState as initialSignaturesState } from './signatures/reducer'
|
||||||
|
import { initialState as initialTransactionsState } from './transactions/reducer'
|
||||||
|
import { initialState as initialUserState } from './user/reducer'
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
lists: {},
|
||||||
|
transactions: {},
|
||||||
|
user: {},
|
||||||
|
_persist: {
|
||||||
|
rehydrated: true,
|
||||||
|
version: 0,
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
chainId: null,
|
||||||
|
fiatOnramp: {
|
||||||
|
availabilityChecked: false,
|
||||||
|
available: false,
|
||||||
|
},
|
||||||
|
openModal: null,
|
||||||
|
popupList: [],
|
||||||
|
},
|
||||||
|
burn: {
|
||||||
|
independentField: 'LIQUIDITY_PERCENT',
|
||||||
|
typedValue: '0',
|
||||||
|
},
|
||||||
|
burnV3: {
|
||||||
|
percent: 0,
|
||||||
|
},
|
||||||
|
logs: {},
|
||||||
|
mint: {
|
||||||
|
independentField: 'CURRENCY_A',
|
||||||
|
leftRangeTypedValue: '',
|
||||||
|
otherTypedValue: '',
|
||||||
|
rightRangeTypedValue: '',
|
||||||
|
startPriceTypedValue: '',
|
||||||
|
typedValue: '',
|
||||||
|
},
|
||||||
|
mintV3: {
|
||||||
|
independentField: 'CURRENCY_A',
|
||||||
|
leftRangeTypedValue: '',
|
||||||
|
rightRangeTypedValue: '',
|
||||||
|
startPriceTypedValue: '',
|
||||||
|
typedValue: '',
|
||||||
|
},
|
||||||
|
multicall: {
|
||||||
|
callResults: {},
|
||||||
|
},
|
||||||
|
wallets: {
|
||||||
|
connectedWallets: [],
|
||||||
|
switchingChain: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('redux migrations', () => {
|
||||||
|
let store: Store
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear()
|
||||||
|
// Re-create the store before each test so it starts with undefined state.
|
||||||
|
store = createDefaultStore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears legacy redux_localstorage_simple values during the initial migration', async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'redux_localstorage_simple_transactions',
|
||||||
|
JSON.stringify({ 1: { test: { info: 'transactions' } } })
|
||||||
|
)
|
||||||
|
localStorage.setItem('redux_localstorage_simple_user', JSON.stringify({ test: 'user' }))
|
||||||
|
localStorage.setItem('redux_localstorage_simple_lists', JSON.stringify({ test: 'lists' }))
|
||||||
|
localStorage.setItem('redux_localstorage_simple_signatures', JSON.stringify({ test: 'signatures' }))
|
||||||
|
|
||||||
|
persistStore(store)
|
||||||
|
// wait for the migration to complete
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
expect(localStorage.getItem('redux_localstorage_simple_transactions')).toBeNull()
|
||||||
|
expect(localStorage.getItem('redux_localstorage_simple_user')).toBeNull()
|
||||||
|
expect(localStorage.getItem('redux_localstorage_simple_lists')).toBeNull()
|
||||||
|
expect(localStorage.getItem('redux_localstorage_simple_signatures')).toBeNull()
|
||||||
|
|
||||||
|
const state = store.getState()
|
||||||
|
expect(state).toMatchObject({
|
||||||
|
...defaultState,
|
||||||
|
// These are migrated values.
|
||||||
|
lists: {
|
||||||
|
test: 'lists',
|
||||||
|
},
|
||||||
|
transactions: {
|
||||||
|
1: {
|
||||||
|
test: { info: 'transactions' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
test: 'user',
|
||||||
|
},
|
||||||
|
signatures: {
|
||||||
|
test: 'signatures',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('initial state with no previous persisted state', async () => {
|
||||||
|
persistStore(store)
|
||||||
|
// wait for the migration to complete
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
const state = store.getState()
|
||||||
|
expect(state).toMatchObject(defaultState)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('migrates from a previous version of the state type', async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persist:interface',
|
||||||
|
JSON.stringify({
|
||||||
|
user: { ...initialUserState, test: 'user' },
|
||||||
|
transactions: initialTransactionsState,
|
||||||
|
lists: initialListsState,
|
||||||
|
signatures: initialSignaturesState,
|
||||||
|
_persist: { version: -1 },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
persistStore(store)
|
||||||
|
// wait for the migration to complete
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||||
|
|
||||||
|
const state = store.getState()
|
||||||
|
expect(state).toMatchObject(defaultState)
|
||||||
|
})
|
||||||
|
})
|
35
src/state/migrations.ts
Normal file
35
src/state/migrations.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { createMigrate, MigrationManifest, PersistedState, PersistMigrate } from 'redux-persist'
|
||||||
|
import { MigrationConfig } from 'redux-persist/es/createMigrate'
|
||||||
|
|
||||||
|
import { migration0 } from './migrations/0'
|
||||||
|
import { legacyLocalStorageMigration } from './migrations/legacy'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These run once per state re-hydration when a version mismatch is detected.
|
||||||
|
* Keep them as lightweight as possible.
|
||||||
|
*
|
||||||
|
* Migration functions should not assume that any value exists in localStorage previously,
|
||||||
|
* because a user may be visiting the site for the first time or have cleared their localStorage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// The target version number is the key
|
||||||
|
export const migrations: MigrationManifest = {
|
||||||
|
0: migration0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a custom migration function for the initial state, because redux-persist
|
||||||
|
// skips migration if there is no initial state, but we want to migrate
|
||||||
|
// previous persisted state from redux-localstorage-simple.
|
||||||
|
export function customCreateMigrate(migrations: MigrationManifest, options: MigrationConfig): PersistMigrate {
|
||||||
|
const defaultMigrate = createMigrate(migrations, options)
|
||||||
|
|
||||||
|
return async (state: PersistedState, currentVersion: number) => {
|
||||||
|
if (state === undefined) {
|
||||||
|
// If no state exists, run the legacy migration to set initial state
|
||||||
|
state = await legacyLocalStorageMigration()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use the default migration process
|
||||||
|
return defaultMigrate(state, currentVersion)
|
||||||
|
}
|
||||||
|
}
|
10
src/state/migrations/0.ts
Normal file
10
src/state/migrations/0.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { PersistedState } from 'redux-persist'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial migration as a proof of concept.
|
||||||
|
*
|
||||||
|
* Legacy migration from redux-localstorage-simple happens in legacy.ts
|
||||||
|
*/
|
||||||
|
export const migration0 = (state: PersistedState) => {
|
||||||
|
return state
|
||||||
|
}
|
128
src/state/migrations/legacy.ts
Normal file
128
src/state/migrations/legacy.ts
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import { DEFAULT_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||||
|
import { persistor } from 'state'
|
||||||
|
|
||||||
|
import { initialState as initialListsState } from '../lists/reducer'
|
||||||
|
import { RouterPreference } from '../routing/types'
|
||||||
|
import { TransactionState } from '../transactions/reducer'
|
||||||
|
import { initialState as initialTransactionsState } from '../transactions/reducer'
|
||||||
|
import { UserState } from '../user/reducer'
|
||||||
|
import { initialState as initialUserState } from '../user/reducer'
|
||||||
|
import { SlippageTolerance } from '../user/types'
|
||||||
|
|
||||||
|
const currentTimestamp = () => new Date().getTime()
|
||||||
|
|
||||||
|
function tryParseOldState<T>(value: string | null, fallback: T): T {
|
||||||
|
try {
|
||||||
|
return value ? JSON.parse(value) : fallback
|
||||||
|
} catch (e) {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These functions handle all migrations that existed before we started tracking version numbers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const legacyLocalStorageMigration = async () => {
|
||||||
|
const oldTransactions = localStorage.getItem('redux_localstorage_simple_transactions')
|
||||||
|
const oldUser = localStorage.getItem('redux_localstorage_simple_user')
|
||||||
|
const oldLists = localStorage.getItem('redux_localstorage_simple_lists')
|
||||||
|
const oldSignatures = localStorage.getItem('redux_localstorage_simple_signatures')
|
||||||
|
|
||||||
|
const newTransactions = tryParseOldState(oldTransactions, initialTransactionsState)
|
||||||
|
const newUser = tryParseOldState(oldUser, initialUserState)
|
||||||
|
const newLists = tryParseOldState(oldLists, initialListsState)
|
||||||
|
const newSignatures = tryParseOldState(oldSignatures, {})
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
user: legacyUserMigrations(newUser),
|
||||||
|
transactions: legacyTransactionMigrations(newTransactions),
|
||||||
|
lists: newLists,
|
||||||
|
signatures: newSignatures,
|
||||||
|
_persist: { version: 0, rehydrated: true },
|
||||||
|
}
|
||||||
|
|
||||||
|
await persistor.flush()
|
||||||
|
|
||||||
|
localStorage.removeItem('redux_localstorage_simple_transactions')
|
||||||
|
localStorage.removeItem('redux_localstorage_simple_user')
|
||||||
|
localStorage.removeItem('redux_localstorage_simple_lists')
|
||||||
|
localStorage.removeItem('redux_localstorage_simple_signatures')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function legacyTransactionMigrations(state: any): TransactionState {
|
||||||
|
// Make a copy of the object so we can mutate it.
|
||||||
|
const result = JSON.parse(JSON.stringify(state))
|
||||||
|
// in case there are any transactions in the store with the old format, remove them
|
||||||
|
Object.keys(result).forEach((chainId) => {
|
||||||
|
const chainTransactions = result[chainId as unknown as number]
|
||||||
|
Object.keys(chainTransactions).forEach((hash) => {
|
||||||
|
if (!('info' in chainTransactions[hash])) {
|
||||||
|
// clear old transactions that don't have the right format
|
||||||
|
delete chainTransactions[hash]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function legacyUserMigrations(state: any): UserState {
|
||||||
|
// Make a copy of the object so we can mutate it.
|
||||||
|
const result = JSON.parse(JSON.stringify(state))
|
||||||
|
// If `selectedWallet` is a WalletConnect v1 wallet, reset to default.
|
||||||
|
if (result.selectedWallet) {
|
||||||
|
const selectedWallet = result.selectedWallet as string
|
||||||
|
if (selectedWallet === 'UNIWALLET' || selectedWallet === 'UNISWAP_WALLET' || selectedWallet === 'WALLET_CONNECT') {
|
||||||
|
delete result.selectedWallet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `userSlippageTolerance` is not present or its value is invalid, reset to default
|
||||||
|
if (
|
||||||
|
typeof result.userSlippageTolerance !== 'number' ||
|
||||||
|
!Number.isInteger(result.userSlippageTolerance) ||
|
||||||
|
result.userSlippageTolerance < 0 ||
|
||||||
|
result.userSlippageTolerance > 5000
|
||||||
|
) {
|
||||||
|
result.userSlippageTolerance = SlippageTolerance.Auto
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
!result.userSlippageToleranceHasBeenMigratedToAuto &&
|
||||||
|
[10, 50, 100].indexOf(result.userSlippageTolerance) !== -1
|
||||||
|
) {
|
||||||
|
result.userSlippageTolerance = SlippageTolerance.Auto
|
||||||
|
result.userSlippageToleranceHasBeenMigratedToAuto = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `userDeadline` is not present or its value is invalid, reset to default
|
||||||
|
if (
|
||||||
|
typeof result.userDeadline !== 'number' ||
|
||||||
|
!Number.isInteger(result.userDeadline) ||
|
||||||
|
result.userDeadline < 60 ||
|
||||||
|
result.userDeadline > 180 * 60
|
||||||
|
) {
|
||||||
|
result.userDeadline = DEFAULT_DEADLINE_FROM_NOW
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `userRouterPreference` is not present, reset to default
|
||||||
|
if (typeof result.userRouterPreference !== 'string') {
|
||||||
|
result.userRouterPreference = RouterPreference.API
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `userRouterPreference` is `AUTO`, migrate to `API`
|
||||||
|
if ((result.userRouterPreference as string) === 'auto') {
|
||||||
|
result.userRouterPreference = RouterPreference.API
|
||||||
|
}
|
||||||
|
|
||||||
|
//If `buyFiatFlowCompleted` is present, delete it using filtering
|
||||||
|
if ('buyFiatFlowCompleted' in result) {
|
||||||
|
//ignoring due to type errors occuring since we now remove this state
|
||||||
|
//@ts-ignore
|
||||||
|
delete result.buyFiatFlowCompleted
|
||||||
|
}
|
||||||
|
|
||||||
|
result.lastUpdateVersionTimestamp = currentTimestamp()
|
||||||
|
return result
|
||||||
|
}
|
@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
|||||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||||
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
|
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
|
||||||
import { useCurrencyBalances } from '../connection/hooks'
|
import { useCurrencyBalances } from '../connection/hooks'
|
||||||
import { AppState } from '../types'
|
import { AppState } from '../reducer'
|
||||||
import { Field, typeInput } from './actions'
|
import { Field, typeInput } from './actions'
|
||||||
|
|
||||||
const ZERO = JSBI.BigInt(0)
|
const ZERO = JSBI.BigInt(0)
|
||||||
|
@ -23,7 +23,7 @@ import { getTickToPrice } from 'utils/getTickToPrice'
|
|||||||
import { BIG_INT_ZERO } from '../../../constants/misc'
|
import { BIG_INT_ZERO } from '../../../constants/misc'
|
||||||
import { PoolState } from '../../../hooks/usePools'
|
import { PoolState } from '../../../hooks/usePools'
|
||||||
import { useCurrencyBalances } from '../../connection/hooks'
|
import { useCurrencyBalances } from '../../connection/hooks'
|
||||||
import { AppState } from '../../types'
|
import { AppState } from '../../reducer'
|
||||||
import {
|
import {
|
||||||
Bound,
|
Bound,
|
||||||
Field,
|
Field,
|
||||||
|
@ -10,9 +10,9 @@ import {
|
|||||||
typeStartPriceInput,
|
typeStartPriceInput,
|
||||||
} from './actions'
|
} from './actions'
|
||||||
|
|
||||||
type FullRange = true
|
export type FullRange = true
|
||||||
|
|
||||||
interface MintState {
|
export interface MintState {
|
||||||
readonly independentField: Field
|
readonly independentField: Field
|
||||||
readonly typedValue: string
|
readonly typedValue: string
|
||||||
readonly startPriceTypedValue: string // for the case when there's no liquidity
|
readonly startPriceTypedValue: string // for the case when there's no liquidity
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
|
import { combineReducers } from '@reduxjs/toolkit'
|
||||||
import multicall from 'lib/state/multicall'
|
import multicall from 'lib/state/multicall'
|
||||||
|
import localForage from 'localforage'
|
||||||
|
import { PersistConfig, persistReducer } from 'redux-persist'
|
||||||
|
import { isDevelopmentEnv } from 'utils/env'
|
||||||
|
|
||||||
import application from './application/reducer'
|
import application from './application/reducer'
|
||||||
import burn from './burn/reducer'
|
import burn from './burn/reducer'
|
||||||
import burnV3 from './burn/v3/reducer'
|
import burnV3 from './burn/v3/reducer'
|
||||||
import lists from './lists/reducer'
|
import lists from './lists/reducer'
|
||||||
import logs from './logs/slice'
|
import logs from './logs/slice'
|
||||||
|
import { customCreateMigrate, migrations } from './migrations'
|
||||||
import mint from './mint/reducer'
|
import mint from './mint/reducer'
|
||||||
import mintV3 from './mint/v3/reducer'
|
import mintV3 from './mint/v3/reducer'
|
||||||
import { routingApi } from './routing/slice'
|
import { routingApi } from './routing/slice'
|
||||||
@ -13,18 +18,45 @@ import transactions from './transactions/reducer'
|
|||||||
import user from './user/reducer'
|
import user from './user/reducer'
|
||||||
import wallets from './wallets/reducer'
|
import wallets from './wallets/reducer'
|
||||||
|
|
||||||
export default {
|
const persistedReducers = {
|
||||||
application,
|
|
||||||
user,
|
user,
|
||||||
transactions,
|
transactions,
|
||||||
signatures,
|
signatures,
|
||||||
|
lists,
|
||||||
|
}
|
||||||
|
|
||||||
|
const appReducer = combineReducers({
|
||||||
|
application,
|
||||||
wallets,
|
wallets,
|
||||||
mint,
|
mint,
|
||||||
mintV3,
|
mintV3,
|
||||||
burn,
|
burn,
|
||||||
burnV3,
|
burnV3,
|
||||||
multicall: multicall.reducer,
|
multicall: multicall.reducer,
|
||||||
lists,
|
|
||||||
logs,
|
logs,
|
||||||
[routingApi.reducerPath]: routingApi.reducer,
|
[routingApi.reducerPath]: routingApi.reducer,
|
||||||
|
...persistedReducers,
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AppState = ReturnType<typeof appReducer>
|
||||||
|
|
||||||
|
const persistConfig: PersistConfig<AppState> = {
|
||||||
|
key: 'interface',
|
||||||
|
version: 0, // see migrations.ts for more details about this version
|
||||||
|
storage: localForage.createInstance({
|
||||||
|
name: 'redux',
|
||||||
|
}),
|
||||||
|
migrate: customCreateMigrate(migrations, { debug: false }),
|
||||||
|
whitelist: Object.keys(persistedReducers),
|
||||||
|
throttle: 1000, // ms
|
||||||
|
serialize: false,
|
||||||
|
// The typescript definitions are wrong - we need this to be false for unserialized storage to work.
|
||||||
|
// We need unserialized storage for inspectable db entries for debugging.
|
||||||
|
// @ts-ignore
|
||||||
|
deserialize: false,
|
||||||
|
debug: isDevelopmentEnv(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const persistedReducer = persistReducer(persistConfig, appReducer)
|
||||||
|
|
||||||
|
export default persistedReducer
|
||||||
|
188
src/state/reducerTypeTest.ts
Normal file
188
src/state/reducerTypeTest.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import { ChainId } from '@uniswap/sdk-core'
|
||||||
|
import { TokenList } from '@uniswap/token-lists'
|
||||||
|
import { ConnectionType } from 'connection/types'
|
||||||
|
import { SupportedLocale } from 'constants/locales'
|
||||||
|
import multicall from 'lib/state/multicall'
|
||||||
|
import { CombinedState } from 'redux'
|
||||||
|
import { assert, Equals } from 'tsafe'
|
||||||
|
|
||||||
|
import { ApplicationModal, ApplicationState, PopupList } from './application/reducer'
|
||||||
|
import { Field as BurnField } from './burn/actions'
|
||||||
|
import { BurnState } from './burn/reducer'
|
||||||
|
import { BurnV3State } from './burn/v3/reducer'
|
||||||
|
import { ListsState } from './lists/reducer'
|
||||||
|
import { LogsState } from './logs/slice'
|
||||||
|
import { Log } from './logs/utils'
|
||||||
|
import { Field } from './mint/actions'
|
||||||
|
import { MintState } from './mint/reducer'
|
||||||
|
import { Field as FieldV3 } from './mint/v3/actions'
|
||||||
|
import { FullRange, MintState as MintV3State } from './mint/v3/reducer'
|
||||||
|
import { AppState } from './reducer'
|
||||||
|
import { routingApi } from './routing/slice'
|
||||||
|
import { RouterPreference } from './routing/types'
|
||||||
|
import { SignatureState } from './signatures/reducer'
|
||||||
|
import { TransactionState } from './transactions/reducer'
|
||||||
|
import { TransactionDetails } from './transactions/types'
|
||||||
|
import { UserState } from './user/reducer'
|
||||||
|
import { SerializedPair, SerializedToken, SlippageTolerance } from './user/types'
|
||||||
|
import { WalletState } from './wallets/reducer'
|
||||||
|
import { Wallet } from './wallets/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING:
|
||||||
|
* Any changes made to the types of the Redux store could potentially require a migration.
|
||||||
|
*
|
||||||
|
* If you're making a change that alters the structure or types of the Redux state,
|
||||||
|
* consider whether existing state stored in users' browsers will still be compatible
|
||||||
|
* with the new types.
|
||||||
|
*
|
||||||
|
* If compatibility could be broken, you may need to create a migration
|
||||||
|
* function that can convert the existing state into a format that's compatible with
|
||||||
|
* the new types, or otherwise adjust the user's persisted state in some way
|
||||||
|
* to prevent undesirable behavior.
|
||||||
|
*
|
||||||
|
* This migration function should be added to the `migrations` object
|
||||||
|
* in our Redux store configuration.
|
||||||
|
*
|
||||||
|
* If no migration is needed, just update the expected types here to fix the typecheck.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ExpectedAppState = CombinedState<{
|
||||||
|
user: UserState
|
||||||
|
transactions: TransactionState
|
||||||
|
signatures: SignatureState
|
||||||
|
lists: ListsState
|
||||||
|
application: ApplicationState
|
||||||
|
wallets: WalletState
|
||||||
|
mint: MintState
|
||||||
|
mintV3: MintV3State
|
||||||
|
burn: BurnState
|
||||||
|
burnV3: BurnV3State
|
||||||
|
multicall: ReturnType<typeof multicall.reducer>
|
||||||
|
logs: LogsState
|
||||||
|
[routingApi.reducerPath]: ReturnType<typeof routingApi.reducer>
|
||||||
|
}>
|
||||||
|
|
||||||
|
assert<Equals<AppState, ExpectedAppState>>()
|
||||||
|
|
||||||
|
interface ExpectedUserState {
|
||||||
|
selectedWallet?: ConnectionType
|
||||||
|
lastUpdateVersionTimestamp?: number
|
||||||
|
userLocale: SupportedLocale | null
|
||||||
|
userRouterPreference: RouterPreference
|
||||||
|
userHideClosedPositions: boolean
|
||||||
|
userSlippageTolerance: number | SlippageTolerance.Auto
|
||||||
|
userSlippageToleranceHasBeenMigratedToAuto: boolean
|
||||||
|
userDeadline: number
|
||||||
|
tokens: {
|
||||||
|
[chainId: number]: {
|
||||||
|
[address: string]: SerializedToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pairs: {
|
||||||
|
[chainId: number]: {
|
||||||
|
[key: string]: SerializedPair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timestamp: number
|
||||||
|
URLWarningVisible: boolean
|
||||||
|
hideBaseWalletBanner: boolean
|
||||||
|
showSurveyPopup?: boolean
|
||||||
|
disabledUniswapX?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<UserState, ExpectedUserState>>()
|
||||||
|
|
||||||
|
interface ExpectedTransactionState {
|
||||||
|
[chainId: number]: {
|
||||||
|
[txHash: string]: TransactionDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<TransactionState, ExpectedTransactionState>>()
|
||||||
|
|
||||||
|
interface ExpectedListsState {
|
||||||
|
readonly byUrl: {
|
||||||
|
readonly [url: string]: {
|
||||||
|
readonly current: TokenList | null
|
||||||
|
readonly pendingUpdate: TokenList | null
|
||||||
|
readonly loadingRequestId: string | null
|
||||||
|
readonly error: string | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readonly lastInitializedDefaultListOfLists?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<ListsState, ExpectedListsState>>()
|
||||||
|
|
||||||
|
interface ExpectedApplicationState {
|
||||||
|
readonly chainId: number | null
|
||||||
|
readonly fiatOnramp: { available: boolean; availabilityChecked: boolean }
|
||||||
|
readonly openModal: ApplicationModal | null
|
||||||
|
readonly popupList: PopupList
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<ApplicationState, ExpectedApplicationState>>()
|
||||||
|
|
||||||
|
interface ExpectedWalletState {
|
||||||
|
connectedWallets: Wallet[]
|
||||||
|
switchingChain: ChainId | false
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<WalletState, ExpectedWalletState>>()
|
||||||
|
|
||||||
|
interface ExpectedMintState {
|
||||||
|
readonly independentField: Field
|
||||||
|
readonly typedValue: string
|
||||||
|
readonly otherTypedValue: string
|
||||||
|
readonly startPriceTypedValue: string
|
||||||
|
readonly leftRangeTypedValue: string
|
||||||
|
readonly rightRangeTypedValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<MintState, ExpectedMintState>>()
|
||||||
|
|
||||||
|
interface ExpectedMintV3State {
|
||||||
|
readonly independentField: FieldV3
|
||||||
|
readonly typedValue: string
|
||||||
|
readonly startPriceTypedValue: string
|
||||||
|
readonly leftRangeTypedValue: string | FullRange
|
||||||
|
readonly rightRangeTypedValue: string | FullRange
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<MintV3State, ExpectedMintV3State>>()
|
||||||
|
|
||||||
|
interface ExpectedBurnState {
|
||||||
|
readonly independentField: BurnField
|
||||||
|
readonly typedValue: string
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<BurnState, ExpectedBurnState>>()
|
||||||
|
|
||||||
|
interface ExpectedBurnV3State {
|
||||||
|
readonly percent: number
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<BurnV3State, ExpectedBurnV3State>>()
|
||||||
|
|
||||||
|
interface ExpectedLogsState {
|
||||||
|
[chainId: number]: {
|
||||||
|
[filterKey: string]: {
|
||||||
|
listeners: number
|
||||||
|
fetchingBlockNumber?: number
|
||||||
|
results?:
|
||||||
|
| {
|
||||||
|
blockNumber: number
|
||||||
|
logs: Log[]
|
||||||
|
error?: undefined
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
blockNumber: number
|
||||||
|
logs?: undefined
|
||||||
|
error: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<LogsState, ExpectedLogsState>>()
|
@ -1,7 +1,6 @@
|
|||||||
import { ChainId } from '@uniswap/sdk-core'
|
import { ChainId } from '@uniswap/sdk-core'
|
||||||
import { createStore, Store } from 'redux'
|
import { createStore, Store } from 'redux'
|
||||||
|
|
||||||
import { updateVersion } from '../global/actions'
|
|
||||||
import reducer, {
|
import reducer, {
|
||||||
addTransaction,
|
addTransaction,
|
||||||
cancelTransaction,
|
cancelTransaction,
|
||||||
@ -20,32 +19,6 @@ describe('transaction reducer', () => {
|
|||||||
store = createStore(reducer, initialState)
|
store = createStore(reducer, initialState)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('updateVersion', () => {
|
|
||||||
it('clears old format transactions that do not have info', () => {
|
|
||||||
store = createStore(reducer, {
|
|
||||||
1: {
|
|
||||||
abc: {
|
|
||||||
hash: 'abc',
|
|
||||||
} as any,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
store.dispatch(updateVersion())
|
|
||||||
expect(store.getState()[ChainId.MAINNET]['abc']).toBeUndefined()
|
|
||||||
})
|
|
||||||
it('keeps old format transactions that do have info', () => {
|
|
||||||
store = createStore(reducer, {
|
|
||||||
1: {
|
|
||||||
abc: {
|
|
||||||
hash: 'abc',
|
|
||||||
info: {},
|
|
||||||
} as any,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
store.dispatch(updateVersion())
|
|
||||||
expect(store.getState()[ChainId.MAINNET]['abc']).toBeTruthy()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('addTransaction', () => {
|
describe('addTransaction', () => {
|
||||||
it('adds the transaction', () => {
|
it('adds the transaction', () => {
|
||||||
const beforeTime = new Date().getTime()
|
const beforeTime = new Date().getTime()
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { createSlice } from '@reduxjs/toolkit'
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
import { ChainId } from '@uniswap/sdk-core'
|
import { ChainId } from '@uniswap/sdk-core'
|
||||||
|
|
||||||
import { updateVersion } from '../global/actions'
|
|
||||||
import { SerializableTransactionReceipt, TransactionDetails, TransactionInfo } from './types'
|
import { SerializableTransactionReceipt, TransactionDetails, TransactionInfo } from './types'
|
||||||
|
|
||||||
// TODO(WEB-2053): update this to be a map of account -> chainId -> txHash -> TransactionDetails
|
// TODO(WEB-2053): update this to be a map of account -> chainId -> txHash -> TransactionDetails
|
||||||
@ -80,20 +79,6 @@ const transactionSlice = createSlice({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
|
||||||
builder.addCase(updateVersion, (transactions) => {
|
|
||||||
// in case there are any transactions in the store with the old format, remove them
|
|
||||||
Object.keys(transactions).forEach((chainId) => {
|
|
||||||
const chainTransactions = transactions[chainId as unknown as number]
|
|
||||||
Object.keys(chainTransactions).forEach((hash) => {
|
|
||||||
if (!('info' in chainTransactions[hash])) {
|
|
||||||
// clear old transactions that don't have the right format
|
|
||||||
delete chainTransactions[hash]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { Reducer } from '@reduxjs/toolkit'
|
|
||||||
|
|
||||||
import reducer from './reducer'
|
|
||||||
|
|
||||||
/* Utility type to extract state type out of a @reduxjs/toolkit Reducer type */
|
|
||||||
type GetState<T> = T extends Reducer<infer State> ? State : never
|
|
||||||
|
|
||||||
export type AppState = {
|
|
||||||
[K in keyof typeof reducer]: GetState<(typeof reducer)[K]>
|
|
||||||
}
|
|
@ -12,7 +12,7 @@ import { UserAddedToken } from 'types/tokens'
|
|||||||
|
|
||||||
import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants/routing'
|
import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants/routing'
|
||||||
import { useDefaultActiveTokens } from '../../hooks/Tokens'
|
import { useDefaultActiveTokens } from '../../hooks/Tokens'
|
||||||
import { AppState } from '../types'
|
import { AppState } from '../reducer'
|
||||||
import {
|
import {
|
||||||
addSerializedPair,
|
addSerializedPair,
|
||||||
addSerializedToken,
|
addSerializedToken,
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { createStore, Store } from 'redux'
|
import { createStore, Store } from 'redux'
|
||||||
import { RouterPreference } from 'state/routing/types'
|
import { RouterPreference } from 'state/routing/types'
|
||||||
|
|
||||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
|
||||||
import { updateVersion } from '../global/actions'
|
|
||||||
import reducer, {
|
import reducer, {
|
||||||
addSerializedPair,
|
addSerializedPair,
|
||||||
addSerializedToken,
|
addSerializedToken,
|
||||||
@ -16,7 +14,6 @@ import reducer, {
|
|||||||
updateUserSlippageTolerance,
|
updateUserSlippageTolerance,
|
||||||
UserState,
|
UserState,
|
||||||
} from './reducer'
|
} from './reducer'
|
||||||
import { SlippageTolerance } from './types'
|
|
||||||
|
|
||||||
function buildSerializedPair(token0Address: string, token1Address: string, chainId: number) {
|
function buildSerializedPair(token0Address: string, token1Address: string, chainId: number) {
|
||||||
return {
|
return {
|
||||||
@ -38,36 +35,6 @@ describe('swap reducer', () => {
|
|||||||
store = createStore(reducer, initialState)
|
store = createStore(reducer, initialState)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('updateVersion', () => {
|
|
||||||
it('has no timestamp originally', () => {
|
|
||||||
expect(store.getState().lastUpdateVersionTimestamp).toBeUndefined()
|
|
||||||
})
|
|
||||||
it('sets the lastUpdateVersionTimestamp', () => {
|
|
||||||
const time = new Date().getTime()
|
|
||||||
store.dispatch(updateVersion())
|
|
||||||
expect(store.getState().lastUpdateVersionTimestamp).toBeGreaterThanOrEqual(time)
|
|
||||||
})
|
|
||||||
it('sets allowed slippage and deadline', () => {
|
|
||||||
store = createStore(reducer, {
|
|
||||||
...initialState,
|
|
||||||
userDeadline: undefined,
|
|
||||||
userSlippageTolerance: undefined,
|
|
||||||
} as any)
|
|
||||||
store.dispatch(updateVersion())
|
|
||||||
expect(store.getState().userDeadline).toEqual(DEFAULT_DEADLINE_FROM_NOW)
|
|
||||||
expect(store.getState().userSlippageTolerance).toEqual(SlippageTolerance.Auto)
|
|
||||||
})
|
|
||||||
it('sets allowed slippage and deadline to auto', () => {
|
|
||||||
store = createStore(reducer, {
|
|
||||||
...initialState,
|
|
||||||
userSlippageTolerance: 10,
|
|
||||||
userSlippageToleranceHasBeenMigratedToAuto: undefined,
|
|
||||||
} as any)
|
|
||||||
store.dispatch(updateVersion())
|
|
||||||
expect(store.getState().userSlippageToleranceHasBeenMigratedToAuto).toEqual(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('updateSelectedWallet', () => {
|
describe('updateSelectedWallet', () => {
|
||||||
it('updates the selected wallet', () => {
|
it('updates the selected wallet', () => {
|
||||||
store.dispatch(updateSelectedWallet({ wallet: 'metamask' }))
|
store.dispatch(updateSelectedWallet({ wallet: 'metamask' }))
|
||||||
|
@ -4,7 +4,6 @@ import { ConnectionType } from '../../connection/types'
|
|||||||
import { SupportedLocale } from '../../constants/locales'
|
import { SupportedLocale } from '../../constants/locales'
|
||||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
||||||
import { RouterPreference } from '../../state/routing/types'
|
import { RouterPreference } from '../../state/routing/types'
|
||||||
import { updateVersion } from '../global/actions'
|
|
||||||
import { SerializedPair, SerializedToken, SlippageTolerance } from './types'
|
import { SerializedPair, SerializedToken, SlippageTolerance } from './types'
|
||||||
|
|
||||||
const currentTimestamp = () => new Date().getTime()
|
const currentTimestamp = () => new Date().getTime()
|
||||||
@ -124,85 +123,6 @@ const userSlice = createSlice({
|
|||||||
state.timestamp = currentTimestamp()
|
state.timestamp = currentTimestamp()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
|
||||||
// After adding a new property to the state, its value will be `undefined` (instead of the default)
|
|
||||||
// for all existing users with a previous version of the state in their localStorage.
|
|
||||||
// In order to avoid this, we need to set a default value for each new property manually during hydration.
|
|
||||||
builder.addCase(updateVersion, (state) => {
|
|
||||||
// If `selectedWallet` is a WalletConnect v1 wallet, reset to default.
|
|
||||||
if (state.selectedWallet) {
|
|
||||||
const selectedWallet = state.selectedWallet as string
|
|
||||||
if (
|
|
||||||
selectedWallet === 'UNIWALLET' ||
|
|
||||||
selectedWallet === 'UNISWAP_WALLET' ||
|
|
||||||
selectedWallet === 'WALLET_CONNECT'
|
|
||||||
) {
|
|
||||||
delete state.selectedWallet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `userSlippageTolerance` is not present or its value is invalid, reset to default
|
|
||||||
if (
|
|
||||||
typeof state.userSlippageTolerance !== 'number' ||
|
|
||||||
!Number.isInteger(state.userSlippageTolerance) ||
|
|
||||||
state.userSlippageTolerance < 0 ||
|
|
||||||
state.userSlippageTolerance > 5000
|
|
||||||
) {
|
|
||||||
state.userSlippageTolerance = SlippageTolerance.Auto
|
|
||||||
} else {
|
|
||||||
if (
|
|
||||||
!state.userSlippageToleranceHasBeenMigratedToAuto &&
|
|
||||||
[10, 50, 100].indexOf(state.userSlippageTolerance) !== -1
|
|
||||||
) {
|
|
||||||
state.userSlippageTolerance = SlippageTolerance.Auto
|
|
||||||
state.userSlippageToleranceHasBeenMigratedToAuto = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `userDeadline` is not present or its value is invalid, reset to default
|
|
||||||
if (
|
|
||||||
typeof state.userDeadline !== 'number' ||
|
|
||||||
!Number.isInteger(state.userDeadline) ||
|
|
||||||
state.userDeadline < 60 ||
|
|
||||||
state.userDeadline > 180 * 60
|
|
||||||
) {
|
|
||||||
state.userDeadline = DEFAULT_DEADLINE_FROM_NOW
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `userRouterPreference` is not present, reset to default
|
|
||||||
if (typeof state.userRouterPreference !== 'string') {
|
|
||||||
state.userRouterPreference = RouterPreference.API
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `userRouterPreference` is `AUTO`, migrate to `API`
|
|
||||||
if ((state.userRouterPreference as string) === 'auto') {
|
|
||||||
state.userRouterPreference = RouterPreference.API
|
|
||||||
}
|
|
||||||
|
|
||||||
//If `buyFiatFlowCompleted` is present, delete it using filtering
|
|
||||||
if ('buyFiatFlowCompleted' in state) {
|
|
||||||
//ignoring due to type errors occuring since we now remove this state
|
|
||||||
//@ts-ignore
|
|
||||||
delete state.buyFiatFlowCompleted
|
|
||||||
}
|
|
||||||
|
|
||||||
// If `buyFiatFlowCompleted` is present, delete it using filtering
|
|
||||||
if ('buyFiatFlowCompleted' in state) {
|
|
||||||
//ignoring due to type errors occuring since we now remove this state
|
|
||||||
//@ts-ignore
|
|
||||||
delete state.buyFiatFlowCompleted
|
|
||||||
}
|
|
||||||
|
|
||||||
//If `buyFiatFlowCompleted` is present, delete it using filtering
|
|
||||||
if ('buyFiatFlowCompleted' in state) {
|
|
||||||
//ignoring due to type errors occuring since we now remove this state
|
|
||||||
//@ts-ignore
|
|
||||||
delete state.buyFiatFlowCompleted
|
|
||||||
}
|
|
||||||
|
|
||||||
state.lastUpdateVersionTimestamp = currentTimestamp()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
@ -4,7 +4,7 @@ import { shallowEqual } from 'react-redux'
|
|||||||
|
|
||||||
import { Wallet } from './types'
|
import { Wallet } from './types'
|
||||||
|
|
||||||
interface WalletState {
|
export interface WalletState {
|
||||||
// Used to track wallets that have been connected by the user in current session, and remove them when deliberately disconnected.
|
// Used to track wallets that have been connected by the user in current session, and remove them when deliberately disconnected.
|
||||||
// Used to compute is_reconnect event property for analytics
|
// Used to compute is_reconnect event property for analytics
|
||||||
connectedWallets: Wallet[]
|
connectedWallets: Wallet[]
|
||||||
|
39
yarn.lock
39
yarn.lock
@ -12952,6 +12952,11 @@ image-q@^1.1.1:
|
|||||||
resolved "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz"
|
resolved "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz"
|
||||||
integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=
|
integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY=
|
||||||
|
|
||||||
|
immediate@~3.0.5:
|
||||||
|
version "3.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||||
|
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||||
|
|
||||||
immer@^9.0.21, immer@^9.0.6, immer@^9.0.7:
|
immer@^9.0.21, immer@^9.0.6, immer@^9.0.7:
|
||||||
version "9.0.21"
|
version "9.0.21"
|
||||||
resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
|
resolved "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
|
||||||
@ -14896,6 +14901,13 @@ levn@~0.3.0:
|
|||||||
prelude-ls "~1.1.2"
|
prelude-ls "~1.1.2"
|
||||||
type-check "~0.3.2"
|
type-check "~0.3.2"
|
||||||
|
|
||||||
|
lie@3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||||
|
integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==
|
||||||
|
dependencies:
|
||||||
|
immediate "~3.0.5"
|
||||||
|
|
||||||
lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.1.0:
|
lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
|
||||||
@ -14999,6 +15011,13 @@ loader-utils@^3.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576"
|
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-3.2.1.tgz#4fb104b599daafd82ef3e1a41fb9265f87e1f576"
|
||||||
integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==
|
integrity sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==
|
||||||
|
|
||||||
|
localforage@^1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4"
|
||||||
|
integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==
|
||||||
|
dependencies:
|
||||||
|
lie "3.1.1"
|
||||||
|
|
||||||
locate-path@^2.0.0:
|
locate-path@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||||
@ -15298,11 +15317,6 @@ merge2@^1.3.0, merge2@^1.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
merge@2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.npmjs.org/merge/-/merge-2.1.0.tgz"
|
|
||||||
integrity sha512-TcuhVDV+e6X457MQAm7xIb19rWhZuEDEho7RrwxMpQ/3GhD5sDlnP188gjQQuweXHy9igdke5oUtVOXX1X8Sxg==
|
|
||||||
|
|
||||||
merkletreejs@^0.3.9:
|
merkletreejs@^0.3.9:
|
||||||
version "0.3.9"
|
version "0.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.9.tgz#cdb364a3b974a44f4eff3446522d7066e0cf95de"
|
resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.9.tgz#cdb364a3b974a44f4eff3446522d7066e0cf95de"
|
||||||
@ -17861,12 +17875,10 @@ reduce-function-call@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match "^1.0.0"
|
balanced-match "^1.0.0"
|
||||||
|
|
||||||
redux-localstorage-simple@^2.3.1:
|
redux-persist@^6.0.0:
|
||||||
version "2.4.0"
|
version "6.0.0"
|
||||||
resolved "https://registry.npmjs.org/redux-localstorage-simple/-/redux-localstorage-simple-2.4.0.tgz"
|
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
|
||||||
integrity sha512-Zj28elJtO4fqXXC+gikonbKhFUkiwlalScYRn3EGUU44Pika1995AqUgzjIcsSPlBhIDV2WudFqa/YI9+3aE9Q==
|
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
|
||||||
dependencies:
|
|
||||||
merge "2.1.0"
|
|
||||||
|
|
||||||
redux-thunk@^2.4.2:
|
redux-thunk@^2.4.2:
|
||||||
version "2.4.2"
|
version "2.4.2"
|
||||||
@ -19846,6 +19858,11 @@ ts-transform-graphql-tag@^0.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ts-transform-graphql-tag/-/ts-transform-graphql-tag-0.2.1.tgz#f596c491196b6a6a65b65a8b99bf6e2314c78017"
|
resolved "https://registry.yarnpkg.com/ts-transform-graphql-tag/-/ts-transform-graphql-tag-0.2.1.tgz#f596c491196b6a6a65b65a8b99bf6e2314c78017"
|
||||||
integrity sha512-gciNzCpVafccayI/VQKU2ROaol4gMpz0t5sAW/jzG/J/wnjPYCn06yKzlM4mkQ5tjCvPmFuZnLYF4i0tUiIiMQ==
|
integrity sha512-gciNzCpVafccayI/VQKU2ROaol4gMpz0t5sAW/jzG/J/wnjPYCn06yKzlM4mkQ5tjCvPmFuZnLYF4i0tUiIiMQ==
|
||||||
|
|
||||||
|
tsafe@^1.6.4:
|
||||||
|
version "1.6.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.4.tgz#048a114761714538c72f16abd25bb247d4e3780e"
|
||||||
|
integrity sha512-l4Z54QFGHO8GF0gBpb3yPGHjkIkIirl8rwW+lMBmtEMzOJeRs8BdjkDEx6nU8Ak9PQVp/KNDtECxTja8MMIDoA==
|
||||||
|
|
||||||
tsconfig-paths@^3.14.1:
|
tsconfig-paths@^3.14.1:
|
||||||
version "3.14.1"
|
version "3.14.1"
|
||||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
|
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
|
||||||
|
Loading…
Reference in New Issue
Block a user