From 9b7e2790338b15041ae224cdc3c8f3439e63305d Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Thu, 30 Jul 2020 19:56:03 -0300 Subject: [PATCH 01/14] Add CLA assistant --- .github/workflows/cla.yml | 24 ++++++++++++++++++++++++ GNOSISCLA.md | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 .github/workflows/cla.yml create mode 100644 GNOSISCLA.md diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..bdc3c8af --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,24 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request: + 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' + # Alpha Release + uses: cla-assistant/github-action@v1.4.2-alpha + 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: 'master' + allowlist: lukasschor,rmeissner,apanizo,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,pcowgill,luarx,#20giacomolicari,gnosis-info,bot* + empty-commit-flag: false + blockchain-storage-flag: false diff --git a/GNOSISCLA.md b/GNOSISCLA.md new file mode 100644 index 00000000..8a7f2a8b --- /dev/null +++ b/GNOSISCLA.md @@ -0,0 +1,33 @@ +### Gnosis Contributor License Agreement + +Thank you for your interest in contributing to open source software projects (“Projects”) made available by Gnosis Ltd or its affiliates (“Gnosis”). This Contributor License Agreement (“Agreement”) sets out the terms governing any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that you submit or have submitted, in any form and in any manner, to Gnosis in respect of any of the Projects (collectively “Contributions”). If you have any questions respecting this Agreement, please contact legal@gnosis.io. + + +You agree that the following terms apply to all of your past, present and future Contributions. Except for the licenses granted in this Agreement, you retain all of your right, title and interest in and to your Contributions. + + +**Copyright License.** You hereby grant, and agree to grant, to Gnosis a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute your Contributions and such derivative works, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. + + +**Patent License.** You hereby grant, and agree to grant, to Gnosis a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer your Contributions, where such license applies only to those patent claims licensable by you that are necessarily infringed by your Contributions alone or by combination of your Contributions with the Project to which such Contributions were submitted, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. + + +**Moral Rights.** To the fullest extent permitted under applicable law, you hereby waive, and agree not to assert, all of your “moral rights” in or relating to your Contributions for the benefit of Gnosis, its assigns, and their respective direct and indirect sublicensees. + + +**Third Party Content/Rights.** If your Contribution includes or is based on any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that were not authored by you (“Third Party Content”) or if you are aware of any third party intellectual property or proprietary rights associated with your Contribution (“Third Party Rights”), then you agree to include with the submission of your Contribution full details respecting such Third Party Content and Third Party Rights. + + +**Representations.** You represent that, other than the Third Party Content and Third Party Rights identified by you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were created in the course of your employment with your past or present employer(s), you represent that such employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer(s) has waived all of their right, title or interest in or to your Contributions. + + +**Disclaimer.** To the fullest extent permitted under applicable law, your Contributions are provided on an "as-is" basis, without any warranties or conditions, express or implied, including, without limitation, any implied warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not required to provide support for your Contributions, except to the extent you desire to provide support. + + +**No Obligation.** You acknowledge that Gnosis is under no obligation to use or incorporate your Contributions into any of the Projects. The decision to use or incorporate your Contributions into any of the Projects will be made at the sole discretion of Gnosis or its authorized delegates. + + +**Disputes.** This Agreement shall be governed by and construed in accordance with the laws of the State of New York, United States of America, without giving effect to its principles or rules regarding conflicts of laws, other than such principles directing application of New York law. The parties hereby submit to venue in, and jurisdiction of the courts located in New York, New York for purposes relating to this Agreement. In the event that any of the provisions of this Agreement shall be held by a court or other tribunal of competent jurisdiction to be unenforceable, the remaining portions hereof shall remain in full force and effect. + + +**Assignment.** You agree that Gnosis may assign this Agreement, and all of its rights, obligations and licenses hereunder. From 86bee9587d343ddecbbc9f75057e2fc5a6626fb3 Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Thu, 30 Jul 2020 21:45:01 -0300 Subject: [PATCH 02/14] Delete GNOSISCLA.md --- GNOSISCLA.md | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 GNOSISCLA.md diff --git a/GNOSISCLA.md b/GNOSISCLA.md deleted file mode 100644 index 8a7f2a8b..00000000 --- a/GNOSISCLA.md +++ /dev/null @@ -1,33 +0,0 @@ -### Gnosis Contributor License Agreement - -Thank you for your interest in contributing to open source software projects (“Projects”) made available by Gnosis Ltd or its affiliates (“Gnosis”). This Contributor License Agreement (“Agreement”) sets out the terms governing any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that you submit or have submitted, in any form and in any manner, to Gnosis in respect of any of the Projects (collectively “Contributions”). If you have any questions respecting this Agreement, please contact legal@gnosis.io. - - -You agree that the following terms apply to all of your past, present and future Contributions. Except for the licenses granted in this Agreement, you retain all of your right, title and interest in and to your Contributions. - - -**Copyright License.** You hereby grant, and agree to grant, to Gnosis a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, and distribute your Contributions and such derivative works, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. - - -**Patent License.** You hereby grant, and agree to grant, to Gnosis a non-exclusive, perpetual, irrevocable, worldwide, fully-paid, royalty-free, transferable patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer your Contributions, where such license applies only to those patent claims licensable by you that are necessarily infringed by your Contributions alone or by combination of your Contributions with the Project to which such Contributions were submitted, with the right to sublicense the foregoing rights through multiple tiers of sublicensees. - - -**Moral Rights.** To the fullest extent permitted under applicable law, you hereby waive, and agree not to assert, all of your “moral rights” in or relating to your Contributions for the benefit of Gnosis, its assigns, and their respective direct and indirect sublicensees. - - -**Third Party Content/Rights.** If your Contribution includes or is based on any source code, object code, bug fixes, configuration changes, tools, specifications, documentation, data, materials, feedback, information or other works of authorship that were not authored by you (“Third Party Content”) or if you are aware of any third party intellectual property or proprietary rights associated with your Contribution (“Third Party Rights”), then you agree to include with the submission of your Contribution full details respecting such Third Party Content and Third Party Rights. - - -**Representations.** You represent that, other than the Third Party Content and Third Party Rights identified by you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were created in the course of your employment with your past or present employer(s), you represent that such employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer(s) has waived all of their right, title or interest in or to your Contributions. - - -**Disclaimer.** To the fullest extent permitted under applicable law, your Contributions are provided on an "as-is" basis, without any warranties or conditions, express or implied, including, without limitation, any implied warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not required to provide support for your Contributions, except to the extent you desire to provide support. - - -**No Obligation.** You acknowledge that Gnosis is under no obligation to use or incorporate your Contributions into any of the Projects. The decision to use or incorporate your Contributions into any of the Projects will be made at the sole discretion of Gnosis or its authorized delegates. - - -**Disputes.** This Agreement shall be governed by and construed in accordance with the laws of the State of New York, United States of America, without giving effect to its principles or rules regarding conflicts of laws, other than such principles directing application of New York law. The parties hereby submit to venue in, and jurisdiction of the courts located in New York, New York for purposes relating to this Agreement. In the event that any of the provisions of this Agreement shall be held by a court or other tribunal of competent jurisdiction to be unenforceable, the remaining portions hereof shall remain in full force and effect. - - -**Assignment.** You agree that Gnosis may assign this Agreement, and all of its rights, obligations and licenses hereunder. From 1b71d5275e36411b83a346a9a5d6e18387b5819e Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Fri, 31 Jul 2020 11:13:34 -0300 Subject: [PATCH 03/14] Typo in username --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index bdc3c8af..0953abf6 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -19,6 +19,6 @@ jobs: path-to-signatures: 'signatures/version1/cla.json' path-to-cla-document: 'https://github.com/gnosis/safe-react/blob/master/GNOSISCLA.md' branch: 'master' - allowlist: lukasschor,rmeissner,apanizo,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,pcowgill,luarx,#20giacomolicari,gnosis-info,bot* + allowlist: lukasschor,rmeissner,apanizo,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,pcowgill,luarx,giacomolicari,gnosis-info,bot* empty-commit-flag: false blockchain-storage-flag: false From 231a412fb84ebd199bc223cece4fd9b73345524b Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Fri, 31 Jul 2020 13:38:18 -0300 Subject: [PATCH 04/14] Update branch --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 0953abf6..a3f05c32 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -18,7 +18,7 @@ jobs: with: path-to-signatures: 'signatures/version1/cla.json' path-to-cla-document: 'https://github.com/gnosis/safe-react/blob/master/GNOSISCLA.md' - branch: 'master' + branch: 'cla-signatures' allowlist: lukasschor,rmeissner,apanizo,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,pcowgill,luarx,giacomolicari,gnosis-info,bot* empty-commit-flag: false blockchain-storage-flag: false From b5d8784e832e588e1b0915472711fd3296356b39 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Tue, 4 Aug 2020 14:19:19 +0400 Subject: [PATCH 05/14] adjust allowlist --- .github/workflows/cla.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a3f05c32..0fd9f344 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -1,24 +1,24 @@ -name: "CLA Assistant" +name: 'CLA Assistant' on: issue_comment: types: [created] pull_request: - types: [opened,closed,synchronize] - + types: [opened, closed, synchronize] + jobs: CLAssistant: runs-on: ubuntu-latest steps: - - name: "CLA Assistant" + - 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' # Alpha Release uses: cla-assistant/github-action@v1.4.2-alpha - env: + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: + 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,rmeissner,apanizo,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,pcowgill,luarx,giacomolicari,gnosis-info,bot* + allowlist: lukasschor,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 From 94156166d26471f5c0fdd88f62ac6caa860e76fa Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Tue, 4 Aug 2020 14:25:49 +0400 Subject: [PATCH 06/14] add comment about github token --- .github/workflows/cla.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 0fd9f344..902fbf89 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -13,6 +13,8 @@ jobs: 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' # Alpha Release uses: cla-assistant/github-action@v1.4.2-alpha + # GitHub token, automatically provided to the action + # (No need to define this secret in the repo settings) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From dcba16830c4c21cefc20cab475b8ed2463ad05be Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Tue, 4 Aug 2020 14:58:44 +0400 Subject: [PATCH 07/14] use gnosis version of github_action for debugging --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 902fbf89..6b54d4b0 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -12,7 +12,7 @@ jobs: - 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' # Alpha Release - uses: cla-assistant/github-action@v1.4.2-alpha + uses: gnosis/github-action@master # GitHub token, automatically provided to the action # (No need to define this secret in the repo settings) env: From 59cb58b14af930529964f889d38096b542a87edd Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Tue, 4 Aug 2020 15:34:18 +0400 Subject: [PATCH 08/14] allowlist adjustments --- .github/workflows/cla.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 6b54d4b0..9e4b5091 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -21,6 +21,6 @@ jobs: 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,rmeissner,germartinez,fernandomg,Agupane,nicosampler,matextrem,gabitoesmiapodo,davidalbela,alongoni,Uxio0,dasanra,miguelmota,francovenica,tschubotz,luarx,giacomolicari,gnosis-info,bot* + 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 From ece2bc6a8c7e77fe6cb215539a38ccf6e56331fb Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Tue, 4 Aug 2020 19:53:30 +0400 Subject: [PATCH 09/14] Fix safe loading (#1203) --- src/routes/load/container/Load.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 61b282f92c678313a21ec484832a83d99e42b0ad Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Tue, 4 Aug 2020 13:09:16 -0300 Subject: [PATCH 10/14] Add support from Pull Requests coming from Forks (#1204) * Add support from Pull Requests coming from Forks * Update cla.yml Co-authored-by: Mikhail Mikheev --- .github/workflows/cla.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 9e4b5091..a6624c24 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -4,6 +4,8 @@ on: types: [created] pull_request: types: [opened, closed, synchronize] + pull_request_target: + types: [opened, closed, synchronize] jobs: CLAssistant: From 5e4affa38d366a6b696a1d3ea1f6dc2a950319e3 Mon Sep 17 00:00:00 2001 From: Mati Dastugue Date: Tue, 4 Aug 2020 13:36:23 -0300 Subject: [PATCH 11/14] Change CLA event --- .github/workflows/cla.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index a6624c24..9778959a 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -2,8 +2,6 @@ name: 'CLA Assistant' on: issue_comment: types: [created] - pull_request: - types: [opened, closed, synchronize] pull_request_target: types: [opened, closed, synchronize] @@ -12,7 +10,7 @@ jobs: 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' + 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 From a0ed0a1f31501b6e4590efb4c2426042b0986a93 Mon Sep 17 00:00:00 2001 From: Fernando Date: Tue, 4 Aug 2020 15:32:09 -0300 Subject: [PATCH 12/14] (Feature) Decode multiSend tx details (#1106) Co-authored-by: Mikhail Mikheev --- src/components/Collapse/index.tsx | 37 ++- src/components/CopyBtn/index.tsx | 11 +- src/components/EtherscanBtn/index.tsx | 15 +- src/components/EtherscanLink/index.tsx | 38 ++- src/components/EtherscanLink/style.ts | 5 +- src/components/Table/sorting.ts | 13 +- src/logic/addressBook/model/addressBook.ts | 2 + .../addressBook/store/reducer/addressBook.ts | 5 +- .../store/reducer/types/addressBook.d.ts | 2 +- .../addressBook/store/selectors/index.ts | 2 +- src/logic/contracts/methodIds.ts | 119 ++------ .../api/fetchTokenCurrenciesBalances.ts | 4 +- .../store/model/currencyValues.ts | 20 +- .../store/reducer/currencyValues.ts | 14 +- .../currencyValues/store/selectors/index.ts | 37 +-- .../tokens/store/actions/fetchSafeTokens.ts | 126 ++++---- src/logic/tokens/utils/humanReadableValue.ts | 5 + .../safe/components/AddressBook/index.tsx | 2 +- .../components/Apps/confirmTransactions.tsx | 6 +- .../safe/components/Balances/Coins/index.tsx | 3 +- .../safe/components/Balances/dataFetcher.ts | 31 +- .../OwnerAddressTableCell/index.tsx | 17 +- .../TxDescription/CustomDescription.tsx | 180 ++++++++++++ .../TxDescription/SettingsDescription.tsx | 153 ++++++++++ .../TxDescription/TransferDescription.tsx | 30 ++ .../ExpandedTx/TxDescription/Value.tsx | 83 ++++++ .../ExpandedTx/TxDescription/index.tsx | 273 +----------------- .../ExpandedTx/TxDescription/styles.ts | 43 +++ .../ExpandedTx/TxDescription/utils.ts | 27 +- .../TxsTable/ExpandedTx/index.tsx | 16 +- .../Transactions/TxsTable/columns.tsx | 48 ++- .../Transactions/TxsTable/index.tsx | 2 +- .../Transactions/TxsTable/test/column.test.ts | 7 +- .../loadOutgoingTransactions.ts | 2 +- .../utils/multiSendDecodedDetails.ts | 61 ++++ .../utils/newTransactionHelpers.ts | 25 ++ .../transactions/utils/transactionHelpers.ts | 10 +- .../transactions/utils/transferDetails.d.ts | 50 ++++ .../transactions/utils/transferDetails.ts | 85 ++++++ src/routes/safe/store/models/transaction.ts | 1 + .../safe/store/models/types/transaction.ts | 7 +- .../safe/store/models/types/transactions.d.ts | 254 ++++++++++++++++ .../store/reducer/cancellationTransactions.ts | 4 + src/routes/safe/store/reducer/types/safe.d.ts | 2 +- src/routes/safe/store/selectors/index.ts | 43 ++- .../safe/store/selectors/transactions.ts | 3 +- src/store/index.ts | 18 +- .../transactions/transactionList.helper.ts | 9 +- 48 files changed, 1377 insertions(+), 573 deletions(-) create mode 100644 src/logic/tokens/utils/humanReadableValue.ts create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/CustomDescription.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/SettingsDescription.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/TransferDescription.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/Value.tsx create mode 100644 src/routes/safe/components/Transactions/TxsTable/ExpandedTx/TxDescription/styles.ts create mode 100644 src/routes/safe/store/actions/transactions/utils/multiSendDecodedDetails.ts create mode 100644 src/routes/safe/store/actions/transactions/utils/newTransactionHelpers.ts create mode 100644 src/routes/safe/store/actions/transactions/utils/transferDetails.d.ts create mode 100644 src/routes/safe/store/actions/transactions/utils/transferDetails.ts create mode 100644 src/routes/safe/store/models/types/transactions.d.ts 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/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/confirmTransactions.tsx b/src/routes/safe/components/Apps/confirmTransactions.tsx index 03efa44f..1847109d 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; ` @@ -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/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 1abfbd59..6431bc16 100644 --- a/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx +++ b/src/routes/safe/components/Transactions/TxsTable/ExpandedTx/index.tsx @@ -7,6 +7,9 @@ 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 EtherScanLink from 'src/components/EtherscanLink' @@ -19,16 +22,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) -const ExpandedTx = ({ cancelTx, tx }) => { +interface ExpandedTxProps { + cancelTx: Transaction + tx: Transaction +} + +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 4db6cb0e..8fd058fd 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 3dc27c1c..808f4d4d 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,12 +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 gasPrice: string gasToken: string isCancellationTx: boolean @@ -74,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 e5347bf7..b78b4cdd 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 From bda71f896e7d6a936d89e59e1c0d0a58a0f6ef65 Mon Sep 17 00:00:00 2001 From: Corey Shirk Date: Wed, 5 Aug 2020 03:30:57 -0400 Subject: [PATCH 13/14] Updating readme.md to reflect material ui version (#1175) Co-authored-by: Mikhail Mikheev Co-authored-by: Mati Dastugue --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b6bb5ffde1b8ec9211c865bc447cb99ebd11e1c0 Mon Sep 17 00:00:00 2001 From: Mikhail Mikheev Date: Thu, 6 Aug 2020 11:33:58 +0400 Subject: [PATCH 14/14] Tech Debt: Safe Apps Refactor (#1110) * apps refactoring wip * apps refactoring wip * type fixes * add useLegalConsent hook in apps * useAppList hook wip * dep nump * useAppList hook wip * fix selecting first app * Remove console.log * dep bump * update persisting app logic * update saveToStorage type * fix crash on apps tab * reuse selectedApp variable in hook * remove initialAppSelected --- .../Apps/components/LegalDisclaimer.tsx | 34 + .../Apps/{ => components}/ManageApps.tsx | 4 +- .../components/Apps/confirmTransactions.tsx | 2 +- .../safe/components/Apps/hooks/useAppList.ts | 107 +++ .../Apps/hooks/useIframeMessageHandler.ts | 52 ++ .../components/Apps/hooks/useLegalConsent.ts | 29 + src/routes/safe/components/Apps/index.tsx | 279 ++---- src/utils/storage/index.ts | 5 +- yarn.lock | 867 +++++++++++++++++- 9 files changed, 1127 insertions(+), 252 deletions(-) create mode 100644 src/routes/safe/components/Apps/components/LegalDisclaimer.tsx rename src/routes/safe/components/Apps/{ => components}/ManageApps.tsx (96%) create mode 100644 src/routes/safe/components/Apps/hooks/useAppList.ts create mode 100644 src/routes/safe/components/Apps/hooks/useIframeMessageHandler.ts create mode 100644 src/routes/safe/components/Apps/hooks/useLegalConsent.ts 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 1847109d..ce24fd77 100644 --- a/src/routes/safe/components/Apps/confirmTransactions.tsx +++ b/src/routes/safe/components/Apps/confirmTransactions.tsx @@ -75,7 +75,7 @@ const confirmTransactions = ( ethBalance: string, nameApp: string, iconApp: string, - txs: Array, + txs: SafeAppTx[], openModal: (modalInfo: GenericModalProps) => void, closeModal: () => void, onConfirm: () => void, 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 b7bbf009..4495bf94 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 StyledIframe = styled.iframe` padding: 15px; @@ -63,11 +60,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() @@ -77,8 +73,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) { @@ -86,58 +110,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) { @@ -149,8 +130,6 @@ function Apps({ closeModal, closeSnackbar, enqueueSnackbar, openModal }) { ) } - const app = getSelectedApp() - return ( {appIsLoading && ( @@ -158,76 +137,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 @@ -242,22 +171,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, @@ -287,8 +210,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 } @@ -303,65 +225,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, { @@ -371,8 +239,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 } @@ -381,9 +248,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 ( @@ -396,26 +263,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/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 b61d5208..7761e33d 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"