diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..9778959a --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,26 @@ +name: 'CLA Assistant' +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +jobs: + CLAssistant: + runs-on: ubuntu-latest + steps: + - name: 'CLA Assistant' + if: (github.event.comment.body == 'recheckcla' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + # Alpha Release + uses: gnosis/github-action@master + # GitHub token, automatically provided to the action + # (No need to define this secret in the repo settings) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-cla-document: 'https://github.com/gnosis/safe-react/blob/master/GNOSISCLA.md' + branch: 'cla-signatures' + allowlist: lukasschor,mikheevm,rmeissner,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,luarx,giacomolicari,gnosis-info,bot* + empty-commit-flag: false + blockchain-storage-flag: false diff --git a/readme.md b/readme.md index 9a0da96e..622e88eb 100644 --- a/readme.md +++ b/readme.md @@ -106,7 +106,7 @@ Add additional notes about how to deploy this on a live system * [Truffle React Box](https://github.com/truffle-box/react-box) - The web framework used * [Ganache](https://github.com/trufflesuite/ganache-cli) - Fast Ethereum RPC client * [React](https://reactjs.org/) - A JS library for building user interfaces -* [Material UI 1.X](https://material-ui-next.com/) - React components that implement Google's Material Design +* [Material UI 4.X](https://material-ui.com/) - React components that implement Google's Material Design * [redux, immutable, reselect, final-form](https://redux.js.org/) - React ecosystem libraries * [Flow](https://flow.org/) - Static Type Checker diff --git a/src/components/Collapse/index.tsx b/src/components/Collapse/index.tsx index 4a2764aa..2f235748 100644 --- a/src/components/Collapse/index.tsx +++ b/src/components/Collapse/index.tsx @@ -7,14 +7,29 @@ import styled from 'styled-components' const Wrapper = styled.div`` +const HeaderWrapper = styled.div`` + +const TitleWrapper = styled.div`` + const Header = styled.div` display: flex; align-items: center; ` -const Title = styled.div`` +interface Collapse { + title: React.ReactElement | string + description?: React.ReactElement | string + collapseClassName?: string + headerWrapperClassName?: string +} -const Collapse = ({ children, description, title }: any) => { +const Collapse: React.FC = ({ + children, + description = null, + title, + collapseClassName, + headerWrapperClassName, +}): React.ReactElement => { const [open, setOpen] = React.useState(false) const handleClick = () => { @@ -23,15 +38,17 @@ const Collapse = ({ children, description, title }: any) => { return ( - {title} -
- - {open ? : } - - {description} -
+ + {title} +
+ + {open ? : } + + {description} +
+
- + {children}
diff --git a/src/components/CopyBtn/index.tsx b/src/components/CopyBtn/index.tsx index 70c06efe..405e7f4b 100644 --- a/src/components/CopyBtn/index.tsx +++ b/src/components/CopyBtn/index.tsx @@ -27,7 +27,13 @@ const useStyles = makeStyles({ }, }) -const CopyBtn = ({ className = '', content, increaseZindex = false }) => { +interface CopyBtnProps { + className?: string + content: string + increaseZindex?: boolean +} + +const CopyBtn = ({ className = '', content, increaseZindex = false }: CopyBtnProps): React.ReactElement => { const [clicked, setClicked] = useState(false) const classes = useStyles() const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {} @@ -51,7 +57,8 @@ const CopyBtn = ({ className = '', content, increaseZindex = false }) => { Copy to clipboard { + onClick={(event) => { + event.stopPropagation() copyToClipboard(content) setClicked(true) }} diff --git a/src/components/EtherscanBtn/index.tsx b/src/components/EtherscanBtn/index.tsx index 812092b8..c81d0be8 100644 --- a/src/components/EtherscanBtn/index.tsx +++ b/src/components/EtherscanBtn/index.tsx @@ -27,7 +27,19 @@ const useStyles = makeStyles({ }, }) -const EtherscanBtn = ({ className = '', increaseZindex = false, type, value }) => { +interface EtherscanBtnProps { + className?: string + increaseZindex?: boolean + type: 'tx' | 'address' + value: string +} + +const EtherscanBtn = ({ + className = '', + increaseZindex = false, + type, + value, +}: EtherscanBtnProps): React.ReactElement => { const classes = useStyles() const customClasses = increaseZindex ? { popper: classes.increasedPopperZindex } : {} @@ -36,6 +48,7 @@ const EtherscanBtn = ({ className = '', increaseZindex = false, type, value }) = event.stopPropagation()} href={getEtherScanLink(type, value)} rel="noopener noreferrer" target="_blank" diff --git a/src/components/EtherscanLink/index.tsx b/src/components/EtherscanLink/index.tsx index 62b348b5..b2a476ff 100644 --- a/src/components/EtherscanLink/index.tsx +++ b/src/components/EtherscanLink/index.tsx @@ -1,4 +1,4 @@ -import { withStyles } from '@material-ui/core/styles' +import { makeStyles } from '@material-ui/core/styles' import cn from 'classnames' import React from 'react' @@ -11,15 +11,29 @@ import Span from 'src/components/layout/Span' import { shortVersionOf } from 'src/logic/wallets/ethAddresses' import EllipsisTransactionDetails from 'src/routes/safe/components/AddressBook/EllipsisTransactionDetails' -const EtherscanLink = ({ classes, cut, knownAddress, type, value }: any) => ( - - - {cut ? shortVersionOf(value, cut) : value} - - - - {knownAddress !== undefined ? : null} - -) +const useStyles = makeStyles(styles) -export default withStyles(styles as any)(EtherscanLink) +interface EtherscanLinkProps { + className?: string + cut?: number + knownAddress?: boolean + type?: 'tx' | 'address' + value: string +} + +const EtherscanLink = ({ className, cut, knownAddress, type, value }: EtherscanLinkProps): React.ReactElement => { + const classes = useStyles() + + return ( + + + {cut ? shortVersionOf(value, cut) : value} + + + + {knownAddress !== undefined ? : null} + + ) +} + +export default EtherscanLink diff --git a/src/components/EtherscanLink/style.ts b/src/components/EtherscanLink/style.ts index 1b553c36..47712bd9 100644 --- a/src/components/EtherscanLink/style.ts +++ b/src/components/EtherscanLink/style.ts @@ -1,6 +1,7 @@ +import { createStyles } from '@material-ui/core/styles' import { secondaryText } from 'src/theme/variables' -export const styles = () => ({ +export const styles = createStyles({ etherscanLink: { display: 'flex', alignItems: 'center', @@ -11,7 +12,7 @@ export const styles = () => ({ }, address: { display: 'block', - flexShrink: '1', + flexShrink: 1, textOverflow: 'ellipsis', whiteSpace: 'nowrap', }, diff --git a/src/components/Table/sorting.ts b/src/components/Table/sorting.ts index cdff017a..3c17111d 100644 --- a/src/components/Table/sorting.ts +++ b/src/components/Table/sorting.ts @@ -4,7 +4,7 @@ export const FIXED = 'fixed' export const buildOrderFieldFrom = (attr: string): string => `${attr}Order` -const desc = (a, b, orderBy, orderProp) => { +const desc = (a: string, b: string, orderBy: string, orderProp: boolean): number => { const order = orderProp ? buildOrderFieldFrom(orderBy) : orderBy if (b[order] < a[order]) { @@ -39,9 +39,8 @@ export const stableSort = (dataArray, cmp, fixed) => { } export const getSorting = ( - order: string, - orderBy?: string, - orderProp?: boolean, -): ((a: unknown, b: unknown) => number) => { - return order === 'desc' ? (a, b) => desc(a, b, orderBy, orderProp) : (a, b) => -desc(a, b, orderBy, orderProp) -} + order: 'desc' | 'asc', + orderBy: string, + orderProp: boolean, +): ((a: string, b: string) => number) => + order === 'desc' ? (a, b) => desc(a, b, orderBy, orderProp) : (a, b) => -desc(a, b, orderBy, orderProp) diff --git a/src/logic/addressBook/model/addressBook.ts b/src/logic/addressBook/model/addressBook.ts index 1b35186c..732027d0 100644 --- a/src/logic/addressBook/model/addressBook.ts +++ b/src/logic/addressBook/model/addressBook.ts @@ -13,3 +13,5 @@ export const makeAddressBookEntry = Record({ name: '', isOwner: false, }) + +export type AddressBookEntry = RecordOf diff --git a/src/logic/addressBook/store/reducer/addressBook.ts b/src/logic/addressBook/store/reducer/addressBook.ts index 2b2d71b1..ea1a46d7 100644 --- a/src/logic/addressBook/store/reducer/addressBook.ts +++ b/src/logic/addressBook/store/reducer/addressBook.ts @@ -1,7 +1,7 @@ import { List, Map } from 'immutable' import { handleActions } from 'redux-actions' -import { makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' +import { AddressBookEntry, makeAddressBookEntry } from 'src/logic/addressBook/model/addressBook' import { ADD_ADDRESS_BOOK } from 'src/logic/addressBook/store/actions/addAddressBook' import { ADD_ENTRY } from 'src/logic/addressBook/store/actions/addAddressBookEntry' import { ADD_OR_UPDATE_ENTRY } from 'src/logic/addressBook/store/actions/addOrUpdateAddressBookEntry' @@ -14,6 +14,9 @@ import { checksumAddress } from 'src/utils/checksumAddress' export const ADDRESS_BOOK_REDUCER_ID = 'addressBook' +export type AddressBookCollection = List +export type AddressBookState = Map> + export const buildAddressBook = (storedAdbk) => { let addressBookBuilt = Map([]) Object.entries(storedAdbk).forEach((adbkProps: any) => { diff --git a/src/logic/addressBook/store/reducer/types/addressBook.d.ts b/src/logic/addressBook/store/reducer/types/addressBook.d.ts index 5dba1ec0..6f2f4c7a 100644 --- a/src/logic/addressBook/store/reducer/types/addressBook.d.ts +++ b/src/logic/addressBook/store/reducer/types/addressBook.d.ts @@ -15,7 +15,7 @@ interface AddressBookReducerStateSerialized extends AddressBookReducerState { export interface AddressBookMap extends Map { toJS(): AddressBookMapSerialized - get(key: string): List + get(key: string, notSetValue: unknown): List } export interface AddressBookReducerMap extends Map { diff --git a/src/logic/addressBook/store/selectors/index.ts b/src/logic/addressBook/store/selectors/index.ts index 62d81409..f552de4b 100644 --- a/src/logic/addressBook/store/selectors/index.ts +++ b/src/logic/addressBook/store/selectors/index.ts @@ -15,7 +15,7 @@ export const getAddressBook = createSelector( (addressBook, safeAddress) => { let result = List([]) if (addressBook) { - result = addressBook.get(safeAddress) + result = addressBook.get(safeAddress, List()) } return result }, diff --git a/src/logic/contracts/methodIds.ts b/src/logic/contracts/methodIds.ts index bc5359bd..51a164bd 100644 --- a/src/logic/contracts/methodIds.ts +++ b/src/logic/contracts/methodIds.ts @@ -1,84 +1,5 @@ import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' - -// SAFE METHODS TO ITS ID -// https://github.com/gnosis/safe-contracts/blob/development/test/safeMethodNaming.js -// https://github.com/gnosis/safe-contracts/blob/development/contracts/GnosisSafe.sol -// [ -// { name: "addOwnerWithThreshold", id: "0x0d582f13" }, -// { name: "DOMAIN_SEPARATOR_TYPEHASH", id: "0x1db61b54" }, -// { name: "isOwner", id: "0x2f54bf6e" }, -// { name: "execTransactionFromModule", id: "0x468721a7" }, -// { name: "signedMessages", id: "0x5ae6bd37" }, -// { name: "enableModule", id: "0x610b5925" }, -// { name: "changeThreshold", id: "0x694e80c3" }, -// { name: "approvedHashes", id: "0x7d832974" }, -// { name: "changeMasterCopy", id: "0x7de7edef" }, -// { name: "SENTINEL_MODULES", id: "0x85e332cd" }, -// { name: "SENTINEL_OWNERS", id: "0x8cff6355" }, -// { name: "getOwners", id: "0xa0e67e2b" }, -// { name: "NAME", id: "0xa3f4df7e" }, -// { name: "nonce", id: "0xaffed0e0" }, -// { name: "getModules", id: "0xb2494df3" }, -// { name: "SAFE_MSG_TYPEHASH", id: "0xc0856ffc" }, -// { name: "SAFE_TX_TYPEHASH", id: "0xccafc387" }, -// { name: "disableModule", id: "0xe009cfde" }, -// { name: "swapOwner", id: "0xe318b52b" }, -// { name: "getThreshold", id: "0xe75235b8" }, -// { name: "domainSeparator", id: "0xf698da25" }, -// { name: "removeOwner", id: "0xf8dc5dd9" }, -// { name: "VERSION", id: "0xffa1ad74" }, -// { name: "setup", id: "0xa97ab18a" }, -// { name: "execTransaction", id: "0x6a761202" }, -// { name: "requiredTxGas", id: "0xc4ca3a9c" }, -// { name: "approveHash", id: "0xd4d9bdcd" }, -// { name: "signMessage", id: "0x85a5affe" }, -// { name: "isValidSignature", id: "0x20c13b0b" }, -// { name: "getMessageHash", id: "0x0a1028c4" }, -// { name: "encodeTransactionData", id: "0xe86637db" }, -// { name: "getTransactionHash", id: "0xd8d11f78" } -// ] - -export const SAFE_METHODS_NAMES = { - ADD_OWNER_WITH_THRESHOLD: 'addOwnerWithThreshold', - CHANGE_THRESHOLD: 'changeThreshold', - REMOVE_OWNER: 'removeOwner', - SWAP_OWNER: 'swapOwner', - ENABLE_MODULE: 'enableModule', - DISABLE_MODULE: 'disableModule', -} - -const METHOD_TO_ID = { - '0xe318b52b': SAFE_METHODS_NAMES.SWAP_OWNER, - '0x0d582f13': SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD, - '0xf8dc5dd9': SAFE_METHODS_NAMES.REMOVE_OWNER, - '0x694e80c3': SAFE_METHODS_NAMES.CHANGE_THRESHOLD, - '0x610b5925': SAFE_METHODS_NAMES.ENABLE_MODULE, - '0xe009cfde': SAFE_METHODS_NAMES.DISABLE_MODULE, -} - -export type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES] -type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom' - -type DecodedValues = Array<{ - name: string - type?: string - value: string -}> - -type SafeDecodedParams = { - [key in SafeMethods]?: DecodedValues -} - -type TokenDecodedParams = { - [key in TokenMethods]?: DecodedValues -} - -export type DecodedMethods = SafeDecodedParams | TokenDecodedParams | null - -export interface DataDecoded { - method: SafeMethods | TokenMethods - parameters: DecodedValues -} +import { DataDecoded, METHOD_TO_ID } from 'src/routes/safe/store/models/types/transactions.d' export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => { const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)] @@ -86,12 +7,12 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => switch (methodId) { // swapOwner case '0xe318b52b': { - const decodedParameters = web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params) + const decodedParameters = web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params) as string[] return { method: METHOD_TO_ID[methodId], parameters: [ - { name: 'oldOwner', value: decodedParameters[1] }, - { name: 'newOwner', value: decodedParameters[2] }, + { name: 'oldOwner', type: 'address', value: decodedParameters[1] }, + { name: 'newOwner', type: 'address', value: decodedParameters[2] }, ], } } @@ -102,8 +23,8 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => return { method: METHOD_TO_ID[methodId], parameters: [ - { name: 'owner', value: decodedParameters[0] }, - { name: '_threshold', value: decodedParameters[1] }, + { name: 'owner', type: 'address', value: decodedParameters[0] }, + { name: '_threshold', type: 'uint', value: decodedParameters[1] }, ], } } @@ -114,8 +35,8 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => return { method: METHOD_TO_ID[methodId], parameters: [ - { name: 'oldOwner', value: decodedParameters[1] }, - { name: '_threshold', value: decodedParameters[2] }, + { name: 'oldOwner', type: 'address', value: decodedParameters[1] }, + { name: '_threshold', type: 'uint', value: decodedParameters[2] }, ], } } @@ -126,7 +47,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => return { method: METHOD_TO_ID[methodId], parameters: [ - { name: '_threshold', value: decodedParameters[0] }, + { name: '_threshold', type: 'uint', value: decodedParameters[0] }, ], } } @@ -137,7 +58,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => return { method: METHOD_TO_ID[methodId], parameters: [ - { name: 'module', type: '', value: decodedParameters[0] }, + { name: 'module', type: 'address', value: decodedParameters[0] }, ], } } @@ -148,8 +69,8 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => return { method: METHOD_TO_ID[methodId], parameters: [ - { name: 'prevModule', type: '', value: decodedParameters[0] }, - { name: 'module', type: '', value: decodedParameters[1] }, + { name: 'prevModule', type: 'address', value: decodedParameters[0] }, + { name: 'module', type: 'address', value: decodedParameters[1] }, ], } } @@ -177,8 +98,8 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'transfer', parameters: [ - { name: 'to', value: decodeParameters[0] }, - { name: 'value', value: decodeParameters[1] }, + { name: 'to', type: '', value: decodeParameters[0] }, + { name: 'value', type: '', value: decodeParameters[1] }, ], } } @@ -189,9 +110,9 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'transferFrom', parameters: [ - { name: 'from', value: decodeParameters[0] }, - { name: 'to', value: decodeParameters[1] }, - { name: 'value', value: decodeParameters[2] }, + { name: 'from', type: '', value: decodeParameters[0] }, + { name: 'to', type: '', value: decodeParameters[1] }, + { name: 'value', type: '', value: decodeParameters[2] }, ], } } @@ -202,9 +123,9 @@ export const decodeMethods = (data: string): DataDecoded | null => { return { method: 'safeTransferFrom', parameters: [ - { name: 'from', value: decodedParameters[0] }, - { name: 'to', value: decodedParameters[1] }, - { name: 'value', value: decodedParameters[2] }, + { name: 'from', type: '', value: decodedParameters[0] }, + { name: 'to', type: '', value: decodedParameters[1] }, + { name: 'value', type: '', value: decodedParameters[2] }, ], } } diff --git a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts index 5c4fc541..3ed8fa9d 100644 --- a/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts +++ b/src/logic/currencyValues/api/fetchTokenCurrenciesBalances.ts @@ -1,9 +1,9 @@ import axios, { AxiosResponse } from 'axios' import { getTxServiceHost } from 'src/config' -import { TokenProps } from '../../tokens/store/model/token' +import { TokenProps } from 'src/logic/tokens/store/model/token' -type BalanceEndpoint = { +export type BalanceEndpoint = { balance: string balanceUsd: string tokenAddress?: string diff --git a/src/logic/currencyValues/store/model/currencyValues.ts b/src/logic/currencyValues/store/model/currencyValues.ts index 8a0c0786..eede6d5f 100644 --- a/src/logic/currencyValues/store/model/currencyValues.ts +++ b/src/logic/currencyValues/store/model/currencyValues.ts @@ -44,17 +44,19 @@ export type BalanceCurrencyRecord = { balanceInSelectedCurrency: string } -export type CurrencyRateValue = { - currencyRate?: number - selectedCurrency?: AVAILABLE_CURRENCIES - currencyBalances?: List -} - -export type CurrencyRateValueRecord = RecordOf - -export const makeBalanceCurrency = Record({ +export const makeBalanceCurrency = Record({ currencyName: '', tokenAddress: '', balanceInBaseCurrency: '', balanceInSelectedCurrency: '', }) + +export type CurrencyRateValueRecord = RecordOf + +export type BalanceCurrencyList = List + +export interface CurrencyRateValue { + currencyRate?: number + selectedCurrency?: AVAILABLE_CURRENCIES + currencyBalances?: BalanceCurrencyList +} diff --git a/src/logic/currencyValues/store/reducer/currencyValues.ts b/src/logic/currencyValues/store/reducer/currencyValues.ts index 51bb8caf..f42d0b73 100644 --- a/src/logic/currencyValues/store/reducer/currencyValues.ts +++ b/src/logic/currencyValues/store/reducer/currencyValues.ts @@ -4,22 +4,30 @@ import { handleActions } from 'redux-actions' import { SET_CURRENCY_BALANCES } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' import { SET_CURRENCY_RATE } from 'src/logic/currencyValues/store/actions/setCurrencyRate' import { SET_CURRENT_CURRENCY } from 'src/logic/currencyValues/store/actions/setSelectedCurrency' +import { CurrencyRateValue } from 'src/logic/currencyValues/store/model/currencyValues' export const CURRENCY_VALUES_KEY = 'currencyValues' +export interface CurrencyReducerMap extends Map { + get(key: K, notSetValue?: unknown): CurrencyRateValue[K] + setIn(keys: [string, K], value: CurrencyRateValue[K]): this +} + +export type CurrencyValuesState = Map + export default handleActions( { - [SET_CURRENCY_RATE]: (state, action) => { + [SET_CURRENCY_RATE]: (state: CurrencyReducerMap, action) => { const { currencyRate, safeAddress } = action.payload return state.setIn([safeAddress, 'currencyRate'], currencyRate) }, - [SET_CURRENCY_BALANCES]: (state, action) => { + [SET_CURRENCY_BALANCES]: (state: CurrencyReducerMap, action) => { const { currencyBalances, safeAddress } = action.payload return state.setIn([safeAddress, 'currencyBalances'], currencyBalances) }, - [SET_CURRENT_CURRENCY]: (state, action) => { + [SET_CURRENT_CURRENCY]: (state: CurrencyReducerMap, action) => { const { safeAddress, selectedCurrency } = action.payload return state.setIn([safeAddress, 'selectedCurrency'], selectedCurrency) diff --git a/src/logic/currencyValues/store/selectors/index.ts b/src/logic/currencyValues/store/selectors/index.ts index 9b278f44..c362dbfb 100644 --- a/src/logic/currencyValues/store/selectors/index.ts +++ b/src/logic/currencyValues/store/selectors/index.ts @@ -1,46 +1,41 @@ -import { List, Map, RecordOf } from 'immutable' import { createSelector } from 'reselect' -import { CURRENCY_VALUES_KEY } from 'src/logic/currencyValues/store/reducer/currencyValues' -import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' -import { AppReduxState } from 'src/store/index' import { - AVAILABLE_CURRENCIES, - BalanceCurrencyRecord, - CurrencyRateValue, - CurrencyRateValueRecord, -} from 'src/logic/currencyValues/store/model/currencyValues' + CURRENCY_VALUES_KEY, + CurrencyReducerMap, + CurrencyValuesState, +} from 'src/logic/currencyValues/store/reducer/currencyValues' +import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' +import { AppReduxState } from 'src/store' +import { CurrencyRateValue } from 'src/logic/currencyValues/store/model/currencyValues' import { BigNumber } from 'bignumber.js' -export const currencyValuesSelector = (state: AppReduxState): Map> => - state[CURRENCY_VALUES_KEY] +export const currencyValuesSelector = (state: AppReduxState): CurrencyValuesState => state[CURRENCY_VALUES_KEY] export const safeFiatBalancesSelector = createSelector( currencyValuesSelector, safeParamAddressFromStateSelector, - (currencyValues, safeAddress): CurrencyRateValueRecord => { + (currencyValues, safeAddress): CurrencyReducerMap | undefined => { if (!currencyValues) return return currencyValues.get(safeAddress) }, ) +const currencyValueSelector = (key: K) => ( + currencyValuesMap?: CurrencyReducerMap, +): CurrencyRateValue[K] => currencyValuesMap?.get(key) + export const safeFiatBalancesListSelector = createSelector( safeFiatBalancesSelector, - (currencyValuesMap): List => { - if (!currencyValuesMap) return - return currencyValuesMap.get('currencyBalances') ? currencyValuesMap.get('currencyBalances') : List([]) - }, + currencyValueSelector('currencyBalances'), ) export const currentCurrencySelector = createSelector( safeFiatBalancesSelector, - (currencyValuesMap): AVAILABLE_CURRENCIES | null => - currencyValuesMap ? currencyValuesMap.get('selectedCurrency') : null, + currencyValueSelector('selectedCurrency'), ) -export const currencyRateSelector = createSelector(safeFiatBalancesSelector, (currencyValuesMap): number | null => - currencyValuesMap ? currencyValuesMap.get('currencyRate') : null, -) +export const currencyRateSelector = createSelector(safeFiatBalancesSelector, currencyValueSelector('currencyRate')) export const safeFiatBalancesTotalSelector = createSelector( safeFiatBalancesListSelector, diff --git a/src/logic/tokens/store/actions/fetchSafeTokens.ts b/src/logic/tokens/store/actions/fetchSafeTokens.ts index ad4bfb05..8efb29cc 100644 --- a/src/logic/tokens/store/actions/fetchSafeTokens.ts +++ b/src/logic/tokens/store/actions/fetchSafeTokens.ts @@ -1,23 +1,71 @@ -import { BigNumber } from 'bignumber.js' +import { backOff } from 'exponential-backoff' import { List, Map } from 'immutable' import { batch } from 'react-redux' - -import fetchTokenCurrenciesBalances from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' -import { setCurrencyBalances } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' -import { AVAILABLE_CURRENCIES, makeBalanceCurrency } from 'src/logic/currencyValues/store/model/currencyValues' -import { CURRENCY_VALUES_KEY } from 'src/logic/currencyValues/store/reducer/currencyValues' -import addTokens from 'src/logic/tokens/store/actions/saveTokens' -import { makeToken } from 'src/logic/tokens/store/model/token' -import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens' -import updateSafe from 'src/routes/safe/store/actions/updateSafe' -import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' import { Dispatch } from 'redux' -import { backOff } from 'exponential-backoff' -import { AppReduxState } from 'src/store' -const humanReadableBalance = (balance, decimals) => new BigNumber(balance).times(`1e-${decimals}`).toFixed() -const noFunc = () => {} -const updateSafeValue = (address) => (valueToUpdate) => updateSafe({ address, ...valueToUpdate }) +import fetchTokenCurrenciesBalances, { + BalanceEndpoint, +} from 'src/logic/currencyValues/api/fetchTokenCurrenciesBalances' +import { setCurrencyBalances } from 'src/logic/currencyValues/store/actions/setCurrencyBalances' +import { + AVAILABLE_CURRENCIES, + CurrencyRateValueRecord, + makeBalanceCurrency, +} from 'src/logic/currencyValues/store/model/currencyValues' +import addTokens from 'src/logic/tokens/store/actions/saveTokens' +import { makeToken, Token } from 'src/logic/tokens/store/model/token' +import { TokenState } from 'src/logic/tokens/store/reducer/tokens' +import updateSafe from 'src/routes/safe/store/actions/updateSafe' +import { AppReduxState } from 'src/store' +import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' +import { SafeRecordProps } from 'src/routes/safe/store/models/safe' +import { + safeActiveTokensSelector, + safeBalancesSelector, + safeBlacklistedTokensSelector, + safeEthBalanceSelector, + safeSelector, +} from 'src/routes/safe/store/selectors' +import { tokensSelector } from 'src/logic/tokens/store/selectors' +import { currencyValuesSelector } from 'src/logic/currencyValues/store/selectors' + +const noFunc = (): void => {} + +const updateSafeValue = (address: string) => (valueToUpdate: Partial) => + updateSafe({ address, ...valueToUpdate }) + +interface ExtractedData { + balances: Map + currencyList: List + ethBalance: string + tokens: List +} + +const extractDataFromResult = (currentTokens: TokenState) => ( + acc: ExtractedData, + { balance, balanceUsd, token, tokenAddress }: BalanceEndpoint, +): ExtractedData => { + if (tokenAddress === null) { + acc.ethBalance = humanReadableValue(balance, 18) + } else { + acc.balances = acc.balances.merge({ [tokenAddress]: humanReadableValue(balance, Number(token.decimals)) }) + + if (currentTokens && !currentTokens.get(tokenAddress)) { + acc.tokens = acc.tokens.push(makeToken({ address: tokenAddress, ...token })) + } + } + + acc.currencyList = acc.currencyList.push( + makeBalanceCurrency({ + currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null, + tokenAddress, + balanceInBaseCurrency: balanceUsd, + balanceInSelectedCurrency: balanceUsd, + }), + ) + + return acc +} const fetchSafeTokens = (safeAddress: string) => async ( dispatch: Dispatch, @@ -25,43 +73,22 @@ const fetchSafeTokens = (safeAddress: string) => async ( ): Promise => { try { const state = getState() - const safe = state[SAFE_REDUCER_ID].getIn(['safes', safeAddress]) - const currentTokens = state[TOKEN_REDUCER_ID] + const safe = safeSelector(state) + const currentTokens = tokensSelector(state) if (!safe) { return } const result = await backOff(() => fetchTokenCurrenciesBalances(safeAddress)) - const currentEthBalance = safe.get('ethBalance') - const safeBalances = safe.get('balances') - const alreadyActiveTokens = safe.get('activeTokens') - const blacklistedTokens = safe.get('blacklistedTokens') - const currencyValues = state[CURRENCY_VALUES_KEY] + const currentEthBalance = safeEthBalanceSelector(state) + const safeBalances = safeBalancesSelector(state) + const alreadyActiveTokens = safeActiveTokensSelector(state) + const blacklistedTokens = safeBlacklistedTokensSelector(state) + const currencyValues = currencyValuesSelector(state) - const { balances, currencyList, ethBalance, tokens } = result.data.reduce( - (acc, { balance, balanceUsd, token, tokenAddress }) => { - if (tokenAddress === null) { - acc.ethBalance = humanReadableBalance(balance, 18) - } else { - acc.balances = acc.balances.merge({ [tokenAddress]: humanReadableBalance(balance, token.decimals) }) - - if (currentTokens && !currentTokens.get(tokenAddress)) { - acc.tokens = acc.tokens.push(makeToken({ address: tokenAddress, ...token })) - } - } - - acc.currencyList = acc.currencyList.push( - makeBalanceCurrency({ - currencyName: balanceUsd ? AVAILABLE_CURRENCIES.USD : null, - tokenAddress, - balanceInBaseCurrency: balanceUsd, - balanceInSelectedCurrency: balanceUsd, - }), - ) - - return acc - }, + const { balances, currencyList, ethBalance, tokens } = result.data.reduce( + extractDataFromResult(currentTokens), { balances: Map(), currencyList: List(), @@ -71,7 +98,7 @@ const fetchSafeTokens = (safeAddress: string) => async ( ) // need to persist those already active tokens, despite its balances - const activeTokens = alreadyActiveTokens.toSet().union( + const activeTokens = alreadyActiveTokens.union( // active tokens by balance, excluding those already blacklisted and the `null` address balances.keySeq().toSet().subtract(blacklistedTokens), ) @@ -80,10 +107,7 @@ const fetchSafeTokens = (safeAddress: string) => async ( const updateActiveTokens = activeTokens.equals(alreadyActiveTokens) ? noFunc : update({ activeTokens }) const updateBalances = balances.equals(safeBalances) ? noFunc : update({ balances }) const updateEthBalance = ethBalance === currentEthBalance ? noFunc : update({ ethBalance }) - const storedCurrencyBalances = - currencyValues && currencyValues.get(safeAddress) - ? currencyValues.get(safeAddress).get('currencyBalances') - : undefined + const storedCurrencyBalances = currencyValues?.get(safeAddress)?.get('currencyBalances') const updateCurrencies = currencyList.equals(storedCurrencyBalances) ? noFunc diff --git a/src/logic/tokens/utils/humanReadableValue.ts b/src/logic/tokens/utils/humanReadableValue.ts new file mode 100644 index 00000000..7716a1d5 --- /dev/null +++ b/src/logic/tokens/utils/humanReadableValue.ts @@ -0,0 +1,5 @@ +import { BigNumber } from 'bignumber.js' + +export const humanReadableValue = (value: number | string, decimals = 18): string => { + return new BigNumber(value).times(`1e-${decimals}`).toFixed() +} diff --git a/src/routes/load/container/Load.tsx b/src/routes/load/container/Load.tsx index 30f5f621..82fca0cf 100644 --- a/src/routes/load/container/Load.tsx +++ b/src/routes/load/container/Load.tsx @@ -66,7 +66,7 @@ const Load = ({ addSafe, network, provider, userAddress }: LoadProps): React.Rea const gnosisSafe = await getGnosisSafeInstanceAt(safeAddress) const ownerAddresses = await gnosisSafe.methods.getOwners().call() - const owners = getOwnersFrom(ownerNames, ownerAddresses.sort()) + const owners = getOwnersFrom(ownerNames, ownerAddresses.slice().sort()) await loadSafe(safeName, safeAddress, owners, addSafe) diff --git a/src/routes/safe/components/AddressBook/index.tsx b/src/routes/safe/components/AddressBook/index.tsx index 167e2e0e..e16f8c3e 100644 --- a/src/routes/safe/components/AddressBook/index.tsx +++ b/src/routes/safe/components/AddressBook/index.tsx @@ -132,7 +132,7 @@ const AddressBookTable = ({ classes }) => { defaultRowsPerPage={25} disableLoadingOnEmptyTable label="Owners" - size={addressBook.size} + size={addressBook?.size || 0} > {(sortedData) => sortedData.map((row, index) => { diff --git a/src/routes/safe/components/Apps/components/LegalDisclaimer.tsx b/src/routes/safe/components/Apps/components/LegalDisclaimer.tsx new file mode 100644 index 00000000..bc711b9d --- /dev/null +++ b/src/routes/safe/components/Apps/components/LegalDisclaimer.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { FixedDialog, Text } from '@gnosis.pm/safe-react-components' + +interface OwnProps { + onCancel: () => void + onConfirm: () => void +} + +const LegalDisclaimer = ({ onCancel, onConfirm }: OwnProps): JSX.Element => ( + + + You are now accessing third-party apps, which we do not own, control, maintain or audit. We are not liable for + any loss you may suffer in connection with interacting with the apps, which is at your own risk. You must read + our Terms, which contain more detailed provisions binding on you relating to the apps. + +
+ + I have read and understood the{' '} +
+ Terms + {' '} + and this Disclaimer, and agree to be bound by them. + + + } + onCancel={onCancel} + onConfirm={onConfirm} + title="Disclaimer" + /> +) + +export default LegalDisclaimer diff --git a/src/routes/safe/components/Apps/ManageApps.tsx b/src/routes/safe/components/Apps/components/ManageApps.tsx similarity index 96% rename from src/routes/safe/components/Apps/ManageApps.tsx rename to src/routes/safe/components/Apps/components/ManageApps.tsx index 404d2e12..60539fc5 100644 --- a/src/routes/safe/components/Apps/ManageApps.tsx +++ b/src/routes/safe/components/Apps/components/ManageApps.tsx @@ -2,8 +2,8 @@ import { ButtonLink, ManageListModal } from '@gnosis.pm/safe-react-components' import React, { useState } from 'react' import appsIconSvg from 'src/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg' -import AddAppForm from './AddAppForm' -import { SafeApp } from './types' +import AddAppForm from '../AddAppForm' +import { SafeApp } from '../types' const FORM_ID = 'add-apps-form' diff --git a/src/routes/safe/components/Apps/confirmTransactions.tsx b/src/routes/safe/components/Apps/confirmTransactions.tsx index 03efa44f..ce24fd77 100644 --- a/src/routes/safe/components/Apps/confirmTransactions.tsx +++ b/src/routes/safe/components/Apps/confirmTransactions.tsx @@ -1,5 +1,4 @@ import { Icon, ModalFooterConfirmation, Text, Title } from '@gnosis.pm/safe-react-components' -import { BigNumber } from 'bignumber.js' import React, { ReactElement } from 'react' import styled from 'styled-components' @@ -13,6 +12,7 @@ import Bold from 'src/components/layout/Bold' import Heading from 'src/components/layout/Heading' import Img from 'src/components/layout/Img' import { getEthAsToken } from 'src/logic/tokens/utils/tokenHelpers' +import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' export type SafeAppTx = { to: string @@ -28,8 +28,6 @@ type GenericModalProps = { onClose: () => void } -const humanReadableBalance = (balance, decimals) => new BigNumber(balance).times(`1e-${decimals}`).toFixed() - const Wrapper = styled.div` margin-bottom: 15px; ` @@ -77,7 +75,7 @@ const confirmTransactions = ( ethBalance: string, nameApp: string, iconApp: string, - txs: Array, + txs: SafeAppTx[], openModal: (modalInfo: GenericModalProps) => void, closeModal: () => void, onConfirm: () => void, @@ -110,7 +108,7 @@ const confirmTransactions = ( Value
Ether - {humanReadableBalance(tx.value, 18)} ETH + {humanReadableValue(tx.value, 18)} ETH
diff --git a/src/routes/safe/components/Apps/hooks/useAppList.ts b/src/routes/safe/components/Apps/hooks/useAppList.ts new file mode 100644 index 00000000..10ad9e9c --- /dev/null +++ b/src/routes/safe/components/Apps/hooks/useAppList.ts @@ -0,0 +1,107 @@ +import { useState, useEffect, useCallback } from 'react' +import { loadFromStorage, saveToStorage } from 'src/utils/storage' +import { getAppInfoFromUrl, staticAppsList } from '../utils' +import { SafeApp, StoredSafeApp } from '../types' + +const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' + +type onAppToggleHandler = (appId: string, enabled: boolean) => Promise +type onAppAddedHandler = (app: SafeApp) => void + +type UseAppListReturnType = { + appList: SafeApp[] + loadingAppList: boolean + onAppToggle: onAppToggleHandler + onAppAdded: onAppAddedHandler +} + +const useAppList = (): UseAppListReturnType => { + const [appList, setAppList] = useState([]) + const [loadingAppList, setLoadingAppList] = useState(true) + + // Load apps list + useEffect(() => { + const loadApps = async () => { + // recover apps from storage: + // * third-party apps added by the user + // * disabled status for both static and third-party apps + const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] + const list = [...persistedAppList] + + staticAppsList.forEach((staticApp) => { + if (!list.some((persistedApp) => persistedApp.url === staticApp.url)) { + list.push(staticApp) + } + }) + + const apps = [] + // using the appURL to recover app info + for (let index = 0; index < list.length; index++) { + try { + const currentApp = list[index] + + const appInfo: any = await getAppInfoFromUrl(currentApp.url) + if (appInfo.error) { + throw Error(`There was a problem trying to load app ${currentApp.url}`) + } + + appInfo.disabled = currentApp.disabled === undefined ? false : currentApp.disabled + + apps.push(appInfo) + } catch (error) { + console.error(error) + } + } + + setAppList(apps) + setLoadingAppList(false) + } + + loadApps() + }, []) + + const onAppToggle: onAppToggleHandler = useCallback( + async (appId, enabled) => { + // update in-memory list + const appListCopy = [...appList] + + const app = appListCopy.find((a) => a.id === appId) + if (!app) { + return + } + + app.disabled = !enabled + setAppList(appListCopy) + + // update storage list + const listToPersist: StoredSafeApp[] = appListCopy.map(({ url, disabled }) => ({ url, disabled })) + saveToStorage(APPS_STORAGE_KEY, listToPersist) + }, + [appList], + ) + + const onAppAdded: onAppAddedHandler = useCallback( + (app) => { + const newAppList = [ + { url: app.url, disabled: false }, + ...appList.map((a) => ({ + url: a.url, + disabled: a.disabled, + })), + ] + saveToStorage(APPS_STORAGE_KEY, newAppList) + + setAppList([...appList, { ...app, disabled: false }]) + }, + [appList], + ) + + return { + appList, + loadingAppList, + onAppToggle, + onAppAdded, + } +} + +export { useAppList } diff --git a/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts new file mode 100644 index 00000000..03c3f5c1 --- /dev/null +++ b/src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts @@ -0,0 +1,52 @@ +import { useEffect } from 'react' + +const useIframeMessageHandler = (): void => { + useEffect(() => { + // const handleIframeMessage = (data) => { + // if (!data || !data.messageId) { + // console.error('ThirdPartyApp: A message was received without message id.') + // return + // } + // switch (data.messageId) { + // case operations.SEND_TRANSACTIONS: { + // const onConfirm = async () => { + // closeModal() + // await sendTransactions(dispatch, safeAddress, data.data, enqueueSnackbar, closeSnackbar, selectedApp.id) + // } + // confirmTransactions( + // safeAddress, + // safeName, + // ethBalance, + // selectedApp.name, + // selectedApp.iconUrl, + // data.data, + // openModal, + // closeModal, + // onConfirm, + // ) + // break + // } + // default: { + // console.error(`ThirdPartyApp: A message was received with an unknown message id ${data.messageId}.`) + // break + // } + // } + // } + // const onIframeMessage = async ({ data, origin }) => { + // if (origin === window.origin) { + // return + // } + // if (!selectedApp.url.includes(origin)) { + // console.error(`ThirdPartyApp: A message was received from an unknown origin ${origin}`) + // return + // } + // handleIframeMessage(data) + // } + // window.addEventListener('message', onIframeMessage) + // return () => { + // window.removeEventListener('message', onIframeMessage) + // } + }, []) +} + +export { useIframeMessageHandler } diff --git a/src/routes/safe/components/Apps/hooks/useLegalConsent.ts b/src/routes/safe/components/Apps/hooks/useLegalConsent.ts new file mode 100644 index 00000000..ec224c87 --- /dev/null +++ b/src/routes/safe/components/Apps/hooks/useLegalConsent.ts @@ -0,0 +1,29 @@ +import { useState, useEffect, useCallback } from 'react' +import { loadFromStorage, saveToStorage } from 'src/utils/storage' + +const APPS_LEGAL_CONSENT_RECEIVED = 'APPS_LEGAL_CONSENT_RECEIVED' + +const useLegalConsent = (): { consentReceived: boolean; onConsentReceipt: () => void } => { + const [consentReceived, setConsentReceived] = useState(false) + + useEffect(() => { + const checkLegalDisclaimer = async () => { + const storedConsentReceived = await loadFromStorage(APPS_LEGAL_CONSENT_RECEIVED) + + if (storedConsentReceived) { + setConsentReceived(true) + } + } + + checkLegalDisclaimer() + }, []) + + const onConsentReceipt = useCallback((): void => { + setConsentReceived(true) + saveToStorage(APPS_LEGAL_CONSENT_RECEIVED, true) + }, []) + + return { consentReceived, onConsentReceipt } +} + +export { useLegalConsent } diff --git a/src/routes/safe/components/Apps/index.tsx b/src/routes/safe/components/Apps/index.tsx index 7e506190..9703dbd1 100644 --- a/src/routes/safe/components/Apps/index.tsx +++ b/src/routes/safe/components/Apps/index.tsx @@ -1,14 +1,16 @@ -import { Card, FixedDialog, FixedIcon, IconText, Loader, Menu, Text, Title } from '@gnosis.pm/safe-react-components' +import { Card, FixedIcon, IconText, Loader, Menu, Title } from '@gnosis.pm/safe-react-components' import { withSnackbar } from 'notistack' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState, useMemo } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useHistory } from 'react-router-dom' import styled from 'styled-components' -import ManageApps from './ManageApps' +import ManageApps from './components/ManageApps' import confirmTransactions from './confirmTransactions' import sendTransactions from './sendTransactions' -import { getAppInfoFromUrl, staticAppsList } from './utils' +import LegalDisclaimer from './components/LegalDisclaimer' +import { useLegalConsent } from './hooks/useLegalConsent' +import { useAppList } from './hooks/useAppList' import LCL from 'src/components/ListContentLayout' import { networkSelector } from 'src/logic/wallets/store/selectors' @@ -19,12 +21,7 @@ import { safeNameSelector, safeParamAddressFromStateSelector, } from 'src/routes/safe/store/selectors' -import { loadFromStorage, saveToStorage } from 'src/utils/storage' import { isSameHref } from 'src/utils/url' -import { SafeApp, StoredSafeApp } from './types' - -const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY' -const APPS_LEGAL_DISCLAIMER_STORAGE_KEY = 'APPS_LEGAL_DISCLAIMER_STORAGE_KEY' const StyledCard = styled(Card)` margin-bottom: 24px; @@ -66,11 +63,10 @@ const operations = { } function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { - const [appList, setAppList] = useState>([]) - const [legalDisclaimerAccepted, setLegalDisclaimerAccepted] = useState(false) - const [selectedApp, setSelectedApp] = useState() - const [loading, setLoading] = useState(true) - const [appIsLoading, setAppIsLoading] = useState(true) + const { appList, loadingAppList, onAppToggle, onAppAdded } = useAppList() + + const [appIsLoading, setAppIsLoading] = useState(true) + const [selectedAppId, setSelectedAppId] = useState() const [iframeEl, setIframeEl] = useState(null) const history = useHistory() @@ -80,8 +76,36 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { const network = useSelector(networkSelector) const ethBalance = useSelector(safeEthBalanceSelector) const dispatch = useDispatch() + const { consentReceived, onConsentReceipt } = useLegalConsent() - const getSelectedApp = useCallback(() => appList.find((e) => e.id === selectedApp), [appList, selectedApp]) + const selectedApp = useMemo(() => appList.find((app) => app.id === selectedAppId), [appList, selectedAppId]) + + const onSelectApp = useCallback( + (appId) => { + if (selectedAppId === appId) { + return + } + + setAppIsLoading(true) + setSelectedAppId(appId) + }, + [selectedAppId], + ) + + useEffect(() => { + const selectFirstEnabledApp = () => { + const firstEnabledApp = appList.find((a) => !a.disabled) + if (firstEnabledApp) { + setSelectedAppId(firstEnabledApp.id) + } + } + + const initialSelect = appList.length && !selectedAppId + const currentAppWasDisabled = selectedApp?.disabled + if (initialSelect || currentAppWasDisabled) { + selectFirstEnabledApp() + } + }, [appList, selectedApp, selectedAppId]) const iframeRef = useCallback((node) => { if (node !== null) { @@ -89,58 +113,15 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { } }, []) - const onSelectApp = useCallback( - (appId) => { - const selectedApp = getSelectedApp() - - if (selectedApp && selectedApp.id === appId) { - return - } - - setAppIsLoading(true) - setSelectedApp(appId) - }, - [getSelectedApp], - ) - const redirectToBalance = () => history.push(`${SAFELIST_ADDRESS}/${safeAddress}/balances`) - const onAcceptLegalDisclaimer = () => { - setLegalDisclaimerAccepted(true) - saveToStorage(APPS_LEGAL_DISCLAIMER_STORAGE_KEY, true) - } - const getContent = () => { if (!selectedApp) { return null } - if (!legalDisclaimerAccepted) { - return ( - - - You are now accessing third-party apps, which we do not own, control, maintain or audit. We are not - liable for any loss you may suffer in connection with interacting with the apps, which is at your own - risk. You must read our Terms, which contain more detailed provisions binding on you relating to the - apps. - -
- - I have read and understood the{' '} - - Terms - {' '} - and this Disclaimer, and agree to be bound by them. - - - } - onCancel={redirectToBalance} - onConfirm={onAcceptLegalDisclaimer} - title="Disclaimer" - /> - ) + if (!consentReceived) { + return } if (network === 'UNKNOWN' || !granted) { @@ -152,8 +133,6 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { ) } - const app = getSelectedApp() - return ( {appIsLoading && ( @@ -161,76 +140,26 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { )} - + ) } - const onAppAdded = (app: SafeApp) => { - const newAppList = [ - { url: app.url, disabled: false }, - ...appList.map((a) => ({ - url: a.url, - disabled: a.disabled, - })), - ] - saveToStorage(APPS_STORAGE_KEY, newAppList) - - setAppList([...appList, { ...app, disabled: false }]) - } - - const selectFirstApp = useCallback( - (apps) => { - const firstEnabledApp = apps.find((a) => !a.disabled) - if (firstEnabledApp) { - onSelectApp(firstEnabledApp.id) - } - }, - [onSelectApp], - ) - - const onAppToggle = async (appId, enabled) => { - // update in-memory list - const copyAppList = [...appList] - - const app = copyAppList.find((a) => a.id === appId) - if (!app) { - return - } - - app.disabled = !enabled - setAppList(copyAppList) - - // update storage list - const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] - let storageApp = persistedAppList.find((a) => a.url === app.url) - - if (!storageApp) { - storageApp = { url: app.url } - storageApp.disabled = !enabled - persistedAppList.push(storageApp) - } else { - storageApp.disabled = !enabled - } - - saveToStorage(APPS_STORAGE_KEY, persistedAppList) - - // select app - const selectedApp = getSelectedApp() - if (!selectedApp || (selectedApp && selectedApp.id === appId)) { - setSelectedApp(undefined) - selectFirstApp(copyAppList) - } - } - - const getEnabledApps = () => appList.filter((a) => !a.disabled) + const enabledApps = useMemo(() => appList.filter((a) => !a.disabled), [appList]) const sendMessageToIframe = useCallback( (messageId, data) => { - const app = getSelectedApp() - iframeEl?.contentWindow.postMessage({ messageId, data }, app.url) + if (iframeEl && selectedApp) { + iframeEl.contentWindow.postMessage({ messageId, data }, selectedApp.url) + } }, - [getSelectedApp, iframeEl], + [iframeEl, selectedApp], ) // handle messages from iframe @@ -245,22 +174,16 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { case operations.SEND_TRANSACTIONS: { const onConfirm = async () => { closeModal() - await sendTransactions( - dispatch, - safeAddress, - data.data, - enqueueSnackbar, - closeSnackbar, - getSelectedApp().id, - ) + + await sendTransactions(dispatch, safeAddress, data.data, enqueueSnackbar, closeSnackbar, selectedApp.id) } confirmTransactions( safeAddress, safeName, ethBalance, - getSelectedApp().name, - getSelectedApp().iconUrl, + selectedApp.name, + selectedApp.iconUrl, data.data, openModal, closeModal, @@ -290,8 +213,7 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { return } - const app = getSelectedApp() - if (!app.url.includes(origin)) { + if (!selectedApp.url.includes(origin)) { console.error(`ThirdPartyApp: A message was received from an unknown origin ${origin}`) return } @@ -306,65 +228,11 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { } }) - // load legalDisclaimer - useEffect(() => { - const checkLegalDisclaimer = async () => { - const legalDisclaimer = await loadFromStorage(APPS_LEGAL_DISCLAIMER_STORAGE_KEY) - - if (legalDisclaimer) { - setLegalDisclaimerAccepted(true) - } - } - - checkLegalDisclaimer() - }, []) - - // Load apps list - useEffect(() => { - const loadApps = async () => { - // recover apps from storage: - // * third-party apps added by the user - // * disabled status for both static and third-party apps - const persistedAppList = (await loadFromStorage(APPS_STORAGE_KEY)) || [] - const list = [...persistedAppList] - - staticAppsList.forEach((staticApp) => { - if (!list.some((persistedApp) => persistedApp.url === staticApp.url)) { - list.push(staticApp) - } - }) - - const apps = [] - // using the appURL to recover app info - for (let index = 0; index < list.length; index++) { - try { - const currentApp = list[index] - - const appInfo: SafeApp = await getAppInfoFromUrl(currentApp.url) - if (appInfo.error) { - throw Error(`There was a problem trying to load app ${currentApp.url}`) - } - - appInfo.disabled = currentApp.disabled === undefined ? false : currentApp.disabled - - apps.push(appInfo) - } catch (error) { - console.error(error) - } - } - - setAppList(apps) - setLoading(false) - selectFirstApp(apps) - } - - if (!appList.length) { - loadApps() - } - }, [appList.length, selectFirstApp]) - // on iframe change useEffect(() => { + const sendMessageToIframe = (messageId, data) => { + iframeEl.contentWindow.postMessage({ messageId, data }, selectedApp.url) + } const onIframeLoaded = () => { setAppIsLoading(false) sendMessageToIframe(operations.ON_SAFE_INFO, { @@ -374,8 +242,7 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { }) } - const app = getSelectedApp() - if (!iframeEl || !selectedApp || !isSameHref(iframeEl.src, app.url)) { + if (!iframeEl || !selectedApp || !isSameHref(iframeEl.src, selectedApp.url)) { return } @@ -384,9 +251,9 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { return () => { iframeEl.removeEventListener('load', onIframeLoaded) } - }, [ethBalance, getSelectedApp, iframeEl, network, safeAddress, selectedApp, sendMessageToIframe]) + }, [ethBalance, iframeEl, network, safeAddress, selectedApp]) - if (loading || !appList.length) { + if (loadingAppList || !appList.length) { return ( @@ -399,26 +266,12 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { - {getEnabledApps().length ? ( + {enabledApps.length ? ( - + {getContent()} - {/* - {getSelectedApp() && getSelectedApp().providedBy && ( - <> -

This App is provided by

- window.open(getSelectedApp().providedBy.url, '_blank')} - size="lg" - testId="manage-tokens-btn" - > - {selectedApp && getSelectedApp().providedBy.name} - - - )} -
*/}
) : ( diff --git a/src/routes/safe/components/Balances/Coins/index.tsx b/src/routes/safe/components/Balances/Coins/index.tsx index 2eb79fdf..ac2feaa4 100644 --- a/src/routes/safe/components/Balances/Coins/index.tsx +++ b/src/routes/safe/components/Balances/Coins/index.tsx @@ -25,6 +25,7 @@ import { BALANCE_TABLE_VALUE_ID, generateColumns, getBalanceData, + BalanceData, } from 'src/routes/safe/components/Balances/dataFetcher' import { extendedSafeTokensSelector, grantedSelector } from 'src/routes/safe/container/selector' import { Skeleton } from '@material-ui/lab' @@ -59,7 +60,7 @@ const Coins = (props: Props): React.ReactElement => { const activeTokens = useSelector(extendedSafeTokensSelector) const currencyValues = useSelector(safeFiatBalancesListSelector) const granted = useSelector(grantedSelector) - const [filteredData, setFilteredData] = React.useState(List()) + const [filteredData, setFilteredData] = React.useState>(List()) React.useMemo(() => { setFilteredData(getBalanceData(activeTokens, selectedCurrency, currencyValues, currencyRate)) diff --git a/src/routes/safe/components/Balances/dataFetcher.ts b/src/routes/safe/components/Balances/dataFetcher.ts index fb0e311e..0bc07158 100644 --- a/src/routes/safe/components/Balances/dataFetcher.ts +++ b/src/routes/safe/components/Balances/dataFetcher.ts @@ -5,9 +5,8 @@ import { FIXED } from 'src/components/Table/sorting' import { formatAmountInUsFormat } from 'src/logic/tokens/utils/formatAmount' import { ETH_ADDRESS } from 'src/logic/tokens/utils/tokenHelpers' import { TableColumn } from 'src/components/Table/types' -import { AVAILABLE_CURRENCIES, BalanceCurrencyRecord } from 'src/logic/currencyValues/store/model/currencyValues' +import { AVAILABLE_CURRENCIES, BalanceCurrencyList } from 'src/logic/currencyValues/store/model/currencyValues' import { Token } from 'src/logic/tokens/store/model/token' -import { BalanceDataRow } from './Coins' export const BALANCE_TABLE_ASSET_ID = 'asset' export const BALANCE_TABLE_BALANCE_ID = 'balance' @@ -15,15 +14,15 @@ export const BALANCE_TABLE_VALUE_ID = 'value' const getTokenPriceInCurrency = ( token: Token, - currencySelected: AVAILABLE_CURRENCIES, - currencyValues: List, - currencyRate: number | null, + currencySelected?: AVAILABLE_CURRENCIES, + currencyValues?: BalanceCurrencyList, + currencyRate?: number, ): string => { if (!currencySelected) { return '' } - const currencyValue = currencyValues.find(({ tokenAddress }) => { + const currencyValue = currencyValues?.find(({ tokenAddress }) => { if (token.address === ETH_ADDRESS && !tokenAddress) { return true } @@ -31,7 +30,7 @@ const getTokenPriceInCurrency = ( return token.address === tokenAddress }) - if (!currencyValue) { + if (!currencyValue || !currencyRate) { return '' } @@ -41,13 +40,20 @@ const getTokenPriceInCurrency = ( return `${formatAmountInUsFormat(balance)} ${currencySelected}` } +export interface BalanceData { + asset: { name: string; logoUri: string; address: string; symbol: string } + balance: string + fixed: boolean + value: string +} + export const getBalanceData = ( activeTokens: List, - currencySelected: AVAILABLE_CURRENCIES, - currencyValues: List, - currencyRate: number, -): BalanceDataRow => { - return activeTokens.map((token) => ({ + currencySelected?: AVAILABLE_CURRENCIES, + currencyValues?: BalanceCurrencyList, + currencyRate?: number, +): List => + activeTokens.map((token) => ({ [BALANCE_TABLE_ASSET_ID]: { name: token.name, logoUri: token.logoUri, @@ -60,7 +66,6 @@ export const getBalanceData = ( [FIXED]: token.symbol === 'ETH', [BALANCE_TABLE_VALUE_ID]: getTokenPriceInCurrency(token, currencySelected, currencyValues, currencyRate), })) -} export const generateColumns = (): List => { const assetColumn: TableColumn = { diff --git a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx index 671d4cea..3dd5db75 100644 --- a/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx +++ b/src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell/index.tsx @@ -4,11 +4,22 @@ import EtherScanLink from 'src/components/EtherscanLink' import Identicon from 'src/components/Identicon' import Block from 'src/components/layout/Block' import Paragraph from 'src/components/layout/Paragraph' -import { useWindowDimensions } from '../../../../container/hooks/useWindowDimensions' +import { useWindowDimensions } from 'src/routes/safe/container/hooks/useWindowDimensions' import { useEffect, useState } from 'react' -const OwnerAddressTableCell = (props) => { - const { address, knownAddress, showLinks, userName } = props +interface OwnerAddressTableCellProps { + address: string + knownAddress?: boolean + showLinks: boolean + userName?: string +} + +const OwnerAddressTableCell = ({ + address, + knownAddress, + showLinks, + userName, +}: OwnerAddressTableCellProps): React.ReactElement => { const [cut, setCut] = useState(undefined) const { width } = useWindowDimensions() diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx new file mode 100644 index 00000000..58d7967a --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx @@ -0,0 +1,180 @@ +import { IconText, Text } from '@gnosis.pm/safe-react-components' +import { makeStyles } from '@material-ui/core/styles' +import React from 'react' +import styled from 'styled-components' + +import { styles } from './styles' +import Value from './Value' + +import Block from 'src/components/layout/Block' +import { + extractMultiSendDecodedData, + MultiSendDetails, +} from 'src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails' +import Bold from 'src/components/layout/Bold' +import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' +import EtherscanLink from 'src/components/EtherscanLink' +import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' +import Collapse from 'src/components/Collapse' +import { useSelector } from 'react-redux' +import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' +import Paragraph from 'src/components/layout/Paragraph' +import LinkWithRef from 'src/components/layout/Link' +import { shortVersionOf } from 'src/logic/wallets/ethAddresses' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' + +export const TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID = 'tx-description-custom-value' +export const TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID = 'tx-description-custom-data' + +const useStyles = makeStyles(styles) + +const TxDetailsMethodName = styled(Text)` + text-indent: 4px; +` +const TxDetailsMethodParam = styled.div` + text-indent: 8px; +` +const InlineText = styled(Text)` + display: inline-flex; +` +const TxDetailsContent = styled.div` + padding: 8px 8px 8px 16px; +` + +const TxInfo = styled.div` + padding: 8px 8px 8px 16px; +` + +const MultiSendCustomData = ({ tx, order }: { tx: MultiSendDetails; order: number }): React.ReactElement => { + const classes = useStyles() + const methodName = tx.data?.method ? ` (${tx.data.method})` : '' + + return ( + <> + } + > + + + Send {humanReadableValue(tx.value)} ETH to: + + + {tx.data && ( + + + {tx.data.method} + + {tx.data?.parameters.map((param, index) => ( + + + + {param.name}({param.type}): + + + + + ))} + + )} + + + + ) +} + +const TxData = ({ data }: { data: string }): React.ReactElement => { + const classes = useStyles() + const [showTxData, setShowTxData] = React.useState(false) + const showExpandBtn = data.length > 20 + + return ( + + {showExpandBtn ? ( + <> + {showTxData ? ( + <> + {data}{' '} + setShowTxData(false)} + rel="noopener noreferrer" + target="_blank" + > + Show Less + + + ) : ( + <> + {shortVersionOf(data, 20)}{' '} + setShowTxData(true)} + rel="noopener noreferrer" + target="_blank" + > + Show More + + + )} + + ) : ( + data + )} + + ) +} + +interface GenericCustomDataProps { + amount?: string + data: string + recipient: string +} + +const GenericCustomData = ({ amount = '0', data, recipient }: GenericCustomDataProps): React.ReactElement => { + const classes = useStyles() + const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) + + return ( + + + Send {amount} to: + {recipientName ? ( + + ) : ( + + )} + + + Data (hex encoded): + + + + ) +} + +interface CustomDescriptionProps { + amount?: string + data: string + recipient: string + storedTx: Transaction +} + +const CustomDescription = ({ amount, data, recipient, storedTx }: CustomDescriptionProps): React.ReactElement => { + const classes = useStyles() + + return storedTx.multiSendTx ? ( + + {extractMultiSendDecodedData(storedTx).txDetails?.map((tx, index) => ( + + ))} + + ) : ( + + ) +} + +export default CustomDescription diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx new file mode 100644 index 00000000..974382b9 --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx @@ -0,0 +1,153 @@ +import { useSelector } from 'react-redux' +import React from 'react' + +import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' +import Block from 'src/components/layout/Block' +import Bold from 'src/components/layout/Bold' +import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' +import EtherscanLink from 'src/components/EtherscanLink' +import Paragraph from 'src/components/layout/Paragraph' +import { SAFE_METHODS_NAMES, SafeMethods } from 'src/routes/safe/store/models/types/transactions.d' + +export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner' +export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner' +export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold' +export const TRANSACTIONS_DESC_ADD_MODULE_TEST_ID = 'tx-description-add-module' +export const TRANSACTIONS_DESC_REMOVE_MODULE_TEST_ID = 'tx-description-remove-module' +export const TRANSACTIONS_DESC_NO_DATA = 'tx-description-no-data' + +interface RemovedOwnerProps { + removedOwner: string +} + +const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement => { + const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, removedOwner)) + + return ( + + Remove owner: + {ownerChangedName ? ( + + ) : ( + + )} + + ) +} + +interface AddedOwnerProps { + addedOwner: string +} + +const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => { + const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, addedOwner)) + + return ( + + Add owner: + {ownerChangedName ? ( + + ) : ( + + )} + + ) +} + +interface NewThresholdProps { + newThreshold: string +} + +const NewThreshold = ({ newThreshold }: NewThresholdProps): React.ReactElement => ( + + Change required confirmations: + + {newThreshold} + + +) + +interface AddModuleProps { + module: string +} + +const AddModule = ({ module }: AddModuleProps): React.ReactElement => ( + + Add module: + + +) + +interface RemoveModuleProps { + module: string +} + +const RemoveModule = ({ module }: RemoveModuleProps): React.ReactElement => ( + + Remove module: + + +) + +interface SettingsDescriptionProps { + action: SafeMethods + addedOwner?: string + newThreshold?: string + removedOwner?: string + module?: string +} + +const SettingsDescription = ({ + action, + addedOwner, + newThreshold, + removedOwner, + module, +}: SettingsDescriptionProps): React.ReactElement => { + if (action === SAFE_METHODS_NAMES.REMOVE_OWNER && removedOwner && newThreshold) { + return ( + <> + + + + ) + } + + if (action === SAFE_METHODS_NAMES.CHANGE_THRESHOLD && newThreshold) { + return + } + + if (action === SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD && addedOwner && newThreshold) { + return ( + <> + + + + ) + } + + if (action === SAFE_METHODS_NAMES.SWAP_OWNER && removedOwner && addedOwner) { + return ( + <> + + + + ) + } + + if (action === SAFE_METHODS_NAMES.ENABLE_MODULE && module) { + return + } + + if (action === SAFE_METHODS_NAMES.DISABLE_MODULE && module) { + return + } + + return ( + + No data available for current transaction + + ) +} + +export default SettingsDescription diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx new file mode 100644 index 00000000..2e206a1f --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { useSelector } from 'react-redux' + +import { TRANSACTIONS_DESC_SEND_TEST_ID } from './index' +import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' +import Block from 'src/components/layout/Block' +import Bold from 'src/components/layout/Bold' +import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' +import EtherscanLink from 'src/components/EtherscanLink' + +interface TransferDescriptionProps { + amount: string + recipient: string +} + +const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => { + const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) + return ( + + Send {amount} to: + {recipientName ? ( + + ) : ( + + )} + + ) +} + +export default TransferDescription diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx new file mode 100644 index 00000000..172515d8 --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx @@ -0,0 +1,83 @@ +import { Text } from '@gnosis.pm/safe-react-components' +import { makeStyles } from '@material-ui/core/styles' +import React from 'react' +import styled from 'styled-components' + +import { styles } from './styles' + +import { + isAddress, + isArrayParameter, +} from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/utils' +import { useWindowDimensions } from 'src/routes/safe/container/hooks/useWindowDimensions' +import SafeEtherscanLink from 'src/components/EtherscanLink' + +const useStyles = makeStyles(styles) + +const InlineText = styled(Text)` + display: inline-flex; +` + +const NestedWrapper = styled.div` + text-indent: 24px; +` + +interface RenderValueProps { + method: string + type: string + value: string | string[] +} + +const EtherscanLink = ({ method, type, value }: RenderValueProps): React.ReactElement => { + const classes = useStyles() + const [cut, setCut] = React.useState(undefined) + const { width } = useWindowDimensions() + + React.useEffect(() => { + if (width <= 900) { + setCut(4) + } else if (width <= 1024) { + setCut(8) + } else { + setCut(12) + } + }, [width]) + + if (isArrayParameter(type)) { + return ( + + {(value as string[]).map((value, index) => ( + + ))} + + ) + } + + return +} + +const GenericValue = ({ method, type, value }: RenderValueProps): React.ReactElement => { + if (isArrayParameter(type)) { + return ( + + {(value as string[]).map((value, index) => ( + + {value} + + ))} + + ) + } + + return {value as string} +} + +const Value = ({ type, ...props }: RenderValueProps): React.ReactElement => { + if (isAddress(type)) { + return + } + + return +} + +export default Value diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx index eee870b5..d2ce012f 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/index.tsx @@ -1,267 +1,22 @@ -import { withStyles } from '@material-ui/core/styles' -import React, { useState } from 'react' -import { useSelector } from 'react-redux' +import { makeStyles } from '@material-ui/core/styles' +import React from 'react' +import { styles } from './styles' import { getTxData } from './utils' +import SettingsDescription from './SettingsDescription' +import CustomDescription from './CustomDescription' +import TransferDescription from './TransferDescription' -import EtherscanLink from 'src/components/EtherscanLink' -import Block from 'src/components/layout/Block' -import Bold from 'src/components/layout/Bold' -import LinkWithRef from 'src/components/layout/Link' -import Paragraph from 'src/components/layout/Paragraph' -import { getNameFromAddressBook } from 'src/logic/addressBook/store/selectors' -import { SAFE_METHODS_NAMES, SafeMethods } from 'src/logic/contracts/methodIds' -import { shortVersionOf } from 'src/logic/wallets/ethAddresses' -import OwnerAddressTableCell from 'src/routes/safe/components/Settings/ManageOwners/OwnerAddressTableCell' import { getTxAmount } from 'src/routes/safe/components/Transactions/TxsTable/columns' +import Block from 'src/components/layout/Block' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' -import { lg, md } from 'src/theme/variables' - -export const TRANSACTIONS_DESC_ADD_OWNER_TEST_ID = 'tx-description-add-owner' -export const TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID = 'tx-description-remove-owner' -export const TRANSACTIONS_DESC_CHANGE_THRESHOLD_TEST_ID = 'tx-description-change-threshold' export const TRANSACTIONS_DESC_SEND_TEST_ID = 'tx-description-send' -export const TRANSACTIONS_DESC_CUSTOM_VALUE_TEST_ID = 'tx-description-custom-value' -export const TRANSACTIONS_DESC_CUSTOM_DATA_TEST_ID = 'tx-description-custom-data' -export const TRANSACTIONS_DESC_ADD_MODULE_TEST_ID = 'tx-description-add-module' -export const TRANSACTIONS_DESC_REMOVE_MODULE_TEST_ID = 'tx-description-remove-module' -export const TRANSACTIONS_DESC_NO_DATA = 'tx-description-no-data' -export const styles = () => ({ - txDataContainer: { - paddingTop: lg, - paddingLeft: md, - paddingBottom: md, - }, - txData: { - wordBreak: 'break-all', - }, - txDataParagraph: { - whiteSpace: 'normal', - }, - linkTxData: { - textDecoration: 'underline', - cursor: 'pointer', - }, -}) +const useStyles = makeStyles(styles) -interface TransferDescriptionProps { - amount: string - recipient: string -} - -const TransferDescription = ({ amount = '', recipient }: TransferDescriptionProps): React.ReactElement => { - const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) - return ( - - Send {amount} to: - {recipientName ? ( - - ) : ( - - )} - - ) -} - -interface RemovedOwnerProps { - removedOwner: string -} - -const RemovedOwner = ({ removedOwner }: RemovedOwnerProps): React.ReactElement => { - const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, removedOwner)) - - return ( - - Remove owner: - {ownerChangedName ? ( - - ) : ( - - )} - - ) -} - -interface AddedOwnerProps { - addedOwner: string -} - -const AddedOwner = ({ addedOwner }: AddedOwnerProps): React.ReactElement => { - const ownerChangedName = useSelector((state) => getNameFromAddressBook(state, addedOwner)) - - return ( - - Add owner: - {ownerChangedName ? ( - - ) : ( - - )} - - ) -} - -interface NewThresholdProps { - newThreshold: string -} - -const NewThreshold = ({ newThreshold }: NewThresholdProps): React.ReactElement => ( - - Change required confirmations: - - {newThreshold} - - -) - -interface AddModuleProps { - module: string -} - -const AddModule = ({ module }: AddModuleProps): React.ReactElement => ( - - Add module: - - -) - -interface RemoveModuleProps { - module: string -} - -const RemoveModule = ({ module }: RemoveModuleProps): React.ReactElement => ( - - Remove module: - - -) - -interface SettingsDescriptionProps { - action: SafeMethods - addedOwner?: string - newThreshold?: string - removedOwner?: string - module?: string -} - -const SettingsDescription = ({ - action, - addedOwner, - newThreshold, - removedOwner, - module, -}: SettingsDescriptionProps): React.ReactElement => { - if (action === SAFE_METHODS_NAMES.REMOVE_OWNER && removedOwner && newThreshold) { - return ( - <> - - - - ) - } - - if (action === SAFE_METHODS_NAMES.CHANGE_THRESHOLD && newThreshold) { - return - } - - if (action === SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD && addedOwner && newThreshold) { - return ( - <> - - - - ) - } - - if (action === SAFE_METHODS_NAMES.SWAP_OWNER && removedOwner && addedOwner) { - return ( - <> - - - - ) - } - - if (action === SAFE_METHODS_NAMES.ENABLE_MODULE && module) { - return - } - - if (action === SAFE_METHODS_NAMES.DISABLE_MODULE && module) { - return - } - - return ( - - No data available for current transaction - - ) -} - -const TxData = (props) => { - const { classes, data } = props - const [showTxData, setShowTxData] = useState(false) - const showExpandBtn = data.length > 20 - return ( - - {showExpandBtn ? ( - <> - {showTxData ? ( - <> - {data}{' '} - setShowTxData(false)} - rel="noopener noreferrer" - target="_blank" - > - Show Less - - - ) : ( - <> - {shortVersionOf(data, 20)}{' '} - setShowTxData(true)} - rel="noopener noreferrer" - target="_blank" - > - Show More - - - )} - - ) : ( - data - )} - - ) -} - -const CustomDescription = ({ amount = 0, classes, data, recipient }: any) => { - const recipientName = useSelector((state) => getNameFromAddressBook(state, recipient)) - return ( - <> - - Send {amount} to: - {recipientName ? ( - - ) : ( - - )} - - - Data (hex encoded): - - - - ) -} - -const TxDescription = ({ classes, tx }) => { +const TxDescription = ({ tx }: { tx: Transaction }): React.ReactElement => { + const classes = useStyles() const { action, addedOwner, @@ -288,9 +43,7 @@ const TxDescription = ({ classes, tx }) => { module={module} /> )} - {!upgradeTx && customTx && ( - - )} + {!upgradeTx && customTx && } {upgradeTx &&
{data}
} {!cancellationTx && !modifySettingsTx && !customTx && !creationTx && !upgradeTx && ( @@ -299,4 +52,4 @@ const TxDescription = ({ classes, tx }) => { ) } -export default withStyles(styles as any)(TxDescription) +export default TxDescription diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts new file mode 100644 index 00000000..d1c1e8d7 --- /dev/null +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts @@ -0,0 +1,43 @@ +import { createStyles } from '@material-ui/core/styles' +import { lg, md } from 'src/theme/variables' + +export const styles = createStyles({ + txDataContainer: { + paddingTop: lg, + paddingLeft: md, + paddingBottom: md, + }, + txData: { + wordBreak: 'break-all', + }, + txDataParagraph: { + whiteSpace: 'normal', + }, + linkTxData: { + textDecoration: 'underline', + cursor: 'pointer', + }, + multiSendTxData: { + marginTop: `-${lg}`, + marginLeft: `-${md}`, + }, + collapse: { + borderBottom: `2px solid rgb(232, 231, 230)`, + }, + collapseHeaderWrapper: { + display: 'flex', + flexDirection: 'row', + alignContent: 'center', + alignItems: 'center', + justifyContent: 'space-between', + padding: '8px 8px 8px 16px', + borderBottom: '2px solid rgb(232, 231, 230)', + + '&:hover': { + cursor: 'pointer', + }, + }, + address: { + display: 'inline-flex', + }, +}) diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts index 64f02975..eebf7115 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/utils.ts @@ -1,4 +1,5 @@ -import { SAFE_METHODS_NAMES } from 'src/logic/contracts/methodIds' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' +import { SAFE_METHODS_NAMES } from 'src/routes/safe/store/models/types/transactions.d' const getSafeVersion = (data) => { const contractAddress = data.substr(340, 40).toLowerCase() @@ -10,8 +11,26 @@ const getSafeVersion = (data) => { ) } -export const getTxData = (tx) => { - const txData: any = {} +interface TxData { + data?: string + recipient?: string + module?: string + action?: string + addedOwner?: string + removedOwner?: string + newThreshold?: string + tokenId?: string + isTokenTransfer?: boolean + isCollectibleTransfer?: boolean + modifySettingsTx?: boolean + customTx?: boolean + cancellationTx?: boolean + creationTx?: boolean + upgradeTx?: boolean +} + +export const getTxData = (tx: Transaction): TxData => { + const txData: TxData = {} if (tx.decodedParams) { if (tx.isTokenTransfer) { @@ -62,6 +81,8 @@ export const getTxData = (tx) => { txData.customTx = true } else { txData.recipient = tx.recipient + txData.data = tx.data + txData.customTx = true } } else if (tx.customTx) { txData.recipient = tx.recipient diff --git a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx index 645f3412..1db95e78 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx @@ -8,9 +8,11 @@ import ApproveTxModal from './ApproveTxModal' import OwnersColumn from './OwnersColumn' import RejectTxModal from './RejectTxModal' import TxDescription from './TxDescription' +import { IncomingTx } from './IncomingTx' +import { CreationTx } from './CreationTx' +import { OutgoingTx } from './OutgoingTx' import { styles } from './style' -import { Transaction } from 'src/routes/safe/store/models/types/transaction' import { getNetwork } from 'src/config' import Block from 'src/components/layout/Block' import Bold from 'src/components/layout/Bold' @@ -21,21 +23,17 @@ import Row from 'src/components/layout/Row' import Span from 'src/components/layout/Span' import IncomingTxDescription from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/IncomingTxDescription' import { INCOMING_TX_TYPES } from 'src/routes/safe/store/models/incomingTransaction' - import { safeNonceSelector, safeThresholdSelector } from 'src/routes/safe/store/selectors' -import { IncomingTx } from './IncomingTx' -import { CreationTx } from './CreationTx' -import { OutgoingTx } from './OutgoingTx' -import { TransactionTypes } from 'src/routes/safe/store/models/types/transaction' +import { Transaction, TransactionTypes } from 'src/routes/safe/store/models/types/transaction' const useStyles = makeStyles(styles as any) -type Props = { +interface ExpandedTxProps { cancelTx: Transaction tx: Transaction } -const ExpandedTx = ({ cancelTx, tx }: Props): React.ReactElement => { +const ExpandedTx = ({ cancelTx, tx }: ExpandedTxProps): React.ReactElement => { const classes = useStyles() const nonce = useSelector(safeNonceSelector) const threshold = useSelector(safeThresholdSelector) diff --git a/src/routes/safe/components/Transactions/TxsTable/columns.tsx b/src/routes/safe/components/Transactions/TxsTable/columns.tsx index 93e09c32..faf7b43f 100644 --- a/src/routes/safe/components/Transactions/TxsTable/columns.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/columns.tsx @@ -8,8 +8,11 @@ import React from 'react' import TxType from './TxType' import { buildOrderFieldFrom } from 'src/components/Table/sorting' +import { TableColumn } from 'src/components/Table/types' import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { INCOMING_TX_TYPES } from 'src/routes/safe/store/models/incomingTransaction' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' +import { CancellationTransactions } from 'src/routes/safe/store/reducer/cancellationTransactions' export const TX_TABLE_ID = 'id' export const TX_TABLE_TYPE_ID = 'type' @@ -20,11 +23,20 @@ export const TX_TABLE_RAW_TX_ID = 'tx' export const TX_TABLE_RAW_CANCEL_TX_ID = 'cancelTx' export const TX_TABLE_EXPAND_ICON = 'expand' -export const formatDate = (date) => format(parseISO(date), 'MMM d, yyyy - HH:mm:ss') +export const formatDate = (date: string): string => format(parseISO(date), 'MMM d, yyyy - HH:mm:ss') const NOT_AVAILABLE = 'n/a' -const getAmountWithSymbol = ({ decimals = 0, symbol = NOT_AVAILABLE, value }, formatted = false) => { +interface AmountData { + decimals?: number | string + symbol?: string + value: number | string +} + +const getAmountWithSymbol = ( + { decimals = 0, symbol = NOT_AVAILABLE, value }: AmountData, + formatted = false, +): string => { const nonFormattedValue = new BigNumber(value).times(`1e-${decimals}`).toFixed() const finalValue = formatted ? formatAmount(nonFormattedValue).toString() : nonFormattedValue const txAmount = finalValue === 'NaN' ? NOT_AVAILABLE : finalValue @@ -32,7 +44,7 @@ const getAmountWithSymbol = ({ decimals = 0, symbol = NOT_AVAILABLE, value }, fo return `${txAmount} ${symbol}` } -export const getIncomingTxAmount = (tx, formatted = true) => { +export const getIncomingTxAmount = (tx: Transaction, formatted = true): string => { // simple workaround to avoid displaying unexpected values for incoming NFT transfer if (INCOMING_TX_TYPES[tx.type] === INCOMING_TX_TYPES.ERC721_TRANSFER) { return `1 ${tx.symbol}` @@ -41,9 +53,9 @@ export const getIncomingTxAmount = (tx, formatted = true) => { return getAmountWithSymbol(tx, formatted) } -export const getTxAmount = (tx, formatted = true) => { +export const getTxAmount = (tx: Transaction, formatted = true): string => { const { decimals = 18, decodedParams, isTokenTransfer, symbol } = tx - const { value } = isTokenTransfer && !!decodedParams && !!decodedParams.transfer ? decodedParams.transfer : tx + const { value } = isTokenTransfer && !!decodedParams?.transfer ? decodedParams.transfer : tx if (tx.isCollectibleTransfer) { return `1 ${tx.symbol}` @@ -56,8 +68,19 @@ export const getTxAmount = (tx, formatted = true) => { return getAmountWithSymbol({ decimals, symbol, value }, formatted) } -const getIncomingTxTableData = (tx) => ({ - [TX_TABLE_ID]: tx.blockNumber, +interface TableData { + amount: string + cancelTx?: Transaction + date: string + dateOrder?: number + id: string + status: string + tx?: Transaction + type: any +} + +const getIncomingTxTableData = (tx: Transaction): TableData => ({ + [TX_TABLE_ID]: tx.blockNumber?.toString() ?? '', [TX_TABLE_TYPE_ID]: , [TX_TABLE_DATE_ID]: formatDate(tx.executionDate), [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: getTime(parseISO(tx.executionDate)), @@ -66,11 +89,11 @@ const getIncomingTxTableData = (tx) => ({ [TX_TABLE_RAW_TX_ID]: tx, }) -const getTransactionTableData = (tx, cancelTx) => { +const getTransactionTableData = (tx: Transaction, cancelTx: Transaction): TableData => { const txDate = tx.submissionDate return { - [TX_TABLE_ID]: tx.blockNumber, + [TX_TABLE_ID]: tx.blockNumber?.toString() ?? '', [TX_TABLE_TYPE_ID]: , [TX_TABLE_DATE_ID]: txDate ? formatDate(txDate) : '', [buildOrderFieldFrom(TX_TABLE_DATE_ID)]: txDate ? getTime(parseISO(txDate)) : null, @@ -81,7 +104,10 @@ const getTransactionTableData = (tx, cancelTx) => { } } -export const getTxTableData = (transactions, cancelTxs) => { +export const getTxTableData = ( + transactions: List, + cancelTxs: CancellationTransactions, +): List => { return transactions.map((tx) => { if (INCOMING_TX_TYPES[tx.type] !== undefined) { return getIncomingTxTableData(tx) @@ -91,7 +117,7 @@ export const getTxTableData = (transactions, cancelTxs) => { }) } -export const generateColumns = () => { +export const generateColumns = (): List => { const nonceColumn = { id: TX_TABLE_ID, disablePadding: false, diff --git a/src/routes/safe/components/Transactions/TxsTable/index.tsx b/src/routes/safe/components/Transactions/TxsTable/index.tsx index bb74eea8..ed745699 100644 --- a/src/routes/safe/components/Transactions/TxsTable/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/index.tsx @@ -84,7 +84,7 @@ const TxsTable = ({ classes }) => { onClick={() => handleTxExpand(row.tx.safeTxHash)} tabIndex={-1} > - {autoColumns.map((column: any) => ( + {autoColumns.map((column) => ( getTxTableData', () => { const mockedCancelTransaction = makeTransaction({ nonce: 1, blockNumber: 123 }) // When - const txTableData = getTxTableData(List([mockedTransaction]), List([mockedCancelTransaction])) + const txTableData = getTxTableData(List([mockedTransaction]), Map( { '1': mockedCancelTransaction })) const txRow = txTableData.first() // Then @@ -23,7 +22,7 @@ describe('TxsTable Columns > getTxTableData', () => { const mockedCancelTransaction = makeTransaction({ nonce: 2, blockNumber: 123 }) // When - const txTableData = getTxTableData(List([mockedTransaction]), List([mockedCancelTransaction])) + const txTableData = getTxTableData(List([mockedTransaction]), Map( { '2': mockedCancelTransaction })) const txRow = txTableData.first() // Then diff --git a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts index 01124891..674fe29e 100644 --- a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts +++ b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts @@ -7,11 +7,11 @@ import { PROVIDER_REDUCER_ID } from 'src/logic/wallets/store/reducer/provider' import { buildTx, isCancelTransaction } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers' import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' import { store } from 'src/store' -import { DataDecoded } from 'src/logic/contracts/methodIds' import fetchTransactions from 'src/routes/safe/store/actions/transactions/fetchTransactions/fetchTransactions' import { Transaction, TransactionTypes } from 'src/routes/safe/store/models/types/transaction' import { Token } from 'src/logic/tokens/store/model/token' import { SafeRecord } from 'src/routes/safe/store/models/safe' +import { DataDecoded } from 'src/routes/safe/store/models/types/transactions.d' export type ConfirmationServiceModel = { confirmationType: string diff --git a/src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts b/src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts new file mode 100644 index 00000000..09a7cefa --- /dev/null +++ b/src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts @@ -0,0 +1,61 @@ +import { TransferDetails } from './transferDetails.d' +import { + DataDecoded, + Operation, + Parameter, + Transfer, + TransferType, +} from 'src/routes/safe/store/models/types/transactions.d' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' +import { + extractERC20TransferDetails, + extractERC721TransferDetails, + extractETHTransferDetails, + extractUnknownTransferDetails, +} from './transferDetails' +import { isMultiSendParameter } from './newTransactionHelpers' + +export type MultiSendDetails = { + operation: keyof typeof Operation + to: string + data: DataDecoded | null + value: number +} + +export type MultiSendDecodedData = { + txDetails?: MultiSendDetails[] + transfersDetails?: TransferDetails[] +} + +export const extractTransferDetails = (transfer: Transfer): TransferDetails => { + switch (TransferType[transfer.type]) { + case TransferType.ERC20_TRANSFER: + return extractERC20TransferDetails(transfer) + case TransferType.ERC721_TRANSFER: + return extractERC721TransferDetails(transfer) + case TransferType.ETHER_TRANSFER: + return extractETHTransferDetails(transfer) + default: + return extractUnknownTransferDetails(transfer) + } +} + +export const extractMultiSendDetails = (parameter: Parameter): MultiSendDetails[] | undefined => { + if (isMultiSendParameter(parameter)) { + return parameter.decodedValue.map((decodedValue) => { + return { + operation: decodedValue.operation, + to: decodedValue.to, + value: decodedValue.value, + data: decodedValue?.decodedData ?? null, + } + }) + } +} + +export const extractMultiSendDecodedData = (tx: Transaction): MultiSendDecodedData => { + const transfersDetails = tx.transfers?.map(extractTransferDetails) + const txDetails = extractMultiSendDetails(tx.dataDecoded?.parameters[0]) + + return { txDetails, transfersDetails } +} diff --git a/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts b/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts new file mode 100644 index 00000000..d10f801a --- /dev/null +++ b/src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts @@ -0,0 +1,25 @@ +import { + EthereumTransaction, + ModuleTransaction, + MultiSendMethodParameter, + MultiSigTransaction, + Parameter, + Transaction, + TxType, +} from 'src/routes/safe/store/models/types/transactions.d' + +export const isMultiSigTx = (tx: Transaction): tx is MultiSigTransaction => { + return TxType[tx.txType] === TxType.MULTISIG_TRANSACTION +} + +export const isModuleTx = (tx: Transaction): tx is ModuleTransaction => { + return TxType[tx.txType] === TxType.MODULE_TRANSACTION +} + +export const isEthereumTx = (tx: Transaction): tx is EthereumTransaction => { + return TxType[tx.txType] === TxType.ETHEREUM_TRANSACTION +} + +export const isMultiSendParameter = (parameter: Parameter): parameter is MultiSendMethodParameter => { + return !!(parameter as MultiSendMethodParameter)?.decodedValue +} diff --git a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts index df876fec..b879b3dd 100644 --- a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts +++ b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts @@ -1,6 +1,6 @@ import { List, Map } from 'immutable' -import { DecodedMethods, decodeMethods } from 'src/logic/contracts/methodIds' +import { decodeMethods } from 'src/logic/contracts/methodIds' import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens' import { getERC20DecimalsAndSymbol, @@ -24,7 +24,7 @@ import { import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions' import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions' -import { store } from 'src/store' +import { AppReduxState, store } from 'src/store' import { safeSelector, safeTransactionsSelector } from 'src/routes/safe/store/selectors' import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions' import { @@ -35,6 +35,7 @@ import { TypedDataUtils } from 'eth-sig-util' import { Token } from 'src/logic/tokens/store/model/token' import { ProviderRecord } from 'src/logic/wallets/store/model/provider' import { SafeRecord } from 'src/routes/safe/store/models/safe' +import { DecodedParams } from 'src/routes/safe/store/models/types/transactions.d' export const isEmptyData = (data?: string | null): boolean => { return !data || data === EMPTY_DATA @@ -130,7 +131,7 @@ export const getRefundParams = async ( return refundParams } -export const getDecodedParams = (tx: TxServiceModel): DecodedMethods => { +export const getDecodedParams = (tx: TxServiceModel): DecodedParams | null => { if (tx.dataDecoded) { return { [tx.dataDecoded.method]: tx.dataDecoded.parameters.reduce( @@ -276,6 +277,7 @@ export const buildTx = async ({ creationTx: tx.creationTx, customTx: isCustomTx, data: tx.data ? tx.data : EMPTY_DATA, + dataDecoded: tx.dataDecoded, decimals: tokenDecimals, decodedParams, executionDate: tx.executionDate, @@ -315,7 +317,7 @@ export type TxToMock = TxArgs & { value: string } -export const mockTransaction = (tx: TxToMock, safeAddress: string, state): Promise => { +export const mockTransaction = (tx: TxToMock, safeAddress: string, state: AppReduxState): Promise => { const submissionDate = new Date().toISOString() const transactionStructure: TxServiceModel = { diff --git a/src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts b/src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts new file mode 100644 index 00000000..a95f1795 --- /dev/null +++ b/src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts @@ -0,0 +1,50 @@ +export interface IncomingTransferDetails { + from: string +} + +export interface OutgoingTransferDetails { + to: string +} + +export interface CommonERC20TransferDetails { + tokenAddress: string + value: string + name: string + txHash: string | null +} + +export interface IncomingERC20TransferDetails extends CommonERC20TransferDetails, IncomingTransferDetails {} + +export interface OutgoingERC20TransferDetails extends CommonERC20TransferDetails, OutgoingTransferDetails {} + +export type ERC20TransferDetails = IncomingERC20TransferDetails | OutgoingERC20TransferDetails + +export interface CommonERC721TransferDetails { + tokenAddress: string + tokenId: string | null + txHash: string | null +} + +export interface IncomingERC721TransferDetails extends CommonERC721TransferDetails, IncomingTransferDetails {} + +export interface OutgoingERC721TransferDetails extends CommonERC721TransferDetails, OutgoingTransferDetails {} + +export type ERC721TransferDetails = IncomingERC721TransferDetails | OutgoingERC721TransferDetails + +export interface CommonETHTransferDetails { + value: string + txHash: string | null +} + +export interface IncomingETHTransferDetails extends CommonETHTransferDetails, IncomingTransferDetails {} + +export interface OutgoingETHTransferDetails extends CommonETHTransferDetails, OutgoingTransferDetails {} + +export type ETHTransferDetails = IncomingETHTransferDetails | OutgoingETHTransferDetails + +export interface UnknownTransferDetails extends IncomingTransferDetails, OutgoingTransferDetails { + value: string + txHash: string +} + +export type TransferDetails = ERC20TransferDetails | ERC721TransferDetails | ETHTransferDetails | UnknownTransferDetails diff --git a/src/routes/safe/store/actions/transactions/utils/transferDetails.ts b/src/routes/safe/store/actions/transactions/utils/transferDetails.ts new file mode 100644 index 00000000..e29ff869 --- /dev/null +++ b/src/routes/safe/store/actions/transactions/utils/transferDetails.ts @@ -0,0 +1,85 @@ +import { Transfer, TxConstants } from 'src/routes/safe/store/models/types/transactions.d' +import { sameAddress } from 'src/logic/wallets/ethAddresses' +import { store } from 'src/store' +import { safeParamAddressFromStateSelector } from 'src/routes/safe/store/selectors' +import { + ERC20TransferDetails, + ERC721TransferDetails, + ETHTransferDetails, + UnknownTransferDetails, +} from './transferDetails.d' +import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue' + +const isIncomingTransfer = (transfer: Transfer): boolean => { + // TODO: prevent using `store` here and receive `safeAddress` as a param + const state = store.getState() + const safeAddress = safeParamAddressFromStateSelector(state) + return sameAddress(transfer.to, safeAddress) +} + +export const extractERC20TransferDetails = (transfer: Transfer): ERC20TransferDetails => { + const erc20TransferDetails = { + tokenAddress: transfer.tokenInfo?.address || TxConstants.UNKNOWN, + value: humanReadableValue(transfer.value, transfer.tokenInfo?.decimals), + name: transfer.tokenInfo?.name || transfer.tokenInfo?.symbol || TxConstants.UNKNOWN, + txHash: transfer.transactionHash, + } + + if (isIncomingTransfer(transfer)) { + return { + ...erc20TransferDetails, + from: transfer.from, + } + } + + return { + ...erc20TransferDetails, + to: transfer.to, + } +} + +export const extractERC721TransferDetails = (transfer: Transfer): ERC721TransferDetails => { + const erc721TransferDetails = { + tokenAddress: transfer.tokenAddress, + tokenId: transfer.tokenId, + txHash: transfer.transactionHash, + } + if (isIncomingTransfer(transfer)) { + return { + ...erc721TransferDetails, + from: transfer.from, + } + } + + return { + ...erc721TransferDetails, + to: transfer.to, + } +} + +export const extractETHTransferDetails = (transfer: Transfer): ETHTransferDetails => { + const ethTransferDetails = { + value: humanReadableValue(transfer.value), + txHash: transfer.transactionHash, + } + if (isIncomingTransfer(transfer)) { + return { + ...ethTransferDetails, + from: transfer.from, + } + } + + return { + ...ethTransferDetails, + to: transfer.to, + } +} + +export const extractUnknownTransferDetails = (transfer: Transfer): UnknownTransferDetails => { + return { + value: transfer?.value || TxConstants.UNKNOWN, + txHash: transfer?.transactionHash || TxConstants.UNKNOWN, + to: transfer?.to || TxConstants.UNKNOWN, + from: transfer?.from || TxConstants.UNKNOWN, + } +} diff --git a/src/routes/safe/store/models/transaction.ts b/src/routes/safe/store/models/transaction.ts index 9be57dab..112baf83 100644 --- a/src/routes/safe/store/models/transaction.ts +++ b/src/routes/safe/store/models/transaction.ts @@ -18,6 +18,7 @@ export const makeTransaction = Record({ creationTx: false, customTx: false, data: null, + dataDecoded: null, decimals: 18, decodedParams: {}, executionDate: '', diff --git a/src/routes/safe/store/models/types/transaction.ts b/src/routes/safe/store/models/types/transaction.ts index 055fedf2..96a6220a 100644 --- a/src/routes/safe/store/models/types/transaction.ts +++ b/src/routes/safe/store/models/types/transaction.ts @@ -1,7 +1,7 @@ import { List, Map, RecordOf } from 'immutable' -import { DecodedMethods } from 'src/logic/contracts/methodIds' import { Confirmation } from './confirmation' import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d' +import { DataDecoded, DecodedParams, Transfer } from './transactions' export enum TransactionTypes { INCOMING = 'incoming', @@ -43,13 +43,14 @@ export type TransactionProps = { creationTx: boolean customTx: boolean data?: string | null + dataDecoded: DataDecoded | null decimals?: (number | string) | null - decodedParams: DecodedMethods + decodedParams: DecodedParams | null executionDate?: string | null executionTxHash?: string | null executor: string factoryAddress: string - fee: string | null // It will be replace with the new TXs types. + fee?: string // It will be replace with the new TXs types. gasPrice: string gasToken: string isCancellationTx: boolean @@ -75,6 +76,7 @@ export type TransactionProps = { submissionDate?: string | null symbol?: string | null transactionHash: string | null + transfers?: Transfer[] type: TransactionTypes upgradeTx: boolean value: string diff --git a/src/routes/safe/store/models/types/transactions.d.ts b/src/routes/safe/store/models/types/transactions.d.ts new file mode 100644 index 00000000..c1f88e77 --- /dev/null +++ b/src/routes/safe/store/models/types/transactions.d.ts @@ -0,0 +1,254 @@ +export enum TxConstants { + MULTI_SEND = 'multiSend', + UNKNOWN = 'UNKNOWN', +} + +export enum Operation { + CALL = 'CALL', + DELEGATE_CALL = 'DELEGATE_CALL', + CREATE = 'CREATE', +} + +// types comes from: https://github.com/gnosis/safe-client-gateway/blob/752e76b6d1d475791dbd7917b174bb41d2d9d8be/src/utils.rs +export enum TransferMethods { + TRANSFER = 'transfer', + TRANSFER_FROM = 'transferFrom', + SAFE_TRANSFER_FROM = 'safeTransferFrom', +} + +export enum SettingsChangeMethods { + SETUP = 'setup', + SET_FALLBACK_HANDLER = 'setFallbackHandler', + ADD_OWNER_WITH_THRESHOLD = 'addOwnerWithThreshold', + REMOVE_OWNER = 'removeOwner', + REMOVE_OWNER_WITH_THRESHOLD = 'removeOwnerWithThreshold', + SWAP_OWNER = 'swapOwner', + CHANGE_THRESHOLD = 'changeThreshold', + CHANGE_MASTER_COPY = 'changeMasterCopy', + ENABLE_MODULE = 'enableModule', + DISABLE_MODULE = 'disableModule', + EXEC_TRANSACTION_FROM_MODULE = 'execTransactionFromModule', + APPROVE_HASH = 'approveHash', + EXEC_TRANSACTION = 'execTransaction', +} + +// note: this extends SAFE_METHODS_NAMES in /logic/contracts/methodIds.ts, we need to figure out which one we are going to use +export type DataDecodedMethod = TransferMethods | SettingsChangeMethods | string + +export interface DecodedValue { + operation: Operation + to: string + value: number + data: string + decodedData: DataDecoded +} + +export interface SingleTransactionMethodParameter { + name: string + type: string + value: string +} + +export interface MultiSendMethodParameter extends SingleTransactionMethodParameter { + decodedValue: DecodedValue[] +} + +export type Parameter = MultiSendMethodParameter | SingleTransactionMethodParameter + +export interface DataDecoded { + method: DataDecodedMethod + parameters: Parameter[] +} + +export enum ConfirmationType { + CONFIRMATION = 'CONFIRMATION', + EXECUTION = 'EXECUTION', +} + +export enum SignatureType { + CONTRACT_SIGNATURE = 'CONTRACT_SIGNATURE', + APPROVED_HASH = 'APPROVED_HASH', + EOA = 'EOA', + ETH_SIGN = 'ETH_SIGN', +} + +export interface Confirmation { + owner: string + submissionDate: string + transactionHash: string | null + confirmationType: ConfirmationType + signature: string + signatureType: SignatureType +} + +export enum TokenType { + ERC20 = 'ERC20', + ERC721 = 'ERC721', + OTHER = 'OTHER', +} + +export interface TokenInfo { + type: TokenType + address: string + name: string + symbol: string + decimals: number + logoUri: string +} + +export enum TransferType { + ETHER_TRANSFER = 'ETHER_TRANSFER', + ERC20_TRANSFER = 'ERC20_TRANSFER', + ERC721_TRANSFER = 'ERC721_TRANSFER', + UNKNOWN = 'UNKNOWN', +} + +export interface Transfer { + type: TransferType + executionDate: string + blockNumber: number + transactionHash: string | null + to: string + value: string | null + tokenId: string | null + tokenAddress: string + tokenInfo: TokenInfo | null + from: string +} + +export enum TxType { + MULTISIG_TRANSACTION = 'MULTISIG_TRANSACTION', + ETHEREUM_TRANSACTION = 'ETHEREUM_TRANSACTION', + MODULE_TRANSACTION = 'MODULE_TRANSACTION', +} + +export interface MultiSigTransaction { + safe: string + to: string + value: string + data: string | null + operation: number + gasToken: string + safeTxGas: number + baseGas: number + gasPrice: string + refundReceiver: string + nonce: number + executionDate: string | null + submissionDate: string + modified: string + blockNumber: number | null + transactionHash: string | null + safeTxHash: string + executor: string | null + isExecuted: boolean + isSuccessful: boolean | null + ethGasPrice: string | null + gasUsed: number | null + fee: string | null + origin: string | null + dataDecoded: DataDecoded | null + confirmationsRequired: number | null + confirmations: Confirmation[] + signatures: string | null + transfers: Transfer[] + txType: TxType.MULTISIG_TRANSACTION +} + +export interface ModuleTransaction { + created: string + executionDate: string + blockNumber: number + transactionHash: string + safe: string + module: string + to: string + value: string + data: string + operation: Operation + transfers: Transfer[] + txType: TxType.MODULE_TRANSACTION +} + +export interface EthereumTransaction { + executionDate: string + to: string + data: string | null + txHash: string + blockNumber: number + transfers: Transfer[] + txType: TxType.ETHEREUM_TRANSACTION + from: string +} + +export type Transaction = MultiSigTransaction | ModuleTransaction | EthereumTransaction + +// SAFE METHODS TO ITS ID +// https://github.com/gnosis/safe-contracts/blob/development/test/safeMethodNaming.js +// https://github.com/gnosis/safe-contracts/blob/development/contracts/GnosisSafe.sol +// [ +// { name: "addOwnerWithThreshold", id: "0x0d582f13" }, +// { name: "DOMAIN_SEPARATOR_TYPEHASH", id: "0x1db61b54" }, +// { name: "isOwner", id: "0x2f54bf6e" }, +// { name: "execTransactionFromModule", id: "0x468721a7" }, +// { name: "signedMessages", id: "0x5ae6bd37" }, +// { name: "enableModule", id: "0x610b5925" }, +// { name: "changeThreshold", id: "0x694e80c3" }, +// { name: "approvedHashes", id: "0x7d832974" }, +// { name: "changeMasterCopy", id: "0x7de7edef" }, +// { name: "SENTINEL_MODULES", id: "0x85e332cd" }, +// { name: "SENTINEL_OWNERS", id: "0x8cff6355" }, +// { name: "getOwners", id: "0xa0e67e2b" }, +// { name: "NAME", id: "0xa3f4df7e" }, +// { name: "nonce", id: "0xaffed0e0" }, +// { name: "getModules", id: "0xb2494df3" }, +// { name: "SAFE_MSG_TYPEHASH", id: "0xc0856ffc" }, +// { name: "SAFE_TX_TYPEHASH", id: "0xccafc387" }, +// { name: "disableModule", id: "0xe009cfde" }, +// { name: "swapOwner", id: "0xe318b52b" }, +// { name: "getThreshold", id: "0xe75235b8" }, +// { name: "domainSeparator", id: "0xf698da25" }, +// { name: "removeOwner", id: "0xf8dc5dd9" }, +// { name: "VERSION", id: "0xffa1ad74" }, +// { name: "setup", id: "0xa97ab18a" }, +// { name: "execTransaction", id: "0x6a761202" }, +// { name: "requiredTxGas", id: "0xc4ca3a9c" }, +// { name: "approveHash", id: "0xd4d9bdcd" }, +// { name: "signMessage", id: "0x85a5affe" }, +// { name: "isValidSignature", id: "0x20c13b0b" }, +// { name: "getMessageHash", id: "0x0a1028c4" }, +// { name: "encodeTransactionData", id: "0xe86637db" }, +// { name: "getTransactionHash", id: "0xd8d11f78" } +// ] + +export const SAFE_METHODS_NAMES = { + ADD_OWNER_WITH_THRESHOLD: 'addOwnerWithThreshold', + CHANGE_THRESHOLD: 'changeThreshold', + REMOVE_OWNER: 'removeOwner', + SWAP_OWNER: 'swapOwner', + ENABLE_MODULE: 'enableModule', + DISABLE_MODULE: 'disableModule', +} + +export const METHOD_TO_ID = { + '0xe318b52b': SAFE_METHODS_NAMES.SWAP_OWNER, + '0x0d582f13': SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD, + '0xf8dc5dd9': SAFE_METHODS_NAMES.REMOVE_OWNER, + '0x694e80c3': SAFE_METHODS_NAMES.CHANGE_THRESHOLD, + '0x610b5925': SAFE_METHODS_NAMES.ENABLE_MODULE, + '0xe009cfde': SAFE_METHODS_NAMES.DISABLE_MODULE, +} + +export type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES] + +type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom' + +type SafeDecodedParams = { + [key in SafeMethods]?: Record +} + +type TokenDecodedParams = { + [key in TokenMethods]?: Record +} + +export type DecodedParams = SafeDecodedParams | TokenDecodedParams | null diff --git a/src/routes/safe/store/reducer/cancellationTransactions.ts b/src/routes/safe/store/reducer/cancellationTransactions.ts index 29059573..679cf7a8 100644 --- a/src/routes/safe/store/reducer/cancellationTransactions.ts +++ b/src/routes/safe/store/reducer/cancellationTransactions.ts @@ -3,9 +3,13 @@ import { handleActions } from 'redux-actions' import { ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS } from 'src/routes/safe/store/actions/transactions/addOrUpdateCancellationTransactions' import { REMOVE_CANCELLATION_TRANSACTION } from 'src/routes/safe/store/actions/transactions/removeCancellationTransaction' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' export const CANCELLATION_TRANSACTIONS_REDUCER_ID = 'cancellationTransactions' +export type CancellationTransactions = Map +export type CancellationTxState = Map + export default handleActions( { [ADD_OR_UPDATE_CANCELLATION_TRANSACTIONS]: (state, action) => { diff --git a/src/routes/safe/store/reducer/types/safe.d.ts b/src/routes/safe/store/reducer/types/safe.d.ts index 6adb94fd..37443eca 100644 --- a/src/routes/safe/store/reducer/types/safe.d.ts +++ b/src/routes/safe/store/reducer/types/safe.d.ts @@ -1,4 +1,4 @@ -import { SafeRecord } from 'src/routes/safe/store/models/safe' +import { SafeRecord, SafeRecordProps } from 'src/routes/safe/store/models/safe' import { Map } from 'immutable' export type SafesMap = Map diff --git a/src/routes/safe/store/selectors/index.ts b/src/routes/safe/store/selectors/index.ts index 6fb83baf..63f02000 100644 --- a/src/routes/safe/store/selectors/index.ts +++ b/src/routes/safe/store/selectors/index.ts @@ -3,7 +3,10 @@ import { matchPath, RouteComponentProps } from 'react-router-dom' import { createSelector } from 'reselect' import { SAFELIST_ADDRESS, SAFE_PARAM_ADDRESS } from 'src/routes/routes' -import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions' +import { + CANCELLATION_TRANSACTIONS_REDUCER_ID, + CancellationTransactions, +} from 'src/routes/safe/store/reducer/cancellationTransactions' import { INCOMING_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/incomingTransactions' import { SAFE_REDUCER_ID, SafesMap } from 'src/routes/safe/store/reducer/safe' import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions' @@ -81,7 +84,7 @@ export const addressBookQueryParamsSelector = (state: AppReduxState): string | n export const safeCancellationTransactionsSelector = createSelector( cancellationTransactionsSelector, safeParamAddressFromStateSelector, - (cancellationTransactions, address) => { + (cancellationTransactions, address): CancellationTransactions => { if (!cancellationTransactions) { return Map() } @@ -118,9 +121,7 @@ export const safeSelector = createSelector( return undefined } const checksumed = checksumAddress(address) - const safe = safes.get(checksumed) - - return safe + return safes.get(checksumed) }, ) @@ -152,13 +153,16 @@ export const safeActiveAssetsListSelector = createSelector(safeActiveAssetsSelec return Set(safeList) }) -export const safeBlacklistedTokensSelector = createSelector(safeSelector, (safe) => { - if (!safe) { - return List() - } +export const safeBlacklistedTokensSelector = createSelector( + safeSelector, + (safe): Set => { + if (!safe) { + return Set() + } - return safe.blacklistedTokens -}) + return safe.blacklistedTokens + }, +) export const safeBlacklistedAssetsSelector = createSelector( safeSelector, @@ -171,23 +175,12 @@ export const safeBlacklistedAssetsSelector = createSelector( }, ) -export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap) => +export const safeActiveAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set => safes.get(safeAddress).get('activeAssets') -export const safeBlacklistedAssetsSelectorBySafe = (safeAddress, safes) => +export const safeBlacklistedAssetsSelectorBySafe = (safeAddress: string, safes: SafesMap): Set => safes.get(safeAddress).get('blacklistedAssets') -export const safeBalancesSelector = createSelector( - safeSelector, - (safe): Map => { - if (!safe) { - return Map() - } - - return safe.balances - }, -) - const baseSafe = makeSafe() export const safeFieldSelector = (field: K) => ( @@ -198,6 +191,8 @@ export const safeNameSelector = createSelector(safeSelector, safeFieldSelector(' export const safeEthBalanceSelector = createSelector(safeSelector, safeFieldSelector('ethBalance')) +export const safeBalancesSelector = createSelector(safeSelector, safeFieldSelector('balances')) + export const safeNeedsUpdateSelector = createSelector(safeSelector, safeFieldSelector('needsUpdate')) export const safeCurrentVersionSelector = createSelector(safeSelector, safeFieldSelector('currentVersion')) diff --git a/src/routes/safe/store/selectors/transactions.ts b/src/routes/safe/store/selectors/transactions.ts index da4b7782..fcb84332 100644 --- a/src/routes/safe/store/selectors/transactions.ts +++ b/src/routes/safe/store/selectors/transactions.ts @@ -2,9 +2,10 @@ import { List } from 'immutable' import { createSelector } from 'reselect' import { safeIncomingTransactionsSelector, safeTransactionsSelector } from 'src/routes/safe/store/selectors' +import { Transaction } from 'src/routes/safe/store/models/types/transaction' export const extendedTransactionsSelector = createSelector( safeTransactionsSelector, safeIncomingTransactionsSelector, - (transactions, incomingTransactions) => List([...transactions, ...incomingTransactions]), + (transactions, incomingTransactions): List => List([...transactions, ...incomingTransactions]), ) diff --git a/src/store/index.ts b/src/store/index.ts index 6d7179e8..0171b0a1 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,7 @@ +import { Map } from 'immutable' import { connectRouter, routerMiddleware, RouterState } from 'connected-react-router' import { createHashHistory } from 'history' -import { applyMiddleware, combineReducers, compose, createStore, CombinedState } from 'redux' +import { applyMiddleware, combineReducers, compose, createStore, CombinedState, PreloadedState, Store } from 'redux' import thunk from 'redux-thunk' import addressBookMiddleware from 'src/logic/addressBook/store/middleware/addressBookMiddleware' @@ -14,7 +15,10 @@ import { } from 'src/logic/collectibles/store/reducer/collectibles' import cookies, { COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies' import currencyValuesStorageMiddleware from 'src/logic/currencyValues/store/middleware' -import currencyValues, { CURRENCY_VALUES_KEY } from 'src/logic/currencyValues/store/reducer/currencyValues' +import currencyValues, { + CURRENCY_VALUES_KEY, + CurrencyReducerMap, +} from 'src/logic/currencyValues/store/reducer/currencyValues' import currentSession, { CURRENT_SESSION_REDUCER_ID } from 'src/logic/currentSession/store/reducer/currentSession' import notifications, { NOTIFICATIONS_REDUCER_ID } from 'src/logic/notifications/store/reducer/notifications' import tokens, { TOKEN_REDUCER_ID, TokenState } from 'src/logic/tokens/store/reducer/tokens' @@ -24,6 +28,7 @@ import notificationsMiddleware from 'src/routes/safe/store/middleware/notificati import safeStorage from 'src/routes/safe/store/middleware/safeStorage' import cancellationTransactions, { CANCELLATION_TRANSACTIONS_REDUCER_ID, + CancellationTxState, } from 'src/routes/safe/store/reducer/cancellationTransactions' import incomingTransactions, { INCOMING_TRANSACTIONS_REDUCER_ID, @@ -31,8 +36,6 @@ import incomingTransactions, { import safe, { SAFE_REDUCER_ID, SafeReducerMap } from 'src/routes/safe/store/reducer/safe' import transactions, { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions' import { NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/OpenSea' -import { Map } from 'immutable' -import { CurrencyRateValueRecord } from 'src/logic/currencyValues/store/model/currencyValues' export const history = createHashHistory() @@ -74,10 +77,10 @@ export type AppReduxState = CombinedState<{ [NFT_TOKENS_REDUCER_ID]: NFTTokens [TOKEN_REDUCER_ID]: TokenState [TRANSACTIONS_REDUCER_ID]: Map - [CANCELLATION_TRANSACTIONS_REDUCER_ID]: Map + [CANCELLATION_TRANSACTIONS_REDUCER_ID]: CancellationTxState [INCOMING_TRANSACTIONS_REDUCER_ID]: Map [NOTIFICATIONS_REDUCER_ID]: Map - [CURRENCY_VALUES_KEY]: Map + [CURRENCY_VALUES_KEY]: CurrencyReducerMap [COOKIES_REDUCER_ID]: Map [ADDRESS_BOOK_REDUCER_ID]: AddressBookReducerMap [CURRENT_SESSION_REDUCER_ID]: Map @@ -86,4 +89,5 @@ export type AppReduxState = CombinedState<{ export const store: any = createStore(reducers, finalCreateStore) -export const aNewStore = (localState?: any) => createStore(reducers, localState, finalCreateStore) +export const aNewStore = (localState?: PreloadedState): Store => + createStore(reducers, localState, finalCreateStore) diff --git a/src/test/utils/transactions/transactionList.helper.ts b/src/test/utils/transactions/transactionList.helper.ts index ad34f463..7d16566d 100644 --- a/src/test/utils/transactions/transactionList.helper.ts +++ b/src/test/utils/transactions/transactionList.helper.ts @@ -1,14 +1,15 @@ -// import { fireEvent } from '@testing-library/react' import { sleep } from 'src/utils/timer' import { shortVersionOf } from 'src/logic/wallets/ethAddresses' -import { TRANSACTIONS_TAB_BTN_TEST_ID } from 'src/routes/safe/components/Layout/index' +import { TRANSACTIONS_TAB_BTN_TEST_ID } from 'src/routes/safe/components/Layout' import { TRANSACTION_ROW_TEST_ID } from 'src/routes/safe/components/Transactions/TxsTable' +import { + TRANSACTIONS_DESC_SEND_TEST_ID, +} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription' import { TRANSACTIONS_DESC_ADD_OWNER_TEST_ID, TRANSACTIONS_DESC_REMOVE_OWNER_TEST_ID, - TRANSACTIONS_DESC_SEND_TEST_ID, -} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription' +} from 'src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription' export const getLastTransaction = async (SafeDom) => { // Travel to transactions diff --git a/src/utils/storage/index.ts b/src/utils/storage/index.ts index bf0e8fce..d2dff5b7 100644 --- a/src/utils/storage/index.ts +++ b/src/utils/storage/index.ts @@ -24,10 +24,7 @@ export const loadFromStorage = async (key: string): Promise | boolean | string | number | Array, -): Promise => { +export const saveToStorage = async (key: string, value: T): Promise => { try { const stringifiedValue = JSON.stringify(value) await storage.set(`${PREFIX}__${key}`, stringifiedValue) diff --git a/yarn.lock b/yarn.lock index f1acd1e3..e1737943 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2327,11 +2327,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.28.tgz#0e36d718a29355ee51cec83b42d921299200f6d9" integrity sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ== -"@types/node@^10.12.18": - version "10.17.28" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.28.tgz#0e36d718a29355ee51cec83b42d921299200f6d9" - integrity sha512-dzjES1Egb4c1a89C7lKwQh8pwjYmlOAG9dW1pBgxEk57tMrLnssOfEthz8kdkNaBd7lIqQx7APm5+mZ619IiCQ== - "@types/node@^10.3.2": version "10.17.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.27.tgz#391cb391c75646c8ad2a7b6ed3bbcee52d1bdf19" @@ -3005,6 +3000,13 @@ ansi-colors@4.1.1, ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" + integrity sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA== + dependencies: + ansi-wrap "^0.1.0" + ansi-colors@^3.0.0: version "3.2.4" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" @@ -3022,6 +3024,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: dependencies: type-fest "^0.11.0" +ansi-gray@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" + integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= + dependencies: + ansi-wrap "0.1.0" + ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -3067,6 +3076,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +ansi-wrap@0.1.0, ansi-wrap@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" + integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= + any-promise@1.3.0, any-promise@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -3126,11 +3140,23 @@ app-module-path@^2.2.0: resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" integrity sha1-ZBqlXft9am8KgUHEucCqULbCTdU= +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= + dependencies: + buffer-equal "^1.0.0" + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== +archy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" + integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= + are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -3172,11 +3198,25 @@ arr-diff@^4.0.0: resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= -arr-flatten@^1.1.0: +arr-filter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" + integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + dependencies: + make-iterator "^1.0.0" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== +arr-map@^2.0.0, arr-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" + integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + dependencies: + make-iterator "^1.0.0" + arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" @@ -3196,6 +3236,11 @@ array-back@^2.0.0: dependencies: typical "^2.6.1" +array-each@^1.0.0, array-each@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" + integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= + array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -3225,6 +3270,35 @@ array-includes@^3.0.3, array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" +array-initial@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" + integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= + dependencies: + array-slice "^1.0.0" + is-number "^4.0.0" + +array-last@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" + integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + dependencies: + is-number "^4.0.0" + +array-slice@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" + integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== + +array-sort@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" + integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== + dependencies: + default-compare "^1.0.0" + get-value "^2.0.6" + kind-of "^5.0.2" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -3335,6 +3409,16 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== +async-done@^1.2.0, async-done@^1.2.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" + integrity sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.2" + process-nextick-args "^2.0.0" + stream-exhaust "^1.0.1" + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -3367,6 +3451,13 @@ async-sema@^3.1.0: resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.1.0.tgz#3a813beb261e4cc58b19213916a48e931e21d21e" integrity sha512-+JpRq3r0zjpRLDruS6q/nC4V5tzsaiu07521677Mdi5i+AkaU/aNJH38rYHJVQ4zvz+SSkjgc8FUI7qIZrR+3g== +async-settle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" + integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + dependencies: + async-done "^1.2.2" + async@0.9.x: version "0.9.2" resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" @@ -4139,6 +4230,21 @@ babylon@^6.18.0: resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== +bach@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" + integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + dependencies: + arr-filter "^1.1.1" + arr-flatten "^1.0.1" + arr-map "^2.0.0" + array-each "^1.0.0" + array-initial "^1.0.0" + array-last "^1.1.1" + async-done "^1.2.2" + async-settle "^1.0.0" + now-and-later "^2.0.0" + backoff@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" @@ -4577,6 +4683,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= + buffer-fill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" @@ -4812,6 +4923,11 @@ camelcase@^2.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= + camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -4927,7 +5043,7 @@ chokidar@3.3.1: optionalDependencies: fsevents "~2.1.2" -chokidar@^2.1.8: +chokidar@^2.0.0, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -5064,6 +5180,15 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + cliui@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" @@ -5091,6 +5216,11 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +clone-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" + integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= + clone-deep@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" @@ -5118,11 +5248,25 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +clone-stats@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" + integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= + clone@^2.0.0, clone@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= +cloneable-readable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" + integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== + dependencies: + inherits "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" + clsx@^1.0.4, clsx@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -5147,6 +5291,15 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +collection-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" + integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= + dependencies: + arr-map "^2.0.2" + for-own "^1.0.0" + make-iterator "^1.0.0" + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -5187,6 +5340,11 @@ color-string@^1.5.2: color-name "^1.0.0" simple-swizzle "^0.2.2" +color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + color@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" @@ -5300,7 +5458,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.5.1, concat-stream@^1.6.2: +concat-stream@^1.5.0, concat-stream@^1.5.1, concat-stream@^1.6.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -5403,7 +5561,7 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0: +convert-source-map@1.7.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== @@ -5447,6 +5605,14 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-props@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" + integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== + dependencies: + each-props "^1.3.0" + is-plain-object "^2.0.1" + core-js-compat@^3.6.2: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" @@ -5986,7 +6152,7 @@ decamelize-keys@^1.0.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -6095,6 +6261,13 @@ deepmerge@^4.2.2: resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +default-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" + integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== + dependencies: + kind-of "^5.0.2" + default-gateway@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b" @@ -6103,6 +6276,11 @@ default-gateway@^4.2.0: execa "^1.0.0" ip-regex "^2.1.0" +default-resolution@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" + integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -6195,6 +6373,11 @@ detect-browser@5.1.0: resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.1.0.tgz#0c51c66b747ad8f98a6832bf3026a5a23a7850ff" integrity sha512-WKa9p+/MNwmTiS+V2AS6eGxic+807qvnV3hC+4z2GTY+F42h1n8AynVTMMc4EJBC32qMs6yjOTpeDEQQt/AVqQ== +detect-file@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" + integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= + detect-indent@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" @@ -6455,6 +6638,14 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +each-props@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" + integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== + dependencies: + is-plain-object "^2.0.1" + object.defaults "^1.1.0" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -6756,7 +6947,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.50: +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50: version "0.10.53" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== @@ -6770,7 +6961,7 @@ es6-error@^4.1.1: resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -es6-iterator@2.0.3, es6-iterator@~2.0.3: +es6-iterator@2.0.3, es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -6792,6 +6983,16 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +es6-weak-map@^2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" + integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== + dependencies: + d "1" + es5-ext "^0.10.46" + es6-iterator "^2.0.3" + es6-symbol "^3.1.1" + escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -7733,6 +7934,13 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +expand-tilde@^2.0.0, expand-tilde@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" + integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= + dependencies: + homedir-polyfill "^1.0.1" + expect@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" @@ -7808,7 +8016,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -7863,6 +8071,16 @@ fake-merkle-patricia-tree@^1.0.1: dependencies: checkpoint-store "^1.1.0" +fancy-log@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" + integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + parse-node-version "^1.0.0" + time-stamp "^1.0.0" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -8121,6 +8339,42 @@ find-versions@^3.2.0: dependencies: semver-regex "^2.0.0" +findup-sync@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" + integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= + dependencies: + detect-file "^1.0.0" + is-glob "^3.1.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + +fined@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" + integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== + dependencies: + expand-tilde "^2.0.2" + is-plain-object "^2.0.3" + object.defaults "^1.1.0" + object.pick "^1.2.0" + parse-filepath "^1.0.1" + +flagged-respawn@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" + integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== + flat-cache@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" @@ -8147,7 +8401,7 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== -flush-write-stream@^1.0.0: +flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== @@ -8191,6 +8445,13 @@ for-own@^0.1.3: dependencies: for-in "^1.0.1" +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= + dependencies: + for-in "^1.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -8318,6 +8579,14 @@ fs-minipass@^2.0.0: dependencies: minipass "^3.0.0" +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" @@ -8471,11 +8740,40 @@ glob-parent@^5.0.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-watcher@^5.0.3: + version "5.0.5" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.5.tgz#aa6bce648332924d9a8489be41e3e5c52d4186dc" + integrity sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw== + dependencies: + anymatch "^2.0.0" + async-done "^1.2.0" + chokidar "^2.0.0" + is-negated-glob "^1.0.0" + just-debounce "^1.0.0" + normalize-path "^3.0.0" + object.defaults "^1.1.0" + glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -8515,6 +8813,26 @@ global-modules@2.0.0: dependencies: global-prefix "^3.0.0" +global-modules@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" + integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== + dependencies: + global-prefix "^1.0.1" + is-windows "^1.0.1" + resolve-dir "^1.0.0" + +global-prefix@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" + integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= + dependencies: + expand-tilde "^2.0.2" + homedir-polyfill "^1.0.1" + ini "^1.3.4" + is-windows "^1.0.1" + which "^1.2.14" + global-prefix@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-3.0.0.tgz#fc85f73064df69f50421f47f883fe5b913ba9b97" @@ -8607,6 +8925,13 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" +glogg@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" + integrity sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== + dependencies: + sparkles "^1.0.0" + got@9.6.0, got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -8644,7 +8969,7 @@ got@^7.1.0: url-parse-lax "^1.0.0" url-to-options "^1.0.1" -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2: +graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -8664,6 +8989,47 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= +gulp-cli@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.3.0.tgz#ec0d380e29e52aa45e47977f0d32e18fd161122f" + integrity sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A== + dependencies: + ansi-colors "^1.0.1" + archy "^1.0.0" + array-sort "^1.0.0" + color-support "^1.1.3" + concat-stream "^1.6.0" + copy-props "^2.0.1" + fancy-log "^1.3.2" + gulplog "^1.0.0" + interpret "^1.4.0" + isobject "^3.0.1" + liftoff "^3.1.0" + matchdep "^2.0.0" + mute-stdout "^1.0.0" + pretty-hrtime "^1.0.0" + replace-homedir "^1.0.0" + semver-greatest-satisfied-range "^1.1.0" + v8flags "^3.2.0" + yargs "^7.1.0" + +gulp@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa" + integrity sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA== + dependencies: + glob-watcher "^5.0.3" + gulp-cli "^2.2.0" + undertaker "^1.2.1" + vinyl-fs "^3.0.0" + +gulplog@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" + integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= + dependencies: + glogg "^1.0.0" + gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -8862,6 +9228,13 @@ home-or-tmp@^2.0.0: os-homedir "^1.0.0" os-tmpdir "^1.0.1" +homedir-polyfill@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" + integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== + dependencies: + parse-passwd "^1.0.0" + hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -9317,7 +9690,7 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" -interpret@^1.0.0: +interpret@^1.0.0, interpret@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== @@ -9329,6 +9702,11 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + invert-kv@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" @@ -9359,6 +9737,14 @@ is-absolute-url@^3.0.3: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== +is-absolute@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" + integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== + dependencies: + is-relative "^1.0.0" + is-windows "^1.0.1" + is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" @@ -9579,6 +9965,11 @@ is-natural-number@^4.0.1: resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= + is-npm@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" @@ -9591,6 +9982,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -9666,6 +10062,13 @@ is-regexp@^1.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= +is-relative@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" + integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== + dependencies: + is-unc-path "^1.0.0" + is-resolvable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" @@ -9720,12 +10123,24 @@ is-typedarray@1.0.0, is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -is-utf8@^0.2.0: +is-unc-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" + integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== + dependencies: + unc-path-regex "^0.1.2" + +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= -is-windows@^1.0.2: +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= + +is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== @@ -10656,6 +11071,11 @@ just-curry-it@^3.1.0: resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg== +just-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" + integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + keccak256@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/keccak256/-/keccak256-1.0.0.tgz#1ba55ce78ed3d63fb7091d045469007da984171d" @@ -10723,7 +11143,7 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0: +kind-of@^5.0.0, kind-of@^5.0.2: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== @@ -10765,6 +11185,14 @@ last-call-webpack-plugin@^3.0.0: lodash "^4.17.5" webpack-sources "^1.1.0" +last-run@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" + integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + dependencies: + default-resolution "^2.0.0" + es6-weak-map "^2.0.1" + latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -10787,6 +11215,20 @@ lazy-val@^1.0.4: resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.4.tgz#882636a7245c2cfe6e0a4e3ba6c5d68a137e5c65" integrity sha512-u93kb2fPbIrfzBuLjZE+w+fJbUUMhNDXxNmMfaqNgpfQf1CO5ZSe2LfsnBqVAk7i/2NF48OSoRj+Xe2VT+lE8Q== +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= + dependencies: + readable-stream "^2.0.5" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + lcid@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" @@ -10799,6 +11241,13 @@ lcov-parse@^1.0.0: resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" integrity sha1-6w1GtUER68VhrLTECO+TY73I9+A= +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= + dependencies: + flush-write-stream "^1.0.2" + left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -10874,6 +11323,20 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +liftoff@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" + integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== + dependencies: + extend "^3.0.0" + findup-sync "^3.0.0" + fined "^1.0.1" + flagged-respawn "^1.0.0" + is-plain-object "^2.0.4" + object.map "^1.0.0" + rechoir "^0.6.2" + resolve "^1.1.7" + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -11218,6 +11681,13 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" +make-iterator@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" + integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== + dependencies: + kind-of "^6.0.2" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -11237,7 +11707,7 @@ map-age-cleaner@^0.1.1: dependencies: p-defer "^1.0.0" -map-cache@^0.2.2: +map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -11259,6 +11729,16 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +matchdep@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" + integrity sha1-xvNINKDY28OzfCfui7yyfHd1WC4= + dependencies: + findup-sync "^2.0.0" + micromatch "^3.0.4" + resolve "^1.4.0" + stack-trace "0.0.10" + matcher@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" @@ -11429,7 +11909,7 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -11781,6 +12261,11 @@ multihashes@^0.4.15, multihashes@~0.4.15: multibase "^0.7.0" varint "^5.0.0" +mute-stdout@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" + integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== + mute-stream@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" @@ -12077,6 +12562,13 @@ normalize-url@^4.1.0: prop-types "^15.7.2" react-is "^16.9.0" +now-and-later@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" + integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== + dependencies: + once "^1.3.2" + npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" @@ -12212,7 +12704,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@4.1.0, object.assign@^4.1.0: +object.assign@4.1.0, object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -12222,6 +12714,16 @@ object.assign@4.1.0, object.assign@^4.1.0: has-symbols "^1.0.0" object-keys "^1.0.11" +object.defaults@^1.0.0, object.defaults@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" + integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= + dependencies: + array-each "^1.0.1" + array-slice "^1.0.0" + for-own "^1.0.0" + isobject "^3.0.0" + object.entries@^1.1.0, object.entries@^1.1.1, object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" @@ -12249,13 +12751,29 @@ object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0 define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -object.pick@^1.3.0: +object.map@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" + integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + +object.pick@^1.2.0, object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= dependencies: isobject "^3.0.1" +object.reduce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" + integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + object.values@^1.1.0, object.values@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" @@ -12300,7 +12818,7 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -12359,6 +12877,13 @@ optionator@^0.8.1, optionator@^0.8.3: type-check "~0.3.2" word-wrap "~1.2.3" +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= + dependencies: + readable-stream "^2.0.1" + original-require@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/original-require/-/original-require-1.0.1.tgz#0f130471584cd33511c5ec38c8d59213f9ac5e20" @@ -12381,6 +12906,13 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" @@ -12569,6 +13101,15 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-filepath@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" + integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= + dependencies: + is-absolute "^1.0.0" + map-cache "^0.2.0" + path-root "^0.1.1" + parse-headers@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.3.tgz#5e8e7512383d140ba02f0c7aa9f49b4399c92515" @@ -12599,6 +13140,16 @@ parse-json@^5.0.0: json-parse-better-errors "^1.0.1" lines-and-columns "^1.1.6" +parse-node-version@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +parse-passwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" + integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= + parse5@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" @@ -12679,6 +13230,18 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-root-regex@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" + integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= + +path-root@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" + integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= + dependencies: + path-root-regex "^0.1.0" + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -13655,12 +14218,17 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" +pretty-hrtime@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@~2.0.0: +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== @@ -13791,7 +14359,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -pumpify@^1.3.3: +pumpify@^1.3.3, pumpify@^1.3.5: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== @@ -14318,7 +14886,7 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -14607,7 +15175,24 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -remove-trailing-separator@^1.0.1: +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + +remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= @@ -14640,6 +15225,20 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +replace-ext@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" + integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== + +replace-homedir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" + integrity sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= + dependencies: + homedir-polyfill "^1.0.1" + is-absolute "^1.0.0" + remove-trailing-separator "^1.1.0" + request-promise-core@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f" @@ -14719,6 +15318,14 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" +resolve-dir@^1.0.0, resolve-dir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" + integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= + dependencies: + expand-tilde "^2.0.0" + global-modules "^1.0.0" + resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" @@ -14729,6 +15336,13 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= + dependencies: + value-or-function "^3.0.0" + resolve-pathname@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" @@ -14767,7 +15381,7 @@ resolve@1.15.0: dependencies: path-parse "^1.0.6" -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.8.1, resolve@~1.17.0: +resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1, resolve@~1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -15120,6 +15734,13 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" +semver-greatest-satisfied-range@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" + integrity sha1-E+jCZYq5aRywzXEJMkAoDTb3els= + dependencies: + sver-compat "^1.5.0" + semver-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338" @@ -15579,6 +16200,11 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sparkles@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" + integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== + spawn-command@^0.0.2-1: version "0.0.2-1" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" @@ -15726,6 +16352,11 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== +stack-trace@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= + stack-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8" @@ -15782,6 +16413,11 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" +stream-exhaust@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" + integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -15834,7 +16470,7 @@ string-length@^3.1.0: astral-regex "^1.0.0" strip-ansi "^5.2.0" -string-width@^1.0.1: +string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -16113,6 +16749,14 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +sver-compat@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" + integrity sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= + dependencies: + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + svg-parser@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" @@ -16345,7 +16989,15 @@ throat@^4.0.0: resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" integrity sha1-iQN8vJLFarGJJua6TLsgDhVnKmo= -through2@^2.0.0, through2@^2.0.3: +through2-filter@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" + integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== + dependencies: + through2 "~2.0.0" + xtend "~4.0.0" + +through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -16363,6 +17015,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +time-stamp@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" + integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= + timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" @@ -16402,6 +17059,14 @@ tmpl@1.0.x: resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -16485,6 +17150,13 @@ to-space-case@^1.0.0: dependencies: to-no-case "^1.0.0" +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= + dependencies: + through2 "^2.0.3" + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" @@ -16779,11 +17451,36 @@ unbzip2-stream@^1.0.9: buffer "^5.2.1" through "^2.3.8" +unc-path-regex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" + integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= + underscore@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== +undertaker-registry@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" + integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + +undertaker@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b" + integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== + dependencies: + arr-flatten "^1.0.1" + arr-map "^2.0.0" + bach "^1.0.0" + collection-map "^1.0.0" + es6-weak-map "^2.0.1" + last-run "^1.1.0" + object.defaults "^1.0.0" + object.reduce "^1.0.0" + undertaker-registry "^1.0.0" + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -16841,6 +17538,14 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" +unique-stream@^2.0.2: + version "2.3.1" + resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" + integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== + dependencies: + json-stable-stringify-without-jsonify "^1.0.1" + through2-filter "^3.0.0" + unique-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-2.0.0.tgz#39c6451f81afb2749de2b233e3f7c5e8843bd89d" @@ -17078,6 +17783,13 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== +v8flags@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.2.0.tgz#b243e3b4dfd731fa774e7492128109a0fe66d656" + integrity sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg== + dependencies: + homedir-polyfill "^1.0.1" + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -17091,6 +17803,11 @@ value-equal@^1.0.1: resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" + integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= + varint@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf" @@ -17115,6 +17832,54 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vinyl-fs@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" + integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + +vinyl@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" + integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== + dependencies: + clone "^2.1.1" + clone-buffer "^1.0.0" + clone-stats "^1.0.0" + cloneable-readable "^1.0.0" + remove-trailing-separator "^1.0.1" + replace-ext "^1.0.0" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -18019,6 +18784,7 @@ 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" @@ -18063,6 +18829,11 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= + which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -18080,7 +18851,7 @@ which@2.0.2, which@^2.0.1: dependencies: isexe "^2.0.0" -which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -18421,6 +19192,11 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + "y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" @@ -18459,6 +19235,14 @@ yargs-parser@13.1.2, yargs-parser@^13.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@5.0.0-security.0: + version "5.0.0-security.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz#4ff7271d25f90ac15643b86076a2ab499ec9ee24" + integrity sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ== + dependencies: + camelcase "^3.0.0" + object.assign "^4.1.0" + yargs-parser@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8" @@ -18542,6 +19326,25 @@ yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" +yargs@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6" + integrity sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g== + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "5.0.0-security.0" + yauzl@^2.10.0, yauzl@^2.4.2: version "2.10.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"