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 { FeatureFlag } from '../../src/featureFlags'
|
||||
import { UserState } from '../../src/state/user/reducer'
|
||||
import { CONNECTED_WALLET_USER_STATE } from '../utils/user-state'
|
||||
import { initialState, UserState } from '../../src/state/user/reducer'
|
||||
import { CONNECTED_WALLET_USER_STATE, setInitialUserState } from '../utils/user-state'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
@ -54,18 +54,12 @@ Cypress.Commands.overwrite(
|
||||
onBeforeLoad(win) {
|
||||
options?.onBeforeLoad?.(win)
|
||||
|
||||
// We want to test from a clean state, so we clear the local storage (which clears redux).
|
||||
win.localStorage.clear()
|
||||
|
||||
// Set initial user state.
|
||||
win.localStorage.setItem(
|
||||
'redux_localstorage_simple_user', // storage key for the user reducer using 'redux-localstorage-simple'
|
||||
JSON.stringify({
|
||||
setInitialUserState(win, {
|
||||
...initialState,
|
||||
hideUniswapWalletBanner: true,
|
||||
...CONNECTED_WALLET_USER_STATE,
|
||||
...(options?.userState ?? {}),
|
||||
})
|
||||
)
|
||||
|
||||
// Set feature flags, if configured.
|
||||
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 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",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-transform-graphql-tag": "^0.2.1",
|
||||
"tsafe": "^1.6.4",
|
||||
"typechain": "^5.0.0",
|
||||
"typescript": "^4.9.4",
|
||||
"webpack": "^5.88.2",
|
||||
@ -235,6 +236,7 @@
|
||||
"inter-ui": "^3.13.1",
|
||||
"jotai": "^1.3.7",
|
||||
"jsbi": "^3.1.4",
|
||||
"localforage": "^1.10.0",
|
||||
"make-plural": "^7.0.0",
|
||||
"ms": "^2.1.3",
|
||||
"multicodec": "^3.0.1",
|
||||
@ -265,7 +267,7 @@
|
||||
"react-window-infinite-loader": "^1.0.8",
|
||||
"rebass": "^4.0.7",
|
||||
"redux": "^4.1.2",
|
||||
"redux-localstorage-simple": "^2.3.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"statsig-react": "^1.22.0",
|
||||
"styled-components": "^5.3.5",
|
||||
"tiny-invariant": "^1.2.0",
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Connector } from '@web3-react/types'
|
||||
import { gnosisSafeConnection, networkConnection } from 'connection'
|
||||
import { getConnection } from 'connection'
|
||||
import { Connection } from 'connection/types'
|
||||
import { useEffect } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { updateSelectedWallet } from 'state/user/reducer'
|
||||
@ -22,22 +21,24 @@ export default function useEagerlyConnect() {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const selectedWallet = useAppSelector((state) => state.user.selectedWallet)
|
||||
|
||||
let selectedConnection: Connection | undefined
|
||||
if (selectedWallet) {
|
||||
try {
|
||||
selectedConnection = getConnection(selectedWallet)
|
||||
} catch {
|
||||
dispatch(updateSelectedWallet({ wallet: undefined }))
|
||||
}
|
||||
}
|
||||
const rehydrated = useAppSelector((state) => state._persist.rehydrated)
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedWallet) return
|
||||
try {
|
||||
const selectedConnection = getConnection(selectedWallet)
|
||||
connect(gnosisSafeConnection.connector)
|
||||
connect(networkConnection.connector)
|
||||
|
||||
if (selectedConnection) {
|
||||
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 { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { AppState } from 'state/reducer'
|
||||
|
||||
import { AppState } from '../types'
|
||||
import {
|
||||
addPopup,
|
||||
ApplicationModal,
|
||||
|
@ -47,7 +47,7 @@ export enum ApplicationModal {
|
||||
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 {
|
||||
readonly chainId: number | null
|
||||
|
@ -6,11 +6,11 @@ import JSBI from 'jsbi'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { ReactNode, useCallback } from 'react'
|
||||
import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { AppState } from 'state/reducer'
|
||||
|
||||
import { useTotalSupply } from '../../hooks/useTotalSupply'
|
||||
import { useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { useTokenBalances } from '../connection/hooks'
|
||||
import { AppState } from '../types'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
export function useBurnState(): AppState['burn'] {
|
||||
|
@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
|
||||
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
interface BurnState {
|
||||
export interface BurnState {
|
||||
readonly independentField: Field
|
||||
readonly typedValue: string
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
|
||||
import { PositionDetails } from 'types/position'
|
||||
import { unwrappedToken } from 'utils/unwrappedToken'
|
||||
|
||||
import { AppState } from '../../types'
|
||||
import { AppState } from '../../reducer'
|
||||
import { selectPercent } from './actions'
|
||||
|
||||
export function useBurnV3State(): AppState['burnV3'] {
|
||||
|
@ -2,7 +2,7 @@ import { createReducer } from '@reduxjs/toolkit'
|
||||
|
||||
import { selectPercent } from './actions'
|
||||
|
||||
interface BurnV3State {
|
||||
export interface BurnV3State {
|
||||
readonly percent: number
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
||||
import { load, save } from 'redux-localstorage-simple'
|
||||
import { isTestEnv } from 'utils/env'
|
||||
import { persistStore } from 'redux-persist'
|
||||
|
||||
import { updateVersion } from './global/actions'
|
||||
import { sentryEnhancer } from './logging'
|
||||
import reducer from './reducer'
|
||||
import { routingApi } from './routing/slice'
|
||||
|
||||
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'signatures', 'lists']
|
||||
|
||||
const store = configureStore({
|
||||
export function createDefaultStore() {
|
||||
return configureStore({
|
||||
reducer,
|
||||
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
|
||||
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)
|
||||
ignoredActionPaths: ['meta.arg', 'meta.baseQueryMeta', 'payload.trade'],
|
||||
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)
|
||||
|
||||
store.dispatch(updateVersion())
|
||||
|
||||
export default store
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TokenAddressMap, tokensToChainTokenMap } from 'lib/hooks/useTokenList/utils'
|
||||
import { useMemo } from 'react'
|
||||
import { useAppSelector } from 'state/hooks'
|
||||
import { AppState } from 'state/reducer'
|
||||
import sortByListPriority from 'utils/listSort'
|
||||
|
||||
import BROKEN_LIST from '../../constants/tokenLists/broken.tokenlist.json'
|
||||
import { AppState } from '../types'
|
||||
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
|
||||
|
||||
type Mutable<T> = {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import tokenSafetyLookup from 'constants/tokenSafetyLookup'
|
||||
import { createStore, Store } from 'redux'
|
||||
import { updateVersion } from 'state/global/actions'
|
||||
|
||||
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 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] }
|
||||
|
||||
const initialState: ListsState = {
|
||||
export const initialState: ListsState = {
|
||||
lastInitializedDefaultListOfLists: DEFAULT_LIST_OF_LISTS,
|
||||
byUrl: {
|
||||
...DEFAULT_LIST_OF_LISTS.reduce<Mutable<ListsState['byUrl']>>((memo, listUrl) => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as Sentry from '@sentry/react'
|
||||
import noop from 'utils/noop'
|
||||
|
||||
import { AppState } from './types'
|
||||
import { AppState } from './reducer'
|
||||
|
||||
/* Utility type to mark all properties of a type as optional */
|
||||
type DeepPartial<T> = T extends object
|
||||
|
@ -3,7 +3,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
|
||||
import { filterToKey, Log } from './utils'
|
||||
|
||||
interface LogsState {
|
||||
export interface LogsState {
|
||||
[chainId: number]: {
|
||||
[filterKey: string]: {
|
||||
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 { PairState, useV2Pair } from '../../hooks/useV2Pairs'
|
||||
import { useCurrencyBalances } from '../connection/hooks'
|
||||
import { AppState } from '../types'
|
||||
import { AppState } from '../reducer'
|
||||
import { Field, typeInput } from './actions'
|
||||
|
||||
const ZERO = JSBI.BigInt(0)
|
||||
|
@ -23,7 +23,7 @@ import { getTickToPrice } from 'utils/getTickToPrice'
|
||||
import { BIG_INT_ZERO } from '../../../constants/misc'
|
||||
import { PoolState } from '../../../hooks/usePools'
|
||||
import { useCurrencyBalances } from '../../connection/hooks'
|
||||
import { AppState } from '../../types'
|
||||
import { AppState } from '../../reducer'
|
||||
import {
|
||||
Bound,
|
||||
Field,
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
typeStartPriceInput,
|
||||
} from './actions'
|
||||
|
||||
type FullRange = true
|
||||
export type FullRange = true
|
||||
|
||||
interface MintState {
|
||||
export interface MintState {
|
||||
readonly independentField: Field
|
||||
readonly typedValue: string
|
||||
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 localForage from 'localforage'
|
||||
import { PersistConfig, persistReducer } from 'redux-persist'
|
||||
import { isDevelopmentEnv } from 'utils/env'
|
||||
|
||||
import application from './application/reducer'
|
||||
import burn from './burn/reducer'
|
||||
import burnV3 from './burn/v3/reducer'
|
||||
import lists from './lists/reducer'
|
||||
import logs from './logs/slice'
|
||||
import { customCreateMigrate, migrations } from './migrations'
|
||||
import mint from './mint/reducer'
|
||||
import mintV3 from './mint/v3/reducer'
|
||||
import { routingApi } from './routing/slice'
|
||||
@ -13,18 +18,45 @@ import transactions from './transactions/reducer'
|
||||
import user from './user/reducer'
|
||||
import wallets from './wallets/reducer'
|
||||
|
||||
export default {
|
||||
application,
|
||||
const persistedReducers = {
|
||||
user,
|
||||
transactions,
|
||||
signatures,
|
||||
lists,
|
||||
}
|
||||
|
||||
const appReducer = combineReducers({
|
||||
application,
|
||||
wallets,
|
||||
mint,
|
||||
mintV3,
|
||||
burn,
|
||||
burnV3,
|
||||
multicall: multicall.reducer,
|
||||
lists,
|
||||
logs,
|
||||
[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 { createStore, Store } from 'redux'
|
||||
|
||||
import { updateVersion } from '../global/actions'
|
||||
import reducer, {
|
||||
addTransaction,
|
||||
cancelTransaction,
|
||||
@ -20,32 +19,6 @@ describe('transaction reducer', () => {
|
||||
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', () => {
|
||||
it('adds the transaction', () => {
|
||||
const beforeTime = new Date().getTime()
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
import { ChainId } from '@uniswap/sdk-core'
|
||||
|
||||
import { updateVersion } from '../global/actions'
|
||||
import { SerializableTransactionReceipt, TransactionDetails, TransactionInfo } from './types'
|
||||
|
||||
// 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 {
|
||||
|
@ -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 { useDefaultActiveTokens } from '../../hooks/Tokens'
|
||||
import { AppState } from '../types'
|
||||
import { AppState } from '../reducer'
|
||||
import {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { createStore, Store } from 'redux'
|
||||
import { RouterPreference } from 'state/routing/types'
|
||||
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
||||
import { updateVersion } from '../global/actions'
|
||||
import reducer, {
|
||||
addSerializedPair,
|
||||
addSerializedToken,
|
||||
@ -16,7 +14,6 @@ import reducer, {
|
||||
updateUserSlippageTolerance,
|
||||
UserState,
|
||||
} from './reducer'
|
||||
import { SlippageTolerance } from './types'
|
||||
|
||||
function buildSerializedPair(token0Address: string, token1Address: string, chainId: number) {
|
||||
return {
|
||||
@ -38,36 +35,6 @@ describe('swap reducer', () => {
|
||||
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', () => {
|
||||
it('updates the selected wallet', () => {
|
||||
store.dispatch(updateSelectedWallet({ wallet: 'metamask' }))
|
||||
|
@ -4,7 +4,6 @@ import { ConnectionType } from '../../connection/types'
|
||||
import { SupportedLocale } from '../../constants/locales'
|
||||
import { DEFAULT_DEADLINE_FROM_NOW } from '../../constants/misc'
|
||||
import { RouterPreference } from '../../state/routing/types'
|
||||
import { updateVersion } from '../global/actions'
|
||||
import { SerializedPair, SerializedToken, SlippageTolerance } from './types'
|
||||
|
||||
const currentTimestamp = () => new Date().getTime()
|
||||
@ -124,85 +123,6 @@ const userSlice = createSlice({
|
||||
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 {
|
||||
|
@ -4,7 +4,7 @@ import { shallowEqual } from 'react-redux'
|
||||
|
||||
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 compute is_reconnect event property for analytics
|
||||
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"
|
||||
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:
|
||||
version "9.0.21"
|
||||
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"
|
||||
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:
|
||||
version "2.1.0"
|
||||
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"
|
||||
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:
|
||||
version "2.0.0"
|
||||
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"
|
||||
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:
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/merkletreejs/-/merkletreejs-0.3.9.tgz#cdb364a3b974a44f4eff3446522d7066e0cf95de"
|
||||
@ -17861,12 +17875,10 @@ reduce-function-call@^1.0.1:
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
redux-localstorage-simple@^2.3.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.npmjs.org/redux-localstorage-simple/-/redux-localstorage-simple-2.4.0.tgz"
|
||||
integrity sha512-Zj28elJtO4fqXXC+gikonbKhFUkiwlalScYRn3EGUU44Pika1995AqUgzjIcsSPlBhIDV2WudFqa/YI9+3aE9Q==
|
||||
dependencies:
|
||||
merge "2.1.0"
|
||||
redux-persist@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
|
||||
integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==
|
||||
|
||||
redux-thunk@^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"
|
||||
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:
|
||||
version "3.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
|
||||
|
Loading…
Reference in New Issue
Block a user