From e06728e085e50c69f18975efe417c0f5eaca3028 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 8 Jul 2021 08:28:46 +0200 Subject: [PATCH 01/17] Remove complexity pages --- packages/yoroi-extension/app/Routes.js | 12 -- .../complexity-level/ComplexityLevelForm.js | 132 ------------------ .../complexity-level/ComplexityLevelForm.scss | 102 -------------- .../components/settings/menu/SettingsMenu.js | 11 -- .../containers/profile/ComplexityLevelPage.js | 124 ---------------- .../profile/ComplexityLevelPage.stories.js | 47 ------- .../app/containers/profile/TermsOfUsePage.js | 13 +- .../profile/TermsOfUsePage.stories.js | 1 + .../categories/ComplexityLevelSettingsPage.js | 81 ----------- .../ComplexityLevelSettingsPage.stories.js | 59 -------- 10 files changed, 12 insertions(+), 570 deletions(-) delete mode 100644 packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js delete mode 100644 packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss delete mode 100644 packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js delete mode 100644 packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js delete mode 100644 packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js delete mode 100644 packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js diff --git a/packages/yoroi-extension/app/Routes.js b/packages/yoroi-extension/app/Routes.js index 6a735017a1..dc09530bb5 100644 --- a/packages/yoroi-extension/app/Routes.js +++ b/packages/yoroi-extension/app/Routes.js @@ -42,8 +42,6 @@ import NoticeBoardPage from './containers/notice-board/NoticeBoardPage'; import VotingPage from './containers/wallet/voting/VotingPage'; import type { ConfigType } from '../config/config-types'; -import ComplexityLevelSettingsPage from './containers/settings/categories/ComplexityLevelSettingsPage'; -import ComplexityLevelPage from './containers/profile/ComplexityLevelPage'; import BlockchainSettingsPage from './containers/settings/categories/BlockchainSettingsPage'; import WalletSwitch from './containers/WalletSwitch'; @@ -72,11 +70,6 @@ export const Routes = ( path={ROUTES.PROFILE.LANGUAGE_SELECTION} component={(props) => } /> - } - /> ( path={ROUTES.SETTINGS.SUPPORT} component={(props) => } /> - } - /> ); diff --git a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js deleted file mode 100644 index 9440fc9ce8..0000000000 --- a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js +++ /dev/null @@ -1,132 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import type { Node } from 'react'; -import { intlShape, defineMessages } from 'react-intl'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import styles from './ComplexityLevelForm.scss'; -import classnames from 'classnames'; -import BeginnerLevel from '../../../assets/images/complexity-level/beginner-level.inline.svg'; -import AdvancedLevel from '../../../assets/images/complexity-level/advanced-level.inline.svg'; -import LocalizableError from '../../../i18n/LocalizableError'; -import { Button } from 'react-polymorph/lib/components/Button'; -import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; -import { ComplexityLevels } from '../../../types/complexityLevelType'; -import type { ComplexityLevelType } from '../../../types/complexityLevelType'; - -const messages = defineMessages({ - subtitle: { - id: 'profile.complexityLevel.subtitle', - defaultMessage: '!!!Understanding blockchain can be difficult, which is why we will try and keep the interface as simple as possible for you' - }, - titleSimpleLevel: { - id: 'profile.complexityLevel.simple', - defaultMessage: '!!!Simple' - }, - titleAdvancedLevel: { - id: 'profile.complexityLevel.advanced', - defaultMessage: '!!!Advanced' - }, - descriptionSimpleLevel: { - id: 'profile.complexityLevel.simple.description', - defaultMessage: '!!!Simplest experience possible. No previous knowledge in Blockchain required. Highly friendly to on-board beginners, and for users that prefer simplicity.' - }, - descriptionAdvancedLevel: { - id: 'profile.complexityLevel.advanced.description', - defaultMessage: '!!!I have a some understanding of blockchain and how cryptography is used to power both the blockchain itself and wallet software. I am okay with seeing options and functionality that critically depend on my understanding of these concepts.' - }, - labelSelectedLevel: { - id: 'profile.complexityLevel.selected.label', - defaultMessage: '!!!Your currently level of Complexity is' - }, - labelChoose: { - id: 'global.label.choose', - defaultMessage: '!!!Choose' - }, -}); -type Props = {| - +complexityLevel: ?ComplexityLevelType, - +onSubmit: ComplexityLevelType => PossiblyAsync, - +isSubmitting: boolean, - +error?: ?LocalizableError -|} - -class ComplexityLevel extends Component { - static defaultProps: {|error: void|} = { - error: undefined - }; - - static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { - intl: intlShape.isRequired, - }; - - render(): Node { - const { intl } = this.context; - const { complexityLevel, isSubmitting } = this.props; - - const levels = [ - { - key: ComplexityLevels.Simple, - name: intl.formatMessage(messages.titleSimpleLevel), - image: , - description: intl.formatMessage(messages.descriptionSimpleLevel), - }, - { - key: ComplexityLevels.Advanced, - name: intl.formatMessage(messages.titleAdvancedLevel), - image: , - description: intl.formatMessage(messages.descriptionAdvancedLevel), - } - ]; - - const buttonClasses = classnames([ - 'secondary', - isSubmitting ? - styles.submitButtonSpinning : - styles.submitButton - ]); - - return ( - <> -
-
- {intl.formatMessage(messages.subtitle)} -
-
- { - complexityLevel && - <> - {intl.formatMessage(messages.labelSelectedLevel)} : {complexityLevel} - - } -
-
- { - levels.map(level => ( -
-
- {level.image} -
-
-
-

{level.name}

-

{level.description}

-
-
-
- )) - } -
-
- - ); - } -} - -export default ComplexityLevel; diff --git a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss deleted file mode 100644 index a31746bc41..0000000000 --- a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss +++ /dev/null @@ -1,102 +0,0 @@ -@import '../../../themes/mixins/loading-spinner'; -@import '../../../themes/mixins/error-message'; - -.component{ - max-width: 650px; - margin: 0 auto; - margin-top: 65px; - .header { - color: var(--theme-wallet-dropdown-secondary-text-color); - text-align: center; - margin-bottom: 50px; - font-size: 1.125rem; - font-weight: bold; - letter-spacing: 0; - line-height: 1.375rem; - } - .description { - color: var(--theme-transactions-list-detail-row-text-color); - font-size: 1rem; - line-height: 1.375rem; - text-align: center; - } - .selected { - font-size: 1rem; - text-align: center; - margin: 1rem 0; - - span { - color: var(--theme-button-outlined-text-color); - font-weight: bold; - text-transform: uppercase; - } - } - .cardsWrapper { - margin: 30px auto; - display: flex; - & > div { - flex: 1; - } - } - .card { - margin-right: 30px; - border-radius: 8px; - background-color: #FFFFFF; - box-shadow: 0 5px 20px 0 rgba(24,26,30,0.08); - overflow: hidden; - &:last-child { - margin-right: 0; - } - .cardImage { - background-color: #e5e5e5; - height:184px; - display: flex; - align-items: center; - justify-content: center; - } - .simple { - background: #F1FDFA; - } - .advanced { - background: #F3F5FD; - } - .cardContent{ - padding: 24px ; - color: var(--theme-transactions-list-text-color); - display: flex; - flex-direction: column; - justify-content: space-between; - height: 300px; - h3 { - font-size: 1.5rem; - letter-spacing: 0; - line-height: 1.875rem; - text-align: center; - text-transform: capitalize; - margin-bottom: 16px; - } - p { - font-size: .75rem; - letter-spacing: 0; - line-height: 1.25rem; - } - } - .submitButton, - .submitButtonSpinning { - display: block !important; - margin: 0; - width: 100%; - } - - .submitButtonSpinning { - @include loading-spinner("../../../assets/images/spinner-light.svg"); - } - - .error { - @include error-message; - text-align: center; - margin-bottom: 1rem; - } - - } -} \ No newline at end of file diff --git a/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js b/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js index 2f528b9c99..56fc2b5241 100644 --- a/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js +++ b/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js @@ -20,10 +20,6 @@ const messages = defineMessages({ id: 'settings.menu.blockchain.link.label', defaultMessage: '!!!Blockchain', }, - levelOfComplexity: { - id: 'settings.menu.levelOfComplexity.link.label', - defaultMessage: '!!!Level of Complexity', - }, externalStorage: { id: 'settings.menu.externalStorage.link.label', defaultMessage: '!!!External Storage', @@ -94,13 +90,6 @@ export default class SettingsMenu extends Component { active={isActiveItem(ROUTES.SETTINGS.SUPPORT)} className="support" /> - - onItemClick(ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY)} - active={isActiveItem(ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY)} - className="levelOfComplexity" - /> ); diff --git a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js deleted file mode 100644 index 6fea692099..0000000000 --- a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js +++ /dev/null @@ -1,124 +0,0 @@ -// @flow -import React, { Component } from 'react'; -import type { Node } from 'react'; -import TopBarLayout from '../../components/layout/TopBarLayout'; -import TopBar from '../../components/topbar/TopBar'; -import StaticTopbarTitle from '../../components/topbar/StaticTopbarTitle'; -import { defineMessages, intlShape } from 'react-intl'; -import TestnetWarningBanner from '../../components/topbar/banners/TestnetWarningBanner'; -import ServerErrorBanner from '../../components/topbar/banners/ServerErrorBanner'; -import type { InjectedOrGenerated } from '../../types/injectedPropsType'; -import { computed } from 'mobx'; -import { ServerStatusErrors } from '../../types/serverStatusErrorType'; -import { observer } from 'mobx-react'; -import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; -import ComplexityLevel from '../../components/profile/complexity-level/ComplexityLevelForm'; -import type { ComplexityLevelType } from '../../types/complexityLevelType'; -import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; -import LocalizableError from '../../i18n/LocalizableError'; -import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; -import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; - -const messages = defineMessages({ - title: { - id: 'profile.complexityLevel.title', - defaultMessage: '!!!Level of Interface Complexity', - }, -}); - -type GeneratedData = typeof ComplexityLevelPage.prototype.generated; -@observer -export default class ComplexityLevelPage extends Component> { - static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { - intl: intlShape.isRequired, - }; - - render(): Node { - - const { checkAdaServerStatus } = this.generated.stores.serverConnectionStore; - - const { selected } = this.generated.stores.wallets; - const isWalletTestnet = selected == null - ? false - : isTestnet(selected.getParent().getNetworkInfo()); - const displayedBanner = checkAdaServerStatus === ServerStatusErrors.Healthy - ? - : ; - - const topbarTitle = ( - - ); - - const topbarElement = ( - ); - return ( - - - - ); - } - - - @computed get generated(): {| - actions: {| - profile: {| - selectComplexityLevel: {| trigger: (params: ComplexityLevelType) => Promise < void > |} - |} - |}, - stores: {| - wallets: {| selected: null | PublicDeriver<> |}, - profile: {| - selectedComplexityLevel: ?ComplexityLevelType, - setComplexityLevelRequest: {| - error: ?LocalizableError, - isExecuting: boolean - |} - |}, - serverConnectionStore: {| - checkAdaServerStatus: ServerStatusErrorType - |} - |} - |} { - if (this.props.generated !== undefined) { - return this.props.generated; - } - if (this.props.stores == null || this.props.actions == null) { - throw new Error(`${nameof(ComplexityLevelPage)} no way to generated props`); - } - const { stores, actions } = this.props; - const profileStore = stores.profile; - - return Object.freeze({ - stores: { - serverConnectionStore: { - checkAdaServerStatus: stores.serverConnectionStore.checkAdaServerStatus, - }, - wallets: { - selected: stores.wallets.selected, - }, - profile: { - setComplexityLevelRequest: { - error: profileStore.setComplexityLevelRequest.error, - isExecuting: profileStore.setComplexityLevelRequest.isExecuting, - }, - selectedComplexityLevel: profileStore.selectedComplexityLevel - }, - }, - actions: { - profile: { - selectComplexityLevel: { trigger: actions.profile.selectComplexityLevel.trigger }, - }, - }, - }); - } -} diff --git a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js deleted file mode 100644 index bda2a18aa3..0000000000 --- a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js +++ /dev/null @@ -1,47 +0,0 @@ -// @flow - -import type { Node } from 'react'; -import React from 'react'; - -import { select, boolean, } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; -import { ServerStatusErrors } from '../../types/serverStatusErrorType'; -import ComplexityLevelPage from './ComplexityLevelPage'; -import { withScreenshot } from 'storycap'; - -export default { - title: `${__filename.split('.')[0]}`, - component: ComplexityLevelPage, - decorators: [withScreenshot], -}; - -export const Generic = (): Node => ( - action('selectComplexityLevel')(req) }, - } - } - }} - /> -); diff --git a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js index e6ac454309..1e46fb0549 100644 --- a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js +++ b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js @@ -17,6 +17,8 @@ import LocalizableError from '../../i18n/LocalizableError'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; +import type { ComplexityLevelType } from '../../types/complexityLevelType'; +import { ComplexityLevels } from '../../types/complexityLevelType'; const messages = defineMessages({ title: { @@ -34,6 +36,11 @@ export default class TermsOfUsePage extends Component void) = (acceptance: any) => { + this.generated.actions.profile.acceptTermsOfUse.trigger(acceptance) + this.generated.actions.profile.selectComplexityLevel.trigger(ComplexityLevels.Simple) + } + render(): Node { const { checkAdaServerStatus } = this.generated.stores.serverConnectionStore; const { selected } = this.generated.stores.wallets; @@ -58,7 +65,7 @@ export default class TermsOfUsePage extends Component @@ -71,7 +78,8 @@ export default class TermsOfUsePage extends Component Promise - |} + |}, + selectComplexityLevel: {| trigger: (params: ComplexityLevelType) => Promise < void > |} |} |}, stores: {| @@ -115,6 +123,7 @@ export default class TermsOfUsePage extends Component ( actions: { profile: { acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) }, + selectComplexityLevel: { trigger: async (req) => action('selectComplexityLevel')(req) }, } } }} diff --git a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js deleted file mode 100644 index dd3c039120..0000000000 --- a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js +++ /dev/null @@ -1,81 +0,0 @@ -// @flow -import type { Node } from 'react'; -import React, { Component } from 'react'; -import { computed } from 'mobx'; -import { observer } from 'mobx-react'; -import type { InjectedOrGenerated } from '../../../types/injectedPropsType'; -import ComplexityLevel from '../../../components/profile/complexity-level/ComplexityLevelForm'; -import LocalizableError from '../../../i18n/LocalizableError'; -import type { ComplexityLevelType } from '../../../types/complexityLevelType'; -import type { ServerStatusErrorType } from '../../../types/serverStatusErrorType'; - -type GeneratedData = typeof ComplexityLevelSettingsPage.prototype.generated; - -@observer -export default class ComplexityLevelSettingsPage - extends Component> { - - @computed get generated(): {| - actions: {| - profile: {| - selectComplexityLevel: {| - trigger: ( - params: ComplexityLevelType - ) => Promise - |} - |} - |}, - stores: {| - profile: {| - selectedComplexityLevel: ?ComplexityLevelType, - setComplexityLevelRequest: {| - error: ?LocalizableError, - isExecuting: boolean - |} - |}, - serverConnectionStore: {| - checkAdaServerStatus: ServerStatusErrorType - |} - |} - |} { - if (this.props.generated !== undefined) { - return this.props.generated; - } - if (this.props.stores == null || this.props.actions == null) { - throw new Error(`${nameof(ComplexityLevelSettingsPage)} no way to generated props`); - } - const { stores, actions } = this.props; - const profileStore = stores.profile; - - return Object.freeze({ - stores: { - serverConnectionStore: { - checkAdaServerStatus: stores.serverConnectionStore.checkAdaServerStatus, - }, - profile: { - setComplexityLevelRequest: { - error: profileStore.setComplexityLevelRequest.error, - isExecuting: profileStore.setComplexityLevelRequest.isExecuting, - }, - selectedComplexityLevel: profileStore.selectedComplexityLevel - }, - }, - actions: { - profile: { - selectComplexityLevel: { trigger: actions.profile.selectComplexityLevel.trigger }, - }, - }, - }); - } - - render(): Node { - return ( - - ); - } -} diff --git a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js deleted file mode 100644 index 95997e028d..0000000000 --- a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js +++ /dev/null @@ -1,59 +0,0 @@ -// @flow - -import type { Node } from 'react'; -import React from 'react'; - -import { select, boolean, } from '@storybook/addon-knobs'; -import { action } from '@storybook/addon-actions'; -import { ServerStatusErrors } from '../../../types/serverStatusErrorType'; -import ComplexityLevelSettingsPage from './ComplexityLevelSettingsPage'; -import { withScreenshot } from 'storycap'; -import { ComplexityLevels } from '../../../types/complexityLevelType'; -import { - walletLookup, -} from '../../../../stories/helpers/WalletCache'; -import { wrapSettings } from '../../../Routes'; -import { mockSettingsProps } from '../Settings.mock'; -import { ROUTES } from '../../../routes-config'; - -export default { - title: `${__filename.split('.')[0]}`, - component: ComplexityLevelSettingsPage, - decorators: [withScreenshot], -}; - -export const Generic = (): Node => { - const lookup = walletLookup([]); - return wrapSettings( - mockSettingsProps({ - location: ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY, - selected: null, - ...lookup, - }), - ( action('selectComplexityLevel')(req) }, - } - } - }} - />) - ); -}; From c01d7c756cd83640cb8170ed2e363cbb62357d4d Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Thu, 8 Jul 2021 08:35:16 +0200 Subject: [PATCH 02/17] Remove complexity page testing --- packages/yoroi-extension/features/settings-ui.feature | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/yoroi-extension/features/settings-ui.feature b/packages/yoroi-extension/features/settings-ui.feature index ec122919a1..99ac246e84 100644 --- a/packages/yoroi-extension/features/settings-ui.feature +++ b/packages/yoroi-extension/features/settings-ui.feature @@ -172,14 +172,6 @@ Feature: Wallet UI Settings And I click on secondary menu "support" item Then I should see support screen - @it-125 - Scenario: Switch complexity levels (IT-125) - And I navigate to the general settings screen - And I click on secondary menu "levelOfComplexity" item - Then The selected level is "ADVANCED" - Then I select the simplest level - Then The selected level is "SIMPLE" - @it-126 Scenario: Yoroi Settings Screen / Blockchain (IT-126) And There is a Byron wallet stored named empty-wallet From 2249cf925450719275b2df673dea9d1b46e18b74 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 12 Jul 2021 09:44:53 +0200 Subject: [PATCH 03/17] Revert "Remove complexity pages" This reverts commit 8f136790b19601bbba804d1b1b3baece1610b082. --- packages/yoroi-extension/app/Routes.js | 12 ++ .../complexity-level/ComplexityLevelForm.js | 132 ++++++++++++++++++ .../complexity-level/ComplexityLevelForm.scss | 102 ++++++++++++++ .../components/settings/menu/SettingsMenu.js | 11 ++ .../containers/profile/ComplexityLevelPage.js | 124 ++++++++++++++++ .../profile/ComplexityLevelPage.stories.js | 47 +++++++ .../app/containers/profile/TermsOfUsePage.js | 13 +- .../profile/TermsOfUsePage.stories.js | 1 - .../categories/ComplexityLevelSettingsPage.js | 81 +++++++++++ .../ComplexityLevelSettingsPage.stories.js | 59 ++++++++ 10 files changed, 570 insertions(+), 12 deletions(-) create mode 100644 packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js create mode 100644 packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss create mode 100644 packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js create mode 100644 packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js create mode 100644 packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js create mode 100644 packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js diff --git a/packages/yoroi-extension/app/Routes.js b/packages/yoroi-extension/app/Routes.js index dc09530bb5..6a735017a1 100644 --- a/packages/yoroi-extension/app/Routes.js +++ b/packages/yoroi-extension/app/Routes.js @@ -42,6 +42,8 @@ import NoticeBoardPage from './containers/notice-board/NoticeBoardPage'; import VotingPage from './containers/wallet/voting/VotingPage'; import type { ConfigType } from '../config/config-types'; +import ComplexityLevelSettingsPage from './containers/settings/categories/ComplexityLevelSettingsPage'; +import ComplexityLevelPage from './containers/profile/ComplexityLevelPage'; import BlockchainSettingsPage from './containers/settings/categories/BlockchainSettingsPage'; import WalletSwitch from './containers/WalletSwitch'; @@ -70,6 +72,11 @@ export const Routes = ( path={ROUTES.PROFILE.LANGUAGE_SELECTION} component={(props) => } /> + } + /> ( path={ROUTES.SETTINGS.SUPPORT} component={(props) => } /> + } + /> ); diff --git a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js new file mode 100644 index 0000000000..9440fc9ce8 --- /dev/null +++ b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.js @@ -0,0 +1,132 @@ +// @flow +import React, { Component } from 'react'; +import type { Node } from 'react'; +import { intlShape, defineMessages } from 'react-intl'; +import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import styles from './ComplexityLevelForm.scss'; +import classnames from 'classnames'; +import BeginnerLevel from '../../../assets/images/complexity-level/beginner-level.inline.svg'; +import AdvancedLevel from '../../../assets/images/complexity-level/advanced-level.inline.svg'; +import LocalizableError from '../../../i18n/LocalizableError'; +import { Button } from 'react-polymorph/lib/components/Button'; +import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; +import { ComplexityLevels } from '../../../types/complexityLevelType'; +import type { ComplexityLevelType } from '../../../types/complexityLevelType'; + +const messages = defineMessages({ + subtitle: { + id: 'profile.complexityLevel.subtitle', + defaultMessage: '!!!Understanding blockchain can be difficult, which is why we will try and keep the interface as simple as possible for you' + }, + titleSimpleLevel: { + id: 'profile.complexityLevel.simple', + defaultMessage: '!!!Simple' + }, + titleAdvancedLevel: { + id: 'profile.complexityLevel.advanced', + defaultMessage: '!!!Advanced' + }, + descriptionSimpleLevel: { + id: 'profile.complexityLevel.simple.description', + defaultMessage: '!!!Simplest experience possible. No previous knowledge in Blockchain required. Highly friendly to on-board beginners, and for users that prefer simplicity.' + }, + descriptionAdvancedLevel: { + id: 'profile.complexityLevel.advanced.description', + defaultMessage: '!!!I have a some understanding of blockchain and how cryptography is used to power both the blockchain itself and wallet software. I am okay with seeing options and functionality that critically depend on my understanding of these concepts.' + }, + labelSelectedLevel: { + id: 'profile.complexityLevel.selected.label', + defaultMessage: '!!!Your currently level of Complexity is' + }, + labelChoose: { + id: 'global.label.choose', + defaultMessage: '!!!Choose' + }, +}); +type Props = {| + +complexityLevel: ?ComplexityLevelType, + +onSubmit: ComplexityLevelType => PossiblyAsync, + +isSubmitting: boolean, + +error?: ?LocalizableError +|} + +class ComplexityLevel extends Component { + static defaultProps: {|error: void|} = { + error: undefined + }; + + static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { + intl: intlShape.isRequired, + }; + + render(): Node { + const { intl } = this.context; + const { complexityLevel, isSubmitting } = this.props; + + const levels = [ + { + key: ComplexityLevels.Simple, + name: intl.formatMessage(messages.titleSimpleLevel), + image: , + description: intl.formatMessage(messages.descriptionSimpleLevel), + }, + { + key: ComplexityLevels.Advanced, + name: intl.formatMessage(messages.titleAdvancedLevel), + image: , + description: intl.formatMessage(messages.descriptionAdvancedLevel), + } + ]; + + const buttonClasses = classnames([ + 'secondary', + isSubmitting ? + styles.submitButtonSpinning : + styles.submitButton + ]); + + return ( + <> +
+
+ {intl.formatMessage(messages.subtitle)} +
+
+ { + complexityLevel && + <> + {intl.formatMessage(messages.labelSelectedLevel)} : {complexityLevel} + + } +
+
+ { + levels.map(level => ( +
+
+ {level.image} +
+
+
+

{level.name}

+

{level.description}

+
+
+
+ )) + } +
+
+ + ); + } +} + +export default ComplexityLevel; diff --git a/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss new file mode 100644 index 0000000000..a31746bc41 --- /dev/null +++ b/packages/yoroi-extension/app/components/profile/complexity-level/ComplexityLevelForm.scss @@ -0,0 +1,102 @@ +@import '../../../themes/mixins/loading-spinner'; +@import '../../../themes/mixins/error-message'; + +.component{ + max-width: 650px; + margin: 0 auto; + margin-top: 65px; + .header { + color: var(--theme-wallet-dropdown-secondary-text-color); + text-align: center; + margin-bottom: 50px; + font-size: 1.125rem; + font-weight: bold; + letter-spacing: 0; + line-height: 1.375rem; + } + .description { + color: var(--theme-transactions-list-detail-row-text-color); + font-size: 1rem; + line-height: 1.375rem; + text-align: center; + } + .selected { + font-size: 1rem; + text-align: center; + margin: 1rem 0; + + span { + color: var(--theme-button-outlined-text-color); + font-weight: bold; + text-transform: uppercase; + } + } + .cardsWrapper { + margin: 30px auto; + display: flex; + & > div { + flex: 1; + } + } + .card { + margin-right: 30px; + border-radius: 8px; + background-color: #FFFFFF; + box-shadow: 0 5px 20px 0 rgba(24,26,30,0.08); + overflow: hidden; + &:last-child { + margin-right: 0; + } + .cardImage { + background-color: #e5e5e5; + height:184px; + display: flex; + align-items: center; + justify-content: center; + } + .simple { + background: #F1FDFA; + } + .advanced { + background: #F3F5FD; + } + .cardContent{ + padding: 24px ; + color: var(--theme-transactions-list-text-color); + display: flex; + flex-direction: column; + justify-content: space-between; + height: 300px; + h3 { + font-size: 1.5rem; + letter-spacing: 0; + line-height: 1.875rem; + text-align: center; + text-transform: capitalize; + margin-bottom: 16px; + } + p { + font-size: .75rem; + letter-spacing: 0; + line-height: 1.25rem; + } + } + .submitButton, + .submitButtonSpinning { + display: block !important; + margin: 0; + width: 100%; + } + + .submitButtonSpinning { + @include loading-spinner("../../../assets/images/spinner-light.svg"); + } + + .error { + @include error-message; + text-align: center; + margin-bottom: 1rem; + } + + } +} \ No newline at end of file diff --git a/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js b/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js index 56fc2b5241..2f528b9c99 100644 --- a/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js +++ b/packages/yoroi-extension/app/components/settings/menu/SettingsMenu.js @@ -20,6 +20,10 @@ const messages = defineMessages({ id: 'settings.menu.blockchain.link.label', defaultMessage: '!!!Blockchain', }, + levelOfComplexity: { + id: 'settings.menu.levelOfComplexity.link.label', + defaultMessage: '!!!Level of Complexity', + }, externalStorage: { id: 'settings.menu.externalStorage.link.label', defaultMessage: '!!!External Storage', @@ -90,6 +94,13 @@ export default class SettingsMenu extends Component { active={isActiveItem(ROUTES.SETTINGS.SUPPORT)} className="support" /> + + onItemClick(ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY)} + active={isActiveItem(ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY)} + className="levelOfComplexity" + /> ); diff --git a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js new file mode 100644 index 0000000000..6fea692099 --- /dev/null +++ b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.js @@ -0,0 +1,124 @@ +// @flow +import React, { Component } from 'react'; +import type { Node } from 'react'; +import TopBarLayout from '../../components/layout/TopBarLayout'; +import TopBar from '../../components/topbar/TopBar'; +import StaticTopbarTitle from '../../components/topbar/StaticTopbarTitle'; +import { defineMessages, intlShape } from 'react-intl'; +import TestnetWarningBanner from '../../components/topbar/banners/TestnetWarningBanner'; +import ServerErrorBanner from '../../components/topbar/banners/ServerErrorBanner'; +import type { InjectedOrGenerated } from '../../types/injectedPropsType'; +import { computed } from 'mobx'; +import { ServerStatusErrors } from '../../types/serverStatusErrorType'; +import { observer } from 'mobx-react'; +import type { $npm$ReactIntl$IntlFormat } from 'react-intl'; +import ComplexityLevel from '../../components/profile/complexity-level/ComplexityLevelForm'; +import type { ComplexityLevelType } from '../../types/complexityLevelType'; +import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; +import LocalizableError from '../../i18n/LocalizableError'; +import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; +import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; + +const messages = defineMessages({ + title: { + id: 'profile.complexityLevel.title', + defaultMessage: '!!!Level of Interface Complexity', + }, +}); + +type GeneratedData = typeof ComplexityLevelPage.prototype.generated; +@observer +export default class ComplexityLevelPage extends Component> { + static contextTypes: {|intl: $npm$ReactIntl$IntlFormat|} = { + intl: intlShape.isRequired, + }; + + render(): Node { + + const { checkAdaServerStatus } = this.generated.stores.serverConnectionStore; + + const { selected } = this.generated.stores.wallets; + const isWalletTestnet = selected == null + ? false + : isTestnet(selected.getParent().getNetworkInfo()); + const displayedBanner = checkAdaServerStatus === ServerStatusErrors.Healthy + ? + : ; + + const topbarTitle = ( + + ); + + const topbarElement = ( + ); + return ( + + + + ); + } + + + @computed get generated(): {| + actions: {| + profile: {| + selectComplexityLevel: {| trigger: (params: ComplexityLevelType) => Promise < void > |} + |} + |}, + stores: {| + wallets: {| selected: null | PublicDeriver<> |}, + profile: {| + selectedComplexityLevel: ?ComplexityLevelType, + setComplexityLevelRequest: {| + error: ?LocalizableError, + isExecuting: boolean + |} + |}, + serverConnectionStore: {| + checkAdaServerStatus: ServerStatusErrorType + |} + |} + |} { + if (this.props.generated !== undefined) { + return this.props.generated; + } + if (this.props.stores == null || this.props.actions == null) { + throw new Error(`${nameof(ComplexityLevelPage)} no way to generated props`); + } + const { stores, actions } = this.props; + const profileStore = stores.profile; + + return Object.freeze({ + stores: { + serverConnectionStore: { + checkAdaServerStatus: stores.serverConnectionStore.checkAdaServerStatus, + }, + wallets: { + selected: stores.wallets.selected, + }, + profile: { + setComplexityLevelRequest: { + error: profileStore.setComplexityLevelRequest.error, + isExecuting: profileStore.setComplexityLevelRequest.isExecuting, + }, + selectedComplexityLevel: profileStore.selectedComplexityLevel + }, + }, + actions: { + profile: { + selectComplexityLevel: { trigger: actions.profile.selectComplexityLevel.trigger }, + }, + }, + }); + } +} diff --git a/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js new file mode 100644 index 0000000000..bda2a18aa3 --- /dev/null +++ b/packages/yoroi-extension/app/containers/profile/ComplexityLevelPage.stories.js @@ -0,0 +1,47 @@ +// @flow + +import type { Node } from 'react'; +import React from 'react'; + +import { select, boolean, } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { ServerStatusErrors } from '../../types/serverStatusErrorType'; +import ComplexityLevelPage from './ComplexityLevelPage'; +import { withScreenshot } from 'storycap'; + +export default { + title: `${__filename.split('.')[0]}`, + component: ComplexityLevelPage, + decorators: [withScreenshot], +}; + +export const Generic = (): Node => ( + action('selectComplexityLevel')(req) }, + } + } + }} + /> +); diff --git a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js index 1e46fb0549..e6ac454309 100644 --- a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js +++ b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.js @@ -17,8 +17,6 @@ import LocalizableError from '../../i18n/LocalizableError'; import type { ServerStatusErrorType } from '../../types/serverStatusErrorType'; import { PublicDeriver } from '../../api/ada/lib/storage/models/PublicDeriver/index'; import { isTestnet } from '../../api/ada/lib/storage/database/prepackaged/networks'; -import type { ComplexityLevelType } from '../../types/complexityLevelType'; -import { ComplexityLevels } from '../../types/complexityLevelType'; const messages = defineMessages({ title: { @@ -36,11 +34,6 @@ export default class TermsOfUsePage extends Component void) = (acceptance: any) => { - this.generated.actions.profile.acceptTermsOfUse.trigger(acceptance) - this.generated.actions.profile.selectComplexityLevel.trigger(ComplexityLevels.Simple) - } - render(): Node { const { checkAdaServerStatus } = this.generated.stores.serverConnectionStore; const { selected } = this.generated.stores.wallets; @@ -65,7 +58,7 @@ export default class TermsOfUsePage extends Component @@ -78,8 +71,7 @@ export default class TermsOfUsePage extends Component Promise - |}, - selectComplexityLevel: {| trigger: (params: ComplexityLevelType) => Promise < void > |} + |} |} |}, stores: {| @@ -123,7 +115,6 @@ export default class TermsOfUsePage extends Component ( actions: { profile: { acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) }, - selectComplexityLevel: { trigger: async (req) => action('selectComplexityLevel')(req) }, } } }} diff --git a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js new file mode 100644 index 0000000000..dd3c039120 --- /dev/null +++ b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.js @@ -0,0 +1,81 @@ +// @flow +import type { Node } from 'react'; +import React, { Component } from 'react'; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import type { InjectedOrGenerated } from '../../../types/injectedPropsType'; +import ComplexityLevel from '../../../components/profile/complexity-level/ComplexityLevelForm'; +import LocalizableError from '../../../i18n/LocalizableError'; +import type { ComplexityLevelType } from '../../../types/complexityLevelType'; +import type { ServerStatusErrorType } from '../../../types/serverStatusErrorType'; + +type GeneratedData = typeof ComplexityLevelSettingsPage.prototype.generated; + +@observer +export default class ComplexityLevelSettingsPage + extends Component> { + + @computed get generated(): {| + actions: {| + profile: {| + selectComplexityLevel: {| + trigger: ( + params: ComplexityLevelType + ) => Promise + |} + |} + |}, + stores: {| + profile: {| + selectedComplexityLevel: ?ComplexityLevelType, + setComplexityLevelRequest: {| + error: ?LocalizableError, + isExecuting: boolean + |} + |}, + serverConnectionStore: {| + checkAdaServerStatus: ServerStatusErrorType + |} + |} + |} { + if (this.props.generated !== undefined) { + return this.props.generated; + } + if (this.props.stores == null || this.props.actions == null) { + throw new Error(`${nameof(ComplexityLevelSettingsPage)} no way to generated props`); + } + const { stores, actions } = this.props; + const profileStore = stores.profile; + + return Object.freeze({ + stores: { + serverConnectionStore: { + checkAdaServerStatus: stores.serverConnectionStore.checkAdaServerStatus, + }, + profile: { + setComplexityLevelRequest: { + error: profileStore.setComplexityLevelRequest.error, + isExecuting: profileStore.setComplexityLevelRequest.isExecuting, + }, + selectedComplexityLevel: profileStore.selectedComplexityLevel + }, + }, + actions: { + profile: { + selectComplexityLevel: { trigger: actions.profile.selectComplexityLevel.trigger }, + }, + }, + }); + } + + render(): Node { + return ( + + ); + } +} diff --git a/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js new file mode 100644 index 0000000000..95997e028d --- /dev/null +++ b/packages/yoroi-extension/app/containers/settings/categories/ComplexityLevelSettingsPage.stories.js @@ -0,0 +1,59 @@ +// @flow + +import type { Node } from 'react'; +import React from 'react'; + +import { select, boolean, } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import { ServerStatusErrors } from '../../../types/serverStatusErrorType'; +import ComplexityLevelSettingsPage from './ComplexityLevelSettingsPage'; +import { withScreenshot } from 'storycap'; +import { ComplexityLevels } from '../../../types/complexityLevelType'; +import { + walletLookup, +} from '../../../../stories/helpers/WalletCache'; +import { wrapSettings } from '../../../Routes'; +import { mockSettingsProps } from '../Settings.mock'; +import { ROUTES } from '../../../routes-config'; + +export default { + title: `${__filename.split('.')[0]}`, + component: ComplexityLevelSettingsPage, + decorators: [withScreenshot], +}; + +export const Generic = (): Node => { + const lookup = walletLookup([]); + return wrapSettings( + mockSettingsProps({ + location: ROUTES.SETTINGS.LEVEL_OF_COMPLEXITY, + selected: null, + ...lookup, + }), + ( action('selectComplexityLevel')(req) }, + } + } + }} + />) + ); +}; From 020a1c8dd4b3569faade502b51784cb731225081 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Mon, 12 Jul 2021 09:57:09 +0200 Subject: [PATCH 04/17] Remove the complexity page from wallet setup --- .../app/containers/profile/TermsOfUsePage.stories.js | 2 +- .../yoroi-extension/app/stores/toplevel/ProfileStore.js | 2 ++ packages/yoroi-extension/features/settings-ui.feature | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js index 29615397fb..bdf4d031e2 100644 --- a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js +++ b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js @@ -41,7 +41,7 @@ export const Generic = (): Node => ( }, actions: { profile: { - acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) }, + acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) } } } }} diff --git a/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js b/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js index ae80a5dd81..b65dcaa0b5 100644 --- a/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js @@ -8,6 +8,7 @@ import { ROUTES } from '../../routes-config'; import type { NetworkRow } from '../../api/ada/lib/storage/database/primitives/tables'; import type { ActionsMap } from '../../actions/index'; import type { StoresMap } from '../index'; +import { ComplexityLevels } from '../../types/complexityLevelType'; export default class ProfileStore extends BaseProfileStore { @observable __selectedNetwork: void | $ReadOnly = undefined; @@ -48,6 +49,7 @@ export default class ProfileStore extends BaseProfileStore Date: Fri, 16 Jul 2021 11:38:32 +0200 Subject: [PATCH 05/17] Remove complexity-page from testing setup --- .../app/containers/profile/TermsOfUsePage.stories.js | 2 +- packages/yoroi-extension/app/stores/toplevel/ProfileStore.js | 1 - .../features/step_definitions/common-steps.js | 5 ----- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js index bdf4d031e2..29615397fb 100644 --- a/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js +++ b/packages/yoroi-extension/app/containers/profile/TermsOfUsePage.stories.js @@ -41,7 +41,7 @@ export const Generic = (): Node => ( }, actions: { profile: { - acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) } + acceptTermsOfUse: { trigger: async (req) => action('acceptTermsOfUse')(req) }, } } }} diff --git a/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js b/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js index b65dcaa0b5..7342e8714a 100644 --- a/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js +++ b/packages/yoroi-extension/app/stores/toplevel/ProfileStore.js @@ -50,7 +50,6 @@ export default class ProfileStore extends BaseProfileStore Date: Wed, 21 Jul 2021 11:06:37 +0200 Subject: [PATCH 06/17] Add "select ADVANCED level" step when running the test for features that not exist on the SIMPLE level --- .../yoroi-extension/features/memo.feature | 4 ++- .../features/migration.feature | 4 +-- .../features/settings-ui.feature | 6 ++-- .../features/step_definitions/common-steps.js | 28 +++++++++++++++++++ .../features/yoroi-transfer.feature | 1 + 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/packages/yoroi-extension/features/memo.feature b/packages/yoroi-extension/features/memo.feature index 4ce23a5638..9f10de3f1e 100644 --- a/packages/yoroi-extension/features/memo.feature +++ b/packages/yoroi-extension/features/memo.feature @@ -37,6 +37,8 @@ Feature: Memos | password | | asdfasdfasdf | And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button Then I should see the summary screen And I expand the top transaction - Then The memo content says "my awesome memo" + Then The memo content says "my awesome memo" \ No newline at end of file diff --git a/packages/yoroi-extension/features/migration.feature b/packages/yoroi-extension/features/migration.feature index 59689b1604..9223b64998 100644 --- a/packages/yoroi-extension/features/migration.feature +++ b/packages/yoroi-extension/features/migration.feature @@ -28,7 +28,6 @@ Feature: Migration # make sure all major functionality work # even if user hasn't launched Yoroi since the very first version Given I import a snapshot named historical-versions/1_0_4/software - Then I select the most complex level Then I accept uri registration Then I should see the summary screen Then I should see a plate EDAO-9229 @@ -55,6 +54,8 @@ Feature: Migration | password | | asdfasdfasdf | And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button Then I should see the summary screen Examples: @@ -67,7 +68,6 @@ Feature: Migration # make sure all major functionality work # even if user hasn't launched Yoroi since the very first version Given I import a snapshot named historical-versions/1_4_0/software - Then I select the most complex level Then I accept uri registration Then I should see the summary screen Then I should see a plate EDAO-9229 diff --git a/packages/yoroi-extension/features/settings-ui.feature b/packages/yoroi-extension/features/settings-ui.feature index ec122919a1..e2d7f33abd 100644 --- a/packages/yoroi-extension/features/settings-ui.feature +++ b/packages/yoroi-extension/features/settings-ui.feature @@ -48,6 +48,8 @@ Feature: Wallet UI Settings | password | | newSecret123 | And I submit the wallet send form + Then I should see the successfully sent page + And I click the transaction page button Then I should see the summary screen Examples: @@ -176,9 +178,9 @@ Feature: Wallet UI Settings Scenario: Switch complexity levels (IT-125) And I navigate to the general settings screen And I click on secondary menu "levelOfComplexity" item - Then The selected level is "ADVANCED" - Then I select the simplest level Then The selected level is "SIMPLE" + Then I select the most complex level + Then The selected level is "ADVANCED" @it-126 Scenario: Yoroi Settings Screen / Blockchain (IT-126) diff --git a/packages/yoroi-extension/features/step_definitions/common-steps.js b/packages/yoroi-extension/features/step_definitions/common-steps.js index 6d4687e01d..8981037812 100644 --- a/packages/yoroi-extension/features/step_definitions/common-steps.js +++ b/packages/yoroi-extension/features/step_definitions/common-steps.js @@ -15,6 +15,11 @@ import { import { truncateLongName, } from '../../app/utils/formatters'; import stableStringify from 'json-stable-stringify'; import type { RestorationInput } from '../mock-chain/TestWallets'; +import { + waitUntilUrlEquals, + navigateTo, +} from '../support/helpers/route-helpers'; +import { camelCase } from 'lodash'; const { promisify } = require('util'); const fs = require('fs'); @@ -253,6 +258,29 @@ Given(/^I have completed the basic setup$/, async function () { await this.waitForElement('.WalletAdd_component'); }); +Given(/^I switched to the advanced level$/, async function(){ + // Navigate to the general settings screen + await navigateTo.call(this, '/settings'); + await navigateTo.call(this, '/settings/general'); + await waitUntilUrlEquals.call(this, '/settings/general'); + await this.waitForElement('.SettingsLayout_component'); + // Click on secondary menu "levelOfComplexity" item + const buttonSelector = `.SettingsMenuItem_component.${camelCase('levelOfComplexity')}`; + await this.click(buttonSelector); + await this.waitForElement( + `${buttonSelector}.SettingsMenuItem_active` + ); + // Select the most complex level + await this.waitForElement('.ComplexityLevelForm_submitButton'); + const levels = await this.driver.findElements(By.css('.ComplexityLevelForm_submitButton')); + await levels[levels.length - 1].click(); // choose most complex level for tests + + // Navigate back to the main page + await navigateTo.call(this, '/wallets/add'); + await waitUntilUrlEquals.call(this, '/wallets/add'); + await this.waitForElement('.WalletAdd_component'); +}) + Then(/^I accept uri registration$/, async function () { await acceptUriPrompt(this); }); diff --git a/packages/yoroi-extension/features/yoroi-transfer.feature b/packages/yoroi-extension/features/yoroi-transfer.feature index 9cce8074e9..ae6ec840db 100644 --- a/packages/yoroi-extension/features/yoroi-transfer.feature +++ b/packages/yoroi-extension/features/yoroi-transfer.feature @@ -3,6 +3,7 @@ Feature: Transfer Yoroi Wallet funds Background: Given I have opened the extension And I have completed the basic setup + And I switched to the advanced level Then I should see the Create wallet screen @it-114 From de397d0d2bb4b72f399fa42e7a4706dc8d132b62 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 22 Jul 2021 23:40:31 +0300 Subject: [PATCH 07/17] Example dapp refactored a bit and added some more logging --- .../yoroi-ergo-connector/example/index.js | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/packages/yoroi-ergo-connector/example/index.js b/packages/yoroi-ergo-connector/example/index.js index 10bac635f9..e8ffc2939a 100644 --- a/packages/yoroi-ergo-connector/example/index.js +++ b/packages/yoroi-ergo-connector/example/index.js @@ -86,7 +86,7 @@ function initDapp() { new wasm.Tokens()); console.log(`boxes selected: ${boxSelection.boxes().len()}`); const outputCandidates = wasm.ErgoBoxCandidates.empty(); - const token = new wasm.Token(wasm.TokenId.from_box_id(wasm.BoxId.from_str(utxos[2].boxId)), wasm.TokenAmount.from_i64(wasm.I64.from_str("1234567890123456789"))); + const token = new wasm.Token(wasm.TokenId.from_box_id(wasm.BoxId.from_str(utxos[utxos.length - 1].boxId)), wasm.TokenAmount.from_i64(wasm.I64.from_str("1234567890123456789"))); const donationBoxBuilder = new wasm.ErgoBoxCandidateBuilder( amountToSendBoxValue, wasm.Contract.pay_to_address(wasm.Address.from_base58(donationAddr)), @@ -137,25 +137,62 @@ console.log(`box: ${JSON.stringify(box)}`); }); status.innerText = "Awaiting transaction signing"; console.log(`${JSON.stringify(correctTx)}`); - ergo - .sign_tx(correctTx) - .then(async signedTx => { - status.innerText = "Transaction signed - awaiting submission" - try { - const sentTxId = await ergo.submit_tx(signedTx); - status.innerText = "Transaction submitted - thank you for your donation!"; - const txTracker = document.createElement("a"); - txTracker.appendChild(document.createTextNode(`Track TX ${sentTxId}`)); - txTracker.href = `https://explorer.ergoplatform.com/en/transactions/${sentTxId}`; - status.appendChild(txTracker); - } catch (e) { - status.innerText = `Transaction could not be sent: ${JSON.stringify(e)}`; - } - }) - .catch(err => { - console.log(`Error: ${JSON.stringify(err)}`); - status.innerText = `Error: ${JSON.stringify(err)}` - }); + + async function signTx(txToBeSigned) { + try { + return await ergo.sign_tx(txToBeSigned); + } catch (err) { + const msg = `[signTx] Error: ${err}`; + console.error(msg, err); + status.innerText = msg + return null; + } + } + + async function submitTx(txToBeSubmitted) { + try { + return await ergo.submit_tx(txToBeSubmitted); + } catch (err) { + const msg = `[submitTx] Error: ${err}`; + console.error(msg, err); + status.innerText = msg + return null; + } + } + + async function processTx(txToBeProcessed) { + const msg = s => { + console.log('[processTx]', s); + status.innerText = s; + }; + const signedTx = await signTx(txToBeProcessed); + if (!signedTx) { + console.log(`No signed tx`); + return null; + } + msg("Transaction signed - awaiting submission"); + const txId = await submitTx(signedTx); + if (!txId) { + console.log(`No submotted tx ID`); + return null; + } + msg("Transaction submitted - thank you for your donation!"); + return txId; + } + + function displayTxId(txId) { + const txTracker = document.createElement("a"); + txTracker.appendChild(document.createTextNode(`Track TX ${txId}`)); + txTracker.href = `https://explorer.ergoplatform.com/en/transactions/${txId}`; + status.appendChild(txTracker); + } + + processTx(correctTx).then(txId => { + console.log('[txId]', txId); + if (txId) { + displayTxId(txId); + } + }); } div.appendChild(valueEntry); div.appendChild(button); From 728088588e1087c2ef0f61fbcded58e4564beaf6 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Fri, 23 Jul 2021 01:07:40 +0300 Subject: [PATCH 08/17] Example dapp refactored a bit --- .../yoroi-ergo-connector/example/index.js | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/packages/yoroi-ergo-connector/example/index.js b/packages/yoroi-ergo-connector/example/index.js index e8ffc2939a..0a3eba7b56 100644 --- a/packages/yoroi-ergo-connector/example/index.js +++ b/packages/yoroi-ergo-connector/example/index.js @@ -54,23 +54,32 @@ function initDapp() { valueEntry.setAttribute("value", Math.floor(result / 10)); const button = document.createElement("button"); button.textContent = "Send"; + + async function getUtxos(amountToSend) { + const fee = BigInt(wasm.TxBuilder.SUGGESTED_TX_FEE().as_i64().to_str()); + const fullAmountToSend = BigInt(1000) * amountToSend + fee; + const utxos = await ergo.get_utxos(fullAmountToSend.toString()); + const filteredUtxos = []; + for (const utxo of utxos) { + try { + await wasm.ErgoBox.from_json(JSON.stringify(utxo)); + filteredUtxos.push(utxo); + } catch (e) { + console.error('[getUtxos] UTxO failed parsing:', utxo, e); + } + } + return filteredUtxos; + } + button.onclick = async function() { status.innerText = "Creating transaction"; const donationAddr = "9hD2Cw6yQL6zzrw3TFgKdwFkBdDdU3ro1xRFmjouDw4NYS2S5RD"; const creationHeight = 398959; const amountToSend = BigInt(valueEntry.value); const amountToSendBoxValue = wasm.BoxValue.from_i64(wasm.I64.from_str(amountToSend.toString())); - const rawUtxos = await ergo.get_utxos((amountToSend + BigInt(wasm.TxBuilder.SUGGESTED_TX_FEE().as_i64().to_str())).toString()); - let utxosValue = BigInt(0); - let utxos = rawUtxos.map(utxo => { - // need to convert strings to numbers for sigma-rust for now - //utxo.value = parseInt(utxo.value, 10); - utxosValue += BigInt(utxo.value); - for (let asset of utxo.assets) { - //asset.amount = parseInt(asset.amount); - } - return utxo; - }); + const utxos = await getUtxos(amountToSend); + let utxosValue = utxos.reduce((acc, utxo) => acc += BigInt(utxo.value), BigInt(0)); + console.log('utxos', utxosValue, utxos); // Testing with p2S inputs since Yoroi won't return those as they don't belong to anyone's wallet //while (utxos.length > 1) { utxos.pop(); } //utxos.unshift({"boxId":"6dd679cc32afd1f56ad74696c7af53c45330148a703da29b3f6b3ca3b09851c3","value":1331719,"ergoTree":"1002040004f2c001d193e4c6b2a573000004047301","assets":[],"additionalRegisters":{},"creationHeight":398959,"transactionId":"d2fbf4b62f262f4bce7973924ae06685aa5ec2313e24716e8b1d86d62789c89b","index":0}); @@ -123,12 +132,13 @@ function initDapp() { //tx.outputs[0].ergoTree = "1002040004f2c001d193e4c6b2a573000004047301"; // and we rebuild it using const correctTx = wasm.UnsignedTransaction.from_json(JSON.stringify(tx)).to_json(); + console.log(`correct tx: ${JSON.stringify(correctTx)}`); console.log(`new id: ${correctTx.id}`); // we must use the exact order chosen as after 0.4.3 in sigma-rust // this can change and might not use all the utxos as the coin selection // might choose a more optimal amount correctTx.inputs = correctTx.inputs.map(box => { -console.log(`box: ${JSON.stringify(box)}`); + console.log(`box: ${JSON.stringify(box)}`); const fullBoxInfo = utxos.find(utxo => utxo.boxId === box.boxId); return { ...fullBoxInfo, @@ -142,7 +152,7 @@ console.log(`box: ${JSON.stringify(box)}`); try { return await ergo.sign_tx(txToBeSigned); } catch (err) { - const msg = `[signTx] Error: ${err}`; + const msg = `[signTx] Error: ${JSON.stringify(err)}`; console.error(msg, err); status.innerText = msg return null; @@ -153,7 +163,7 @@ console.log(`box: ${JSON.stringify(box)}`); try { return await ergo.submit_tx(txToBeSubmitted); } catch (err) { - const msg = `[submitTx] Error: ${err}`; + const msg = `[submitTx] Error: ${JSON.stringify(err)}`; console.error(msg, err); status.innerText = msg return null; From 7572a8002fadd6969fc4fb097fbe77ca7a7ec0e5 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 28 Jul 2021 00:21:46 +0300 Subject: [PATCH 09/17] Refactoring ergo remote-fetcher to remove a lot of duplication --- .../api/ergo/lib/state-fetch/remoteFetcher.js | 175 ++++++++---------- 1 file changed, 80 insertions(+), 95 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index 7ac42f4516..6ca83dd35a 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -40,6 +40,38 @@ import { decode, encode } from 'bs58'; // populated by ConfigWebpackPlugin declare var CONFIG: ConfigType; +function errClass(Err: Function): (* => Function) { + return _ => new Err(); +} + +async function axiosPost(url: string, params: { + data: *, + responseMapper?: Object => T, + callerName: string, + errorFactory: (error: *) => Function, +}): Promise { + return await axios(url, { + method: 'post', + timeout: 2 * CONFIG.app.walletRefreshInterval, + data: params.data, + headers: { + 'yoroi-version': this.getLastLaunchVersion(), + 'yoroi-locale': this.getCurrentLocale() + } + }).then(response => { + console.log(`AXIOS[${url}] > `, response); + const mapper = params.responseMapper; + const data = response.data; + return mapper ? mapper(data) : data; + }).catch((error) => { + Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); + const err = params.errorFactory(error); + if (err) { + throw err; + } + }); +} + /** * Makes calls to Yoroi backend service * https://github.com/Emurgo/yoroi-graphql-migration-backend @@ -63,73 +95,37 @@ export class RemoteFetcher implements IFetcher { getUTXOsForAddresses: AddressUtxoRequest => Promise = async (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getUTXOsForAddresses)} missing backend url`); - return await axios( - `${BackendService}/api/txs/utxoForAddresses`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } + return await axiosPost(`${BackendService}/api/txs/utxoForAddresses`, { + data: { addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) }, + callerName: nameof(this.getUTXOsForAddresses), + errorFactory: errClass(GetUtxosForAddressesApiError), + responseMapper: data => { + return data.map((resp: ElementOf) => ({ + ...resp, + receiver: decode(resp.receiver).toString('hex'), + })) } - ).then(response => response.data.map((resp: ElementOf) => ({ - ...resp, - receiver: decode(resp.receiver).toString('hex'), - }))) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getUTXOsForAddresses)} error: ` + stringifyError(error)); - throw new GetUtxosForAddressesApiError(); - }); + }); } getTxsBodiesForUTXOs: TxBodiesRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getTxsBodiesForUTXOs)} missing backend url`); - return axios( - `${BackendService}/api/txs/txBodies`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - txHashes: body.txHashes - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => response.data) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getTxsBodiesForUTXOs)} error: ` + stringifyError(error)); - throw new GetTxsBodiesForUTXOsApiError(); - }); + return axiosPost(`${BackendService}/api/txs/txBodies`, { + data: { txHashes: body.txHashes }, + callerName: nameof(this.getTxsBodiesForUTXOs), + errorFactory: errClass(GetTxsBodiesForUTXOsApiError), + }); } getUTXOsSumsForAddresses: UtxoSumRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getUTXOsSumsForAddresses)} missing backend url`); - return axios( - `${BackendService}/api/txs/utxoSumForAddresses`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => response.data) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getUTXOsSumsForAddresses)} error: ` + stringifyError(error)); - throw new GetUtxosSumsForAddressesApiError(); - }); + return axiosPost(`${BackendService}/api/txs/utxoSumForAddresses`, { + data: { addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) }, + callerName: nameof(this.getUTXOsSumsForAddresses), + errorFactory: errClass(GetUtxosSumsForAddressesApiError), + }); } getTransactionsHistoryForAddresses: HistoryRequest => Promise = (request) => { @@ -138,45 +134,33 @@ export class RemoteFetcher implements IFetcher { if (BackendService == null) throw new Error(`${nameof(this.getTransactionsHistoryForAddresses)} missing backend url`); return fixHistoryFunc(async body => { - return axios( - `${BackendService}/api/v2/txs/history`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - ...rest, - addresses: body.addresses, - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => { - const data: HistoryResponse = response.data; - for (const datum of data) { - for (let i = 0; i < datum.inputs.length; i++) { - // TODO: remove this once this ticket is merged - // https://github.com/ergoplatform/explorer-backend/issues/92 - if (datum.inputs[i].assets == null) { - datum.inputs[i].assets = []; - } - } - } - return data; - }) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getTransactionsHistoryForAddresses)} error: ` + stringifyError(error)); + return axiosPost(`${BackendService}/api/v2/txs/history`, { + data: { ...rest, addresses: body.addresses }, + callerName: nameof(this.getTransactionsHistoryForAddresses), + errorFactory: error => { const errorMessage = error.response.data.error; if ( errorMessage === 'REFERENCE_BLOCK_MISMATCH' || errorMessage === 'REFERENCE_TX_NOT_FOUND' || errorMessage === 'REFERENCE_BEST_BLOCK_MISMATCH' ) { - throw new RollbackApiError(); + return new RollbackApiError(); } - throw new GetTxHistoryForAddressesApiError(); - }); + return new GetTxHistoryForAddressesApiError(); + }, + responseMapper: (data: HistoryResponse) => { + for (const datum of data) { + for (let i = 0; i < datum.inputs.length; i++) { + // TODO: remove this once this ticket is merged + // https://github.com/ergoplatform/explorer-backend/issues/92 + if (datum.inputs[i].assets == null) { + datum.inputs[i].assets = []; + } + } + } + return data; + }, + }) })(request); } @@ -204,6 +188,7 @@ export class RemoteFetcher implements IFetcher { const { network, ...rest } = body; const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.sendTx)} missing backend url`); + console.log('[sendTx]', rest); return axios( `${BackendService}/api/txs/signed`, { @@ -218,13 +203,13 @@ export class RemoteFetcher implements IFetcher { ).then(response => ({ txId: response.data.id, })) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.sendTx)} error: ` + stringifyError(error)); - if (error.request.response.includes('Invalid witness')) { - throw new InvalidWitnessError(); - } - throw new SendTransactionApiError(); - }); + .catch((error) => { + Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.sendTx)} error: ` + stringifyError(error)); + if (error.request.response.includes('Invalid witness')) { + throw new InvalidWitnessError(); + } + throw new SendTransactionApiError(); + }); } getAssetInfo: AssetInfoRequest => Promise = (request) => { From 8526a06bbf4471e1e9d62f8db9df4e53c44621c6 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 28 Jul 2021 01:44:50 +0300 Subject: [PATCH 10/17] Fixed the extracted function in the ergo remote fetcher --- .../api/ergo/lib/state-fetch/remoteFetcher.js | 72 ++++++++++--------- 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index 6ca83dd35a..89c2bb670d 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -44,34 +44,6 @@ function errClass(Err: Function): (* => Function) { return _ => new Err(); } -async function axiosPost(url: string, params: { - data: *, - responseMapper?: Object => T, - callerName: string, - errorFactory: (error: *) => Function, -}): Promise { - return await axios(url, { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: params.data, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - }).then(response => { - console.log(`AXIOS[${url}] > `, response); - const mapper = params.responseMapper; - const data = response.data; - return mapper ? mapper(data) : data; - }).catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); - const err = params.errorFactory(error); - if (err) { - throw err; - } - }); -} - /** * Makes calls to Yoroi backend service * https://github.com/Emurgo/yoroi-graphql-migration-backend @@ -92,10 +64,41 @@ export class RemoteFetcher implements IFetcher { this.getPlatform = getPlatform; } - getUTXOsForAddresses: AddressUtxoRequest => Promise = async (body) => { + axiosPost: (string, { + data: *, + responseMapper?: Object => T, + callerName: string, + errorFactory: (error: *) => Function, + }) => Promise = (url, params): Promise => { + console.debug(`AXIOS[${url}] CALLING > `, params); + return axios(url, { + method: 'post', + timeout: 2 * CONFIG.app.walletRefreshInterval, + data: params.data, + headers: { + 'yoroi-version': this.getLastLaunchVersion(), + 'yoroi-locale': this.getCurrentLocale() + } + }).then(response => { + console.debug(`AXIOS[${url}] RSP > `, response); + const mapper = params.responseMapper; + const data = response.data; + return mapper ? mapper(data) : data; + }).catch((error) => { + console.debug(`AXIOS[${url}] ERR > `, error); + Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); + const err = params.errorFactory(error); + if (err) { + throw err; + } + }); + } + + + getUTXOsForAddresses: AddressUtxoRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getUTXOsForAddresses)} missing backend url`); - return await axiosPost(`${BackendService}/api/txs/utxoForAddresses`, { + return this.axiosPost(`${BackendService}/api/txs/utxoForAddresses`, { data: { addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) }, callerName: nameof(this.getUTXOsForAddresses), errorFactory: errClass(GetUtxosForAddressesApiError), @@ -111,7 +114,7 @@ export class RemoteFetcher implements IFetcher { getTxsBodiesForUTXOs: TxBodiesRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getTxsBodiesForUTXOs)} missing backend url`); - return axiosPost(`${BackendService}/api/txs/txBodies`, { + return this.axiosPost(`${BackendService}/api/txs/txBodies`, { data: { txHashes: body.txHashes }, callerName: nameof(this.getTxsBodiesForUTXOs), errorFactory: errClass(GetTxsBodiesForUTXOsApiError), @@ -121,7 +124,7 @@ export class RemoteFetcher implements IFetcher { getUTXOsSumsForAddresses: UtxoSumRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getUTXOsSumsForAddresses)} missing backend url`); - return axiosPost(`${BackendService}/api/txs/utxoSumForAddresses`, { + return this.axiosPost(`${BackendService}/api/txs/utxoSumForAddresses`, { data: { addresses: body.addresses.map(addr => encode(Buffer.from(addr, 'hex'))) }, callerName: nameof(this.getUTXOsSumsForAddresses), errorFactory: errClass(GetUtxosSumsForAddressesApiError), @@ -133,8 +136,9 @@ export class RemoteFetcher implements IFetcher { const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getTransactionsHistoryForAddresses)} missing backend url`); + const self = this; return fixHistoryFunc(async body => { - return axiosPost(`${BackendService}/api/v2/txs/history`, { + return await self.axiosPost(`${BackendService}/api/v2/txs/history`, { data: { ...rest, addresses: body.addresses }, callerName: nameof(this.getTransactionsHistoryForAddresses), errorFactory: error => { @@ -160,7 +164,7 @@ export class RemoteFetcher implements IFetcher { } return data; }, - }) + }); })(request); } From c2cc17131b84170ca1cc3c1e6ae5fc1fadbc6b23 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 28 Jul 2021 02:08:46 +0300 Subject: [PATCH 11/17] Refactoring ergo remote-fetcher to remove a lot of duplication --- .../api/ergo/lib/state-fetch/remoteFetcher.js | 160 +++++++----------- 1 file changed, 65 insertions(+), 95 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index 89c2bb670d..0f5c1308f6 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -44,6 +44,39 @@ function errClass(Err: Function): (* => Function) { return _ => new Err(); } +const axiosRequest: (RemoteFetcher, string) => (string, { + data?: *, + responseMapper?: Object => T, + callerName: string, + errorFactory: (error: *) => Function, +}) => Promise = (fetcher, method) => (url, params): Promise => { + const debug = (s, p) => { + console.debug(`AXIOS[${method}][${url}] ${s} > `, p); + }; + debug('CALLING', params); + return axios(url, { + method, + timeout: 2 * CONFIG.app.walletRefreshInterval, + headers: { + 'yoroi-version': fetcher.getLastLaunchVersion(), + 'yoroi-locale': fetcher.getCurrentLocale() + }, + ...(params.data ? { data: params.data } : {}), + }).then(response => { + debug('RSP', response); + const mapper = params.responseMapper; + const data = response.data; + return mapper ? mapper(data) : data; + }).catch((error) => { + debug('ERR', error); + Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); + const err = params.errorFactory(error); + if (err) { + throw err; + } + }); +}; + /** * Makes calls to Yoroi backend service * https://github.com/Emurgo/yoroi-graphql-migration-backend @@ -64,35 +97,19 @@ export class RemoteFetcher implements IFetcher { this.getPlatform = getPlatform; } + axiosGet: (string, { + data: *, + responseMapper?: Object => T, + callerName: string, + errorFactory: (error: *) => Function, + }) => Promise = axiosRequest(this, 'get'); + axiosPost: (string, { data: *, responseMapper?: Object => T, callerName: string, errorFactory: (error: *) => Function, - }) => Promise = (url, params): Promise => { - console.debug(`AXIOS[${url}] CALLING > `, params); - return axios(url, { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: params.data, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - }).then(response => { - console.debug(`AXIOS[${url}] RSP > `, response); - const mapper = params.responseMapper; - const data = response.data; - return mapper ? mapper(data) : data; - }).catch((error) => { - console.debug(`AXIOS[${url}] ERR > `, error); - Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); - const err = params.errorFactory(error); - if (err) { - throw err; - } - }); - } + }) => Promise = axiosRequest(this, 'post'); getUTXOsForAddresses: AddressUtxoRequest => Promise = (body) => { @@ -171,96 +188,49 @@ export class RemoteFetcher implements IFetcher { getBestBlock: BestBlockRequest => Promise = (body) => { const { BackendService } = body.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getBestBlock)} missing backend url`); - return axios( - `${BackendService}/api/v2/bestblock`, - { - method: 'get', - timeout: 2 * CONFIG.app.walletRefreshInterval, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => response.data) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getBestBlock)} error: ` + stringifyError(error)); - throw new GetBestBlockError(); - }); + return this.axiosGet(`${BackendService}/api/v2/bestblock`, { + callerName: nameof(this.getBestBlock), + errorFactory: errClass(GetBestBlockError), + }); } sendTx: SignedRequest => Promise = (body) => { const { network, ...rest } = body; const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.sendTx)} missing backend url`); - console.log('[sendTx]', rest); - return axios( - `${BackendService}/api/txs/signed`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: rest, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() + return this.axiosPost(`${BackendService}/api/txs/signed`, { + data: rest, + callerName: nameof(this.sendTx), + errorFactory: error => { + if (error.request.response.includes('Invalid witness')) { + return new InvalidWitnessError(); } - } - ).then(response => ({ - txId: response.data.id, - })) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.sendTx)} error: ` + stringifyError(error)); - if (error.request.response.includes('Invalid witness')) { - throw new InvalidWitnessError(); - } - throw new SendTransactionApiError(); + return new SendTransactionApiError(); + }, + responseMapper: ({ id }) => ({ txId: id }), }); } getAssetInfo: AssetInfoRequest => Promise = (request) => { const { BackendService } = request.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.getAssetInfo)} missing backend url`); - return axios( - `${BackendService}/api/assets/info`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - assetIds: request.assetIds - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => response.data) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getAssetInfo)} error: ` + stringifyError(error)); - throw new GetAssetInfoApiError(); - }); + return this.axiosPost(`${BackendService}/api/assets/info`, { + data: { assetIds: request.assetIds }, + callerName: nameof(this.getAssetInfo), + errorFactory: errClass(GetAssetInfoApiError), + }); } checkAddressesInUse: FilterUsedRequest => Promise = (request) => { const { BackendService } = request.network.Backend; if (BackendService == null) throw new Error(`${nameof(this.checkAddressesInUse)} missing backend url`); + const self = this; return fixFilterFunc(body => { - return axios( - `${BackendService}/api/v2/addresses/filterUsed`, - { - method: 'post', - timeout: 2 * CONFIG.app.walletRefreshInterval, - data: { - addresses: body.addresses - }, - headers: { - 'yoroi-version': this.getLastLaunchVersion(), - 'yoroi-locale': this.getCurrentLocale() - } - } - ).then(response => response.data) - .catch((error) => { - Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.checkAddressesInUse)} error: ` + stringifyError(error)); - throw new CheckAddressesInUseApiError(); - }); + return self.axiosPost(`${BackendService}/api/v2/addresses/filterUsed`, { + data: { addresses: body.addresses }, + callerName: nameof(this.checkAddressesInUse), + errorFactory: errClass(CheckAddressesInUseApiError), + }); })(request); } } From e874514c5ebdb693a75fb497c94acaa41aecccdf Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Wed, 28 Jul 2021 02:45:49 +0300 Subject: [PATCH 12/17] Added bigint parsing to axios. And added mapping from server bigint responses to proper string values. --- .../yoroi-extension/app/api/ergo/index.js | 12 ++- .../api/ergo/lib/state-fetch/remoteFetcher.js | 77 ++++++++++++------- packages/yoroi-extension/package-lock.json | 8 ++ packages/yoroi-extension/package.json | 1 + 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/index.js b/packages/yoroi-extension/app/api/ergo/index.js index dee168aa25..223159d41b 100644 --- a/packages/yoroi-extension/app/api/ergo/index.js +++ b/packages/yoroi-extension/app/api/ergo/index.js @@ -53,7 +53,9 @@ import type { HistoryFunc, BestBlockFunc, AssetInfoFunc, - SendFunc, SignedResponse, + SendFunc, + SignedResponse, + RemoteUnspentOutput, } from './lib/state-fetch/types'; import type { FilterFunc, @@ -105,6 +107,14 @@ import type { DefaultTokenEntry } from '../common/lib/MultiToken'; import { hasSendAllDefault, builtSendTokenList } from '../common/index'; import { getReceiveAddress } from '../../stores/stateless/addressStores'; +export function fixUtxoToStringValues(utxo: RemoteUnspentOutput): RemoteUnspentOutput { + utxo.value = String(utxo.value); + utxo.assets.forEach(a => { + a.amount = String(a.amount); + }); + return utxo; +} + // getTransactionRowsToExport export type GetTransactionRowsToExportRequest = {| diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index 0f5c1308f6..83c5abf36c 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -1,41 +1,51 @@ // @flow import type { - AddressUtxoRequest, AddressUtxoResponse, - TxBodiesRequest, TxBodiesResponse, - UtxoSumRequest, UtxoSumResponse, - HistoryFunc, HistoryRequest, HistoryResponse, - BestBlockRequest, BestBlockResponse, - SignedRequest, SignedResponse, - AssetInfoRequest, AssetInfoResponse, + AddressUtxoRequest, + AddressUtxoResponse, + AssetInfoRequest, + AssetInfoResponse, + BestBlockRequest, + BestBlockResponse, + HistoryFunc, + HistoryRequest, + HistoryResponse, + SignedRequest, + SignedResponse, + TxBodiesRequest, + TxBodiesResponse, + UtxoSumRequest, + UtxoSumResponse, } from './types'; import type { - FilterFunc, FilterUsedRequest, FilterUsedResponse, + FilterFunc, + FilterUsedRequest, + FilterUsedResponse, } from '../../../common/lib/state-fetch/currencySpecificTypes'; import type { IFetcher } from './IFetcher'; import axios from 'axios'; +import { Logger, stringifyError } from '../../../../utils/logging'; import { - Logger, - stringifyError -} from '../../../../utils/logging'; -import { + CheckAddressesInUseApiError, + GetAssetInfoApiError, + GetBestBlockError, + GetTxHistoryForAddressesApiError, GetTxsBodiesForUTXOsApiError, GetUtxosForAddressesApiError, GetUtxosSumsForAddressesApiError, - GetTxHistoryForAddressesApiError, - GetBestBlockError, - SendTransactionApiError, - GetAssetInfoApiError, - CheckAddressesInUseApiError, InvalidWitnessError, RollbackApiError, + SendTransactionApiError, } from '../../../common/errors'; import type { ConfigType } from '../../../../../config/config-types'; +import { fixUtxoToStringValues } from '../../index'; import { decode, encode } from 'bs58'; +import JSONBigInt from 'json-bigint'; +import cloneDeep from 'lodash/cloneDeep' // populated by ConfigWebpackPlugin declare var CONFIG: ConfigType; @@ -51,7 +61,7 @@ const axiosRequest: (RemoteFetcher, string) => (string, { errorFactory: (error: *) => Function, }) => Promise = (fetcher, method) => (url, params): Promise => { const debug = (s, p) => { - console.debug(`AXIOS[${method}][${url}] ${s} > `, p); + console.debug(`AXIOS[${method}][${url}] ${s} > `, cloneDeep(p)); }; debug('CALLING', params); return axios(url, { @@ -61,12 +71,21 @@ const axiosRequest: (RemoteFetcher, string) => (string, { 'yoroi-version': fetcher.getLastLaunchVersion(), 'yoroi-locale': fetcher.getCurrentLocale() }, + transformResponse: resp => { + debug('RSPRAW', resp); + return JSONBigInt.parse(resp); + }, ...(params.data ? { data: params.data } : {}), }).then(response => { debug('RSP', response); const mapper = params.responseMapper; const data = response.data; - return mapper ? mapper(data) : data; + if (!mapper) { + return data; + } + const res = mapper(data); + debug('RSPMAP', res); + return res; }).catch((error) => { debug('ERR', error); Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); @@ -121,7 +140,7 @@ export class RemoteFetcher implements IFetcher { errorFactory: errClass(GetUtxosForAddressesApiError), responseMapper: data => { return data.map((resp: ElementOf) => ({ - ...resp, + ...fixUtxoToStringValues(resp), receiver: decode(resp.receiver).toString('hex'), })) } @@ -135,6 +154,12 @@ export class RemoteFetcher implements IFetcher { data: { txHashes: body.txHashes }, callerName: nameof(this.getTxsBodiesForUTXOs), errorFactory: errClass(GetTxsBodiesForUTXOsApiError), + responseMapper: data => { + Object.values(data).forEach(({ outputs }) => { + outputs.forEach(o => fixUtxoToStringValues(o)); + }); + return data; + } }); } @@ -171,13 +196,10 @@ export class RemoteFetcher implements IFetcher { }, responseMapper: (data: HistoryResponse) => { for (const datum of data) { - for (let i = 0; i < datum.inputs.length; i++) { - // TODO: remove this once this ticket is merged - // https://github.com/ergoplatform/explorer-backend/issues/92 - if (datum.inputs[i].assets == null) { - datum.inputs[i].assets = []; - } - } + datum.outputs.forEach(o => fixUtxoToStringValues(o)); + // TODO: the inputs fix might potentially be removed + // https://github.com/ergoplatform/explorer-backend/issues/92 + datum.inputs.forEach(i => { i.assets = i.assets ?? [] }) } return data; }, @@ -198,6 +220,7 @@ export class RemoteFetcher implements IFetcher { const { network, ...rest } = body; const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.sendTx)} missing backend url`); + // todo: fix values from strings to BigInts and then use the lib for encoding return this.axiosPost(`${BackendService}/api/txs/signed`, { data: rest, callerName: nameof(this.sendTx), diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index e7d0eae7fc..0b2705e8f1 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -23985,6 +23985,14 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 13b3c4cf26..4ddfad8155 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -182,6 +182,7 @@ "es6-error": "4.1.1", "file-saver": "2.0.5", "jdenticon": "3.1.0", + "json-bigint": "1.0.0", "jspdf": "1.5.3", "lodash": "4.17.20", "lovefield": "2.1.12", From d82710f9e1bb41de989665ef8cf42c778793b800 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 29 Jul 2021 01:25:04 +0300 Subject: [PATCH 13/17] Added big-number encoding for all sent requests, added transforming tx-values from strings to big-numbers before sending signed to ergo backend, to keep the JSON-number api contract --- .../api/ergo/lib/state-fetch/remoteFetcher.js | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index 83c5abf36c..f45f40e319 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -46,6 +46,7 @@ import { fixUtxoToStringValues } from '../../index'; import { decode, encode } from 'bs58'; import JSONBigInt from 'json-bigint'; import cloneDeep from 'lodash/cloneDeep' +import BigNumber from 'bignumber.js'; // populated by ConfigWebpackPlugin declare var CONFIG: ConfigType; @@ -61,7 +62,7 @@ const axiosRequest: (RemoteFetcher, string) => (string, { errorFactory: (error: *) => Function, }) => Promise = (fetcher, method) => (url, params): Promise => { const debug = (s, p) => { - console.debug(`AXIOS[${method}][${url}] ${s} > `, cloneDeep(p)); + Logger.debug(`AXIOS[${method}][${url}] ${s} > `, cloneDeep(p)); }; debug('CALLING', params); return axios(url, { @@ -75,6 +76,14 @@ const axiosRequest: (RemoteFetcher, string) => (string, { debug('RSPRAW', resp); return JSONBigInt.parse(resp); }, + transformRequest: data => { + if (!data) { + return data; + } + const res = JSONBigInt.stringify(data); + debug('REQFIX', res); + return res; + }, ...(params.data ? { data: params.data } : {}), }).then(response => { debug('RSP', response); @@ -217,10 +226,15 @@ export class RemoteFetcher implements IFetcher { } sendTx: SignedRequest => Promise = (body) => { - const { network, ...rest } = body; + const { network, ...rest } = cloneDeep(body); const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.sendTx)} missing backend url`); - // todo: fix values from strings to BigInts and then use the lib for encoding + rest.outputs?.forEach(o => { + o.value = new BigNumber(o.value); + o.assets?.forEach(a => { + a.amount = new BigNumber(a.amount); + }) + }); return this.axiosPost(`${BackendService}/api/txs/signed`, { data: rest, callerName: nameof(this.sendTx), From 1351a55039ee50912972c05035a69ccbbe5c6db9 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 29 Jul 2021 01:56:25 +0300 Subject: [PATCH 14/17] Flow fixes --- .../yoroi-extension/app/api/ergo/index.js | 6 +- .../api/ergo/lib/state-fetch/remoteFetcher.js | 112 ++++++++++-------- 2 files changed, 66 insertions(+), 52 deletions(-) diff --git a/packages/yoroi-extension/app/api/ergo/index.js b/packages/yoroi-extension/app/api/ergo/index.js index 223159d41b..4d3c486c88 100644 --- a/packages/yoroi-extension/app/api/ergo/index.js +++ b/packages/yoroi-extension/app/api/ergo/index.js @@ -107,9 +107,11 @@ import type { DefaultTokenEntry } from '../common/lib/MultiToken'; import { hasSendAllDefault, builtSendTokenList } from '../common/index'; import { getReceiveAddress } from '../../stores/stateless/addressStores'; -export function fixUtxoToStringValues(utxo: RemoteUnspentOutput): RemoteUnspentOutput { +export function fixUtxoToStringValues(utxo: T): T { + // $FlowFixMe[incompatible-use] utxo.value = String(utxo.value); - utxo.assets.forEach(a => { + // $FlowFixMe[incompatible-use] + utxo.assets?.forEach(a => { a.amount = String(a.amount); }); return utxo; diff --git a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js index f45f40e319..1d31990ee4 100644 --- a/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js +++ b/packages/yoroi-extension/app/api/ergo/lib/state-fetch/remoteFetcher.js @@ -55,55 +55,65 @@ function errClass(Err: Function): (* => Function) { return _ => new Err(); } -const axiosRequest: (RemoteFetcher, string) => (string, { - data?: *, +type AxiosGet = {| responseMapper?: Object => T, callerName: string, errorFactory: (error: *) => Function, -}) => Promise = (fetcher, method) => (url, params): Promise => { - const debug = (s, p) => { - Logger.debug(`AXIOS[${method}][${url}] ${s} > `, cloneDeep(p)); - }; - debug('CALLING', params); - return axios(url, { - method, - timeout: 2 * CONFIG.app.walletRefreshInterval, - headers: { - 'yoroi-version': fetcher.getLastLaunchVersion(), - 'yoroi-locale': fetcher.getCurrentLocale() - }, - transformResponse: resp => { - debug('RSPRAW', resp); - return JSONBigInt.parse(resp); - }, - transformRequest: data => { - if (!data) { - return data; +|}; +type AxiosPost = {| + data?: *, + ...AxiosGet, +|}; +type AxiosParams = AxiosGet | AxiosPost; +type AxiosMethod = 'get' | 'post'; +type AxiosFunc = (string, AxiosParams) => Promise + +function axiosRequest(fetcher: RemoteFetcher, method: AxiosMethod): AxiosFunc { + return (url, params) => { + const debug = (s: string, p: *) => { + // eslint-disable-next-line no-console + console.debug(`AXIOS[${method}][${url}] ${s} > `, cloneDeep(p)); + }; + debug('CALLING', params); + return axios(url, { + method, + timeout: 2 * CONFIG.app.walletRefreshInterval, + headers: { + 'yoroi-version': fetcher.getLastLaunchVersion(), + 'yoroi-locale': fetcher.getCurrentLocale() + }, + transformResponse: resp => { + debug('RSPRAW', resp); + return JSONBigInt.parse(resp); + }, + transformRequest: data => { + if (!data) { + return data; + } + const res = JSONBigInt.stringify(data); + debug('REQFIX', res); + return res; + }, + ...(params.data ? { data: params.data } : {}), + }).then(response => { + debug('RSP', response); + const mapper: ?(Object => T) = params.responseMapper; + const data = response.data; + if (!mapper) { + // forcing the type, since the mapper has not been specified + const t: T = data; + return t; } - const res = JSONBigInt.stringify(data); - debug('REQFIX', res); + const res: T = mapper(data); + debug('RSPMAP', res); return res; - }, - ...(params.data ? { data: params.data } : {}), - }).then(response => { - debug('RSP', response); - const mapper = params.responseMapper; - const data = response.data; - if (!mapper) { - return data; - } - const res = mapper(data); - debug('RSPMAP', res); - return res; - }).catch((error) => { - debug('ERR', error); - Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); - const err = params.errorFactory(error); - if (err) { - throw err; - } - }); -}; + }).catch((error) => { + debug('ERR', error); + Logger.error(`${nameof(RemoteFetcher)}::${params.callerName} error: ` + stringifyError(error)); + throw (params.errorFactory(error) ?? error); + }); + }; +} /** * Makes calls to Yoroi backend service @@ -125,19 +135,18 @@ export class RemoteFetcher implements IFetcher { this.getPlatform = getPlatform; } - axiosGet: (string, { - data: *, + axiosGet: (string, {| responseMapper?: Object => T, callerName: string, errorFactory: (error: *) => Function, - }) => Promise = axiosRequest(this, 'get'); + |}) => Promise = axiosRequest(this, 'get'); - axiosPost: (string, { + axiosPost: (string, {| data: *, responseMapper?: Object => T, callerName: string, errorFactory: (error: *) => Function, - }) => Promise = axiosRequest(this, 'post'); + |}) => Promise = axiosRequest(this, 'post'); getUTXOsForAddresses: AddressUtxoRequest => Promise = (body) => { @@ -164,6 +173,7 @@ export class RemoteFetcher implements IFetcher { callerName: nameof(this.getTxsBodiesForUTXOs), errorFactory: errClass(GetTxsBodiesForUTXOsApiError), responseMapper: data => { + // $FlowFixMe[incompatible-use] Object.values(data).forEach(({ outputs }) => { outputs.forEach(o => fixUtxoToStringValues(o)); }); @@ -229,9 +239,11 @@ export class RemoteFetcher implements IFetcher { const { network, ...rest } = cloneDeep(body); const { BackendService } = network.Backend; if (BackendService == null) throw new Error(`${nameof(this.sendTx)} missing backend url`); - rest.outputs?.forEach(o => { + rest.outputs.forEach(o => { + // $FlowFixMe[incompatible-type] o.value = new BigNumber(o.value); o.assets?.forEach(a => { + // $FlowFixMe[cannot-write] a.amount = new BigNumber(a.amount); }) }); From fbc8a3b4b2120c3bc554ecbf837148d721ecb69d Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 29 Jul 2021 01:57:15 +0300 Subject: [PATCH 15/17] Fix using an observable field --- .../app/ergo-connector/containers/ConnectContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js index e272c77940..25365aaf54 100644 --- a/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js +++ b/packages/yoroi-extension/app/ergo-connector/containers/ConnectContainer.js @@ -64,7 +64,7 @@ export default class ConnectContainer extends Component< throw new Error(`${nameof(chromeMessage)} connecting to a wallet but no connect message found`); } const result = this.generated.stores.connector.currentConnectorWhitelist; - const whitelist = result.length === 0 ? [] : result; + const whitelist = result.length ? [...result] : []; whitelist.push({ url: chromeMessage.url, publicDeriverId }); await this.generated.actions.connector.updateConnectorWhitelist.trigger({ whitelist }); From 93aec755283c90d79ffe3a6b9aa069c87b528aeb Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 29 Jul 2021 01:57:50 +0300 Subject: [PATCH 16/17] Lint fixes --- packages/yoroi-extension/app/api/ergo/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/yoroi-extension/app/api/ergo/index.js b/packages/yoroi-extension/app/api/ergo/index.js index 4d3c486c88..865fd8f5dc 100644 --- a/packages/yoroi-extension/app/api/ergo/index.js +++ b/packages/yoroi-extension/app/api/ergo/index.js @@ -55,7 +55,6 @@ import type { AssetInfoFunc, SendFunc, SignedResponse, - RemoteUnspentOutput, } from './lib/state-fetch/types'; import type { FilterFunc, From fb372bb51d780fad96f8c58667d7bfcc3526e428 Mon Sep 17 00:00:00 2001 From: vantuz-subhuman Date: Thu, 29 Jul 2021 02:19:27 +0300 Subject: [PATCH 17/17] Version bump 4.5.7005 --- packages/yoroi-extension/package-lock.json | 2 +- packages/yoroi-extension/package.json | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/yoroi-extension/package-lock.json b/packages/yoroi-extension/package-lock.json index bd6f2ab7f2..3aa41a3f7e 100644 --- a/packages/yoroi-extension/package-lock.json +++ b/packages/yoroi-extension/package-lock.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.5.7004", + "version": "4.5.7005", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/yoroi-extension/package.json b/packages/yoroi-extension/package.json index 08d3b9da65..cd4560e0dc 100644 --- a/packages/yoroi-extension/package.json +++ b/packages/yoroi-extension/package.json @@ -1,6 +1,6 @@ { "name": "yoroi", - "version": "4.5.7004", + "version": "4.5.7005", "description": "Cardano ADA wallet", "scripts": { "dev:build": "rimraf dev/ && babel-node scripts/build --type=debug", @@ -183,7 +183,6 @@ "file-saver": "2.0.5", "json-bigint": "1.0.0", "jdenticon": "3.1.0", - "json-bigint": "1.0.0", "jspdf": "1.5.3", "lodash": "4.17.20", "lovefield": "2.1.12",