Tech Debt: Validator Type definitions (#1108)
* type validators * safeSelector types * history 5.0.0 breaking changes adaptation * replace simpleMemoize with memoize from lodash because of typing issues * add type definitions for history and react-router-dom * type fixes * yarn lock update * fix router state * more type improvements * validator tests wip * add tests for validators, remove duplicated validators * add error messages to tests * fix minValue error message for inclusive param * Replace jsx.element with react.reactelement * Fix uniqueAddress validator argument type * remove comment in AddCustomToken validator * use absolute import for saferecord in safe paage container
This commit is contained in:
parent
93448b550a
commit
f62bbffdd3
|
@ -22,7 +22,6 @@ module.exports = {
|
|||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }],
|
||||
},
|
||||
settings: {
|
||||
|
|
|
@ -10,6 +10,4 @@ public
|
|||
scripts
|
||||
src/assets
|
||||
src/config
|
||||
test
|
||||
*.spec*
|
||||
*.test*
|
||||
test
|
|
@ -220,6 +220,10 @@
|
|||
"web3": "1.2.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/history": "4.6.2",
|
||||
"@types/lodash.memoize": "^4.1.6",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@testing-library/jest-dom": "5.11.1",
|
||||
"@testing-library/react": "10.4.7",
|
||||
"@testing-library/user-event": "12.0.13",
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
import {
|
||||
required,
|
||||
mustBeInteger,
|
||||
mustBeFloat,
|
||||
maxValue,
|
||||
mustBeUrl,
|
||||
minValue,
|
||||
mustBeEthereumAddress,
|
||||
minMaxLength,
|
||||
uniqueAddress,
|
||||
differentFrom,
|
||||
ADDRESS_REPEATED_ERROR,
|
||||
} from 'src/components/forms/validator'
|
||||
|
||||
describe('Forms > Validators', () => {
|
||||
describe('Required validator', () => {
|
||||
const REQUIRED_ERROR_MSG = 'Required'
|
||||
|
||||
it('Returns undefined for a non-empty string', () => {
|
||||
expect(required('Im not empty')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for an empty string', () => {
|
||||
expect(required('')).toEqual(REQUIRED_ERROR_MSG)
|
||||
})
|
||||
|
||||
it('Returns an error message for a string containing only spaces', () => {
|
||||
expect(required(' ')).toEqual(REQUIRED_ERROR_MSG)
|
||||
})
|
||||
})
|
||||
|
||||
describe('mustBeInteger validator', () => {
|
||||
const MUST_BE_INTEGER_ERROR_MSG = 'Must be an integer'
|
||||
|
||||
it('Returns undefined for an integer number string', () => {
|
||||
expect(mustBeInteger('10')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for a float number', () => {
|
||||
expect(mustBeInteger('1.0')).toEqual(MUST_BE_INTEGER_ERROR_MSG)
|
||||
})
|
||||
|
||||
it('Returns an error message for a non-number string', () => {
|
||||
expect(mustBeInteger('iamnotanumber')).toEqual(MUST_BE_INTEGER_ERROR_MSG)
|
||||
})
|
||||
})
|
||||
|
||||
describe('mustBeFloat validator', () => {
|
||||
const MUST_BE_FLOAT_ERR_MSG = 'Must be a number'
|
||||
|
||||
it('Returns undefined for a float number string', () => {
|
||||
expect(mustBeFloat('1.0')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for a non-number string', () => {
|
||||
expect(mustBeFloat('iamnotanumber')).toEqual(MUST_BE_FLOAT_ERR_MSG)
|
||||
})
|
||||
})
|
||||
|
||||
describe('minValue validator', () => {
|
||||
const getMinValueErrMsg = (minValue: number, inclusive = true): string =>
|
||||
`Should be greater than ${inclusive ? 'or equal to ' : ''}${minValue}`
|
||||
|
||||
it('Returns undefined for a number greater than minimum', () => {
|
||||
const minimum = Math.random()
|
||||
const number = (minimum + 1).toString()
|
||||
|
||||
expect(minValue(minimum)(number)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for a number less than minimum', () => {
|
||||
const minimum = Math.random()
|
||||
const number = (minimum - 1).toString()
|
||||
|
||||
expect(minValue(minimum)(number)).toEqual(getMinValueErrMsg(minimum))
|
||||
})
|
||||
|
||||
it('Returns an error message for a number equal to minimum with false inclusive param', () => {
|
||||
const minimum = Math.random()
|
||||
const number = (minimum - 1).toString()
|
||||
|
||||
expect(minValue(minimum, false)(number)).toEqual(getMinValueErrMsg(minimum, false))
|
||||
})
|
||||
|
||||
it('Returns an error message for a non-number string', () => {
|
||||
expect(minValue(1)('imnotanumber')).toEqual(getMinValueErrMsg(1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('mustBeUrl validator', () => {
|
||||
const MUST_BE_URL_ERR_MSG = 'Please, provide a valid url'
|
||||
|
||||
it('Returns undefined for a valid url', () => {
|
||||
expect(mustBeUrl('https://gnosis-safe.io')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for an valid url', () => {
|
||||
expect(mustBeUrl('gnosis-safe')).toEqual(MUST_BE_URL_ERR_MSG)
|
||||
})
|
||||
})
|
||||
|
||||
describe('maxValue validator', () => {
|
||||
const getMaxValueErrMsg = (maxValue: number): string => `Maximum value is ${maxValue}`
|
||||
|
||||
it('Returns undefined for a number less than maximum', () => {
|
||||
const max = Math.random()
|
||||
const number = (max - 1).toString()
|
||||
|
||||
expect(maxValue(max)(number)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns undefined for a number equal to maximum', () => {
|
||||
const max = Math.random()
|
||||
|
||||
expect(maxValue(max)(max.toString())).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for a number greater than maximum', () => {
|
||||
const max = Math.random()
|
||||
const number = (max + 1).toString()
|
||||
|
||||
expect(maxValue(max)(number)).toEqual(getMaxValueErrMsg(max))
|
||||
})
|
||||
|
||||
it('Returns an error message for a non-number string', () => {
|
||||
expect(maxValue(1)('imnotanumber')).toEqual(getMaxValueErrMsg(1))
|
||||
})
|
||||
})
|
||||
|
||||
describe('mustBeEthereumAddress validator', () => {
|
||||
const MUST_BE_ETH_ADDRESS_ERR_MSG = 'Address should be a valid Ethereum address or ENS name'
|
||||
|
||||
it('Returns undefined for a valid ethereum address', async () => {
|
||||
expect(await mustBeEthereumAddress('0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for an address with an invalid checksum', async () => {
|
||||
expect(await mustBeEthereumAddress('0xde0b295669a9FD93d5F28D9Ec85E40f4cb697BAe')).toEqual(
|
||||
MUST_BE_ETH_ADDRESS_ERR_MSG,
|
||||
)
|
||||
})
|
||||
|
||||
it('Returns an error message for non-address string', async () => {
|
||||
expect(await mustBeEthereumAddress('notanaddress')).toEqual(MUST_BE_ETH_ADDRESS_ERR_MSG)
|
||||
})
|
||||
})
|
||||
|
||||
describe('minMaxLength validator', () => {
|
||||
const getMinMaxLenErrMsg = (minLen: number, maxLen: number): string => `Should be ${minLen} to ${maxLen} symbols`
|
||||
|
||||
it('Returns undefined for a string between minimum and maximum length', async () => {
|
||||
expect(minMaxLength(1, 10)('length7')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for a string with length greater than maximum', async () => {
|
||||
const minMaxLengths: [number, number] = [1, 5]
|
||||
|
||||
expect(minMaxLength(...minMaxLengths)('length7')).toEqual(getMinMaxLenErrMsg(...minMaxLengths))
|
||||
})
|
||||
|
||||
it('Returns an error message for a string with length less than minimum', async () => {
|
||||
const minMaxLengths: [number, number] = [7, 10]
|
||||
|
||||
expect(minMaxLength(...minMaxLengths)('string')).toEqual(getMinMaxLenErrMsg(...minMaxLengths))
|
||||
})
|
||||
})
|
||||
|
||||
describe('uniqueAddress validator', () => {
|
||||
it('Returns undefined for an address not contained in the passed array', async () => {
|
||||
const addresses = ['0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe']
|
||||
|
||||
expect(uniqueAddress(addresses)('0xe7e3272a84cf3fe180345b9f7234ba705eB5E2CA')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for an address already contained in the array', async () => {
|
||||
const addresses = ['0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe']
|
||||
|
||||
expect(uniqueAddress(addresses)(addresses[0])).toEqual(ADDRESS_REPEATED_ERROR)
|
||||
})
|
||||
})
|
||||
|
||||
describe('differentFrom validator', () => {
|
||||
const getDifferentFromErrMsg = (diffValue: string): string => `Value should be different than ${diffValue}`
|
||||
|
||||
it('Returns undefined for different values', async () => {
|
||||
expect(differentFrom('a')('b')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Returns an error message for equal values', async () => {
|
||||
expect(differentFrom('a')('a')).toEqual(getDifferentFromErrMsg('a'))
|
||||
})
|
||||
})
|
||||
})
|
|
@ -2,20 +2,14 @@ import { List } from 'immutable'
|
|||
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
import { getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import memoize from 'lodash.memoize'
|
||||
|
||||
export const simpleMemoize = (fn) => {
|
||||
let lastArg
|
||||
let lastResult
|
||||
return (arg, ...args) => {
|
||||
if (arg !== lastArg) {
|
||||
lastArg = arg
|
||||
lastResult = fn(arg, ...args)
|
||||
}
|
||||
return lastResult
|
||||
}
|
||||
}
|
||||
type ValidatorReturnType = string | undefined
|
||||
type GenericValidatorType = (...args: unknown[]) => ValidatorReturnType
|
||||
type AsyncValidator = (...args: unknown[]) => Promise<ValidatorReturnType>
|
||||
type Validator = GenericValidatorType | AsyncValidator
|
||||
|
||||
export const required = (value?: string) => {
|
||||
export const required = (value?: string): ValidatorReturnType => {
|
||||
const required = 'Required'
|
||||
|
||||
if (!value) {
|
||||
|
@ -29,30 +23,15 @@ export const required = (value?: string) => {
|
|||
return undefined
|
||||
}
|
||||
|
||||
export const mustBeInteger = (value: string) =>
|
||||
export const mustBeInteger = (value: string): ValidatorReturnType =>
|
||||
!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined
|
||||
|
||||
export const mustBeFloat = (value: string) => (value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||
|
||||
export const greaterThan = (min: number | string) => (value: string) => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should be greater than ${min}`
|
||||
}
|
||||
|
||||
export const equalOrGreaterThan = (min: number | string) => (value: string): undefined | string => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) >= Number(min)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should be equal or greater than ${min}`
|
||||
}
|
||||
export const mustBeFloat = (value: string): ValidatorReturnType =>
|
||||
value && Number.isNaN(Number(value)) ? 'Must be a number' : undefined
|
||||
|
||||
const regexQuery = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i
|
||||
const url = new RegExp(regexQuery)
|
||||
export const mustBeUrl = (value: string) => {
|
||||
export const mustBeUrl = (value: string): ValidatorReturnType => {
|
||||
if (url.test(value)) {
|
||||
return undefined
|
||||
}
|
||||
|
@ -60,73 +39,68 @@ export const mustBeUrl = (value: string) => {
|
|||
return 'Please, provide a valid url'
|
||||
}
|
||||
|
||||
export const minValue = (min: number | string) => (value: string) => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) >= Number(min)) {
|
||||
export const minValue = (min: number | string, inclusive = true) => (value: string): ValidatorReturnType => {
|
||||
if (Number.parseFloat(value) > Number(min) || (inclusive && Number.parseFloat(value) >= Number(min))) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should be at least ${min}`
|
||||
return `Should be greater than ${inclusive ? 'or equal to ' : ''}${min}`
|
||||
}
|
||||
|
||||
export const maxValueCheck = (max: number | string, value: string): string | undefined => {
|
||||
if (!max || Number.isNaN(Number(value)) || parseFloat(value) <= parseFloat(max.toString())) {
|
||||
export const maxValue = (max: number | string) => (value: string): ValidatorReturnType => {
|
||||
if (!max || parseFloat(value) <= parseFloat(max.toString())) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Maximum value is ${max}`
|
||||
}
|
||||
|
||||
export const maxValue = (max: number | string) => (value: string) => {
|
||||
return maxValueCheck(max, value)
|
||||
}
|
||||
export const ok = (): undefined => undefined
|
||||
|
||||
export const ok = () => undefined
|
||||
export const mustBeEthereumAddress = memoize(
|
||||
(address: string): ValidatorReturnType => {
|
||||
const startsWith0x = address.startsWith('0x')
|
||||
const isAddress = getWeb3().utils.isAddress(address)
|
||||
|
||||
export const mustBeEthereumAddress = simpleMemoize((address: string) => {
|
||||
const startsWith0x = address.startsWith('0x')
|
||||
const isAddress = getWeb3().utils.isAddress(address)
|
||||
return startsWith0x && isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
|
||||
},
|
||||
)
|
||||
|
||||
return startsWith0x && isAddress ? undefined : 'Address should be a valid Ethereum address or ENS name'
|
||||
})
|
||||
export const mustBeEthereumContractAddress = memoize(
|
||||
async (address: string): Promise<ValidatorReturnType> => {
|
||||
const contractCode = await getWeb3().eth.getCode(address)
|
||||
|
||||
export const mustBeEthereumContractAddress = simpleMemoize(async (address: string) => {
|
||||
const contractCode = await getWeb3().eth.getCode(address)
|
||||
return !contractCode || contractCode.replace('0x', '').replace(/0/g, '') === ''
|
||||
? 'Address should be a valid Ethereum contract address or ENS name'
|
||||
: undefined
|
||||
},
|
||||
)
|
||||
|
||||
return !contractCode || contractCode.replace('0x', '').replace(/0/g, '') === ''
|
||||
? 'Address should be a valid Ethereum contract address or ENS name'
|
||||
: undefined
|
||||
})
|
||||
|
||||
export const minMaxLength = (minLen, maxLen) => (value) =>
|
||||
export const minMaxLength = (minLen: number, maxLen: number) => (value: string): ValidatorReturnType =>
|
||||
value.length >= +minLen && value.length <= +maxLen ? undefined : `Should be ${minLen} to ${maxLen} symbols`
|
||||
|
||||
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
|
||||
|
||||
export const uniqueAddress = (addresses: string[] | List<string>) =>
|
||||
simpleMemoize((value: string[]) => {
|
||||
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
|
||||
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
|
||||
})
|
||||
export const uniqueAddress = (addresses: string[] | List<string>): GenericValidatorType =>
|
||||
memoize(
|
||||
(value: string): ValidatorReturnType => {
|
||||
const addressAlreadyExists = addresses.some((address) => sameAddress(value, address))
|
||||
return addressAlreadyExists ? ADDRESS_REPEATED_ERROR : undefined
|
||||
},
|
||||
)
|
||||
|
||||
export const composeValidators = (...validators) => (value) =>
|
||||
validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
export const composeValidators = (...validators: Validator[]) => (value: unknown): ValidatorReturnType =>
|
||||
validators.reduce(
|
||||
(error: string | undefined, validator: GenericValidatorType): ValidatorReturnType => error || validator(value),
|
||||
undefined,
|
||||
)
|
||||
|
||||
export const inLimit = (limit, base, baseText, symbol = 'ETH') => (value) => {
|
||||
const amount = Number(value)
|
||||
const max = limit - base
|
||||
if (amount <= max) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `Should not exceed ${max} ${symbol} (amount to reach ${baseText})`
|
||||
}
|
||||
|
||||
export const differentFrom = (diffValue: number | string) => (value: string) => {
|
||||
export const differentFrom = (diffValue: number | string) => (value: string): ValidatorReturnType => {
|
||||
if (value === diffValue.toString()) {
|
||||
return `Value should be different than ${value}`
|
||||
return `Value should be different than ${diffValue}`
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const noErrorsOn = (name, errors) => errors[name] === undefined
|
||||
export const noErrorsOn = (name: string, errors: Record<string, unknown>): boolean => errors[name] === undefined
|
||||
|
|
|
@ -13,7 +13,7 @@ class EtherscanService {
|
|||
}
|
||||
|
||||
_fetch = memoize(
|
||||
async (url, contractAddress) => {
|
||||
async (url: string, contractAddress: string) => {
|
||||
let params: any = {
|
||||
module: 'contract',
|
||||
action: 'getAbi',
|
||||
|
|
|
@ -7,5 +7,5 @@ const proxyCodeV10 =
|
|||
const oldProxyCode =
|
||||
'0x60806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680634555d5c91461008b5780635c60da1b146100b6575b73ffffffffffffffffffffffffffffffffffffffff600054163660008037600080366000845af43d6000803e6000811415610086573d6000fd5b3d6000f35b34801561009757600080fd5b506100a061010d565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100cb610116565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60006002905090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050905600'
|
||||
|
||||
export const isProxyCode = (codeToCheck) =>
|
||||
export const isProxyCode = (codeToCheck: string): boolean =>
|
||||
codeToCheck === oldProxyCode || codeToCheck === proxyCodeV10
|
||||
|
|
|
@ -3,8 +3,8 @@ import ProxyFactorySol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSaf
|
|||
import GnosisSafeSol from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafe.json'
|
||||
import SafeProxy from '@gnosis.pm/safe-contracts/build/contracts/GnosisSafeProxy.json'
|
||||
import { ensureOnce } from 'src/utils/singleton'
|
||||
import { simpleMemoize } from 'src/components/forms/validator'
|
||||
import { getNetworkIdFrom, getWeb3 } from 'src/logic/wallets/getWeb3'
|
||||
import memoize from 'lodash.memoize'
|
||||
import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3'
|
||||
import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions'
|
||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { isProxyCode } from 'src/logic/contracts/historicProxyCode'
|
||||
|
@ -34,8 +34,8 @@ const createProxyFactoryContract = (web3, networkId) => {
|
|||
return proxyFactory
|
||||
}
|
||||
|
||||
export const getGnosisSafeContract = simpleMemoize(createGnosisSafeContract)
|
||||
const getCreateProxyFactoryContract = simpleMemoize(createProxyFactoryContract)
|
||||
export const getGnosisSafeContract = memoize(createGnosisSafeContract)
|
||||
const getCreateProxyFactoryContract = memoize(createProxyFactoryContract)
|
||||
|
||||
const instantiateMasterCopies = async () => {
|
||||
const web3 = getWeb3()
|
||||
|
@ -55,7 +55,7 @@ const createMasterCopies = async () => {
|
|||
const accounts = await web3.eth.getAccounts()
|
||||
const userAccount = accounts[0]
|
||||
|
||||
const ProxyFactory = getCreateProxyFactoryContract(web3)
|
||||
const ProxyFactory = getCreateProxyFactoryContract(web3, 4447)
|
||||
proxyFactoryMaster = await ProxyFactory.new({ from: userAccount, gas: '5000000' })
|
||||
|
||||
const GnosisSafe = getGnosisSafeContract(web3)
|
||||
|
@ -95,18 +95,18 @@ export const estimateGasForDeployingSafe = async (
|
|||
return gas * parseInt(gasPrice, 10)
|
||||
}
|
||||
|
||||
export const getGnosisSafeInstanceAt = simpleMemoize(async (safeAddress): Promise<any> => {
|
||||
export const getGnosisSafeInstanceAt = memoize(async (safeAddress: string): Promise<any> => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
return GnosisSafe.at(safeAddress)
|
||||
})
|
||||
|
||||
const cleanByteCodeMetadata = (bytecode) => {
|
||||
const cleanByteCodeMetadata = (bytecode: string): string => {
|
||||
const metaData = 'a165'
|
||||
return bytecode.substring(0, bytecode.lastIndexOf(metaData))
|
||||
}
|
||||
|
||||
export const validateProxy = async (safeAddress) => {
|
||||
export const validateProxy = async (safeAddress: string): Promise<boolean> => {
|
||||
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
|
||||
const web3 = getWeb3()
|
||||
const code = await web3.eth.getCode(safeAddress)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { List } from 'immutable'
|
||||
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
|
||||
|
||||
export const sameAddress = (firstAddress, secondAddress) => {
|
||||
export const sameAddress = (firstAddress: string, secondAddress: string): boolean => {
|
||||
if (!firstAddress) {
|
||||
return false
|
||||
}
|
||||
|
@ -12,7 +14,7 @@ export const sameAddress = (firstAddress, secondAddress) => {
|
|||
return firstAddress.toLowerCase() === secondAddress.toLowerCase()
|
||||
}
|
||||
|
||||
export const shortVersionOf = (value, cut) => {
|
||||
export const shortVersionOf = (value: string, cut: number): string => {
|
||||
if (!value) {
|
||||
return 'Unknown'
|
||||
}
|
||||
|
@ -25,7 +27,7 @@ export const shortVersionOf = (value, cut) => {
|
|||
return `${value.substring(0, cut)}...${value.substring(final)}`
|
||||
}
|
||||
|
||||
export const isUserOwner = (safe, userAccount) => {
|
||||
export const isUserAnOwner = (safe: SafeRecord, userAccount: string): boolean => {
|
||||
if (!safe) {
|
||||
return false
|
||||
}
|
||||
|
@ -42,6 +44,7 @@ export const isUserOwner = (safe, userAccount) => {
|
|||
return owners.find((owner) => sameAddress(owner.address, userAccount)) !== undefined
|
||||
}
|
||||
|
||||
export const isUserOwnerOnAnySafe = (safes, userAccount) => safes.some((safe) => isUserOwner(safe, userAccount))
|
||||
export const isUserAnOwnerOfAnySafe = (safes: List<SafeRecord> | SafeRecord[], userAccount: string): boolean =>
|
||||
safes.some((safe: SafeRecord) => isUserAnOwner(safe, userAccount))
|
||||
|
||||
export const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
export const isValidEnsName = (name: string): boolean => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
|
|
|
@ -3,7 +3,7 @@ import queryString from 'query-string'
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import Opening from '../../opening'
|
||||
import Layout from '../components/Layout'
|
||||
|
@ -81,7 +81,14 @@ export const createSafe = (values, userAccount) => {
|
|||
return promiEvent
|
||||
}
|
||||
|
||||
const Open = ({ addSafe, network, provider, userAccount }) => {
|
||||
interface OwnProps extends RouteComponentProps {
|
||||
userAccount: string
|
||||
network: string
|
||||
provider: string
|
||||
addSafe: any
|
||||
}
|
||||
|
||||
const Open = ({ addSafe, network, provider, userAccount }: OwnProps): React.ReactElement => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showProgress, setShowProgress] = useState(false)
|
||||
const [creationTxPromise, setCreationTxPromise] = useState()
|
||||
|
|
|
@ -23,7 +23,7 @@ import { addAddressBookEntry } from 'src/logic/addressBook/store/actions/addAddr
|
|||
import { removeAddressBookEntry } from 'src/logic/addressBook/store/actions/removeAddressBookEntry'
|
||||
import { updateAddressBookEntry } from 'src/logic/addressBook/store/actions/updateAddressBookEntry'
|
||||
import { getAddressBookListSelector } from 'src/logic/addressBook/store/selectors'
|
||||
import { isUserOwnerOnAnySafe } from 'src/logic/wallets/ethAddresses'
|
||||
import { isUserAnOwnerOfAnySafe } from 'src/logic/wallets/ethAddresses'
|
||||
import CreateEditEntryModal from 'src/routes/safe/components/AddressBook/CreateEditEntryModal'
|
||||
import DeleteEntryModal from 'src/routes/safe/components/AddressBook/DeleteEntryModal'
|
||||
import {
|
||||
|
@ -136,7 +136,7 @@ const AddressBookTable = ({ classes }) => {
|
|||
>
|
||||
{(sortedData) =>
|
||||
sortedData.map((row, index) => {
|
||||
const userOwner = isUserOwnerOnAnySafe(safesList, row.address)
|
||||
const userOwner = isUserAnOwnerOfAnySafe(safesList, row.address)
|
||||
const hideBorderBottom = index >= 3 && index === sortedData.size - 1 && classes.noBorderBottom
|
||||
return (
|
||||
<TableRow
|
||||
|
|
|
@ -32,7 +32,7 @@ const EthAddressInput = ({
|
|||
name,
|
||||
onScannedValue,
|
||||
text,
|
||||
}: EthAddressInputProps) => {
|
||||
}: EthAddressInputProps): React.ReactElement => {
|
||||
const classes = useStyles()
|
||||
const validatorsList = [isRequired && required, mustBeEthereumAddress, isContract && mustBeEthereumContractAddress]
|
||||
const validate = composeValidators(...validatorsList.filter((_) => _))
|
||||
|
|
|
@ -19,7 +19,7 @@ import Field from 'src/components/forms/Field'
|
|||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import TextField from 'src/components/forms/TextField'
|
||||
import TextareaField from 'src/components/forms/TextareaField'
|
||||
import { composeValidators, maxValue, mustBeFloat, equalOrGreaterThan } from 'src/components/forms/validator'
|
||||
import { composeValidators, maxValue, mustBeFloat, minValue } from 'src/components/forms/validator'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
import ButtonLink from 'src/components/layout/ButtonLink'
|
||||
|
@ -230,7 +230,7 @@ const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contrac
|
|||
placeholder="Value*"
|
||||
text="Value*"
|
||||
type="text"
|
||||
validate={composeValidators(mustBeFloat, maxValue(ethBalance), equalOrGreaterThan(0))}
|
||||
validate={composeValidators(mustBeFloat, maxValue(ethBalance), minValue(0))}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -17,14 +17,7 @@ import { ScanQRWrapper } from 'src/components/ScanQRModal/ScanQRWrapper'
|
|||
import Field from 'src/components/forms/Field'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import TextField from 'src/components/forms/TextField'
|
||||
import {
|
||||
composeValidators,
|
||||
greaterThan,
|
||||
maxValue,
|
||||
maxValueCheck,
|
||||
mustBeFloat,
|
||||
required,
|
||||
} from 'src/components/forms/validator'
|
||||
import { composeValidators, minValue, maxValue, mustBeFloat, required } from 'src/components/forms/validator'
|
||||
import Block from 'src/components/layout/Block'
|
||||
import Button from 'src/components/layout/Button'
|
||||
import ButtonLink from 'src/components/layout/ButtonLink'
|
||||
|
@ -102,7 +95,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
|||
const selectedTokenRecord = tokens.find((token) => token.address === values?.token)
|
||||
|
||||
return {
|
||||
amount: maxValueCheck(selectedTokenRecord?.balance, values.amount),
|
||||
amount: maxValue(selectedTokenRecord?.balance)(values.amount),
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -247,7 +240,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
|||
validate={composeValidators(
|
||||
required,
|
||||
mustBeFloat,
|
||||
greaterThan(0),
|
||||
minValue(0),
|
||||
maxValue(selectedTokenRecord?.balance),
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { simpleMemoize } from 'src/components/forms/validator'
|
||||
import memoize from 'lodash.memoize'
|
||||
import { isERC721Contract } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const addressIsAssetContract = simpleMemoize(async (tokenAddress) => {
|
||||
export const addressIsAssetContract = memoize(async (tokenAddress) => {
|
||||
const isAsset = await isERC721Contract(tokenAddress)
|
||||
if (!isAsset) {
|
||||
return 'Not a asset address'
|
||||
|
@ -12,7 +12,7 @@ export const addressIsAssetContract = simpleMemoize(async (tokenAddress) => {
|
|||
|
||||
// eslint-disable-next-line
|
||||
export const doesntExistInAssetsList = (assetsList) =>
|
||||
simpleMemoize((tokenAddress) => {
|
||||
memoize((tokenAddress) => {
|
||||
const tokenIndex = assetsList.findIndex(({ address }) => sameAddress(address, tokenAddress))
|
||||
|
||||
if (tokenIndex !== -1) {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { simpleMemoize } from 'src/components/forms/validator'
|
||||
import memoize from 'lodash.memoize'
|
||||
|
||||
import { isAddressAToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { sameAddress } from 'src/logic/wallets/ethAddresses'
|
||||
// import { getStandardTokenContract } from 'src/logic/tokens/store/actions/fetchTokens'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const addressIsTokenContract = simpleMemoize(async (tokenAddress) => {
|
||||
export const addressIsTokenContract = memoize(async (tokenAddress) => {
|
||||
// SECOND APPROACH:
|
||||
// They both seem to work the same
|
||||
// const tokenContract = await getStandardTokenContract()
|
||||
|
@ -24,7 +22,7 @@ export const addressIsTokenContract = simpleMemoize(async (tokenAddress) => {
|
|||
|
||||
// eslint-disable-next-line
|
||||
export const doesntExistInTokenList = (tokenList) =>
|
||||
simpleMemoize((tokenAddress) => {
|
||||
memoize((tokenAddress: string) => {
|
||||
const tokenIndex = tokenList.findIndex(({ address }) => sameAddress(address, tokenAddress))
|
||||
|
||||
if (tokenIndex !== -1) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import Tab from '@material-ui/core/Tab'
|
|||
import Tabs from '@material-ui/core/Tabs'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import React from 'react'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import { styles } from './style'
|
||||
|
||||
|
@ -19,11 +19,8 @@ import { AppsIcon } from 'src/routes/safe/components/assets/AppsIcon'
|
|||
import { BalancesIcon } from 'src/routes/safe/components/assets/BalancesIcon'
|
||||
import { TransactionsIcon } from 'src/routes/safe/components/assets/TransactionsIcon'
|
||||
|
||||
interface Props {
|
||||
interface Props extends RouteComponentProps {
|
||||
classes: Record<string, any>
|
||||
match: Record<string, any>
|
||||
history: Record<string, any>
|
||||
location: Record<string, any>
|
||||
}
|
||||
|
||||
const BalancesLabel = (
|
||||
|
|
|
@ -2,7 +2,7 @@ import { GenericModal } from '@gnosis.pm/safe-react-components'
|
|||
import { makeStyles } from '@material-ui/core/styles'
|
||||
import React, { useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Redirect, Route, Switch, withRouter } from 'react-router-dom'
|
||||
import { Redirect, Route, Switch, withRouter, RouteComponentProps } from 'react-router-dom'
|
||||
|
||||
import Receive from '../Balances/Receive'
|
||||
|
||||
|
@ -32,16 +32,13 @@ const Balances = React.lazy(() => import('../Balances'))
|
|||
const TxsTable = React.lazy(() => import('src/routes/safe/components/Transactions/TxsTable'))
|
||||
const AddressBookTable = React.lazy(() => import('src/routes/safe/components/AddressBook'))
|
||||
|
||||
interface Props {
|
||||
interface Props extends RouteComponentProps {
|
||||
sendFunds: Record<string, any>
|
||||
showReceive: boolean
|
||||
onShow: (value: string) => void
|
||||
onHide: (value: string) => void
|
||||
showSendFunds: (value: string) => void
|
||||
hideSendFunds: () => void
|
||||
match: Record<string, any>
|
||||
location: Record<string, any>
|
||||
history: Record<string, any>
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(styles as any)
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import { List, Map } from 'immutable'
|
||||
import { createSelector } from 'reselect'
|
||||
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { tokensSelector } from 'src/logic/tokens/store/selectors'
|
||||
import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers'
|
||||
import { isUserOwner } from 'src/logic/wallets/ethAddresses'
|
||||
import { isUserAnOwner } from 'src/logic/wallets/ethAddresses'
|
||||
import { userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
|
||||
import { safeActiveTokensSelector, safeBalancesSelector, safeSelector } from 'src/routes/safe/store/selectors'
|
||||
import { Token } from 'src/logic/tokens/store/model/token'
|
||||
import { SafeRecord } from 'src/routes/safe/store/models/safe'
|
||||
|
||||
export const grantedSelector = createSelector(userAccountSelector, safeSelector, (userAccount, safe) =>
|
||||
isUserOwner(safe, userAccount),
|
||||
export const grantedSelector = createSelector(
|
||||
userAccountSelector,
|
||||
safeSelector,
|
||||
(userAccount: string, safe: SafeRecord): boolean => isUserAnOwner(safe, userAccount),
|
||||
)
|
||||
|
||||
const safeEthAsTokenSelector = createSelector(safeSelector, (safe): Token | undefined => {
|
||||
const safeEthAsTokenSelector = createSelector(safeSelector, (safe?: SafeRecord): Token | undefined => {
|
||||
if (!safe) {
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import closeSnackbarAction from 'src/logic/notifications/store/actions/closeSnac
|
|||
import enqueueSnackbar from 'src/logic/notifications/store/actions/enqueueSnackbar'
|
||||
import { getAwaitingTransactions } from 'src/logic/safe/transactions/awaitingTransactions'
|
||||
import { getSafeVersionInfo } from 'src/logic/safe/utils/safeVersion'
|
||||
import { isUserOwner } from 'src/logic/wallets/ethAddresses'
|
||||
import { isUserAnOwner } from 'src/logic/wallets/ethAddresses'
|
||||
import { userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { getIncomingTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns'
|
||||
import { grantedSelector } from 'src/routes/safe/container/selector'
|
||||
|
@ -85,7 +85,7 @@ const notificationsMiddleware = (store) => (next) => async (action) => {
|
|||
const safes = safesMapSelector(state)
|
||||
const currentSafe = safes.get(safeAddress)
|
||||
|
||||
if (!isUserOwner(currentSafe, userAddress) || awaitingTransactions.size === 0) {
|
||||
if (!isUserAnOwner(currentSafe, userAddress) || awaitingTransactions.size === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { List, Map, Set } from 'immutable'
|
||||
import { matchPath } from 'react-router-dom'
|
||||
import { matchPath, RouteComponentProps } from 'react-router-dom'
|
||||
import { createSelector } from 'reselect'
|
||||
import { SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS } from 'src/routes/routes'
|
||||
|
||||
|
@ -16,7 +16,7 @@ const safesStateSelector = (state: AppReduxState) => state[SAFE_REDUCER_ID]
|
|||
|
||||
export const safesMapSelector = (state: AppReduxState): SafesMap => safesStateSelector(state).get('safes')
|
||||
|
||||
export const safesListSelector = createSelector(safesMapSelector, (safes) => safes.toList())
|
||||
export const safesListSelector = createSelector(safesMapSelector, (safes): List<SafeRecord> => safes.toList())
|
||||
|
||||
export const safesCountSelector = createSelector(safesMapSelector, (safes) => safes.size)
|
||||
|
||||
|
@ -33,7 +33,9 @@ const cancellationTransactionsSelector = (state: AppReduxState) => state[CANCELL
|
|||
const incomingTransactionsSelector = (state: AppReduxState) => state[INCOMING_TRANSACTIONS_REDUCER_ID]
|
||||
|
||||
export const safeParamAddressFromStateSelector = (state: AppReduxState): string | null => {
|
||||
const match = matchPath(state.router.location.pathname, { path: `${SAFELIST_ADDRESS}/:safeAddress` })
|
||||
const match = matchPath<{ safeAddress: string }>(state.router.location.pathname, {
|
||||
path: `${SAFELIST_ADDRESS}/:safeAddress`,
|
||||
})
|
||||
|
||||
if (match) {
|
||||
return checksumAddress(match.params.safeAddress)
|
||||
|
@ -42,7 +44,10 @@ export const safeParamAddressFromStateSelector = (state: AppReduxState): string
|
|||
return null
|
||||
}
|
||||
|
||||
export const safeParamAddressSelector = (state, props) => {
|
||||
export const safeParamAddressSelector = (
|
||||
state: AppReduxState,
|
||||
props: RouteComponentProps<{ [SAFE_PARAM_ADDRESS]?: string }>,
|
||||
): string => {
|
||||
const urlAdd = props.match.params[SAFE_PARAM_ADDRESS]
|
||||
return urlAdd ? checksumAddress(urlAdd) : ''
|
||||
}
|
||||
|
@ -105,15 +110,19 @@ export const safeIncomingTransactionsSelector = createSelector(
|
|||
},
|
||||
)
|
||||
|
||||
export const safeSelector = createSelector(safesMapSelector, safeParamAddressFromStateSelector, (safes, address):
|
||||
| SafeRecord
|
||||
| undefined => {
|
||||
if (!address) {
|
||||
return undefined
|
||||
}
|
||||
const checksumed = checksumAddress(address)
|
||||
return safes.get(checksumed)
|
||||
})
|
||||
export const safeSelector = createSelector(
|
||||
safesMapSelector,
|
||||
safeParamAddressFromStateSelector,
|
||||
(safes: SafesMap, address: string): SafeRecord | undefined => {
|
||||
if (!address) {
|
||||
return undefined
|
||||
}
|
||||
const checksumed = checksumAddress(address)
|
||||
const safe = safes.get(checksumed)
|
||||
|
||||
return safe
|
||||
},
|
||||
)
|
||||
|
||||
export const safeActiveTokensSelector = createSelector(
|
||||
safeSelector,
|
||||
|
|
|
@ -32,7 +32,7 @@ import safe, { SAFE_REDUCER_ID, SafeReducerMap } from 'src/routes/safe/store/red
|
|||
import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions'
|
||||
import { NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea'
|
||||
|
||||
export const history = createHashHistory({ hashType: 'slash' })
|
||||
export const history = createHashHistory()
|
||||
|
||||
// eslint-disable-next-line
|
||||
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
|
|
|
@ -107,7 +107,7 @@ export const renderSafeView = (store, address) => {
|
|||
|
||||
const INTERVAL = 500
|
||||
const MAX_TIMES_EXECUTED = 30
|
||||
export const whenSafeDeployed = () => new Promise((resolve, reject) => {
|
||||
export const whenSafeDeployed = () => new Promise<string>((resolve, reject) => {
|
||||
let times = 0
|
||||
const interval = setInterval(() => {
|
||||
if (times >= MAX_TIMES_EXECUTED) {
|
||||
|
|
|
@ -44,7 +44,7 @@ const renderOpenSafeForm = async (localStore) => {
|
|||
)
|
||||
}
|
||||
|
||||
const deploySafe = async (createSafeForm, threshold, numOwners) => {
|
||||
const deploySafe = async (createSafeForm, threshold, numOwners): Promise<string> => {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
|
||||
|
@ -112,7 +112,7 @@ const deploySafe = async (createSafeForm, threshold, numOwners) => {
|
|||
return whenSafeDeployed()
|
||||
}
|
||||
|
||||
const aDeployedSafe = async (specificStore, threshold = 1, numOwners = 1) => {
|
||||
const aDeployedSafe = async (specificStore, threshold = 1, numOwners = 1): Promise<string> => {
|
||||
const safe = await renderOpenSafeForm(specificStore)
|
||||
await sleep(1500)
|
||||
const safeAddress = await deploySafe(safe, threshold, numOwners)
|
||||
|
|
54
yarn.lock
54
yarn.lock
|
@ -2219,7 +2219,17 @@
|
|||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/hoist-non-react-statics@*":
|
||||
"@types/history@*":
|
||||
version "4.7.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.6.tgz#ed8fc802c45b8e8f54419c2d054e55c9ea344356"
|
||||
integrity sha512-GRTZLeLJ8ia00ZH8mxMO8t0aC9M1N9bN461Z2eaRurJo6Fpa+utgCwLzI4jQHcrdzuzp5WPN9jRwpsCQ1VhJ5w==
|
||||
|
||||
"@types/history@4.6.2":
|
||||
version "4.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0"
|
||||
integrity sha512-eVAb52MJ4lfPLiO9VvTgv8KaZDEIqCwhv+lXOMLlt4C1YHTShgmMULEg0RrCbnqfYd6QKfHsMp0MiX0vWISpSw==
|
||||
|
||||
"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.0":
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||
|
@ -2273,6 +2283,18 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash.memoize@^4.1.6":
|
||||
version "4.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.6.tgz#3221f981790a415cab1a239f25c17efd8b604c23"
|
||||
integrity sha512-mYxjKiKzRadRJVClLKxS4wb3Iy9kzwJ1CkbyKiadVxejnswnRByyofmPMscFKscmYpl36BEEhCMPuWhA1R/1ZQ==
|
||||
dependencies:
|
||||
"@types/lodash" "*"
|
||||
|
||||
"@types/lodash@*":
|
||||
version "4.14.157"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.157.tgz#fdac1c52448861dfde1a2e1515dbc46e54926dc8"
|
||||
integrity sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||
|
@ -2334,6 +2356,33 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@^7.1.9":
|
||||
version "7.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.9.tgz#280c13565c9f13ceb727ec21e767abe0e9b4aec3"
|
||||
integrity sha512-mpC0jqxhP4mhmOl3P4ipRsgTgbNofMRXJb08Ms6gekViLj61v1hOZEKWDCyWsdONr6EjEA6ZHXC446wdywDe0w==
|
||||
dependencies:
|
||||
"@types/hoist-non-react-statics" "^3.3.0"
|
||||
"@types/react" "*"
|
||||
hoist-non-react-statics "^3.3.0"
|
||||
redux "^4.0.0"
|
||||
|
||||
"@types/react-router-dom@^5.1.5":
|
||||
version "5.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.5.tgz#7c334a2ea785dbad2b2dcdd83d2cf3d9973da090"
|
||||
integrity sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
"@types/react-router" "*"
|
||||
|
||||
"@types/react-router@*":
|
||||
version "5.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.8.tgz#4614e5ba7559657438e17766bb95ef6ed6acc3fa"
|
||||
integrity sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==
|
||||
dependencies:
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@^4.2.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d"
|
||||
|
@ -14895,7 +14944,7 @@ redux-thunk@^2.3.0:
|
|||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||
|
||||
redux@4.0.5:
|
||||
redux@4.0.5, redux@^4.0.0:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||
|
@ -18456,7 +18505,6 @@ websocket@^1.0.31:
|
|||
dependencies:
|
||||
debug "^2.2.0"
|
||||
es5-ext "^0.10.50"
|
||||
gulp "^4.0.2"
|
||||
nan "^2.14.0"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
yaeti "^0.0.6"
|
||||
|
|
Loading…
Reference in New Issue