From 0807ba6dc21950b5f6e9fb5299a42942c131fb01 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 18 Jan 2021 16:11:22 +0100 Subject: [PATCH 01/37] [DDW-543] Update bignumber.js from v5 to v9 Implemented all breaking changes: https://github.com/MikeMcl/bignumber.js/blob/master/CHANGELOG.md --- package.json | 2 +- source/renderer/app/api/api.js | 4 ++-- .../Step2ConfirmationDialog.js | 2 +- .../app/components/wallet/WalletSendForm.js | 2 +- source/renderer/app/stores/StakingStore.js | 2 +- source/renderer/app/utils/formatters.js | 22 +++++++++---------- source/renderer/app/utils/transaction.js | 4 ++-- tests/transactions/e2e/steps/transactions.js | 2 +- .../e2e/steps/transfer-funds-wizard.js | 2 +- yarn.lock | 6 ++--- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 55ef3eca99..53e3a06cd4 100644 --- a/package.json +++ b/package.json @@ -174,7 +174,7 @@ "dependencies": { "@cardano-foundation/ledgerjs-hw-app-cardano": "2.1.0", "aes-js": "3.1.2", - "bignumber.js": "5.0.0", + "bignumber.js": "9.0.1", "bip39": "2.3.0", "blake2b": "2.1.3", "blakejs": "1.1.0", diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 4c5b0328ee..1c1a7af6a0 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -562,7 +562,7 @@ export default class AdaApi { const withdrawal = new BigNumber(w.amount.quantity).dividedBy( LOVELACES_PER_ADA ); - withdrawals = withdrawals.add(withdrawal); + withdrawals = withdrawals.plus(withdrawal); }); }); return { withdrawals }; @@ -1691,7 +1691,7 @@ export default class AdaApi { MIN_REWARDS_REDEMPTION_RECEIVER_BALANCE ); // Amount is set to either wallet's balance in case balance is less than 3 ADA or 1 ADA in order to avoid min UTXO affecting transaction fees calculation - const amount = walletBalance.lessThan( + const amount = walletBalance.isLessThan( minRewardsReceiverBalance.times( MIN_REWARDS_REDEMPTION_RECEIVER_BALANCE * 3 ) diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step2ConfirmationDialog.js b/source/renderer/app/components/staking/redeem-itn-rewards/Step2ConfirmationDialog.js index 3029d373a8..3a2413ff7d 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step2ConfirmationDialog.js +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step2ConfirmationDialog.js @@ -156,7 +156,7 @@ export default class Step2ConfirmationDialog extends Component { MIN_REWARDS_REDEMPTION_RECEIVER_BALANCE ); const differenceBetweenAmountAndFee = amount.minus(transactionFees); - const calculatedTransactionFees = differenceBetweenAmountAndFee.lessThan( + const calculatedTransactionFees = differenceBetweenAmountAndFee.isLessThan( minRewardsReceiverBalance ) ? amount diff --git a/source/renderer/app/components/wallet/WalletSendForm.js b/source/renderer/app/components/wallet/WalletSendForm.js index 7cda63534b..f759a214aa 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.js +++ b/source/renderer/app/components/wallet/WalletSendForm.js @@ -356,7 +356,7 @@ export default class WalletSendForm extends Component { let total = null; if (isTransactionFeeCalculated) { fees = transactionFee.toFormat(currencyMaxFractionalDigits); - total = amount.add(transactionFee).toFormat(currencyMaxFractionalDigits); + total = amount.plus(transactionFee).toFormat(currencyMaxFractionalDigits); } const buttonClasses = classnames(['primary', styles.nextButton]); diff --git a/source/renderer/app/stores/StakingStore.js b/source/renderer/app/stores/StakingStore.js index 41d26a4d98..6f09d45a1b 100644 --- a/source/renderer/app/stores/StakingStore.js +++ b/source/renderer/app/stores/StakingStore.js @@ -693,7 +693,7 @@ export default class StakingStore extends Store { syncState, } = inputWallet; const { withdrawals } = this.stores.transactions; - const reward = rewards.add(withdrawals[walletId]); + const reward = rewards.plus(withdrawals[walletId]); const syncingProgress = get(syncState, 'progress.quantity', ''); return { wallet, reward, isRestoring, syncingProgress }; }; diff --git a/source/renderer/app/utils/formatters.js b/source/renderer/app/utils/formatters.js index bc31bf11c8..6751a7ffd6 100644 --- a/source/renderer/app/utils/formatters.js +++ b/source/renderer/app/utils/formatters.js @@ -41,31 +41,31 @@ export const shortNumber = (value: number | BigNumber): string => { let formattedAmount = ''; if (amount.isZero()) { formattedAmount = '0'; - } else if (amount.lessThan(1000)) { - formattedAmount = `${amount.round( + } else if (amount.isLessThan(1000)) { + formattedAmount = `${amount.decimalPlaces( DECIMAL_PLACES_IN_ADA, BigNumber.ROUND_DOWN )}`; - } else if (amount.lessThan(1000000)) { + } else if (amount.isLessThan(1000000)) { formattedAmount = `${amount .dividedBy(1000) - .round(1, BigNumber.ROUND_DOWN)}K`; - } else if (amount.lessThan(1000000000)) { + .decimalPlaces(1, BigNumber.ROUND_DOWN)}K`; + } else if (amount.isLessThan(1000000000)) { formattedAmount = `${amount .dividedBy(1000000) - .round(1, BigNumber.ROUND_DOWN)}M`; - } else if (amount.lessThan(1000000000000)) { + .decimalPlaces(1, BigNumber.ROUND_DOWN)}M`; + } else if (amount.isLessThan(1000000000000)) { formattedAmount = `${amount .dividedBy(1000000000) - .round(1, BigNumber.ROUND_DOWN)}B`; - } else if (amount.lessThan(1000000000000000)) { + .decimalPlaces(1, BigNumber.ROUND_DOWN)}B`; + } else if (amount.isLessThan(1000000000000000)) { formattedAmount = `${amount .dividedBy(1000000000000) - .round(1, BigNumber.ROUND_DOWN)}T`; + .decimalPlaces(1, BigNumber.ROUND_DOWN)}T`; } else { formattedAmount = `${amount .dividedBy(1000000000000000) - .round(1, BigNumber.ROUND_DOWN)}Q`; + .decimalPlaces(1, BigNumber.ROUND_DOWN)}Q`; } return formattedAmount; }; diff --git a/source/renderer/app/utils/transaction.js b/source/renderer/app/utils/transaction.js index 6387874c1b..cc008a52a5 100644 --- a/source/renderer/app/utils/transaction.js +++ b/source/renderer/app/utils/transaction.js @@ -85,10 +85,10 @@ export const isTransactionAmountInFilterRange = ( ? new BigNumber(0) : new BigNumber(toAmount); const compareFrom = fromAmount - ? amount.absoluteValue().greaterThanOrEqualTo(min) + ? amount.absoluteValue().isGreaterThanOrEqualTo(min) : true; const compareTo = toAmount - ? amount.absoluteValue().lessThanOrEqualTo(max) + ? amount.absoluteValue().isLessThanOrEqualTo(max) : true; return compareFrom && compareTo; diff --git a/tests/transactions/e2e/steps/transactions.js b/tests/transactions/e2e/steps/transactions.js index 944b5f6f96..869f3fde08 100644 --- a/tests/transactions/e2e/steps/transactions.js +++ b/tests/transactions/e2e/steps/transactions.js @@ -234,7 +234,7 @@ Then(/^the latest transaction should show:$/, async function(table) { // NOTE: we use "add()" as this is outgoing transaction and amount is a negative value! const transactionAmount = new BigNumber(transactionAmounts[0]); const transactionAmountWithoutFees = transactionAmount - .add(this.fees) + .plus(this.fees) .toFormat(DECIMAL_PLACES_IN_ADA); expect(expectedData.amountWithoutFees).to.equal(transactionAmountWithoutFees); }); diff --git a/tests/wallets/e2e/steps/transfer-funds-wizard.js b/tests/wallets/e2e/steps/transfer-funds-wizard.js index a20ac37114..003b7dae29 100644 --- a/tests/wallets/e2e/steps/transfer-funds-wizard.js +++ b/tests/wallets/e2e/steps/transfer-funds-wizard.js @@ -95,7 +95,7 @@ Then(/^I should see increased rewards wallet balance and 0 ADA in Daedalus Balan async function() { const rewardsSelector = '.SidebarWalletsMenu_wallets button:nth-child(1) .SidebarWalletMenuItem_info'; const balanceSelector = '.SidebarWalletsMenu_wallets button:nth-child(2) .SidebarWalletMenuItem_info'; - const transferSumWithoutFees = this.rewardsWalletAmount.add(this.balanceWalletAmount); + const transferSumWithoutFees = this.rewardsWalletAmount.plus(this.balanceWalletAmount); const transferSumWithFees = transferSumWithoutFees.minus(this.transferFee); const initialRewardsFormattedAmount = formattedWalletAmount(this.rewardsWalletAmount, true, false); const initialBallanceFormattedAmount = formattedWalletAmount(this.balanceWalletAmount, true, false); diff --git a/yarn.lock b/yarn.lock index 1c16f6723e..d89bf93886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3326,9 +3326,9 @@ bigi@^1.1.0, bigi@^1.4.0, bigi@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" -bignumber.js@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-5.0.0.tgz#fbce63f09776b3000a83185badcde525daf34833" +bignumber.js@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" bignumber.js@^9.0.0: version "9.0.1" From 417fcf25f041a71a1bb6616c438509ae7ede7cd5 Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Thu, 21 Jan 2021 08:54:29 +0100 Subject: [PATCH 02/37] [DDW-543] Adds CHANGELOG entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2d417413..a9bbec41f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog ========= +## vNext + +### Chores + +- Updated `bignumber.js` package ([PR 2305](https://github.com/input-output-hk/daedalus/pull/2305)) + ## 3.2.0-FC1 ### Features From c79b6776cddae043d759b3e41cf568594e151ead Mon Sep 17 00:00:00 2001 From: Yakov Karavelov Date: Thu, 21 Jan 2021 03:47:07 -0800 Subject: [PATCH 03/37] [DDW-536] Service agreement can be skipped by accessing to Settings (#2304) * [DDW-536]: Disable certain menu items on terms of use page * [DDW-536]: Update changelog * [DDW-536]: Disable certain menus on initial settings screen as well * [DDW-536]: Fix terms of use page menu handling * [DDW-536]: Handle menu items based on terms of use acceptance status of profile store * [DDW-536]: Rename variables and functions for disabling menu items * [DDW-536]: Update menu items initialization * [DDW-536] Updates CHANGELOG * [DDW-536]: Remove unnecessary channel for disabling menu items * [DDW-536] Updates CHANGELOG * [DDW-536] Updates comment * [DDW-536]: Handle navigation enabling properly when rebuilding app menu Co-authored-by: Nikola Glumac --- CHANGELOG.md | 6 ++++++ source/common/ipc/api.js | 13 ++++++++++++- source/main/index.js | 18 +++++++++++++++++- .../enableApplicationMenuNavigationChannel.js | 13 +++++++++++++ source/main/menus/osx.js | 9 +++++---- source/main/menus/win-linux.js | 9 +++++---- source/main/utils/buildAppMenus.js | 9 ++++++--- .../containers/profile/InitialSettingsPage.js | 7 ++++++- .../settings/categories/GeneralSettingsPage.js | 7 ++++++- .../enableApplicationMenuNavigationChannel.js | 13 +++++++++++++ source/renderer/app/stores/ProfileStore.js | 9 +++++++-- 11 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 source/main/ipc/enableApplicationMenuNavigationChannel.js create mode 100644 source/renderer/app/ipc/enableApplicationMenuNavigationChannel.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2d417413..feb18c5e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Changelog ========= +## vNext + +### Chores + +- Disabled application menu navigation before the "Terms of use" have been accepted ([PR 2304](https://github.com/input-output-hk/daedalus/pull/2304)) + ## 3.2.0-FC1 ### Features diff --git a/source/common/ipc/api.js b/source/common/ipc/api.js index c03b899ac3..98b9f2378c 100644 --- a/source/common/ipc/api.js +++ b/source/common/ipc/api.js @@ -177,7 +177,10 @@ export type SubmitBugReportRequestMainResponse = void; * Channel to rebuild the electron application menu after the language setting changes */ export const REBUILD_APP_MENU_CHANNEL = 'REBUILD_APP_MENU_CHANNEL'; -export type RebuildAppMenuRendererRequest = { isUpdateAvailable: boolean }; +export type RebuildAppMenuRendererRequest = { + isUpdateAvailable: boolean, + isNavigationEnabled: boolean, +}; export type RebuildAppMenuMainResponse = void; /** @@ -295,6 +298,14 @@ export const GENERATE_WALLET_MIGRATION_REPORT_CHANNEL = export type GenerateWalletMigrationReportRendererRequest = WalletMigrationReportData; export type GenerateWalletMigrationReportMainResponse = void; +/** + * Channel for enabling application menu navigation + */ +export const ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL = + 'ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL'; +export type EnableApplicationMenuNavigationRendererRequest = void; +export type EnableApplicationMenuNavigationMainResponse = void; + /** * Channel for generating wallet migration report */ diff --git a/source/main/index.js b/source/main/index.js index 0edc4a8680..6826650256 100644 --- a/source/main/index.js +++ b/source/main/index.js @@ -37,6 +37,7 @@ import type { CheckDiskSpaceResponse } from '../common/types/no-disk-space.types import { logUsedVersion } from './utils/logUsedVersion'; import { setStateSnapshotLogChannel } from './ipc/set-log-state-snapshot'; import { generateWalletMigrationReportChannel } from './ipc/generateWalletMigrationReportChannel'; +import { enableApplicationMenuNavigationChannel } from './ipc/enableApplicationMenuNavigationChannel'; import { pauseActiveDownloads } from './ipc/downloadManagerChannel'; // import { isHardwareWalletSupportEnabled, isLedgerEnabled } from '../renderer/app/config/hardwareWalletsConfig'; @@ -202,7 +203,21 @@ const onAppReady = async () => { await safeExit(); }); - buildAppMenus(mainWindow, cardanoNode, locale, { isUpdateAvailable: false }); + buildAppMenus(mainWindow, cardanoNode, locale, { + isUpdateAvailable: false, + isNavigationEnabled: false, + }); + + await enableApplicationMenuNavigationChannel.onReceive( + () => + new Promise((resolve) => { + buildAppMenus(mainWindow, cardanoNode, locale, { + isUpdateAvailable: false, + isNavigationEnabled: true, + }); + resolve(); + }) + ); await rebuildApplicationMenu.onReceive( (data) => @@ -210,6 +225,7 @@ const onAppReady = async () => { locale = getLocale(network); buildAppMenus(mainWindow, cardanoNode, locale, { isUpdateAvailable: data.isUpdateAvailable, + isNavigationEnabled: data.isNavigationEnabled, }); mainWindow.updateTitle(locale); resolve(); diff --git a/source/main/ipc/enableApplicationMenuNavigationChannel.js b/source/main/ipc/enableApplicationMenuNavigationChannel.js new file mode 100644 index 0000000000..4bf790e972 --- /dev/null +++ b/source/main/ipc/enableApplicationMenuNavigationChannel.js @@ -0,0 +1,13 @@ +// @flow +import { MainIpcChannel } from './lib/MainIpcChannel'; +import { ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL } from '../../common/ipc/api'; +import type { + EnableApplicationMenuNavigationMainResponse, + EnableApplicationMenuNavigationRendererRequest, +} from '../../common/ipc/api'; + +export const enableApplicationMenuNavigationChannel: // IpcChannel +MainIpcChannel< + EnableApplicationMenuNavigationRendererRequest, + EnableApplicationMenuNavigationMainResponse +> = new MainIpcChannel(ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL); diff --git a/source/main/menus/osx.js b/source/main/menus/osx.js index 6817e8b3f6..db2a1d554c 100644 --- a/source/main/menus/osx.js +++ b/source/main/menus/osx.js @@ -19,6 +19,7 @@ export const osxMenu = ( translations: {}, locale: string, isUpdateAvailable: boolean, + isNavigationEnabled: boolean, translation: Function = getTranslation(translations, id) ) => [ { @@ -29,7 +30,7 @@ export const osxMenu = ( click() { actions.openAboutDialog(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { type: 'separator' }, { @@ -38,7 +39,7 @@ export const osxMenu = ( click() { actions.openSettingsPage(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { label: translation('daedalus.walletSettings'), @@ -46,7 +47,7 @@ export const osxMenu = ( click() { actions.openWalletSettingsPage(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { type: 'separator' }, { @@ -190,7 +191,7 @@ export const osxMenu = ( click() { actions.openDaedalusDiagnosticsDialog(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, ]), }, diff --git a/source/main/menus/win-linux.js b/source/main/menus/win-linux.js index e6954eeda2..ebbca67db5 100644 --- a/source/main/menus/win-linux.js +++ b/source/main/menus/win-linux.js @@ -19,6 +19,7 @@ export const winLinuxMenu = ( translations: {}, locale: string, isUpdateAvailable: boolean, + isNavigationEnabled: boolean, translation: Function = getTranslation(translations, id) ) => [ { @@ -29,7 +30,7 @@ export const winLinuxMenu = ( click() { actions.openAboutDialog(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { label: translation('daedalus.close'), @@ -97,7 +98,7 @@ export const winLinuxMenu = ( click() { actions.openSettingsPage(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { label: translation('daedalus.walletSettings'), @@ -105,7 +106,7 @@ export const winLinuxMenu = ( click() { actions.openWalletSettingsPage(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, { type: 'separator', @@ -201,7 +202,7 @@ export const winLinuxMenu = ( click() { actions.openDaedalusDiagnosticsDialog(); }, - enabled: !isUpdateAvailable, + enabled: !isUpdateAvailable && isNavigationEnabled, }, ]), }, diff --git a/source/main/utils/buildAppMenus.js b/source/main/utils/buildAppMenus.js index 055477e4ca..8b7a23ce9a 100644 --- a/source/main/utils/buildAppMenus.js +++ b/source/main/utils/buildAppMenus.js @@ -16,11 +16,12 @@ export const buildAppMenus = async ( locale: string, data: { isUpdateAvailable: boolean, + isNavigationEnabled: boolean, } ) => { const { ABOUT, DAEDALUS_DIAGNOSTICS } = DIALOGS; const { SETTINGS, WALLET_SETTINGS } = PAGES; - const { isUpdateAvailable } = data; + const { isUpdateAvailable, isNavigationEnabled } = data; const { isMacOS, isBlankScreenFixActive } = environment; const translations = require(`../locales/${locale}`); @@ -106,7 +107,8 @@ export const buildAppMenus = async ( menuActions, translations, locale, - isUpdateAvailable + isUpdateAvailable, + isNavigationEnabled ) ); Menu.setApplicationMenu(menu); @@ -118,7 +120,8 @@ export const buildAppMenus = async ( menuActions, translations, locale, - isUpdateAvailable + isUpdateAvailable, + isNavigationEnabled ) ); mainWindow.setMenu(menu); diff --git a/source/renderer/app/containers/profile/InitialSettingsPage.js b/source/renderer/app/containers/profile/InitialSettingsPage.js index 1db67991d2..cab493c7e6 100644 --- a/source/renderer/app/containers/profile/InitialSettingsPage.js +++ b/source/renderer/app/containers/profile/InitialSettingsPage.js @@ -23,8 +23,13 @@ export default class InitialSettingsPage extends Component { const { updateUserLocalSetting } = actions.profile; updateUserLocalSetting.trigger({ param, value }); const { isUpdateAvailable } = stores.appUpdate; + const { areTermsOfUseAccepted: isNavigationEnabled } = stores.profile; + if (param === 'locale') { - await rebuildApplicationMenu.send({ isUpdateAvailable }); + await rebuildApplicationMenu.send({ + isUpdateAvailable, + isNavigationEnabled, + }); } }; diff --git a/source/renderer/app/containers/settings/categories/GeneralSettingsPage.js b/source/renderer/app/containers/settings/categories/GeneralSettingsPage.js index bd5387e7e3..82abd5c216 100644 --- a/source/renderer/app/containers/settings/categories/GeneralSettingsPage.js +++ b/source/renderer/app/containers/settings/categories/GeneralSettingsPage.js @@ -13,10 +13,15 @@ export default class GeneralSettingsPage extends Component { handleSelectItem = async (param: string, value: string) => { const { actions, stores } = this.props; const { isUpdateAvailable } = stores.appUpdate; + const { areTermsOfUseAccepted: isNavigationEnabled } = stores.profile; const { updateUserLocalSetting } = actions.profile; + updateUserLocalSetting.trigger({ param, value }); if (param === 'locale') { - await rebuildApplicationMenu.send({ isUpdateAvailable }); + await rebuildApplicationMenu.send({ + isUpdateAvailable, + isNavigationEnabled, + }); } }; diff --git a/source/renderer/app/ipc/enableApplicationMenuNavigationChannel.js b/source/renderer/app/ipc/enableApplicationMenuNavigationChannel.js new file mode 100644 index 0000000000..2373f8ce40 --- /dev/null +++ b/source/renderer/app/ipc/enableApplicationMenuNavigationChannel.js @@ -0,0 +1,13 @@ +// @flow +import { ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL } from '../../../common/ipc/api'; +import type { + EnableApplicationMenuNavigationMainResponse, + EnableApplicationMenuNavigationRendererRequest, +} from '../../../common/ipc/api'; +import { RendererIpcChannel } from './lib/RendererIpcChannel'; + +export const enableApplicationMenuNavigationChannel: // IpcChannel +RendererIpcChannel< + EnableApplicationMenuNavigationMainResponse, + EnableApplicationMenuNavigationRendererRequest +> = new RendererIpcChannel(ENABLE_APPLICATION_MENU_NAVIGATION_CHANNEL); diff --git a/source/renderer/app/stores/ProfileStore.js b/source/renderer/app/stores/ProfileStore.js index d1c6200a83..dac935fa04 100644 --- a/source/renderer/app/stores/ProfileStore.js +++ b/source/renderer/app/stores/ProfileStore.js @@ -14,6 +14,7 @@ import { logger } from '../utils/logging'; import { setStateSnapshotLogChannel } from '../ipc/setStateSnapshotLogChannel'; import { getDesktopDirectoryPathChannel } from '../ipc/getDesktopDirectoryPathChannel'; import { getSystemLocaleChannel } from '../ipc/getSystemLocaleChannel'; +import { enableApplicationMenuNavigationChannel } from '../ipc/enableApplicationMenuNavigationChannel'; import { LOCALES } from '../../../common/types/locales.types'; import { compressLogsChannel, @@ -302,10 +303,14 @@ export default class ProfileStore extends Store { _acceptTermsOfUse = async () => { await this.setTermsOfUseAcceptanceRequest.execute(); await this.getTermsOfUseAcceptanceRequest.execute(); + await enableApplicationMenuNavigationChannel.send(); }; - _getTermsOfUseAcceptance = () => { - this.getTermsOfUseAcceptanceRequest.execute(); + _getTermsOfUseAcceptance = async () => { + await this.getTermsOfUseAcceptanceRequest.execute(); + if (this.getTermsOfUseAcceptanceRequest.result) { + await enableApplicationMenuNavigationChannel.send(); + } }; _acceptDataLayerMigration = async () => { From a0f0b757ec21c80a1f538bd4c74db69be3d316d3 Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Thu, 21 Jan 2021 14:32:58 +0100 Subject: [PATCH 04/37] [DDW-543] Inlcude bignumber.js in installers --- installers/common/MacInstaller.hs | 1 + yarn2nix.nix | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/installers/common/MacInstaller.hs b/installers/common/MacInstaller.hs index 7851c083ff..af17638bd5 100644 --- a/installers/common/MacInstaller.hs +++ b/installers/common/MacInstaller.hs @@ -279,6 +279,7 @@ buildElectronApp darwinConfig@DarwinConfig{dcAppName, dcAppNameApp} installerCon , "cross-fetch" , "trezor-connect" , "js-chain-libs-node" + , "bignumber.js" ] mapM_ (\lib -> do cptree ("../node_modules" lib) ((fromText pathtoapp) "Contents/Resources/app/node_modules" lib) diff --git a/yarn2nix.nix b/yarn2nix.nix index 7e3b51e86f..bcf1b147e9 100644 --- a/yarn2nix.nix +++ b/yarn2nix.nix @@ -121,7 +121,7 @@ yarn2nix.mkYarnPackage { rm -rf $out/resources/app/{installers,launcher-config.yaml,gulpfile.js,home} mkdir -pv $out/resources/app/node_modules - cp -rv $node_modules/{\@babel,regenerator-runtime,node-fetch,\@trezor,runtypes,parse-uri,randombytes,safe-buffer,bip66,pushdata-bitcoin,bitcoin-ops,typeforce,varuint-bitcoin,bigi,create-hash,merkle-lib,blake2b,nanoassert,blake2b-wasm,bs58check,bs58,base-x,create-hmac,ecurve,wif,ms,keccak,trezor-link,semver-compare,protobufjs-old-fixed-webpack,bytebuffer-old-fixed-webpack,long,object.values,define-properties,object-keys,has,function-bind,es-abstract,has-symbols,json-stable-stringify,tiny-worker,hd-wallet,cashaddrjs,big-integer,queue,inherits,bchaddrjs,cross-fetch,trezor-connect,js-chain-libs-node} $out/resources/app/node_modules + cp -rv $node_modules/{\@babel,regenerator-runtime,node-fetch,\@trezor,runtypes,parse-uri,randombytes,safe-buffer,bip66,pushdata-bitcoin,bitcoin-ops,typeforce,varuint-bitcoin,bigi,create-hash,merkle-lib,blake2b,nanoassert,blake2b-wasm,bs58check,bs58,base-x,create-hmac,ecurve,wif,ms,keccak,trezor-link,semver-compare,protobufjs-old-fixed-webpack,bytebuffer-old-fixed-webpack,long,object.values,define-properties,object-keys,has,function-bind,es-abstract,has-symbols,json-stable-stringify,tiny-worker,hd-wallet,cashaddrjs,big-integer,queue,inherits,bchaddrjs,cross-fetch,trezor-connect,js-chain-libs-node,bignumber.js} $out/resources/app/node_modules cd $out/resources/app/ unzip ${./nix/windows-usb-libs.zip} @@ -164,7 +164,7 @@ yarn2nix.mkYarnPackage { mkdir -p $out/share/fonts ln -sv $out/share/daedalus/renderer/assets $out/share/fonts/daedalus mkdir -pv $out/share/daedalus/node_modules - cp -rv $node_modules/{\@babel,regenerator-runtime,node-fetch,\@trezor,runtypes,parse-uri,randombytes,safe-buffer,bip66,pushdata-bitcoin,bitcoin-ops,typeforce,varuint-bitcoin,bigi,create-hash,merkle-lib,blake2b,nanoassert,blake2b-wasm,bs58check,bs58,base-x,create-hmac,ecurve,wif,ms,keccak,trezor-link,semver-compare,protobufjs-old-fixed-webpack,bytebuffer-old-fixed-webpack,long,object.values,define-properties,object-keys,has,function-bind,es-abstract,has-symbols,json-stable-stringify,tiny-worker,hd-wallet,cashaddrjs,big-integer,queue,inherits,bchaddrjs,cross-fetch,trezor-connect,js-chain-libs-node} $out/share/daedalus/node_modules/ + cp -rv $node_modules/{\@babel,regenerator-runtime,node-fetch,\@trezor,runtypes,parse-uri,randombytes,safe-buffer,bip66,pushdata-bitcoin,bitcoin-ops,typeforce,varuint-bitcoin,bigi,create-hash,merkle-lib,blake2b,nanoassert,blake2b-wasm,bs58check,bs58,base-x,create-hmac,ecurve,wif,ms,keccak,trezor-link,semver-compare,protobufjs-old-fixed-webpack,bytebuffer-old-fixed-webpack,long,object.values,define-properties,object-keys,has,function-bind,es-abstract,has-symbols,json-stable-stringify,tiny-worker,hd-wallet,cashaddrjs,big-integer,queue,inherits,bchaddrjs,cross-fetch,trezor-connect,js-chain-libs-node,bignumber.js} $out/share/daedalus/node_modules/ find $out $NIX_BUILD_TOP -name '*.node' mkdir -pv $out/share/daedalus/build From 6fcec5468faf0914a85f6041dfa05f324f060d61 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 22 Jan 2021 11:01:42 +0100 Subject: [PATCH 05/37] [DDW-543] Fix bignumber issues on stake pools page --- .../DelegationStepsChooseWalletDialog.js | 2 +- .../staking/dialogs/DelegationSetupWizardDialogContainer.js | 2 +- source/renderer/app/utils/transaction.js | 2 +- source/renderer/app/utils/walletsForStakePoolsRanking.js | 2 +- tests/transactions/e2e/steps/transactions.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.js b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.js index bf7bb4e95a..a3199e1478 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.js +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsChooseWalletDialog.js @@ -151,7 +151,7 @@ export default class DelegationStepsChooseWalletDialog extends Component< // Wallet is restoring if (isRestoring) errorMessage = messages.errorRestoringWallet; // Wallet only has Reward balance - else if (!amount.isZero() && amount.equals(reward)) + else if (!amount.isZero() && amount.isEqualTo(reward)) errorMessage = messages.errorMinDelegationFundsRewardsOnly; // Wallet balance < min delegation funds else errorMessage = messages.errorMinDelegationFunds; diff --git a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js index fe1ea0a06a..da6dbb0dc5 100644 --- a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js +++ b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js @@ -68,7 +68,7 @@ export default class DelegationSetupWizardDialogContainer extends Component< ) => walletAmount && walletAmount.gte(new BigNumber(MIN_DELEGATION_FUNDS)) && - !walletAmount.equals(walletReward); + !walletAmount.isEqualTo(walletReward); get selectedWalletId() { return get( diff --git a/source/renderer/app/utils/transaction.js b/source/renderer/app/utils/transaction.js index cc008a52a5..bcea6930e9 100644 --- a/source/renderer/app/utils/transaction.js +++ b/source/renderer/app/utils/transaction.js @@ -268,7 +268,7 @@ export const validateFilterForm = (values: { if ( fromAmount && toAmount && - new BigNumber(fromAmount).greaterThan(new BigNumber(toAmount)) + new BigNumber(fromAmount).isGreaterThan(new BigNumber(toAmount)) ) { invalidFields.toAmount = true; } diff --git a/source/renderer/app/utils/walletsForStakePoolsRanking.js b/source/renderer/app/utils/walletsForStakePoolsRanking.js index 6a5debb161..0bd5b60cf5 100644 --- a/source/renderer/app/utils/walletsForStakePoolsRanking.js +++ b/source/renderer/app/utils/walletsForStakePoolsRanking.js @@ -6,7 +6,7 @@ import { MIN_DELEGATION_FUNDS } from '../config/stakingConfig'; export const getFilteredWallets = (wallets: Array): Array => { return wallets.filter( (w: Wallet) => - w.amount.greaterThanOrEqualTo(new BigNumber(MIN_DELEGATION_FUNDS)) && + w.amount.isGreaterThanOrEqualTo(new BigNumber(MIN_DELEGATION_FUNDS)) && !w.isLegacy ); }; diff --git a/tests/transactions/e2e/steps/transactions.js b/tests/transactions/e2e/steps/transactions.js index 869f3fde08..dedb408c12 100644 --- a/tests/transactions/e2e/steps/transactions.js +++ b/tests/transactions/e2e/steps/transactions.js @@ -123,7 +123,7 @@ When(/^the transaction fees are calculated$/, async function() { '.AmountInputSkin_fees' ); const transactionFeeAmount = new BigNumber(transactionFeeText.substr(2, 8)); - return transactionFeeAmount.greaterThan(0) ? transactionFeeAmount : false; + return transactionFeeAmount.isGreaterThan(0) ? transactionFeeAmount : false; }); }); From 0b6be89f9555c7f9b942e28ca6a208b937033c34 Mon Sep 17 00:00:00 2001 From: Danilo Prates Date: Fri, 29 Jan 2021 10:46:07 -0300 Subject: [PATCH 06/37] [DDW-481] Switching SMASH servers (#2259) * [DDW-481] Basic UI * [DDW-481] CHANGELOG * [DDW-481] Input UI and validation * [DDW-481] API init * [DDW-481] Api progress and cardano wallet update * [DDW-481] API and Store handling progress * [DDW-481] Api, Store and changes handling * [DDW-481] Api, Store and changes handling progress * [DDW-481] Error handling and UI progress * [DDW-481] Smash Server in the Stake Pools list * [DDW-481] Translation manager * [DDW-481] Flow and Lint issues * [DDW-481] Fix error * [DDW-481] General improvements * [DDW-481] Disable wallet import - enabled for testing * [DDW-481] Translation manager * [DDW-481] Text and styling adjustments * [DDW-481] Japanese translation * [DDW-481] Missing Japanese character * [DDW-481] Build without AdaPools for testing purposes * [DDW-481] Add back AddPools SMASH server * [DDW-481] Fix flow issues * [DDW-481] Update cardano wallet * [DDW-481] Flow and Lint errors * [DDW-481] Input buttons * [DDW-481] Input logic progress * [DDW-481] Input logic progress * [DDW-481] Error logic progress * [DDW-481] Styling adjustments * [DDW-481] Logic progress * [DDW-481] Logic progress - working * [DDW-481] Flow and Lint issues * [DDW-481] Adjustments * [DDW-481] Adjustments * [DDW-481] Logic improvements * [DDW-481] Flow and Lint issues * [DDW-481] Adjustments * [DDW-481] Adjustments * [DDW-481] Adds AdaPools back to the list of known servers * [DDW-481] Updates CHANGELOG * [DDW-481] Disable screen when it is syncing * [DDW-481] Adjustments * [DDW-481] Styling adjustment * [DDW-481] Simplify logic * [DDW-481] Improve Successfuly Update message * [DDW-481] Minor improvement * [DDW-481] Prevent FTP urls * [DDW-481] Adjustments * [DDW-481] Adjustments * [DDW-481] Fix URL error and add Direct and None options * [DDW-481] smashUrl from the Cardano Launcher * [DDW-481] Small UI fix * [DDW-481] Stake Pools fetching tracker - Init * [DDW-481] Stake Pools fetching tracker - Init * [DDW-481] SP fetch tracker and spinner * [DDW-481] Layout adjustments * [DDW-481] Improve logic * [DDW-481] Replace icon with opacity * [DDW-481] Small styling adjustments * [DDW-481] Copy and translation * [DDW-481] New layout with option descriptions * [DDW-481] Updates CHANGELOG * [DDW-481] New copy and layout adjustments * [DDW-481] Japanese translation and layout adjustments * [DDW-481] Japanese translation and layout adjustments * [DDW-481] Translation manager * [DDW-481] Add initial checking back and remove NONE option * [DDW-481] Change URL validator to match the API * [DDW-481] Fix flow and lint issues * [DDW-481] Adjustments * [DDW-481] Small adjustment * [DDW-481] Styling adjustments * [DDW-481] Adjustments * [DDW-481] Adjustment * [DDW-481] Success message * [DDW-481] Implement Localhost for SMASH selection and correct JP translation * [DDW-481] Translation manager * [DDW-481] Adjustments * [DDW-481] Fix flow and lint issues * [DDW-481] Fix error when submitting an empty url * [DDW-481] Styling adjustment * [DDW-481] Fix Japanese link * [DDW-481] Translation manager * [DDW-481] English Only text in JP link * [DDW-481] Fix Stake Pools icons color * [DDW-481] Adjustments * [DDW-481] Replace strings for const variables * [DDW-481] Preserve SMASH setting value on Daedalus restart * [DDW-481] Prevent link line-break * [DDW-481] Loading stake for Select * [DDW-481] Fix inline input error tooltip validation * [DDW-481] Code improvements and cleanup Co-authored-by: Nikola Glumac --- CHANGELOG.md | 4 + source/common/config/electron-store.config.js | 1 + source/common/types/electron-store.types.js | 3 +- source/main/cardano/CardanoNode.js | 3 - source/main/cardano/CardanoWalletLauncher.js | 10 - source/main/cardano/setup.js | 2 - source/main/config.js | 1 + source/main/preload.js | 3 + source/renderer/app/Routes.js | 5 + .../renderer/app/actions/staking-actions.js | 2 + source/renderer/app/api/api.js | 82 ++++ source/renderer/app/api/errors.js | 5 + .../requests/checkSmashServerHealth.js | 17 + .../api/staking/requests/getSmashSettings.js | 13 + .../staking/requests/updateSmashSettings.js | 22 ++ source/renderer/app/api/staking/types.js | 26 ++ source/renderer/app/api/utils/localStorage.js | 9 + .../images/smash-settings-ic.inline.svg | 13 + .../app/assets/images/spinner-ic.inline.svg | 13 + .../app/assets/images/spinner-tiny.inline.svg | 19 + .../settings/categories/StakePoolsSettings.js | 328 ++++++++++++++++ .../categories/StakePoolsSettings.scss | 145 +++++++ .../components/settings/menu/SettingsMenu.js | 24 +- .../staking/stake-pools/StakePools.js | 117 ++++-- .../staking/stake-pools/StakePools.scss | 62 ++- .../wallet/settings/WalletSettings.js | 32 +- .../widgets/forms/InlineEditingInput.js | 286 ++++++++++---- .../widgets/forms/InlineEditingInput.scss | 145 ++++++- source/renderer/app/config/numbersConfig.js | 4 + source/renderer/app/config/stakingConfig.js | 61 ++- .../app/containers/settings/Settings.js | 9 +- .../categories/StakePoolsSettingsPage.js | 38 ++ .../containers/staking/StakePoolsListPage.js | 15 +- .../containers/wallet/WalletSettingsPage.js | 2 +- source/renderer/app/domains/ApiError.js | 3 +- .../app/i18n/locales/defaultMessages.json | 361 ++++++++++++++++-- source/renderer/app/i18n/locales/en-US.json | 25 +- source/renderer/app/i18n/locales/ja-JP.json | 25 +- source/renderer/app/routes-config.js | 1 + source/renderer/app/stores/StakingStore.js | 161 +++++++- source/renderer/app/types/stakingTypes.js | 1 + source/renderer/app/utils/staking.js | 29 ++ storybook/stories/common/Widgets.stories.js | 26 ++ .../settings/general/General.stories.js | 10 + .../stories/settings/utils/SettingsWrapper.js | 3 + .../stories/staking/StakePools.stories.js | 5 +- .../settings/WalletSettingsScreen.stories.js | 2 +- 47 files changed, 1972 insertions(+), 201 deletions(-) create mode 100644 source/renderer/app/api/staking/requests/checkSmashServerHealth.js create mode 100644 source/renderer/app/api/staking/requests/getSmashSettings.js create mode 100644 source/renderer/app/api/staking/requests/updateSmashSettings.js create mode 100644 source/renderer/app/assets/images/smash-settings-ic.inline.svg create mode 100644 source/renderer/app/assets/images/spinner-ic.inline.svg create mode 100644 source/renderer/app/assets/images/spinner-tiny.inline.svg create mode 100644 source/renderer/app/components/settings/categories/StakePoolsSettings.js create mode 100644 source/renderer/app/components/settings/categories/StakePoolsSettings.scss create mode 100644 source/renderer/app/containers/settings/categories/StakePoolsSettingsPage.js create mode 100644 source/renderer/app/utils/staking.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b22a0453..34c2a72fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ Changelog ## vNext +### Features + +- Added SMASH server configuration options ([PR 2259](https://github.com/input-output-hk/daedalus/pull/2259)) + ### Chores - Updated `bignumber.js` package ([PR 2305](https://github.com/input-output-hk/daedalus/pull/2305)) diff --git a/source/common/config/electron-store.config.js b/source/common/config/electron-store.config.js index 665d1e8150..a0829cc185 100644 --- a/source/common/config/electron-store.config.js +++ b/source/common/config/electron-store.config.js @@ -30,4 +30,5 @@ export const STORAGE_KEYS: { DOWNLOAD_MANAGER: 'DOWNLOAD-MANAGER', APP_AUTOMATIC_UPDATE_FAILED: 'APP-AUTOMATIC-UPDATE-FAILED', APP_UPDATE_COMPLETED: 'APP-UPDATE-COMPLETED', + SMASH_SERVER: 'SMASH-SERVER', }; diff --git a/source/common/types/electron-store.types.js b/source/common/types/electron-store.types.js index 8315462143..94b183cda8 100644 --- a/source/common/types/electron-store.types.js +++ b/source/common/types/electron-store.types.js @@ -19,7 +19,8 @@ export type StorageKey = | 'WALLET-MIGRATION-STATUS' | 'DOWNLOAD-MANAGER' | 'APP-AUTOMATIC-UPDATE-FAILED' - | 'APP-UPDATE-COMPLETED'; + | 'APP-UPDATE-COMPLETED' + | 'SMASH-SERVER'; export type StoreMessage = { type: StorageType, diff --git a/source/main/cardano/CardanoNode.js b/source/main/cardano/CardanoNode.js index f22d3d3dbe..643e6497da 100644 --- a/source/main/cardano/CardanoNode.js +++ b/source/main/cardano/CardanoNode.js @@ -81,7 +81,6 @@ export type CardanoNodeConfig = { syncTolerance: string, cliBin: string, // Path to cardano-cli executable isStaging: boolean, - smashUrl?: string, }; const CARDANO_UPDATE_EXIT_CODE = 20; @@ -297,7 +296,6 @@ export class CardanoNode { syncTolerance, cliBin, isStaging, - smashUrl, } = config; this._config = config; @@ -356,7 +354,6 @@ export class CardanoNode { walletLogFile, cliBin, isStaging, - smashUrl, }); this._node = node; diff --git a/source/main/cardano/CardanoWalletLauncher.js b/source/main/cardano/CardanoWalletLauncher.js index 7f8ab36bd9..34dfa70520 100644 --- a/source/main/cardano/CardanoWalletLauncher.js +++ b/source/main/cardano/CardanoWalletLauncher.js @@ -38,7 +38,6 @@ export type WalletOpts = { walletLogFile: WriteStream, cliBin: string, isStaging: boolean, - smashUrl?: string, }; export async function CardanoWalletLauncher(walletOpts: WalletOpts): Launcher { @@ -57,7 +56,6 @@ export async function CardanoWalletLauncher(walletOpts: WalletOpts): Launcher { walletLogFile, cliBin, isStaging, - smashUrl, } = walletOpts; // TODO: Update launcher config to pass number const syncToleranceSeconds = parseInt(syncTolerance.replace('s', ''), 10); @@ -130,14 +128,6 @@ export async function CardanoWalletLauncher(walletOpts: WalletOpts): Launcher { launcherConfig.networkName = TESTNET; logger.info('Launching Wallet with --testnet flag'); } - if (smashUrl) { - logger.info('Launching Wallet with --pool-metadata-fetching flag', { - poolMetadataSource: { smashUrl }, - }); - merge(launcherConfig, { - poolMetadataSource: { smashUrl }, - }); - } merge(launcherConfig, { nodeConfig, tlsConfiguration }); break; case CardanoNodeImplementationOptions.JORMUNGANDR: diff --git a/source/main/cardano/setup.js b/source/main/cardano/setup.js index 80ae28098b..cd8c770f4f 100644 --- a/source/main/cardano/setup.js +++ b/source/main/cardano/setup.js @@ -49,7 +49,6 @@ const startCardanoNode = ( syncTolerance, cliBin, isStaging, - smashUrl, } = launcherConfig; const logFilePath = `${logsPrefix}/pub/`; const config = { @@ -66,7 +65,6 @@ const startCardanoNode = ( syncTolerance, cliBin, isStaging, - smashUrl, startupTimeout: NODE_STARTUP_TIMEOUT, startupMaxRetries: NODE_STARTUP_MAX_RETRIES, shutdownTimeout: NODE_SHUTDOWN_TIMEOUT, diff --git a/source/main/config.js b/source/main/config.js index 23631d0eb4..9579d9d96a 100644 --- a/source/main/config.js +++ b/source/main/config.js @@ -121,6 +121,7 @@ export const { legacyStateDir, logsPrefix, isFlight, + smashUrl, } = launcherConfig; export const appLogsFolderPath = logsPrefix; export const pubLogsFolderPath = path.join(appLogsFolderPath, 'pub'); diff --git a/source/main/preload.js b/source/main/preload.js index af13cf98af..a3777e4bf7 100644 --- a/source/main/preload.js +++ b/source/main/preload.js @@ -11,6 +11,7 @@ import { legacyStateDir, nodeImplementation, isFlight, + smashUrl, } from './config'; import { SHELLEY_LOCAL, @@ -58,7 +59,9 @@ process.once('loaded', () => { isIncentivizedTestnet: _isIncentivizedTestnet, isFlight, legacyStateDir, + smashUrl, }); + // Expose require for Spectron! if (_process.env.NODE_ENV === 'test') { // $FlowFixMe diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index c82d059099..641c856bda 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -8,6 +8,7 @@ import Root from './containers/Root'; import InitialSettingsPage from './containers/profile/InitialSettingsPage'; import Settings from './containers/settings/Settings'; import GeneralSettingsPage from './containers/settings/categories/GeneralSettingsPage'; +import StakePoolsSettingsPage from './containers/settings/categories/StakePoolsSettingsPage'; import SupportSettingsPage from './containers/settings/categories/SupportSettingsPage'; import TermsOfUseSettingsPage from './containers/settings/categories/TermsOfUseSettingsPage'; import TermsOfUsePage from './containers/profile/TermsOfUsePage'; @@ -88,6 +89,10 @@ export const Routes = withRouter(() => ( path={ROUTES.SETTINGS.GENERAL} component={GeneralSettingsPage} /> + = new Action(); requestCSVFileSuccess: Action = new Action(); + selectSmashServerUrl: Action<{ smashServerUrl: string }> = new Action(); + resetSmashServerError: Action = new Action(); /* ---------- Redeem ITN Rewards ---------- */ onRedeemStart: Action = new Action(); onConfigurationContinue: Action = new Action(); diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 1c1a7af6a0..252fdd7e65 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -78,6 +78,9 @@ import { getStakePools } from './staking/requests/getStakePools'; import { getDelegationFee } from './staking/requests/getDelegationFee'; import { joinStakePool } from './staking/requests/joinStakePool'; import { quitStakePool } from './staking/requests/quitStakePool'; +import { getSmashSettings } from './staking/requests/getSmashSettings'; +import { checkSmashServerHealth } from './staking/requests/checkSmashServerHealth'; +import { updateSmashSettings } from './staking/requests/updateSmashSettings'; // Utility functions import { cardanoFaultInjectionChannel } from '../ipc/cardano.ipc'; @@ -95,6 +98,8 @@ import { filterLogData } from '../../../common/utils/logging'; // Config constants import { LOVELACES_PER_ADA } from '../config/numbersConfig'; import { + SMASH_SERVER_STATUSES, + SMASH_SERVERS_LIST, DELEGATION_DEPOSIT, MIN_REWARDS_REDEMPTION_RECEIVER_BALANCE, REWARDS_REDEMPTION_FEE_CALCULATION_AMOUNT, @@ -189,6 +194,9 @@ import type { GetRedeemItnRewardsFeeResponse, RequestRedeemItnRewardsRequest, RequestRedeemItnRewardsResponse, + GetSmashSettingsApiResponse, + CheckSmashServerHealthApiResponse, + PoolMetadataSource, } from './staking/types'; import type { StakePoolProps } from '../domains/StakePool'; import type { FaultInjectionIpcRequest } from '../../../common/types/cardano-node.types'; @@ -1678,6 +1686,80 @@ export default class AdaApi { } }; + getSmashSettings = async (): Promise => { + logger.debug('AdaApi::getSmashSettings called'); + try { + const { + pool_metadata_source: poolMetadataSource, + } = await getSmashSettings(this.config); + logger.debug('AdaApi::getSmashSettings success', { poolMetadataSource }); + return poolMetadataSource; + } catch (error) { + logger.error('AdaApi::getSmashSettings error', { error }); + throw new ApiError(error); + } + }; + + checkSmashServerIsValid = async (url: string): Promise => { + logger.debug('AdaApi::checkSmashServerIsValid called', { + parameters: { url }, + }); + try { + if (url === SMASH_SERVERS_LIST.direct.url) { + return true; + } + const { + health, + }: CheckSmashServerHealthApiResponse = await checkSmashServerHealth( + this.config, + url + ); + const isValid = health === SMASH_SERVER_STATUSES.AVAILABLE; + logger.debug('AdaApi::checkSmashServerIsValid success', { isValid }); + return isValid; + } catch (error) { + logger.error('AdaApi::checkSmashServerIsValid error', { error }); + throw new ApiError(error); + } + }; + + updateSmashSettings = async ( + poolMetadataSource: PoolMetadataSource + ): Promise => { + logger.debug('AdaApi::updateSmashSettings called', { + parameters: { poolMetadataSource }, + }); + try { + const isSmashServerValid = await this.checkSmashServerIsValid( + poolMetadataSource + ); + if (!isSmashServerValid) { + const error = { + code: 'invalid_smash_server', + }; + throw new ApiError(error); + } + await updateSmashSettings(this.config, poolMetadataSource); + logger.debug('AdaApi::updateSmashSettings success', { + poolMetadataSource, + }); + } catch (error) { + const id = get(error, 'id'); + const message = get(error, 'values.message'); + if ( + id === 'api.errors.GenericApiError' && + message === + 'Error parsing query parameter url failed: URI must not contain a path/query/fragment.' + ) { + throw new ApiError({ + code: 'invalid_smash_server', + }); + } + logger.error('AdaApi::updateSmashSettings error', { error }); + throw new ApiError(error); + } + }; + getRedeemItnRewardsFee = async ( request: GetRedeemItnRewardsFeeRequest ): Promise => { diff --git a/source/renderer/app/api/errors.js b/source/renderer/app/api/errors.js index ddca71f329..21e2a37a06 100644 --- a/source/renderer/app/api/errors.js +++ b/source/renderer/app/api/errors.js @@ -106,4 +106,9 @@ export const messages = defineMessages({ description: '"Funds cannot be transferred from this wallet because it contains some unspent transaction outputs (UTXOs), with amounts of ada that are too small to be migrated." error message', }, + invalidSmashServer: { + id: 'api.errors.invalidSmashServer', + defaultMessage: '!!!This URL is not a valid SMASH server', + description: '"This URL is not a valid SMASH server" error message', + }, }); diff --git a/source/renderer/app/api/staking/requests/checkSmashServerHealth.js b/source/renderer/app/api/staking/requests/checkSmashServerHealth.js new file mode 100644 index 0000000000..c4010a526d --- /dev/null +++ b/source/renderer/app/api/staking/requests/checkSmashServerHealth.js @@ -0,0 +1,17 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { CheckSmashServerHealthApiResponse } from '../types'; +import { request } from '../../utils/request'; + +export const checkSmashServerHealth = ( + config: RequestConfig, + url?: string +): Promise => + request( + { + method: 'GET', + path: '/v2/smash/health', + ...config, + }, + { url } + ); diff --git a/source/renderer/app/api/staking/requests/getSmashSettings.js b/source/renderer/app/api/staking/requests/getSmashSettings.js new file mode 100644 index 0000000000..390c8a9001 --- /dev/null +++ b/source/renderer/app/api/staking/requests/getSmashSettings.js @@ -0,0 +1,13 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { GetSmashSettingsResponse } from '../types'; +import { request } from '../../utils/request'; + +export const getSmashSettings = ( + config: RequestConfig +): Promise => + request({ + method: 'GET', + path: '/v2/settings', + ...config, + }); diff --git a/source/renderer/app/api/staking/requests/updateSmashSettings.js b/source/renderer/app/api/staking/requests/updateSmashSettings.js new file mode 100644 index 0000000000..c12078e85f --- /dev/null +++ b/source/renderer/app/api/staking/requests/updateSmashSettings.js @@ -0,0 +1,22 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { PoolMetadataSource } from '../types'; +import { request } from '../../utils/request'; + +export const updateSmashSettings = ( + config: RequestConfig, + poolMetadataSource: PoolMetadataSource +): Promise => + request( + { + method: 'PUT', + path: '/v2/settings', + ...config, + }, + {}, + { + settings: { + pool_metadata_source: poolMetadataSource, + }, + } + ); diff --git a/source/renderer/app/api/staking/types.js b/source/renderer/app/api/staking/types.js index a10166e21d..6f4048ea58 100644 --- a/source/renderer/app/api/staking/types.js +++ b/source/renderer/app/api/staking/types.js @@ -115,3 +115,29 @@ export type RequestRedeemItnRewardsRequest = { }; export type RequestRedeemItnRewardsResponse = BigNumber; + +export type PoolMetadataSource = 'none' | 'direct' | string; + +export type UpdateSmashSettingsRequest = { + settings: { + pool_metadata_source: PoolMetadataSource, + }, +}; + +export type GetSmashSettingsResponse = { + pool_metadata_source: PoolMetadataSource, +}; + +export type GetSmashSettingsApiResponse = PoolMetadataSource; + +export type SmashServerStatuses = + | 'available' + | 'unavailable' + | 'unreachable' + | 'no_smash_configured'; + +export type CheckSmashServerHealthApiResponse = { + health: SmashServerStatuses, +}; + +export type CheckSmashServerHealthResponse = boolean; diff --git a/source/renderer/app/api/utils/localStorage.js b/source/renderer/app/api/utils/localStorage.js index 673dd4519e..131e7b27bf 100644 --- a/source/renderer/app/api/utils/localStorage.js +++ b/source/renderer/app/api/utils/localStorage.js @@ -275,6 +275,15 @@ export default class LocalStorageApi { unsetAppUpdateCompleted = (): Promise => LocalStorageApi.unset(keys.APP_UPDATE_COMPLETED); + getSmashServer = (): Promise => + LocalStorageApi.get(keys.SMASH_SERVER); + + setSmashServer = (smashServerUrl: string): Promise => + LocalStorageApi.set(keys.SMASH_SERVER, smashServerUrl); + + unsetSmashServer = (): Promise => + LocalStorageApi.unset(keys.SMASH_SERVER); + // Paired Hardware wallets (software <-> hardware wallet / device) getHardwareWalletsLocalData = (): Promise => LocalStorageApi.get(keys.HARDWARE_WALLETS, {}); diff --git a/source/renderer/app/assets/images/smash-settings-ic.inline.svg b/source/renderer/app/assets/images/smash-settings-ic.inline.svg new file mode 100644 index 0000000000..8c51e1fecc --- /dev/null +++ b/source/renderer/app/assets/images/smash-settings-ic.inline.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/spinner-ic.inline.svg b/source/renderer/app/assets/images/spinner-ic.inline.svg new file mode 100644 index 0000000000..da22dc634c --- /dev/null +++ b/source/renderer/app/assets/images/spinner-ic.inline.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/spinner-tiny.inline.svg b/source/renderer/app/assets/images/spinner-tiny.inline.svg new file mode 100644 index 0000000000..0ca7caf5d9 --- /dev/null +++ b/source/renderer/app/assets/images/spinner-tiny.inline.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/source/renderer/app/components/settings/categories/StakePoolsSettings.js b/source/renderer/app/components/settings/categories/StakePoolsSettings.js new file mode 100644 index 0000000000..df0bbb8a24 --- /dev/null +++ b/source/renderer/app/components/settings/categories/StakePoolsSettings.js @@ -0,0 +1,328 @@ +// @flow +import React, { Component } from 'react'; +import { map } from 'lodash'; +import { Select } from 'react-polymorph/lib/components/Select'; +import { Input } from 'react-polymorph/lib/components/Input'; +import { Link } from 'react-polymorph/lib/components/Link'; +import SVGInline from 'react-svg-inline'; +import { observer } from 'mobx-react'; +import { + defineMessages, + intlShape, + FormattedMessage, + FormattedHTMLMessage, +} from 'react-intl'; +import { getSmashServerIdFromUrl } from '../../../utils/staking'; +import InlineEditingInput from '../../widgets/forms/InlineEditingInput'; +import styles from './StakePoolsSettings.scss'; +import { + SMASH_SERVERS_LIST, + SMASH_SERVER_TYPES, + SMASH_URL_VALIDATOR, +} from '../../../config/stakingConfig'; +import type { SmashServerType } from '../../../types/stakingTypes'; +import spinningIcon from '../../../assets/images/spinner-ic.inline.svg'; + +import LocalizableError from '../../../i18n/LocalizableError'; + +const messages = defineMessages({ + description: { + id: 'settings.stakePools.smash.description', + defaultMessage: + '!!!The {link} is an off-chain metadata server that enables the fast loading of stake pool details. Stake pools are also curated and each server has a different curation policy.', + description: 'description for the Stake Pools settings page.', + }, + descriptionLinkLabel: { + id: 'settings.stakePools.smash.descriptionLinkLabel', + defaultMessage: '!!!Stakepool Metadata Aggregation Server (SMASH)', + description: 'description for the Stake Pools settings page.', + }, + descriptionLinkUrl: { + id: 'settings.stakePools.smash.descriptionLinkUrl', + defaultMessage: + '!!!https://iohk.io/en/blog/posts/2020/11/17/in-pools-we-trust/', + description: 'description for the Stake Pools settings page.', + }, + descriptionIOHKContent1: { + id: 'settings.stakePools.smash.descriptionIOHKContent1', + defaultMessage: + '!!!The IOHK server ensures that registered stake pools are valid, helps to avoid duplicated ticker names or trademarks, and checks that the pools do not feature potentially offensive or harmful information.', + description: 'description for the Stake Pools settings page.', + }, + descriptionIOHKContent2: { + id: 'settings.stakePools.smash.descriptionIOHKContent2', + defaultMessage: + '!!!This allows us to deal with any scams, trolls, or abusive behavior by filtering out potentially problematic actors. {link} about the IOHK SMASH server.', + description: 'description for the Stake Pools settings page.', + }, + descriptionIOHKLinkLabel: { + id: 'settings.stakePools.smash.descriptionIOHKLinkLabel', + defaultMessage: '!!!Read more', + description: 'description for the Stake Pools settings page.', + }, + descriptionIOHKLinkUrl: { + id: 'settings.stakePools.smash.descriptionIOHKLinkUrl', + defaultMessage: + '!!!https://iohk.io/en/blog/posts/2020/11/17/in-pools-we-trust/', + description: 'description for the Stake Pools settings page.', + }, + descriptionNone: { + id: 'settings.stakePools.smash.descriptionNone', + defaultMessage: + '!!!This option is not recommended! Without the off-chain metadata server your Daedalus client will fetch this data by contacting every stake pool individually, which is a very slow and resource-consuming process. The list of stake pools received is not curated, so Daedalus will receive legitimate pools, duplicates, and fake pools. An added risk to this process is that your antivirus or antimalware software could recognize the thousands of network requests as malicious behavior by the Daedalus client.', + description: 'description for the Stake Pools settings page.', + }, + smashSelectLabel: { + id: 'settings.stakePools.smash.select.label', + defaultMessage: '!!!Off-chain metadata server (SMASH)', + description: + 'smashSelectLabel for the "Smash" selection on the Stake Pools settings page.', + }, + smashSelectIOHKServer: { + id: 'settings.stakePools.smash.select.IOHKServer', + defaultMessage: '!!!IOHK (Recommended)', + description: + 'smashSelectCustomServer option for the "Smash" selection on the Stake Pools settings page.', + }, + smashSelectDirect: { + id: 'settings.stakePools.smash.select.direct', + defaultMessage: '!!!None - let my Daedalus client fetch the data', + description: + 'smashSelectCustomServer option for the "Smash" selection on the Stake Pools settings page.', + }, + smashSelectCustomServer: { + id: 'settings.stakePools.smash.select.customServer', + defaultMessage: '!!!Custom server', + description: + 'smashSelectCustomServer option for the "Smash" selection on the Stake Pools settings page.', + }, + smashURLInputLabel: { + id: 'settings.stakePools.smashUrl.input.label', + defaultMessage: '!!!SMASH server URL', + description: + 'smashURLInputLabel for the "Smash Custom Server" selection on the Stake Pools settings page.', + }, + smashUrlInputPlaceholder: { + id: 'settings.stakePools.smashUrl.input.placeholder', + defaultMessage: '!!!Enter custom server URL', + description: + 'smashUrlInputPlaceholder for the "Smash Custom Server" selection on the Stake Pools settings page.', + }, + smashUrlInputInvalidUrl: { + id: 'settings.stakePools.smashUrl.input.invalidUrl', + defaultMessage: '!!!Invalid URL', + description: + 'smashUrlInputInvalidUrl for the "Smash Custom Server" selection on the Stake Pools settings page.', + }, + changesSaved: { + id: 'inline.editing.input.changesSaved', + defaultMessage: '!!!Your changes have been saved', + description: + 'Message "Your changes have been saved" for inline editing (eg. on Profile Settings page).', + }, +}); + +type Props = { + smashServerUrl: string, + smashServerUrlError?: ?LocalizableError, + onSelectSmashServerUrl: Function, + onResetSmashServerError: Function, + isLoading: boolean, + onOpenExternalLink: Function, +}; + +type State = { + editingSmashServerUrl: string, + successfullyUpdated: boolean, + wasLoading: boolean, +}; + +@observer +export default class StakePoolsSettings extends Component { + static contextTypes = { + intl: intlShape.isRequired, + }; + + /* eslint-disable react/no-unused-state */ + // Disabling eslint due to a [known issue](https://github.com/yannickcr/eslint-plugin-react/issues/2061) + // `wasLoading` is actually used in the `getDerivedStateFromProps` method + static getDerivedStateFromProps( + { isLoading, smashServerUrlError }: Props, + { wasLoading }: State + ) { + const successfullyUpdated = + wasLoading && !isLoading && !smashServerUrlError; + return { + successfullyUpdated, + wasLoading: isLoading, + }; + } + + state = { + editingSmashServerUrl: this.props.smashServerUrl, + successfullyUpdated: false, + wasLoading: false, + }; + + componentWillUnmount() { + this.props.onResetSmashServerError(); + } + + handleSubmit = (url: string) => { + if (this.handleIsValid(url)) { + this.setState({ + editingSmashServerUrl: url, + }); + this.props.onSelectSmashServerUrl(url); + } + }; + + handleOnSelectSmashServerType = (smashServerType: SmashServerType) => { + const { onSelectSmashServerUrl, onResetSmashServerError } = this.props; + onResetSmashServerError(); + let editingSmashServerUrl = ''; + if (smashServerType !== SMASH_SERVER_TYPES.CUSTOM) { + editingSmashServerUrl = SMASH_SERVERS_LIST[smashServerType].url; + onSelectSmashServerUrl(editingSmashServerUrl); + } + this.setState({ + editingSmashServerUrl, + }); + }; + + handleIsValid = (url: string) => url === '' || SMASH_URL_VALIDATOR.test(url); + + smashSelectMessages = { + iohk: , + direct: this.context.intl.formatMessage(messages.smashSelectDirect), + custom: this.context.intl.formatMessage(messages.smashSelectCustomServer), + none: null, + }; + + render() { + const { smashServerUrlError, isLoading, onOpenExternalLink } = this.props; + const { intl } = this.context; + const { editingSmashServerUrl, successfullyUpdated } = this.state; + const smashServerType = getSmashServerIdFromUrl(editingSmashServerUrl); + + const selectedLabel = + this.smashSelectMessages[smashServerType] || smashServerType; + + const smashSelectOptions = map(SMASH_SERVER_TYPES, (value) => ({ + label: this.smashSelectMessages[value] || value, + value, + })); + + const errorMessage = smashServerUrlError + ? intl.formatMessage(smashServerUrlError) + : null; + + return ( +
+
+ + onOpenExternalLink( + intl.formatMessage(messages.descriptionLinkUrl) + ) + } + label={intl.formatMessage(messages.descriptionLinkLabel)} + /> + ), + }} + /> +
+ + {!isLoading ? ( + ( +
+ {label} + +
+ )} + selectedOption={selectedLabel} + disabled + /> + )} + + {smashServerType === SMASH_SERVER_TYPES.CUSTOM && ( + + )} + {smashServerType === SMASH_SERVER_TYPES.IOHK && ( +
+

{intl.formatMessage(messages.descriptionIOHKContent1)}

+

+ + onOpenExternalLink( + intl.formatMessage(messages.descriptionIOHKLinkUrl) + ) + } + label={intl.formatMessage( + messages.descriptionIOHKLinkLabel + )} + /> + ), + }} + /> +

+
+ )} + + {smashServerType === SMASH_SERVER_TYPES.DIRECT && ( +
+ +
+ )} +
+ ); + } +} diff --git a/source/renderer/app/components/settings/categories/StakePoolsSettings.scss b/source/renderer/app/components/settings/categories/StakePoolsSettings.scss new file mode 100644 index 0000000000..da0a50fc00 --- /dev/null +++ b/source/renderer/app/components/settings/categories/StakePoolsSettings.scss @@ -0,0 +1,145 @@ +@import '../../../themes/mixins/error-message'; + +.component { + margin-bottom: 20px; + + :global { + .RadioSet_radiosContainer { + flex-direction: column; + } + .SimpleOptions_option em { + opacity: 0.5; + } + } +} +.description { + color: var(--theme-support-settings-text-color); + display: block; + font-family: var(--font-light); + font-size: 16px; + line-height: 1.38; + margin-bottom: 20px; + p { + margin-bottom: 12px; + } + b { + font-family: var(--font-regular); + } + li { + list-style: decimal; + margin-left: 20px; + } + em { + color: var(--theme-delegation-steps-intro-link-color); + } + .link { + font-size: 16px; + } +} +.smashServerUrl { + margin-top: 20px; + :global { + .SimpleInput_errored { + border-color: var(--rp-input-border-color-errored); + } + } +} +.smashServerUrlError { + @include error-message; + margin-bottom: 1rem; + text-align: center; +} + +.optionDescription { + color: var(--theme-support-settings-text-color); + display: block; + font-family: var(--font-light); + font-size: 14px; + line-height: 1.38; + margin-top: 20px; + p { + margin-bottom: 12px; + } + b { + font-family: var(--font-bold); + } + :global { + .SimpleLink_root { + font-family: var(--font-regular); + word-break: break-word; + } + } +} + +.selectionRenderer { + color: var(--theme-support-settings-text-color); + font-family: var(--rp-theme-font-regular); + line-height: var(--rp-input-line-height); + padding: 14px 20px 0; + position: relative; + em { + opacity: 0.5; + } + .icon { + display: inline-block; + position: absolute; + right: 20px; + svg { + animation: spinner 1.5s linear infinite; + height: 12px; + position: relative; + top: 1px; + width: 12px; + g { + stroke: var(--rp-select-arrow-bg-color); + } + } + } +} + +@keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.disabledInput { + input { + background-color: var(--rp-input-bg-color, #fafbfc); + border: 1px solid var(--rp-input-border-color, #c6cdd6); + color: var(--rp-input-text-color); + height: 50px; + } + :global { + .SimpleFormField_inputWrapper { + height: 50px; + } + } +} + +@keyframes animateSavingResultLabel { + 0% { + opacity: 0.5; + } + 50% { + opacity: 0.5; + } + 100% { + opacity: 0; + } +} + +.savingResultLabel { + animation: animateSavingResultLabel 3s; + color: var(--theme-input-right-floating-text-success-color); + font-family: var(--font-regular); + font-size: 13px; + opacity: 0; + position: absolute; + right: 0; + text-align: right; + top: 5px; +} diff --git a/source/renderer/app/components/settings/menu/SettingsMenu.js b/source/renderer/app/components/settings/menu/SettingsMenu.js index dee4a3c685..de71379963 100644 --- a/source/renderer/app/components/settings/menu/SettingsMenu.js +++ b/source/renderer/app/components/settings/menu/SettingsMenu.js @@ -12,6 +12,11 @@ const messages = defineMessages({ defaultMessage: '!!!General', description: 'Label for the "General" link in the settings menu.', }, + stakePools: { + id: 'settings.menu.stakePools.link.label', + defaultMessage: '!!!Stake Pools', + description: 'Label for the "Support" link in the settings menu.', + }, support: { id: 'settings.menu.support.link.label', defaultMessage: '!!!Support', @@ -31,6 +36,8 @@ const messages = defineMessages({ type Props = { isFlight: boolean, + isSyncing: boolean, + currentRoute: string, isActiveItem: Function, onItemClick: Function, }; @@ -41,9 +48,16 @@ export default class SettingsMenu extends Component { intl: intlShape.isRequired, }; + componentDidUpdate() { + const { isSyncing, currentRoute, onItemClick } = this.props; + if (currentRoute === ROUTES.SETTINGS.STAKE_POOLS && isSyncing) { + onItemClick(ROUTES.SETTINGS.GENERAL); + } + } + render() { const { intl } = this.context; - const { onItemClick, isActiveItem, isFlight } = this.props; + const { onItemClick, isActiveItem, isFlight, isSyncing } = this.props; return (
@@ -54,6 +68,14 @@ export default class SettingsMenu extends Component { active={isActiveItem(ROUTES.SETTINGS.GENERAL)} className="general" /> + {!isSyncing && ( + onItemClick(ROUTES.SETTINGS.STAKE_POOLS)} + active={isActiveItem(ROUTES.SETTINGS.STAKE_POOLS)} + className="stakePools" + /> + )} {!isFlight && !global.isShelleyTestnet && ( , getStakePoolById: Function, + onSmashSettingsClick: Function, + smashServerUrl: ?string, maxDelegationFunds: number, }; @@ -130,9 +160,12 @@ export default class StakePools extends Component { onOpenExternalLink, currentTheme, isLoading, + isFetching, isRanking, stakePoolsDelegatingList, getStakePoolById, + smashServerUrl, + onSmashSettingsClick, maxDelegationFunds, } = this.props; const { @@ -153,10 +186,13 @@ export default class StakePools extends Component { IS_RANKING_DATA_AVAILABLE && stakePool.nonMyopicMemberRewards ).length; - const listTitleMessage = search.trim().length - ? messages.listTitleWithSearch + const listTitleMessage = isFetching + ? messages.listTitleLoading : messages.listTitle; + const listTitleSearchMessage = + !!search.trim().length && intl.formatMessage(messages.listTitleSearch); + const loadingSpinner = ( { isLoading ? styles.isLoading : null, ]); + const smashServer = smashServerUrl + ? getSmashServerNameFromUrl(smashServerUrl) + : null; + + const tinyLoadingSpinner = isFetching && ( + + ); + + const smashSettings = ( + + ); + return (
{isLoading ? ( @@ -209,12 +267,17 @@ export default class StakePools extends Component { onListView={this.handleListView} isListView={isListView} isGridView={isGridView} + smashServer={smashServer} isClearTooltipOpeningDownward /> {stakePoolsDelegatingList.length > 0 && (

- {intl.formatMessage(messages.delegatingListTitle)} + + + {intl.formatMessage(messages.delegatingListTitle)} + +

{ {isListView && (

- + + + {intl.formatMessage(listTitleMessage)} + {listTitleSearchMessage} + {intl.formatMessage(messages.listTitleStakePools, { + pools: filteredStakePoolsList.length, + })} + + {tinyLoadingSpinner} + + {smashSettings}

{ {isGridView && (

- + + + {intl.formatMessage(listTitleMessage)} + {listTitleSearchMessage} + {intl.formatMessage(messages.listTitleStakePools, { + pools: filteredStakePoolsList.length, + })} + + {tinyLoadingSpinner} + + {smashSettings}

span:first-child { + opacity: 0.5; + } + } + + .smashSettings { + cursor: pointer; + display: flex; + font-size: 16px; + > span:first-child { + color: var(--theme-staking-stake-pools-title-color); + display: inline-block; + max-width: 570px; + opacity: 0.5; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + white-space: nowrap; + } + &:hover { + svg { + opacity: 1; + } + } + } + .smashSettingsIcon svg { + margin-left: 4px; + opacity: 0.6; + width: 10px; + g { + fill: var(--theme-staking-stake-pools-title-color); + } + } + .tinySpinner { + display: inline-block; + margin: -5px 0 0 6px; + vertical-align: middle; + svg { + animation: rotating-spinner 1.2s linear infinite; + height: 10px; + width: 10px; + circle { + stroke: var(--theme-staking-stake-pools-title-color); + } + } + } + + @keyframes rotating-spinner { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } } } diff --git a/source/renderer/app/components/wallet/settings/WalletSettings.js b/source/renderer/app/components/wallet/settings/WalletSettings.js index e54df640cc..5ac02a31fc 100644 --- a/source/renderer/app/components/wallet/settings/WalletSettings.js +++ b/source/renderer/app/components/wallet/settings/WalletSettings.js @@ -78,16 +78,12 @@ type Props = { onFieldValueChange: Function, onStartEditing: Function, onStopEditing: Function, - onCancelEditing: Function, + onCancel: Function, onVerifyRecoveryPhrase: Function, onCopyWalletPublicKey: Function, nameValidator: Function, - activeField: ?string, - isSubmitting: boolean, isIncentivizedTestnet: boolean, - isInvalid: boolean, isLegacy: boolean, - lastUpdatedField: ?string, changeSpendingPasswordDialog: Node, walletPublicKeyQRCodeDialogContainer: Node, deleteWalletDialogContainer: Node, @@ -130,7 +126,7 @@ export default class WalletSettings extends Component { componentWillUnmount() { // This call is used to prevent display of old successfully-updated messages - this.props.onCancelEditing(); + this.props.onCancel(); } onBlockForm = () => { @@ -222,15 +218,11 @@ export default class WalletSettings extends Component { onFieldValueChange, onStartEditing, onStopEditing, - onCancelEditing, + onCancel, onVerifyRecoveryPhrase, nameValidator, - activeField, - isSubmitting, isIncentivizedTestnet, - isInvalid, isLegacy, - lastUpdatedField, changeSpendingPasswordDialog, recoveryPhraseVerificationDate, recoveryPhraseVerificationStatus, @@ -268,22 +260,18 @@ export default class WalletSettings extends Component { onStartEditing('name')} - onStopEditing={onStopEditing} - onCancelEditing={onCancelEditing} + onFocus={() => onStartEditing('name')} + onBlur={onStopEditing} + onCancel={onCancel} onSubmit={(value) => onFieldValueChange('name', value)} isValid={nameValidator} - validationErrorMessage={intl.formatMessage( + valueErrorMessage={intl.formatMessage( globalMessages.invalidWalletName )} - successfullyUpdated={ - !isSubmitting && !isInvalid && lastUpdatedField === 'name' - } - inputBlocked={isFormBlocked} + readOnly={isFormBlocked} /> {!isHardwareWallet && ( diff --git a/source/renderer/app/components/widgets/forms/InlineEditingInput.js b/source/renderer/app/components/widgets/forms/InlineEditingInput.js index e09eddea07..41584dda10 100644 --- a/source/renderer/app/components/widgets/forms/InlineEditingInput.js +++ b/source/renderer/app/components/widgets/forms/InlineEditingInput.js @@ -1,14 +1,22 @@ // @flow +/* eslint-disable react/no-did-update-set-state */ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; +import { get } from 'lodash'; +import { Button } from 'react-polymorph/lib/components/Button'; import vjf from 'mobx-react-form/lib/validators/VJF'; +import SVGInline from 'react-svg-inline'; import classnames from 'classnames'; import { Input } from 'react-polymorph/lib/components/Input'; -import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; import ReactToolboxMobxForm from '../../../utils/ReactToolboxMobxForm'; import styles from './InlineEditingInput.scss'; import { FORM_VALIDATION_DEBOUNCE_WAIT } from '../../../config/timingConfig'; +import penIcon from '../../../assets/images/pen.inline.svg'; +import crossIcon from '../../../assets/images/close-cross.inline.svg'; +import arrowIcon from '../../../assets/images/arrow-right.inline.svg'; +import spinningIcon from '../../../assets/images/spinner-ic.inline.svg'; +import { ENTER_KEY_CODE, ESCAPE_KEY_CODE } from '../../../config/numbersConfig'; const messages = defineMessages({ change: { @@ -31,43 +39,56 @@ const messages = defineMessages({ type Props = { className?: string, - isActive: boolean, - inputFieldLabel: string, - inputFieldValue: string, - onStartEditing: Function, - onStopEditing: Function, - onCancelEditing: Function, + label: string, + value: string, + placeholder?: string, + onFocus?: Function, + onCancel?: Function, + onBlur?: Function, onSubmit: Function, isValid: Function, - validationErrorMessage: string, - successfullyUpdated: boolean, - inputBlocked?: boolean, + valueErrorMessage?: string, + errorMessage?: ?string, + disabled?: boolean, + readOnly?: boolean, maxLength?: number, + isLoading?: boolean, + validateOnChange?: boolean, + successfullyUpdated?: boolean, }; type State = { isActive: boolean, + hasChanged: boolean, + successfullyUpdated: boolean, }; @observer export default class InlineEditingInput extends Component { - state = { - isActive: false, + static defaultProps = { + validateOnChange: true, + valueErrorMessage: '', }; static contextTypes = { intl: intlShape.isRequired, }; + state = { + isActive: false, + hasChanged: false, + successfullyUpdated: false, + }; + validator = new ReactToolboxMobxForm( { fields: { inputField: { - value: this.props.inputFieldValue, + value: this.props.value, validators: [ ({ field }) => [ - this.props.isValid(field.value), - this.props.validationErrorMessage, + this.props.isValid(field.value) && this.state.isActive, + this.props.valueErrorMessage || null, ], ], }, @@ -76,7 +97,7 @@ export default class InlineEditingInput extends Component { { plugins: { vjf: vjf() }, options: { - validateOnChange: true, + validateOnChange: this.props.validateOnChange, validationDebounceWait: FORM_VALIDATION_DEBOUNCE_WAIT, }, } @@ -84,120 +105,247 @@ export default class InlineEditingInput extends Component { submit = () => { this.validator.submit({ - onSuccess: (form) => { + onSuccess: async (form) => { + this.setInputBlur(); const { inputField } = form.values(); - if (inputField !== this.props.inputFieldValue) { - this.props.onSubmit(inputField); - this.props.onStopEditing(); - } else { - this.props.onCancelEditing(); + const { onSubmit, errorMessage } = this.props; + if (!!inputField && (inputField !== this.props.value || errorMessage)) { + this.setState({ + hasChanged: true, + successfullyUpdated: false, + }); + await onSubmit(inputField); + this.setState({ + hasChanged: false, + }); } - this.setState({ isActive: false }); }, }); }; handleInputKeyDown = (event: KeyboardEvent) => { - if (event.which === 13) { - // ENTER key - this.onBlur(); - } - if (event.which === 27) { - // ESCAPE key + if (event.which === ENTER_KEY_CODE) { + this.submit(); + } else if (event.which === ESCAPE_KEY_CODE) { this.onCancel(); } }; onFocus = () => { - this.setState({ isActive: true }); - this.props.onStartEditing(); + const { disabled, onFocus, readOnly } = this.props; + if (!disabled && !readOnly) { + this.setState({ + isActive: true, + }); + if (onFocus) onFocus(); + } }; - onBlur = () => { - if (this.state.isActive) { - this.submit(); + onBlur = (event: InputEvent) => { + event.stopPropagation(); + event.preventDefault(); + const { disabled, readOnly, onBlur } = this.props; + this.setState({ + isActive: false, + }); + if (!disabled && !readOnly && onBlur) { + onBlur(); } }; onCancel = () => { + const { value, onCancel, errorMessage } = this.props; + const inputField = this.validator.$('inputField'); + const newValue = !errorMessage ? value : ''; + inputField.set(newValue); + if (onCancel) onCancel(); + this.setInputFocus(); + this.setState({ + hasChanged: true, + }); + }; + + setInputFocus = () => { + const input = this.inputElement; + if (input instanceof HTMLElement) input.focus(); + }; + + setInputBlur = () => { + const input = this.inputElement; + if (input instanceof HTMLElement) input.blur(); + }; + + onChange = (...props: KeyboardEvent) => { + this.setState({ + hasChanged: true, + }); const inputField = this.validator.$('inputField'); - inputField.value = this.props.inputFieldValue; - this.setState({ isActive: false }); - this.props.onCancelEditing(); + inputField.onChange(...props); }; - componentDidUpdate() { - if (this.props.isActive) { - const { inputBlocked } = this.props; - // eslint-disable-next-line no-unused-expressions - this.inputField && !inputBlocked && this.inputField.focus(); + componentDidUpdate({ value: prevValue, errorMessage: prevError }: Props) { + const { value: nextValue, errorMessage: nextError } = this.props; + const inputField = this.validator.$('inputField'); + + // If there's an error, we focus the input again + if (nextError) { + this.setInputFocus(); + } else if (prevError && !nextError) { + // else we blur it + this.setInputBlur(); + } + + // In case the `value` prop was updated + // we need to manually update the ReactToolboxMobxForm input field + if (prevValue !== nextValue) { + inputField.set(nextValue); + if (nextValue === '') { + this.setState({ + hasChanged: false, + }); + } + } + + // If the `value` props was updated + // after a submit action + // we show the `success` message + const successfullyUpdated = !!nextValue && prevValue !== nextValue; + if (successfullyUpdated) { + this.setState({ + successfullyUpdated, + }); } } - inputField: Input; + inputElement: HTMLElement; + + preventDefaultHelper = (event: KeyboardEvent) => { + event.preventDefault(); + event.stopPropagation(); + }; render() { const { validator } = this; const { className, - inputFieldLabel, - isActive, - inputBlocked, + label, maxLength, + placeholder, + disabled, + readOnly, + isLoading, + errorMessage, } = this.props; + const { isActive, hasChanged } = this.state; let { successfullyUpdated } = this.props; + if (successfullyUpdated === undefined) { + ({ successfullyUpdated } = this.state); + } const { intl } = this.context; const inputField = validator.$('inputField'); + let error; + if (inputField.error) error = inputField.error; + else if (!hasChanged) error = !!errorMessage; + + const showEditButton = + !isActive && !isLoading && !hasChanged && label.length && !readOnly; + const showFocusButtons = + !isLoading && !disabled && !readOnly && (isActive || hasChanged); + const showLoadingButton = isLoading; + const componentStyles = classnames([ className, styles.component, isActive ? null : styles.inactive, + readOnly ? styles.readOnly : null, + isLoading ? styles.isLoading : null, + showEditButton || showLoadingButton ? styles.twoButtons : null, + showFocusButtons ? styles.twoButtons : null, ]); const inputStyles = classnames([ successfullyUpdated ? 'input_animateSuccess' : null, isActive ? null : 'input_cursorPointer', ]); - - if (isActive) successfullyUpdated = false; + const buttonsWrapperStyles = classnames([ + styles.buttonsWrapper, + readOnly ? styles.readOnly : null, + ]); + const editButtonStyles = classnames([styles.button, styles.editButton]); + const cancelButtonStyles = classnames([styles.button, styles.cancelButton]); + const okButtonStyles = classnames([styles.button, styles.okButton]); + const submittingButtonStyles = classnames([ + styles.button, + styles.submittingButton, + ]); return ( -
+
this.handleInputKeyDown(event)} - error={isActive || inputBlocked ? inputField.error : null} - disabled={!isActive} + error={isActive ? error : !!error} + disabled={disabled} + readOnly={readOnly} ref={(input) => { - this.inputField = input; + if (!this.inputElement) { + this.inputElement = get(input, 'inputElement.current'); + } }} - skin={InputSkin} /> - {isActive && ( - - )} +
+ {showEditButton && ( +
{successfullyUpdated && (
{intl.formatMessage(messages.changesSaved)}
)} + + {errorMessage && !hasChanged && ( +
{errorMessage}
+ )}
); } diff --git a/source/renderer/app/components/widgets/forms/InlineEditingInput.scss b/source/renderer/app/components/widgets/forms/InlineEditingInput.scss index 2342b15b08..bfc00d3f04 100644 --- a/source/renderer/app/components/widgets/forms/InlineEditingInput.scss +++ b/source/renderer/app/components/widgets/forms/InlineEditingInput.scss @@ -1,3 +1,5 @@ +@import '../../../themes/mixins/error-message'; + .component { margin-bottom: 20px; position: relative; @@ -8,6 +10,12 @@ color: var(--theme-input-text-color); } + &:hover { + .editButton { + opacity: 1; + } + } + &.inactive { &:hover { input { @@ -16,20 +24,127 @@ } } + &.readOnly { + input:read-only { + background: var(--rp-input-bg-color-disabled); + border: none; + } + &:hover { + input { + cursor: text; + } + } + } + + &.oneButton { + :global { + input { + padding-right: 49px; + } + } + } + + &.twoButtons { + :global { + input { + padding-right: 88px; + } + } + } + + .buttonsWrapper { + cursor: text; + height: 28px; + margin-right: 11px; + position: absolute; + right: 1px; + top: 42px; + } + .button { - bottom: 14px; - color: var(--theme-label-button-color); + background-color: var(--theme-button-flat-background-color); + border-radius: 3px; cursor: pointer; - font-family: var(--font-light); - font-size: 16px; - line-height: 1.38; - opacity: 0.5; - position: absolute; - right: 22px; - text-transform: lowercase; + height: 28px; + width: 28px; + &:hover { + background-color: var(--theme-button-flat-background-color-hover); + } + &:active { + background-color: var(--theme-button-flat-background-color-active); + } + .icon { + svg { + height: 12px; + position: relative; + top: 1px; + width: 12px; + } + } + } + .editButton { + opacity: 0; + transition: opacity 0.25s; &:hover { opacity: 1; } + .icon { + svg path { + stroke: var(--theme-button-flat-text-color); + } + } + } + .cancelButton { + .icon { + svg { + height: 10px; + width: 10px; + g { + fill: var(--theme-button-flat-text-color); + } + } + } + } + .okButton { + background-color: var(--theme-button-primary-background-color); + margin-left: 11px; + &:hover { + background-color: var(--theme-button-primary-background-color-hover); + } + &:active { + background-color: var(--theme-button-primary-background-color-active); + } + .icon { + svg path { + stroke: var(--theme-button-primary-text-color); + } + } + } + .submittingButton { + &:hover { + background-color: var(--theme-button-flat-background-color); + cursor: default; + } + &:active { + background-color: var(--theme-button-flat-background-color); + } + .icon { + animation: spinner 1.5s linear; + animation-iteration-count: infinite; + display: inline-block; + svg g { + stroke: var(--theme-button-flat-text-color); + } + } + } + + @keyframes spinner { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } @keyframes animateSavingResultLabel { @@ -55,4 +170,16 @@ text-align: right; top: 5px; } + + :global { + .SimpleFormField_label { + display: inline-block; + } + } +} + +.errorMessage { + @include error-message; + margin-top: 20px; + text-align: center; } diff --git a/source/renderer/app/config/numbersConfig.js b/source/renderer/app/config/numbersConfig.js index 58113d95f1..f83b0fae34 100644 --- a/source/renderer/app/config/numbersConfig.js +++ b/source/renderer/app/config/numbersConfig.js @@ -9,3 +9,7 @@ export const LOVELACES_PER_ADA = 1000000; export const MAX_INTEGER_PLACES_IN_ADA = 11; export const DECIMAL_PLACES_IN_ADA = 6; export const TX_AGE_POLLING_THRESHOLD = 15 * 60 * 1000; // 15 minutes | unit: milliseconds + +// Keyboard events +export const ENTER_KEY_CODE = 13; +export const ESCAPE_KEY_CODE = 27; diff --git a/source/renderer/app/config/stakingConfig.js b/source/renderer/app/config/stakingConfig.js index 6c232bd424..8eb4636455 100644 --- a/source/renderer/app/config/stakingConfig.js +++ b/source/renderer/app/config/stakingConfig.js @@ -1,9 +1,58 @@ // @flow import type { RedeemItnRewardsStep, + SmashServerType, DelegationAction, } from '../types/stakingTypes'; +import type { SmashServerStatuses } from '../api/staking/types'; + +const { smashUrl } = global; + +export const SMASH_SERVERS_LIST: { + [key: SmashServerType]: { + name: string, + url: string, + }, +} = { + iohk: { + name: 'IOHK', + url: smashUrl, + }, + // Metadata is fetched directly in URLs registered on chain, + direct: { + name: 'direct', + url: 'direct', + }, +}; + +export const SMASH_SERVER_TYPES: { + [key: string]: SmashServerType, +} = { + IOHK: 'iohk', + CUSTOM: 'custom', + DIRECT: 'direct', +}; + +export const SMASH_SERVER_INVALID_TYPES: { + [key: string]: SmashServerType, +} = { + NONE: 'none', +}; + +export const SMASH_SERVER_STATUSES: { + [key: string]: SmashServerStatuses, +} = { + AVAILABLE: 'available', + UNAVAILABLE: 'unavailable', + UNREACHABLE: 'unreachable', + NO_SMASH_CONFIGURED: 'no_smash_configured', +}; + +export const SMASH_URL_VALIDATOR = new RegExp( + '^(direct|https?://[a-zA-Z0-9-_~.]+(:[0-9]+)?/?)$' +); + export const RANKING_SLIDER_RATIO = 60; export const MIN_DELEGATION_FUNDS = 10; export const MIN_DELEGATION_FUNDS_LOG = Math.log(MIN_DELEGATION_FUNDS); @@ -36,10 +85,12 @@ export const RECENT_STAKE_POOLS_COUNT = 6; // Timers -export const STAKE_POOL_TRANSACTION_CHECK_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds; -export const STAKE_POOL_TRANSACTION_CHECKER_TIMEOUT = 30 * 1000; // 30 seconds | unit: milliseconds; -export const STAKE_POOLS_INTERVAL = 1 * 60 * 1000; // 1 minute | unit: milliseconds; -export const STAKE_POOLS_FAST_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds; +export const STAKE_POOL_TRANSACTION_CHECK_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds +export const STAKE_POOL_TRANSACTION_CHECKER_TIMEOUT = 30 * 1000; // 30 seconds | unit: milliseconds +export const STAKE_POOLS_INTERVAL = 1 * 60 * 1000; // 1 minute | unit: milliseconds +export const STAKE_POOLS_FAST_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds +export const STAKE_POOLS_FETCH_TRACKER_INTERVAL = 30 * 1000; // 30 seconds | unit: milliseconds +export const STAKE_POOLS_FETCH_TRACKER_CYCLES = 6; // Redeem ITN Rewards @@ -66,4 +117,4 @@ export const IS_RANKING_DATA_AVAILABLE = true; export const IS_SATURATION_DATA_AVAILABLE = true; -export const EPOCH_COUNTDOWN_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds; +export const EPOCH_COUNTDOWN_INTERVAL = 1 * 1000; // 1 second | unit: milliseconds diff --git a/source/renderer/app/containers/settings/Settings.js b/source/renderer/app/containers/settings/Settings.js index 20a190eabd..9b143384d0 100644 --- a/source/renderer/app/containers/settings/Settings.js +++ b/source/renderer/app/containers/settings/Settings.js @@ -27,11 +27,16 @@ export default class Settings extends Component { render() { const { isFlight } = global; - const { actions, children, stores } = this.props; - const { location } = stores.router; + const { actions, stores, children } = this.props; + const { networkStatus, app, router } = stores; + const { isSynced } = networkStatus; + const { currentRoute } = app; + const { location } = router; const menu = ( actions.router.goToRoute.trigger({ route })} isActiveItem={this.isActivePage} /> diff --git a/source/renderer/app/containers/settings/categories/StakePoolsSettingsPage.js b/source/renderer/app/containers/settings/categories/StakePoolsSettingsPage.js new file mode 100644 index 0000000000..41d24f4665 --- /dev/null +++ b/source/renderer/app/containers/settings/categories/StakePoolsSettingsPage.js @@ -0,0 +1,38 @@ +// @flow +import React, { Component } from 'react'; +import { inject, observer } from 'mobx-react'; +import StakePoolsSettings from '../../../components/settings/categories/StakePoolsSettings'; +import type { InjectedProps } from '../../../types/injectedPropsType'; + +@inject('stores', 'actions') +@observer +export default class StakePoolsSettingsPage extends Component { + static defaultProps = { actions: null, stores: null }; + + handleSelectSmashServerUrl = (smashServerUrl: string) => { + this.props.actions.staking.selectSmashServerUrl.trigger({ smashServerUrl }); + }; + + render() { + const { stores, actions } = this.props; + const { + smashServerUrl, + smashServerUrlError, + smashServerLoading, + } = stores.staking; + const { openExternalLink } = stores.app; + const { resetSmashServerError } = actions.staking; + // If `smashServerUrl` is null, waits for it to be set + if (!smashServerUrl) return false; + return ( + + ); + } +} diff --git a/source/renderer/app/containers/staking/StakePoolsListPage.js b/source/renderer/app/containers/staking/StakePoolsListPage.js index 8cc1015fb3..895ab08d4d 100644 --- a/source/renderer/app/containers/staking/StakePoolsListPage.js +++ b/source/renderer/app/containers/staking/StakePoolsListPage.js @@ -5,6 +5,7 @@ import StakePools from '../../components/staking/stake-pools/StakePools'; import StakePoolsRankingLoader from '../../components/staking/stake-pools/StakePoolsRankingLoader'; import DelegationSetupWizardDialogContainer from './dialogs/DelegationSetupWizardDialogContainer'; import DelegationSetupWizardDialog from '../../components/staking/delegation-setup-wizard/DelegationSetupWizardDialog'; +import { ROUTES } from '../../routes-config'; import type { InjectedProps } from '../../types/injectedPropsType'; type Props = InjectedProps; @@ -38,6 +39,12 @@ export default class StakePoolsListPage extends Component { stakingActions.rankStakePools.trigger(); }; + handleSmashSettingsClick = () => { + this.props.actions.router.goToRoute.trigger({ + route: ROUTES.SETTINGS.STAKE_POOLS, + }); + }; + render() { const { uiDialogs, @@ -57,11 +64,12 @@ export default class StakePoolsListPage extends Component { fetchingStakePoolsFailed, recentStakePools, getStakePoolById, + smashServerUrl, maxDelegationFunds, + isFetchingStakePools, } = staking; const { all } = wallets; - const isLoading = - !isSynced || fetchingStakePoolsFailed || stakePools.length === 0; + const isLoading = !isSynced || fetchingStakePoolsFailed; const isRanking = !isLoading && staking.isRanking && stakePoolsRequest.isExecuting; @@ -80,8 +88,11 @@ export default class StakePoolsListPage extends Component { stake={stake} onDelegate={this.handleDelegate} isLoading={isLoading} + isFetching={isFetchingStakePools} isRanking={isRanking} getStakePoolById={getStakePoolById} + smashServerUrl={smashServerUrl} + onSmashSettingsClick={this.handleSmashSettingsClick} maxDelegationFunds={maxDelegationFunds} /> {isRanking && } diff --git a/source/renderer/app/containers/wallet/WalletSettingsPage.js b/source/renderer/app/containers/wallet/WalletSettingsPage.js index d952e241b4..6690ee6e68 100644 --- a/source/renderer/app/containers/wallet/WalletSettingsPage.js +++ b/source/renderer/app/containers/wallet/WalletSettingsPage.js @@ -124,7 +124,7 @@ export default class WalletSettingsPage extends Component { } onStartEditing={(field) => startEditingWalletField.trigger({ field })} onStopEditing={stopEditingWalletField.trigger} - onCancelEditing={cancelEditingWalletField.trigger} + onCancel={cancelEditingWalletField.trigger} onVerifyRecoveryPhrase={recoveryPhraseVerificationContinue.trigger} onCopyWalletPublicKey={this.handleCopyWalletPublicKey} getWalletPublicKey={this.handleGetWalletPublicKey} diff --git a/source/renderer/app/domains/ApiError.js b/source/renderer/app/domains/ApiError.js index 49972c74e5..162a4589f5 100644 --- a/source/renderer/app/domains/ApiError.js +++ b/source/renderer/app/domains/ApiError.js @@ -45,7 +45,8 @@ type KnownErrorType = | 'not_implemented' | 'wallet_not_responding' | 'address_already_exists' - | 'utxo_too_small'; + | 'utxo_too_small' + | 'invalid_smash_server'; type LoggingType = { msg?: string, diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index e2cc12dffa..8ee73f72a8 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -271,6 +271,20 @@ "column": 20, "line": 102 } + }, + { + "defaultMessage": "!!!This URL is not a valid SMASH server", + "description": "\"This URL is not a valid SMASH server\" error message", + "end": { + "column": 3, + "line": 113 + }, + "file": "source/renderer/app/api/errors.js", + "id": "api.errors.invalidSmashServer", + "start": { + "column": 22, + "line": 109 + } } ], "path": "source/renderer/app/api/errors.json" @@ -2161,6 +2175,235 @@ ], "path": "source/renderer/app/components/settings/categories/DisplaySettings.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!The {link} is an off-chain metadata server that enables the fast loading of stake pool details. Stake pools are also curated and each server has a different curation policy.", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 34 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.description", + "start": { + "column": 15, + "line": 29 + } + }, + { + "defaultMessage": "!!!Stakepool Metadata Aggregation Server (SMASH)", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 39 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionLinkLabel", + "start": { + "column": 24, + "line": 35 + } + }, + { + "defaultMessage": "!!!https://iohk.io/en/blog/posts/2020/11/17/in-pools-we-trust/", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 45 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionLinkUrl", + "start": { + "column": 22, + "line": 40 + } + }, + { + "defaultMessage": "!!!The IOHK server ensures that registered stake pools are valid, helps to avoid duplicated ticker names or trademarks, and checks that the pools do not feature potentially offensive or harmful information.", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 51 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionIOHKContent1", + "start": { + "column": 27, + "line": 46 + } + }, + { + "defaultMessage": "!!!This allows us to deal with any scams, trolls, or abusive behavior by filtering out potentially problematic actors. {link} about the IOHK SMASH server.", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 57 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionIOHKContent2", + "start": { + "column": 27, + "line": 52 + } + }, + { + "defaultMessage": "!!!Read more", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 62 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionIOHKLinkLabel", + "start": { + "column": 28, + "line": 58 + } + }, + { + "defaultMessage": "!!!https://iohk.io/en/blog/posts/2020/11/17/in-pools-we-trust/", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 68 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionIOHKLinkUrl", + "start": { + "column": 26, + "line": 63 + } + }, + { + "defaultMessage": "!!!This option is not recommended! Without the off-chain metadata server your Daedalus client will fetch this data by contacting every stake pool individually, which is a very slow and resource-consuming process. The list of stake pools received is not curated, so Daedalus will receive legitimate pools, duplicates, and fake pools. An added risk to this process is that your antivirus or antimalware software could recognize the thousands of network requests as malicious behavior by the Daedalus client.", + "description": "description for the Stake Pools settings page.", + "end": { + "column": 3, + "line": 74 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.descriptionNone", + "start": { + "column": 19, + "line": 69 + } + }, + { + "defaultMessage": "!!!Off-chain metadata server (SMASH)", + "description": "smashSelectLabel for the \"Smash\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 80 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.select.label", + "start": { + "column": 20, + "line": 75 + } + }, + { + "defaultMessage": "!!!IOHK (Recommended)", + "description": "smashSelectCustomServer option for the \"Smash\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 86 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.select.IOHKServer", + "start": { + "column": 25, + "line": 81 + } + }, + { + "defaultMessage": "!!!None - let my Daedalus client fetch the data", + "description": "smashSelectCustomServer option for the \"Smash\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 92 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.select.direct", + "start": { + "column": 21, + "line": 87 + } + }, + { + "defaultMessage": "!!!Custom server", + "description": "smashSelectCustomServer option for the \"Smash\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 98 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smash.select.customServer", + "start": { + "column": 27, + "line": 93 + } + }, + { + "defaultMessage": "!!!SMASH server URL", + "description": "smashURLInputLabel for the \"Smash Custom Server\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 104 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smashUrl.input.label", + "start": { + "column": 22, + "line": 99 + } + }, + { + "defaultMessage": "!!!Enter custom server URL", + "description": "smashUrlInputPlaceholder for the \"Smash Custom Server\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 110 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smashUrl.input.placeholder", + "start": { + "column": 28, + "line": 105 + } + }, + { + "defaultMessage": "!!!Invalid URL", + "description": "smashUrlInputInvalidUrl for the \"Smash Custom Server\" selection on the Stake Pools settings page.", + "end": { + "column": 3, + "line": 116 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "settings.stakePools.smashUrl.input.invalidUrl", + "start": { + "column": 27, + "line": 111 + } + }, + { + "defaultMessage": "!!!Your changes have been saved", + "description": "Message \"Your changes have been saved\" for inline editing (eg. on Profile Settings page).", + "end": { + "column": 3, + "line": 122 + }, + "file": "source/renderer/app/components/settings/categories/StakePoolsSettings.js", + "id": "inline.editing.input.changesSaved", + "start": { + "column": 16, + "line": 117 + } + } + ], + "path": "source/renderer/app/components/settings/categories/StakePoolsSettings.json" + }, { "descriptors": [ { @@ -2323,17 +2566,31 @@ } }, { - "defaultMessage": "!!!Support", + "defaultMessage": "!!!Stake Pools", "description": "Label for the \"Support\" link in the settings menu.", "end": { "column": 3, "line": 19 }, "file": "source/renderer/app/components/settings/menu/SettingsMenu.js", + "id": "settings.menu.stakePools.link.label", + "start": { + "column": 14, + "line": 15 + } + }, + { + "defaultMessage": "!!!Support", + "description": "Label for the \"Support\" link in the settings menu.", + "end": { + "column": 3, + "line": 24 + }, + "file": "source/renderer/app/components/settings/menu/SettingsMenu.js", "id": "settings.menu.support.link.label", "start": { "column": 11, - "line": 15 + "line": 20 } }, { @@ -2341,13 +2598,13 @@ "description": "Label for the \"Terms of service\" link in the settings menu.", "end": { "column": 3, - "line": 24 + "line": 29 }, "file": "source/renderer/app/components/settings/menu/SettingsMenu.js", "id": "settings.menu.termsOfUse.link.label", "start": { "column": 14, - "line": 20 + "line": 25 } }, { @@ -2355,13 +2612,13 @@ "description": "Label for the \"Themes\" link in the settings menu.", "end": { "column": 3, - "line": 29 + "line": 34 }, "file": "source/renderer/app/components/settings/menu/SettingsMenu.js", "id": "settings.menu.display.link.label", "start": { "column": 11, - "line": 25 + "line": 30 } } ], @@ -5376,41 +5633,69 @@ "description": "\"delegatingListTitle\" for the Stake Pools page.", "end": { "column": 3, - "line": 23 + "line": 30 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.delegatingListTitle", "start": { "column": 23, - "line": 19 + "line": 26 } }, { - "defaultMessage": "!!!Stake pools ({pools})", + "defaultMessage": "!!!Stake pools", "description": "\"listTitle\" for the Stake Pools page.", "end": { "column": 3, - "line": 28 + "line": 35 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.listTitle", "start": { "column": 13, - "line": 24 + "line": 31 } }, { - "defaultMessage": "!!!Stake pools. Search results: ({pools})", - "description": "\"listTitle\" for the Stake Pools page.", + "defaultMessage": "!!!Loading stake pools", + "description": "\"listTitleLoading\" for the Stake Pools page.", "end": { "column": 3, - "line": 33 + "line": 40 + }, + "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", + "id": "staking.stakePools.listTitleLoading", + "start": { + "column": 20, + "line": 36 + } + }, + { + "defaultMessage": "!!!Stake pools. Search results:", + "description": "\"listTitleSearch\" for the Stake Pools page.", + "end": { + "column": 3, + "line": 45 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", - "id": "staking.stakePools.listTitleWithSearch", + "id": "staking.stakePools.listTitleSearch", + "start": { + "column": 19, + "line": 41 + } + }, + { + "defaultMessage": "!!!({pools})", + "description": "\"listTitleStakePools\" for the Stake Pools page.", + "end": { + "column": 3, + "line": 50 + }, + "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", + "id": "staking.stakePools.listTitleStakePools", "start": { "column": 23, - "line": 29 + "line": 46 } }, { @@ -5418,13 +5703,41 @@ "description": "Loading stake pool message for the Delegation center body section.", "end": { "column": 3, - "line": 39 + "line": 56 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.loadingStakePoolsMessage", "start": { "column": 28, - "line": 34 + "line": 51 + } + }, + { + "defaultMessage": "!!!Moderated by", + "description": "moderatedBy message for the Delegation center body section.", + "end": { + "column": 3, + "line": 61 + }, + "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", + "id": "staking.stakePools.moderatedBy", + "start": { + "column": 15, + "line": 57 + } + }, + { + "defaultMessage": "!!!Unmoderated", + "description": "unmoderated message for the Delegation center body section.", + "end": { + "column": 3, + "line": 66 + }, + "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", + "id": "staking.stakePools.unmoderated", + "start": { + "column": 15, + "line": 62 } } ], @@ -13355,13 +13668,13 @@ "description": "Label \"change\" on inline editing inputs in inactive state.", "end": { "column": 3, - "line": 18 + "line": 26 }, "file": "source/renderer/app/components/widgets/forms/InlineEditingInput.js", "id": "inline.editing.input.change.label", "start": { "column": 10, - "line": 14 + "line": 22 } }, { @@ -13369,13 +13682,13 @@ "description": "Label \"cancel\" on inline editing inputs in inactive state.", "end": { "column": 3, - "line": 23 + "line": 31 }, "file": "source/renderer/app/components/widgets/forms/InlineEditingInput.js", "id": "inline.editing.input.cancel.label", "start": { "column": 10, - "line": 19 + "line": 27 } }, { @@ -13383,13 +13696,13 @@ "description": "Message \"Your changes have been saved\" for inline editing (eg. on Profile Settings page).", "end": { "column": 3, - "line": 29 + "line": 37 }, "file": "source/renderer/app/components/widgets/forms/InlineEditingInput.js", "id": "inline.editing.input.changesSaved", "start": { "column": 16, - "line": 24 + "line": 32 } } ], diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index 7d5ffdc666..c2c4c16278 100755 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -15,6 +15,7 @@ "api.errors.WalletFileImportError": "Wallet could not be imported, please make sure you are providing a correct file.", "api.errors.inputsDepleted": "Cannot send from a wallet that contains only rewards balances.", "api.errors.invalidAddress": "Please enter a valid address.", + "api.errors.invalidSmashServer": "This URL is not a valid SMASH server", "api.errors.nothingToMigrate": "Funds cannot be transferred from this wallet because it contains some unspent transaction outputs (UTXOs), with amounts of ada that are too small to be migrated.", "api.errors.utxoTooSmall": "Invalid transaction.", "appUpdate.overlay.button.installUpdate.label": "Install the update and restart Daedalus", @@ -275,8 +276,24 @@ "settings.display.themeNames.yellow": "Yellow", "settings.menu.display.link.label": "Themes", "settings.menu.general.link.label": "General", + "settings.menu.stakePools.link.label": "Stake pools", "settings.menu.support.link.label": "Support", "settings.menu.termsOfUse.link.label": "Terms of service", + "settings.stakePools.smash.description": "The {link} is an off-chain metadata server that enables the fast loading of stake pool details. Stake pools are also curated and each server has a different curation policy.", + "settings.stakePools.smash.descriptionIOHKContent1": "The IOHK server ensures that registered stake pools are valid, helps to avoid duplicated ticker names or trademarks, and checks that the pools do not feature potentially offensive or harmful information.", + "settings.stakePools.smash.descriptionIOHKContent2": "This allows us to deal with any scams, trolls, or abusive behavior by filtering out potentially problematic actors. {link} about the IOHK SMASH server.", + "settings.stakePools.smash.descriptionIOHKLinkLabel": "Read more", + "settings.stakePools.smash.descriptionIOHKLinkUrl": "https://iohk.io/en/blog/posts/2020/11/17/in-pools-we-trust/", + "settings.stakePools.smash.descriptionLinkLabel": "Stakepool Metadata Aggregation Server (SMASH)", + "settings.stakePools.smash.descriptionLinkUrl": "https://docs.cardano.org/en/latest/getting-started/stake-pool-operators/SMASH-metadata-management.html", + "settings.stakePools.smash.descriptionNone": "This option is not recommended! Without the off-chain metadata server your Daedalus client will fetch this data by contacting every stake pool individually, which is a very slow and resource-consuming process. The list of stake pools received is not curated, so Daedalus will receive legitimate pools, duplicates, and fake pools. An added risk to this process is that your antivirus or antimalware software could recognize the thousands of network requests as malicious behavior by the Daedalus client.", + "settings.stakePools.smash.select.IOHKServer": "IOHK (recommended)", + "settings.stakePools.smash.select.customServer": "Custom server", + "settings.stakePools.smash.select.direct": "None - fetch the data directly", + "settings.stakePools.smash.select.label": "Off-chain metadata server (SMASH)", + "settings.stakePools.smashUrl.input.invalidUrl": "Invalid URL", + "settings.stakePools.smashUrl.input.label": "SMASH server URL", + "settings.stakePools.smashUrl.input.placeholder": "Enter custom server URL", "settings.support.faq.content": "If you are experiencing a problem, please look for guidance using the list of {faqLink} on the support pages. If you can’t find a solution, please submit a support ticket.", "settings.support.faq.faqLink": "Known Issues", "settings.support.faq.faqLinkURL": "https://daedaluswallet.io/known-issues/", @@ -457,9 +474,12 @@ "staking.rewards.title": "Earned delegation rewards", "staking.stakePools.delegatingListTitle": "Staking pools you are delegating to", "staking.stakePools.learnMore": "Learn more", - "staking.stakePools.listTitle": "Stake pools ({pools})", - "staking.stakePools.listTitleWithSearch": "Stake pools. Search results: ({pools})", + "staking.stakePools.listTitle": "Stake pools", + "staking.stakePools.listTitleLoading": "Loading stake pools", + "staking.stakePools.listTitleSearch": ". Search results:", + "staking.stakePools.listTitleStakePools": " ({pools})", "staking.stakePools.loadingStakePoolsMessage": "Loading stake pools", + "staking.stakePools.moderatedBy": "Moderated by {smashServer}", "staking.stakePools.noDataDashTooltip": "Data not available yet", "staking.stakePools.rankingAllWallets": "all your wallets", "staking.stakePools.rankingAllWalletsEnd": ".", @@ -509,6 +529,7 @@ "staking.stakePools.tooltip.retirement": "Retirement in {retirementFromNow}", "staking.stakePools.tooltip.saturation": "Saturation:", "staking.stakePools.tooltip.saturationTooltip": "Saturation measures the stake in the pool and indicates the point at which rewards stop increasing with increases in stake. This capping mechanism encourages decentralization by discouraging users from delegating to oversaturated stake pools.", + "staking.stakePools.unmoderated": "Unmoderated", "static.about.buildInfo": "{platform} build {build}, {apiName} Node {nodeVersion}, {apiName} Wallet {apiVersion}", "static.about.buildInfoForITN": "{platform} build {build}, with {apiName} {apiVersion}", "static.about.content.cardano.headline": "Cardano Team:", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index e7659088aa..2fc93650ea 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -15,6 +15,7 @@ "api.errors.WalletFileImportError": "ウォレットをインポートできませんでした。有効なファイルを指定していることを確認してください。", "api.errors.inputsDepleted": "報酬残高のみを含むウォレットからの送信はできません。", "api.errors.invalidAddress": "有効なアドレスを入力してください。", + "api.errors.invalidSmashServer": "このURLは有効なSMASHサーバーではありません", "api.errors.nothingToMigrate": "このウォレットに保有されている未使用トランザクションアウトプット(UTXO)の一部に、移行するために十分なADAが入っていないため、このウォレットから資金を移すことはできません。", "api.errors.utxoTooSmall": "無効なトランザクションです。", "appUpdate.overlay.button.installUpdate.label": "更新をインストールしてDaedalusを再起動する", @@ -275,8 +276,24 @@ "settings.display.themeNames.yellow": "イエロー", "settings.menu.display.link.label": "テーマ", "settings.menu.general.link.label": "一般", + "settings.menu.stakePools.link.label": "ステークプール", "settings.menu.support.link.label": "ユーザーサポート", "settings.menu.termsOfUse.link.label": "サービス利用規約", + "settings.stakePools.smash.description": "{link}は、ステークプールの詳細を素早くロードすることを可能にする、オフチェーンのメタデータサーバーです。ステークプールは、サーバーごとに異なるポリシーに基づいて調整されます。", + "settings.stakePools.smash.descriptionIOHKContent1": "IOHKサーバーは、登録されたステークプールが有効であることを確認し、ティッカー名や商標の重複を回避し、プールが攻撃的または害となる可能性のある情報を有していないかチェックします。", + "settings.stakePools.smash.descriptionIOHKContent2": "これで問題を起こす可能性のあるアクターをフィルタリングすることにより、詐欺や荒らし、虐待行為に対処することができます。IOHKのSMASHサーバーの詳細は{link}。", + "settings.stakePools.smash.descriptionIOHKLinkLabel": "こちらをご覧ください", + "settings.stakePools.smash.descriptionIOHKLinkUrl": "https://forum.cardano.org/t/iohk-smash/42230", + "settings.stakePools.smash.descriptionLinkLabel": "ステークプールメタデータアグリゲーションサーバー(SMASH)(英語のみ)", + "settings.stakePools.smash.descriptionLinkUrl": "https://docs.cardano.org/en/latest/getting-started/stake-pool-operators/SMASH-metadata-management.html", + "settings.stakePools.smash.descriptionNone": "このオプションは推奨されません。オフチェーンメタデータサーバーを使用しない場合、Daedalusクライアントはすべてのステークプールと個別に接触してこのデータをフェッチすることになります。このプロセスは大量の時間とリソースを消費します。受信したステークプールのリストは調整されていないため、Daedalusは合法的なプールも、重複やフェイクのプールもすべて取得します。このプロセスの追加的リスクとして、ウィルス対策ソフトウェアが何千ものネットワークリクエストを、Daedalusクライアントによる悪意のある行動と見なす可能性があります。", + "settings.stakePools.smash.select.IOHKServer": "IOHK(推奨)", + "settings.stakePools.smash.select.customServer": "カスタムサーバー", + "settings.stakePools.smash.select.direct": "使用しない(直接データをフェッチする)", + "settings.stakePools.smash.select.label": "オフチェーンメタデータサーバー(SMASH)", + "settings.stakePools.smashUrl.input.invalidUrl": "無効なURL", + "settings.stakePools.smashUrl.input.label": "SMASHサーバーURL", + "settings.stakePools.smashUrl.input.placeholder": "カスタムサーバーを入力してください", "settings.support.faq.content": "問題が発生している場合は、サポートページにある{faqLink}リストを参照して解決方法を探してください。解決方法が見つからない場合はサポートリクエストを送信してください。", "settings.support.faq.faqLink": "既知の問題", "settings.support.faq.faqLinkURL": "https://daedaluswallet.io/ja/known-issues/", @@ -457,9 +474,12 @@ "staking.rewards.title": "獲得した委任報酬", "staking.stakePools.delegatingListTitle": "現在委任しているステークプール", "staking.stakePools.learnMore": "もっと知る", - "staking.stakePools.listTitle": "ステークプール ({pools})", - "staking.stakePools.listTitleWithSearch": "ステークプール検索結果:({pools})", + "staking.stakePools.listTitle": "ステークプール", + "staking.stakePools.listTitleLoading": "ステークプールをロード中", + "staking.stakePools.listTitleSearch": "。検索結果:", + "staking.stakePools.listTitleStakePools": "({pools})", "staking.stakePools.loadingStakePoolsMessage": "ステークプールをロードしています", + "staking.stakePools.moderatedBy": "{smashServer}が調整済み", "staking.stakePools.noDataDashTooltip": "まだ利用できるデータがありません", "staking.stakePools.rankingAllWallets": "すべてのウォレット", "staking.stakePools.rankingAllWalletsEnd": "の合計額を基にランク付けされています。", @@ -509,6 +529,7 @@ "staking.stakePools.tooltip.retirement": "あと{retirementFromNow}で終了", "staking.stakePools.tooltip.saturation": "飽和:", "staking.stakePools.tooltip.saturationTooltip": "プールのステーク数を計測し、プール数が増加しても、報酬増加に反映されなくなる時点を示すもので、この上限を定めるメカニズムにより、ユーザーが飽和状態のステークプールに委任することを抑制することで、分散化を促進する", + "staking.stakePools.unmoderated": "未調整", "static.about.buildInfo": "{platform} ビルド {build}, {apiName} Node {nodeVersion}, {apiName} Wallet {apiVersion}", "static.about.buildInfoForITN": "{platform} ビルド {build}, {apiName} {apiVersion}", "static.about.content.cardano.headline": "Cardanoチーム:", diff --git a/source/renderer/app/routes-config.js b/source/renderer/app/routes-config.js index 08447d8bf1..29a0708602 100644 --- a/source/renderer/app/routes-config.js +++ b/source/renderer/app/routes-config.js @@ -33,6 +33,7 @@ export const ROUTES = { SETTINGS: { ROOT: '/settings', GENERAL: '/settings/general', + STAKE_POOLS: '/settings/stake-pools', TERMS_OF_USE: '/settings/terms-of-service', SUPPORT: '/settings/support', DISPLAY: '/settings/display', diff --git a/source/renderer/app/stores/StakingStore.js b/source/renderer/app/stores/StakingStore.js index 6f09d45a1b..1b10a3eef7 100644 --- a/source/renderer/app/stores/StakingStore.js +++ b/source/renderer/app/stores/StakingStore.js @@ -12,8 +12,13 @@ import { STAKE_POOL_TRANSACTION_CHECKER_TIMEOUT, STAKE_POOLS_INTERVAL, STAKE_POOLS_FAST_INTERVAL, + STAKE_POOLS_FETCH_TRACKER_INTERVAL, + STAKE_POOLS_FETCH_TRACKER_CYCLES, REDEEM_ITN_REWARDS_STEPS as steps, INITIAL_DELEGATION_FUNDS, + SMASH_SERVERS_LIST, + SMASH_SERVER_TYPES, + SMASH_SERVER_INVALID_TYPES, CIRCULATING_SUPPLY, } from '../config/stakingConfig'; import type { @@ -22,6 +27,7 @@ import type { JoinStakePoolRequest, GetDelegationFeeRequest, QuitStakePoolRequest, + PoolMetadataSource, } from '../api/staking/types'; import Wallet from '../domains/Wallet'; import StakePool from '../domains/StakePool'; @@ -40,6 +46,9 @@ export default class StakingStore extends Store { @observable selectedDelegationWalletId = null; @observable stake = INITIAL_DELEGATION_FUNDS; @observable isRanking = false; + @observable smashServerUrl: ?string = null; + @observable smashServerUrlError: ?LocalizableError = null; + @observable smashServerLoading: boolean = false; /* ---------- Redeem ITN Rewards ---------- */ @observable redeemStep: ?RedeemItnRewardsStep = null; @@ -54,16 +63,25 @@ export default class StakingStore extends Store { @observable configurationStepError: ?LocalizableError = null; @observable confirmationStepError: ?LocalizableError = null; + /* ---------- Stake Pools Fetching Tracker ---------- */ + @observable isFetchingStakePools: boolean = false; + @observable numberOfStakePoolsFetched: number = 0; + @observable cyclesWithoutIncreasingStakePools: number = 0; + pollingStakePoolsInterval: ?IntervalID = null; refreshPolling: ?IntervalID = null; delegationCheckTimeInterval: ?IntervalID = null; adaValue: BigNumber = new BigNumber(82650.15); percentage: number = 14; + stakePoolsFetchTrackerInterval: ?IntervalID = null; _delegationFeeCalculationWalletId: ?string = null; setup() { - const { staking: stakingActions } = this.actions; + const { + staking: stakingActions, + networkStatus: networkStatusActions, + } = this.actions; this.refreshPolling = setInterval( this.getStakePoolsData, @@ -91,10 +109,13 @@ export default class StakingStore extends Store { stakingActions.fakeStakePoolsLoading.listen(this._setFakePoller); stakingActions.updateDelegatingStake.listen(this._setStake); stakingActions.rankStakePools.listen(this._rankStakePools); + stakingActions.selectSmashServerUrl.listen(this._selectSmashServerUrl); + stakingActions.resetSmashServerError.listen(this._resetSmashServerError); stakingActions.selectDelegationWallet.listen( this._setSelectedDelegationWalletId ); stakingActions.requestCSVFile.listen(this._requestCSVFile); + networkStatusActions.isSyncedAndReady.listen(this._getSmashSettingsRequest); // ========== MOBX REACTIONS =========== // this.registerReactions([this._pollOnSync]); @@ -120,9 +141,39 @@ export default class StakingStore extends Store { @observable requestRedeemItnRewardsRequest: Request = new Request( this.api.ada.requestRedeemItnRewards ); + @observable getSmashSettingsRequest: Request = new Request( + this.api.ada.getSmashSettings + ); + @observable + updateSmashSettingsRequest: Request = new Request( + this.api.ada.updateSmashSettings + ); // =================== PUBLIC API ==================== // + @action _getSmashSettingsRequest = async () => { + this.smashServerLoading = true; + let smashServerUrl: string = await this.getSmashSettingsRequest.execute(); + + const localSmashServer = await this.api.localStorage.getSmashServer(); + + // If the server wasn't set, sets it for IOHK + if ( + !smashServerUrl || + smashServerUrl === SMASH_SERVER_INVALID_TYPES.NONE || + (smashServerUrl === SMASH_SERVER_TYPES.DIRECT && + localSmashServer !== SMASH_SERVER_TYPES.DIRECT) + ) { + smashServerUrl = SMASH_SERVERS_LIST.iohk.url; + await this.updateSmashSettingsRequest.execute(smashServerUrl); + } + + runInAction(() => { + this.smashServerUrl = smashServerUrl; + this.smashServerLoading = false; + }); + }; + @action _setSelectedDelegationWalletId = (walletId: string) => { this.selectedDelegationWalletId = walletId; }; @@ -136,6 +187,77 @@ export default class StakingStore extends Store { this.getStakePoolsData(); }; + @action _selectSmashServerUrl = async ({ + smashServerUrl, + }: { + smashServerUrl: string, + }) => { + if (smashServerUrl && smashServerUrl !== this.smashServerUrl) { + try { + this.smashServerUrlError = null; + // Retrieves the API update + this.smashServerLoading = true; + await this.updateSmashSettingsRequest.execute(smashServerUrl); + // Refreshes the Stake Pools list + this.getStakePoolsData(); + // Starts the SPs fetch tracker + this._startStakePoolsFetchTracker(); + // Updates the Smash Server URL + runInAction(() => { + this.smashServerUrl = smashServerUrl; + this.smashServerUrlError = null; + this.smashServerLoading = false; + }); + // Update + await this.api.localStorage.setSmashServer(smashServerUrl); + } catch (error) { + runInAction(() => { + this.smashServerUrlError = error; + this.smashServerLoading = false; + }); + } + } + }; + + @action _startStakePoolsFetchTracker = () => { + this._stopStakePoolsFetchTracker(); + this.isFetchingStakePools = true; + this.stakePoolsFetchTrackerInterval = setInterval( + this._stakePoolsFetchTracker, + STAKE_POOLS_FETCH_TRACKER_INTERVAL + ); + this.getStakePoolsData(true); + }; + + @action _stakePoolsFetchTracker = () => { + const lastNumberOfStakePoolsFetched = this.numberOfStakePoolsFetched; + this.numberOfStakePoolsFetched = this.stakePools.length; + if ( + lastNumberOfStakePoolsFetched === this.numberOfStakePoolsFetched && + this.numberOfStakePoolsFetched > 0 + ) { + this.cyclesWithoutIncreasingStakePools++; + } + if ( + this.cyclesWithoutIncreasingStakePools >= STAKE_POOLS_FETCH_TRACKER_CYCLES + ) { + this._stopStakePoolsFetchTracker(); + } + }; + + @action _stopStakePoolsFetchTracker = () => { + clearInterval(this.stakePoolsFetchTrackerInterval); + this.numberOfStakePoolsFetched = 0; + this.cyclesWithoutIncreasingStakePools = 0; + this.isFetchingStakePools = false; + this.getStakePoolsData(); + }; + + @action _resetSmashServerError = () => { + this.smashServerUrlError = null; + this.smashServerLoading = false; + }; + @action _joinStakePool = async (request: JoinStakePoolRequest) => { const { walletId, stakePoolId, passphrase, isHardwareWallet } = request; @@ -377,7 +499,7 @@ export default class StakingStore extends Store { return isShelleyPending; } - @action getStakePoolsData = async () => { + @action getStakePoolsData = async (isSmash?: boolean) => { const { isConnected, isSynced, @@ -395,16 +517,16 @@ export default class StakingStore extends Store { 10 ); await this.stakePoolsRequest.execute(stakeInLovelace).promise; - this._resetPolling(false); + this._resetPolling(isSmash ? 'smash' : 'regular'); } catch (error) { - this._resetPolling(true); + this._resetPolling('failed'); } this._resetIsRanking(); }; - @action _resetPolling = (fetchFailed: boolean, kill?: boolean) => { - if (kill) { - this.fetchingStakePoolsFailed = fetchFailed; + @action _resetPolling = (type?: 'regular' | 'failed' | 'kill' | 'smash') => { + if (type === 'kill') { + this.fetchingStakePoolsFailed = true; if (this.pollingStakePoolsInterval) { clearInterval(this.pollingStakePoolsInterval); this.pollingStakePoolsInterval = null; @@ -413,9 +535,7 @@ export default class StakingStore extends Store { clearInterval(this.refreshPolling); this.refreshPolling = null; } - return; - } - if (fetchFailed) { + } else if (type === 'failed') { this.fetchingStakePoolsFailed = true; if (this.pollingStakePoolsInterval) { clearInterval(this.pollingStakePoolsInterval); @@ -433,12 +553,15 @@ export default class StakingStore extends Store { clearInterval(this.refreshPolling); this.refreshPolling = null; } - if (!this.pollingStakePoolsInterval) { - this.pollingStakePoolsInterval = setInterval( - this.getStakePoolsData, - STAKE_POOLS_INTERVAL - ); - } + clearInterval(this.pollingStakePoolsInterval); + const isSmash = type === 'smash'; + const interval = isSmash + ? STAKE_POOLS_FETCH_TRACKER_INTERVAL + : STAKE_POOLS_INTERVAL; + this.pollingStakePoolsInterval = setInterval( + () => this.getStakePoolsData(isSmash), + interval + ); } }; @@ -471,7 +594,7 @@ export default class StakingStore extends Store { // Regular fetching way with faked response that throws error. if ((_pollingBlocked || !isConnected) && !this.refreshPolling) { - this._resetPolling(true); + this._resetPolling('failed'); return; } @@ -479,7 +602,7 @@ export default class StakingStore extends Store { throw new Error('Faked "Stake pools" fetch error'); } catch (error) { if (!this.refreshPolling) { - this._resetPolling(true); + this._resetPolling('failed'); } } } @@ -666,7 +789,7 @@ export default class StakingStore extends Store { this.getStakePoolsData(); } else { this._resetIsRanking(); - this._resetPolling(true, true); + this._resetPolling('kill'); } }; diff --git a/source/renderer/app/types/stakingTypes.js b/source/renderer/app/types/stakingTypes.js index a3a2a8e3b8..48d90e0874 100644 --- a/source/renderer/app/types/stakingTypes.js +++ b/source/renderer/app/types/stakingTypes.js @@ -1,4 +1,5 @@ // @flow export type RedeemItnRewardsStep = 'configuration' | 'confirmation' | 'result'; +export type SmashServerType = 'iohk' | 'custom' | 'direct' | 'none'; export type DelegationAction = 'join' | 'quit'; diff --git a/source/renderer/app/utils/staking.js b/source/renderer/app/utils/staking.js new file mode 100644 index 0000000000..c77d789351 --- /dev/null +++ b/source/renderer/app/utils/staking.js @@ -0,0 +1,29 @@ +// @flow +import { reduce } from 'lodash'; +import { + SMASH_SERVERS_LIST, + SMASH_SERVER_TYPES, +} from '../config/stakingConfig'; +import type { SmashServerType } from '../types/stakingTypes'; + +export const getSmashServerNameFromUrl = (smashServerUrl: string): string => + reduce( + SMASH_SERVERS_LIST, + (result, { name, url }) => { + if (url === smashServerUrl) result = name; + return result; + }, + smashServerUrl + ); + +export const getSmashServerIdFromUrl = ( + smashServerUrl: string +): SmashServerType => + reduce( + SMASH_SERVERS_LIST, + (result, { url }, id) => { + if (url === smashServerUrl) result = id; + return result; + }, + SMASH_SERVER_TYPES.CUSTOM + ); diff --git a/storybook/stories/common/Widgets.stories.js b/storybook/stories/common/Widgets.stories.js index 8d5aa10af8..688bcb0e61 100644 --- a/storybook/stories/common/Widgets.stories.js +++ b/storybook/stories/common/Widgets.stories.js @@ -4,6 +4,7 @@ import { defineMessages, IntlProvider } from 'react-intl'; import { storiesOf } from '@storybook/react'; import { observable, action as mobxAction } from 'mobx'; import { action } from '@storybook/addon-actions'; +import { withKnobs, boolean, number, text } from '@storybook/addon-knobs'; import StoryDecorator from '../_support/StoryDecorator'; import StoryProvider from '../_support/StoryProvider'; import StoryLayout from '../_support/StoryLayout'; @@ -11,6 +12,7 @@ import enMessages from '../../../source/renderer/app/i18n/locales/en-US.json'; import jpMessages from '../../../source/renderer/app/i18n/locales/ja-JP.json'; import BigButtonForDialogs from '../../../source/renderer/app/components/widgets/BigButtonForDialogs'; import MnemonicInputWidget from '../../../source/renderer/app/components/widgets/forms/MnemonicInputWidget'; +import InlineEditingInput from '../../../source/renderer/app/components/widgets/forms/InlineEditingInput'; import createIcon from '../../../source/renderer/app/assets/images/create-ic.inline.svg'; import importIcon from '../../../source/renderer/app/assets/images/import-ic.inline.svg'; import joinSharedIcon from '../../../source/renderer/app/assets/images/join-shared-ic.inline.svg'; @@ -95,8 +97,32 @@ storiesOf('Common|Widgets', module) ); }) + .addDecorator(withKnobs) + // ====== Stories ====== + .add('InlineEditingInput', () => ( +
+
+ value && value.length > 3 && value !== 'error'} + validationErrorMessage={text('validationErrorMessage', 'Error!')} + successfullyUpdated={boolean('successfullyUpdated', true)} + isActive={boolean('isActive', true)} + isSubmitting={boolean('isSubmitting', false)} + inputBlocked={boolean('inputBlocked', false)} + disabled={boolean('disabled', false)} + readOnly={boolean('readOnly', false)} + maxLength={number('maxLength')} + /> +
+
+ )) + .add('BigButtonForDialogs', (props: { locale: string }) => (
diff --git a/storybook/stories/settings/general/General.stories.js b/storybook/stories/settings/general/General.stories.js index 1aa702b7cc..c6d35e8969 100644 --- a/storybook/stories/settings/general/General.stories.js +++ b/storybook/stories/settings/general/General.stories.js @@ -16,6 +16,7 @@ import { locales, themesIds } from '../../_support/config'; // Screens import ProfileSettingsForm from '../../../../source/renderer/app/components/widgets/forms/ProfileSettingsForm'; +import StakePoolsSettings from '../../../../source/renderer/app/components/settings/categories/StakePoolsSettings'; import DisplaySettings from '../../../../source/renderer/app/components/settings/categories/DisplaySettings'; import SupportSettings from '../../../../source/renderer/app/components/settings/categories/SupportSettings'; import TermsOfUseSettings from '../../../../source/renderer/app/components/settings/categories/TermsOfUseSettings'; @@ -47,6 +48,15 @@ storiesOf('Settings|General', module) currentTimeFormat={TIME_OPTIONS[0].value} /> )) + .add('Stake Pools', () => ( + + )) .add('Themes', () => ( { const menu = ( pageNames[item])} isActiveItem={(item) => { const itemName = context.story diff --git a/storybook/stories/staking/StakePools.stories.js b/storybook/stories/staking/StakePools.stories.js index e541a5bad1..4f31f55363 100644 --- a/storybook/stories/staking/StakePools.stories.js +++ b/storybook/stories/staking/StakePools.stories.js @@ -1,6 +1,6 @@ // @flow import React from 'react'; -import { number } from '@storybook/addon-knobs'; +import { number, boolean } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; import StakePools from '../../../source/renderer/app/components/staking/stake-pools/StakePools'; @@ -43,6 +43,7 @@ export const StakePoolsStory = (props: Props) => ( STAKE_POOLS[20], STAKE_POOLS[36], ]} + isFetching={boolean('isFetching', false)} onOpenExternalLink={action('onOpenExternalLink')} currentTheme={props.currentTheme} currentLocale={props.locale} @@ -53,6 +54,8 @@ export const StakePoolsStory = (props: Props) => ( rankStakePools={() => null} wallets={dummyWallets} getStakePoolById={() => null} + onSmashSettingsClick={action('onSmashSettingsClick')} + smashServerUrl="https://smash.cardano-mainnet.iohk.io" maxDelegationFunds={maxDelegationFunds} /> ); diff --git a/storybook/stories/wallets/settings/WalletSettingsScreen.stories.js b/storybook/stories/wallets/settings/WalletSettingsScreen.stories.js index 1e385deb8d..67ee42051c 100644 --- a/storybook/stories/wallets/settings/WalletSettingsScreen.stories.js +++ b/storybook/stories/wallets/settings/WalletSettingsScreen.stories.js @@ -140,7 +140,7 @@ export default (props: { currentTheme: string, locale: Locale }) => { isSubmitting={false} lastUpdatedField={null} nameValidator={() => true} - onCancelEditing={() => {}} + onCancel={() => {}} onFieldValueChange={() => {}} onStartEditing={() => {}} onStopEditing={() => {}} From 0b754c7c597fe3161f8a06a47432ee4f20ef8862 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 29 Jan 2021 15:18:06 +0100 Subject: [PATCH 07/37] [DDW-543] Integrate improved numeric input (#2318) * [DDW-543] Integrate improved numeric input * [DDW-543] Upgrade to improved numeric input The new version of numeric input can now handle any input (number, string, null, big numbers) and always returns a string value representing the floating point number with fixed precision (as configured) * [DDW-543] Fix flow issue * [DDW-543] Adds CHANGELOG entry Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + package.json | 2 +- .../app/components/wallet/WalletSendForm.js | 12 +++++----- .../wallet/transactions/FilterDialog.js | 22 +++++++++---------- yarn.lock | 6 ++--- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c2a72fdb..04c10c1799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog ### Chores +- Updated `react-polymorph` package ([PR 2318](https://github.com/input-output-hk/daedalus/pull/2318)) - Updated `bignumber.js` package ([PR 2305](https://github.com/input-output-hk/daedalus/pull/2305)) - Disabled application menu navigation before the "Terms of use" have been accepted ([PR 2304](https://github.com/input-output-hk/daedalus/pull/2304)) diff --git a/package.json b/package.json index 59c4c1e424..b1357cf1b2 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "react-intl": "2.7.2", "react-lottie": "1.2.3", "react-markdown": "4.3.1", - "react-polymorph": "0.9.7-rc.11", + "react-polymorph": "0.9.7-rc.17", "react-router": "5.2.0", "react-router-dom": "5.2.0", "react-svg-inline": "2.1.1", diff --git a/source/renderer/app/components/wallet/WalletSendForm.js b/source/renderer/app/components/wallet/WalletSendForm.js index f759a214aa..52281fa44b 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.js +++ b/source/renderer/app/components/wallet/WalletSendForm.js @@ -213,7 +213,7 @@ export default class WalletSendForm extends Component { placeholder: `0${ this.getCurrentNumberFormat().decimalSeparator }${'0'.repeat(this.props.currencyMaxFractionalDigits)}`, - value: null, + value: '', validators: [ async ({ field, form }) => { if (field.value === null) { @@ -349,8 +349,7 @@ export default class WalletSendForm extends Component { const receiverField = form.$('receiver'); const receiverFieldProps = receiverField.bind(); const amountFieldProps = amountField.bind(); - - const amount = new BigNumber(amountFieldProps.value || 0); + const amount = new BigNumber(amountFieldProps.value); let fees = null; let total = null; @@ -391,12 +390,11 @@ export default class WalletSendForm extends Component {
{ this._isCalculatingTransactionFee = true; diff --git a/source/renderer/app/components/wallet/transactions/FilterDialog.js b/source/renderer/app/components/wallet/transactions/FilterDialog.js index abbc3cc0c5..37abc17123 100644 --- a/source/renderer/app/components/wallet/transactions/FilterDialog.js +++ b/source/renderer/app/components/wallet/transactions/FilterDialog.js @@ -233,12 +233,12 @@ export default class FilterDialog extends Component { fromAmount: { type: 'number', label: '', - value: fromAmount ? Number(fromAmount) : '', + value: fromAmount, }, toAmount: { type: 'number', label: '', - value: toAmount ? Number(toAmount) : '', + value: toAmount, }, }, }); @@ -287,8 +287,8 @@ export default class FilterDialog extends Component { this.form.select('dateRange').set(dateRange); this.form.select('fromDate').set(fromDate); this.form.select('toDate').set(toDate); - this.form.select('fromAmount').set(fromAmount ? Number(fromAmount) : ''); - this.form.select('toAmount').set(toAmount ? Number(toAmount) : ''); + this.form.select('fromAmount').set(fromAmount); + this.form.select('toAmount').set(toAmount); this.form.select('incomingChecked').set(incomingChecked); this.form.select('outgoingChecked').set(outgoingChecked); }; @@ -489,24 +489,22 @@ export default class FilterDialog extends Component {
diff --git a/yarn.lock b/yarn.lock index d89bf93886..2032be38fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12554,9 +12554,9 @@ react-modal@3.1.12: prop-types "^15.5.10" warning "^3.0.0" -react-polymorph@0.9.7-rc.11: - version "0.9.7-rc.11" - resolved "https://registry.yarnpkg.com/react-polymorph/-/react-polymorph-0.9.7-rc.11.tgz#e297dd5cbe0dc359dff10e881c5558c2080b7d38" +react-polymorph@0.9.7-rc.17: + version "0.9.7-rc.17" + resolved "https://registry.yarnpkg.com/react-polymorph/-/react-polymorph-0.9.7-rc.17.tgz#febe8d146e110118afa9ee0cee128af04a86397b" dependencies: "@tippyjs/react" "4.2.0" create-react-context "0.2.2" From de7333db4ec4628ad53aee981673001b7b5c6d88 Mon Sep 17 00:00:00 2001 From: Samuel Leathers Date: Mon, 1 Feb 2021 14:30:44 -0500 Subject: [PATCH 08/37] [DDW-557] Update cardano-wallet (#2325) * cardano-wallet: bump to pre-release for mary compatibility * Adds CHANGELOG entry, updates cardano-wallet and cardano-node display values * Disable Byron wallet migration * Fix Shelley era detection * [DW-557] Fix rewards screen for Staging network * [DDW-557] Fix ITN rewards redemption * [DDW-557] Improves ITN redemption code styles Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + nix/sources.json | 18 ++++++++-------- source/main/environment.js | 2 +- source/renderer/app/api/api.js | 4 ++-- source/renderer/app/api/network/types.js | 7 ++++++- .../renderer/app/components/layout/TopBar.js | 21 +++++++++++-------- .../Step1ConfigurationDialog.scss | 6 ++++++ source/renderer/app/config/walletsConfig.js | 3 +++ .../containers/staking/StakingRewardsPage.js | 8 ++++++- .../Step3ResultContainer.js | 4 ++-- source/renderer/app/stores/StakingStore.js | 11 ++++++---- 11 files changed, 56 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c10c1799..16a5154d29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog ### Chores +- Updated `cardano-wallet` to version `2021-01-28` and `cardano-node` to version `1.25.1` ([PR 2270](https://github.com/input-output-hk/daedalus/pull/2270)) - Updated `react-polymorph` package ([PR 2318](https://github.com/input-output-hk/daedalus/pull/2318)) - Updated `bignumber.js` package ([PR 2305](https://github.com/input-output-hk/daedalus/pull/2305)) - Disabled application menu navigation before the "Terms of use" have been accepted ([PR 2304](https://github.com/input-output-hk/daedalus/pull/2304)) diff --git a/nix/sources.json b/nix/sources.json index 2a1700070f..ad3eb85189 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -24,17 +24,17 @@ "url_template": "https://github.com///archive/.tar.gz" }, "cardano-wallet": { - "branch": "tags/v2020-12-08", + "branch": "refs/tags/v2021-01-28", "description": "Official Wallet Backend & API for Cardano decentralized", "homepage": null, "owner": "input-output-hk", "repo": "cardano-wallet", - "rev": "4e49b1f12ae7d653eca149b60d67f2a1a578104a", - "sha256": "1ypkyn2s12nxwrk1ims8vrhhyy1xl3v5y35yxrwvqp8y8m2sac2x", + "rev": "e82e58a9032627e2ae56e52d39cf303898b50f88", + "sha256": "0xszv7k531p7jsragvhy5248ihni3alajzd5csjalpv28vd4j0sk", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-wallet/archive/4e49b1f12ae7d653eca149b60d67f2a1a578104a.tar.gz", + "url": "https://github.com/input-output-hk/cardano-wallet/archive/e82e58a9032627e2ae56e52d39cf303898b50f88.tar.gz", "url_template": "https://github.com///archive/.tar.gz", - "version": "v2021-01-12" + "version": "v2021-01-28" }, "gitignore": { "branch": "master", @@ -61,15 +61,15 @@ "url_template": "https://github.com///archive/.tar.gz" }, "iohk-nix": { - "branch": "nixpkgs-bump", + "branch": "master", "description": "nix scripts shared across projects", "homepage": null, "owner": "input-output-hk", "repo": "iohk-nix", - "rev": "940f3b7c16b9e7d4493d400280eedf2c936aa5c0", - "sha256": "1m5c8abw3bi5ij99vbcb5bpjywljy72yig5bbm4158r1mck7hljd", + "rev": "4efc38924c64c23a582c84950c8c25f72ff049cc", + "sha256": "0nhwyrd0xc72yj5q3jqa2wl4khp4g7n72i45cxy2rgn9nrp8wqh0", "type": "tarball", - "url": "https://github.com/input-output-hk/iohk-nix/archive/940f3b7c16b9e7d4493d400280eedf2c936aa5c0.tar.gz", + "url": "https://github.com/input-output-hk/iohk-nix/archive/4efc38924c64c23a582c84950c8c25f72ff049cc.tar.gz", "url_template": "https://github.com///archive/.tar.gz" }, "js-chain-libs": { diff --git a/source/main/environment.js b/source/main/environment.js index f0497432a9..9fc7e0e576 100644 --- a/source/main/environment.js +++ b/source/main/environment.js @@ -60,7 +60,7 @@ const isIncentivizedTestnetSelfnode = checkIsIncentivizedTestnetSelfnode( const isDevelopment = checkIsDevelopment(NETWORK); const isWatchMode = process.env.IS_WATCH_MODE; const API_VERSION = process.env.API_VERSION || 'dev'; -const NODE_VERSION = '1.24.2'; // TODO: pick up this value from process.env +const NODE_VERSION = '1.25.1'; // TODO: pick up this value from process.env const mainProcessID = get(process, 'ppid', '-'); const rendererProcessID = process.pid; const PLATFORM = os.platform(); diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 252fdd7e65..a5e08eb126 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -2081,7 +2081,7 @@ export default class AdaApi { decentralization_level: decentralizationLevel, desired_pool_number: desiredPoolNumber, minimum_utxo_value: minimumUtxoValue, - hardfork_at: hardforkAt, + eras, } = networkParameters; const blockchainStartTime = moment(blockchain_start_time).valueOf(); @@ -2095,7 +2095,7 @@ export default class AdaApi { decentralizationLevel, desiredPoolNumber, minimumUtxoValue, - hardforkAt: hardforkAt || null, + hardforkAt: eras.shelley || null, }; } catch (error) { logger.error('AdaApi::getNetworkParameters error', { error }); diff --git a/source/renderer/app/api/network/types.js b/source/renderer/app/api/network/types.js index 82ad11bdad..6be5bb3355 100644 --- a/source/renderer/app/api/network/types.js +++ b/source/renderer/app/api/network/types.js @@ -120,5 +120,10 @@ export type GetNetworkParametersApiResponse = { decentralization_level: DecentralizationLevel, desired_pool_number: number, minimum_utxo_value: MinimumUtxoValue, - hardfork_at?: HardforkAt, + eras: { + byron?: HardforkAt, + shelley?: HardforkAt, + allegra?: HardforkAt, + mary?: HardforkAt, + }, }; diff --git a/source/renderer/app/components/layout/TopBar.js b/source/renderer/app/components/layout/TopBar.js index 6308a2d621..bd0ab42e0c 100644 --- a/source/renderer/app/components/layout/TopBar.js +++ b/source/renderer/app/components/layout/TopBar.js @@ -4,6 +4,7 @@ import SVGInline from 'react-svg-inline'; import type { Node } from 'react'; import classNames from 'classnames'; import { observer } from 'mobx-react'; +import { IS_BYRON_WALLET_MIGRATION_ENABLED } from '../../config/walletsConfig'; import LegacyBadge, { LEGACY_BADGE_MODES } from '../notifications/LegacyBadge'; import LegacyNotification from '../notifications/LegacyNotification'; import Wallet from '../../domains/Wallet'; @@ -94,15 +95,17 @@ export default class TopBar extends Component { )} {children}
- {hasLegacyNotification && activeWallet && ( - - )} + {IS_BYRON_WALLET_MIGRATION_ENABLED && + hasLegacyNotification && + activeWallet && ( + + )} ); } diff --git a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss index 614cfd6db2..19d6eace2f 100644 --- a/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss +++ b/source/renderer/app/components/staking/redeem-itn-rewards/Step1ConfigurationDialog.scss @@ -23,6 +23,12 @@ .recoveryPhrase { margin-bottom: 20px; + + :global { + .SimpleOptions_option { + padding: 14px 20px; + } + } } .walletsDropdownWrapper { diff --git a/source/renderer/app/config/walletsConfig.js b/source/renderer/app/config/walletsConfig.js index 5ac9457ba0..30fb75fe6b 100644 --- a/source/renderer/app/config/walletsConfig.js +++ b/source/renderer/app/config/walletsConfig.js @@ -33,3 +33,6 @@ export const RECOVERY_PHRASE_WORD_COUNT_OPTIONS = { export const WALLET_PUBLIC_KEY_NOTIFICATION_SEGMENT_LENGTH = 15; export const WALLET_PUBLIC_KEY_SHARING_ENABLED = false; + +// Byron wallet migration has been temporarily disabled due to missing Api support after Mary HF +export const IS_BYRON_WALLET_MIGRATION_ENABLED = false; diff --git a/source/renderer/app/containers/staking/StakingRewardsPage.js b/source/renderer/app/containers/staking/StakingRewardsPage.js index 9b6ce9588d..2c3a7b41ed 100644 --- a/source/renderer/app/containers/staking/StakingRewardsPage.js +++ b/source/renderer/app/containers/staking/StakingRewardsPage.js @@ -39,11 +39,17 @@ export default class StakingRewardsPage extends Component { wallets, } = this.props.stores; const { isIncentivizedTestnet, isShelleyTestnet } = global; - const { isMainnet, isTestnet, isTest } = networkStatus.environment; + const { + isMainnet, + isStaging, + isTestnet, + isTest, + } = networkStatus.environment; const { requestCSVFile } = this.props.actions.staking; if ( isMainnet || + isStaging || isTestnet || isIncentivizedTestnet || isShelleyTestnet || diff --git a/source/renderer/app/containers/staking/dialogs/redeem-itn-rewards/Step3ResultContainer.js b/source/renderer/app/containers/staking/dialogs/redeem-itn-rewards/Step3ResultContainer.js index 22726c34e4..c5fb98e898 100644 --- a/source/renderer/app/containers/staking/dialogs/redeem-itn-rewards/Step3ResultContainer.js +++ b/source/renderer/app/containers/staking/dialogs/redeem-itn-rewards/Step3ResultContainer.js @@ -20,11 +20,11 @@ export default class Step3ResultContainer extends Component { redeemWallet, transactionFees, redeemedRewards, - stakingSuccess, + redeemSuccess, } = stores.staking; const { onResultContinue } = actions.staking; if (!redeemWallet) throw new Error('Redeem wallet required'); - if (stakingSuccess) { + if (redeemSuccess) { return ( { this.redeemedRewards = redeemedRewards; - this.stakingSuccess = true; + this.redeemSuccess = true; this.redeemStep = steps.RESULT; this.confirmationStepError = null; this.isSubmittingReedem = false; @@ -749,7 +752,7 @@ export default class StakingStore extends Store { this.confirmationStepError = error; this.isSubmittingReedem = false; if (error.id !== 'api.errors.IncorrectPasswordError') { - this.stakingSuccess = false; + this.redeemSuccess = false; this.redeemStep = steps.RESULT; } }); @@ -767,7 +770,7 @@ export default class StakingStore extends Store { @action _resetRedeemItnRewards = () => { this.isSubmittingReedem = false; this.isCalculatingReedemFees = false; - this.stakingSuccess = null; + this.redeemSuccess = null; this.redeemWallet = null; this.transactionFees = null; this.redeemedRewards = null; From 4678b373b48f30995a911a67d40c2b1fc8ef5520 Mon Sep 17 00:00:00 2001 From: Yakov Karavelov Date: Mon, 1 Feb 2021 14:36:37 -0500 Subject: [PATCH 09/37] [DDW-504] Number format for syncing percentage and stake pools (#2313) * [DDW-504]: Fix number formatting issues on stake pools, restore notification and block sync status popups * [DDW-504]: FUpdate changelog * [DDW-504]: Apply proper number formats on diagnostics modal * [DDW-504]: Remove unnecessary formatters defined in main thread * [DDW-504]: Update diagnostics dialog data formattion Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + .../notifications/RestoreNotification.js | 3 +- .../staking/stake-pools/StakePools.js | 5 +- .../components/status/DaedalusDiagnostics.js | 31 +- .../components/widgets/NodeSyncStatusIcon.js | 3 +- .../app/i18n/locales/defaultMessages.json | 288 +++++++++--------- source/renderer/app/utils/formatters.js | 32 +- 7 files changed, 201 insertions(+), 162 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16a5154d29..820fb465cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog ### Chores +- Fixed number format for syncing percentage and stake pools count ([PR 2313](https://github.com/input-output-hk/daedalus/pull/2313)) - Updated `cardano-wallet` to version `2021-01-28` and `cardano-node` to version `1.25.1` ([PR 2270](https://github.com/input-output-hk/daedalus/pull/2270)) - Updated `react-polymorph` package ([PR 2318](https://github.com/input-output-hk/daedalus/pull/2318)) - Updated `bignumber.js` package ([PR 2305](https://github.com/input-output-hk/daedalus/pull/2305)) diff --git a/source/renderer/app/components/notifications/RestoreNotification.js b/source/renderer/app/components/notifications/RestoreNotification.js index 237d71c7a3..ab929cfc48 100644 --- a/source/renderer/app/components/notifications/RestoreNotification.js +++ b/source/renderer/app/components/notifications/RestoreNotification.js @@ -5,6 +5,7 @@ import { observer } from 'mobx-react'; import SVGInline from 'react-svg-inline'; import { defineMessages, intlShape } from 'react-intl'; import spinnerIcon from '../../assets/images/spinner-dark.inline.svg'; +import { formattedNumber } from '../../utils/formatters'; import styles from './RestoreNotification.scss'; const messages = defineMessages({ @@ -40,7 +41,7 @@ export default class RestoreNotification extends Component {
{intl.formatMessage(messages.activeRestoreMessage, { - percentage: restoreProgress, + percentage: formattedNumber(restoreProgress), })} diff --git a/source/renderer/app/components/staking/stake-pools/StakePools.js b/source/renderer/app/components/staking/stake-pools/StakePools.js index c0ae2837bc..53b10c20fb 100644 --- a/source/renderer/app/components/staking/stake-pools/StakePools.js +++ b/source/renderer/app/components/staking/stake-pools/StakePools.js @@ -13,6 +13,7 @@ import LoadingSpinner from '../../widgets/LoadingSpinner'; import Wallet from '../../../domains/Wallet'; import styles from './StakePools.scss'; import { getFilteredStakePoolsList } from './helpers'; +import { formattedNumber } from '../../../utils/formatters'; import StakePool from '../../../domains/StakePool'; import { IS_RANKING_DATA_AVAILABLE, @@ -301,7 +302,7 @@ export default class StakePools extends Component { {intl.formatMessage(listTitleMessage)} {listTitleSearchMessage} {intl.formatMessage(messages.listTitleStakePools, { - pools: filteredStakePoolsList.length, + pools: formattedNumber(filteredStakePoolsList.length), })} {tinyLoadingSpinner} @@ -333,7 +334,7 @@ export default class StakePools extends Component { {intl.formatMessage(listTitleMessage)} {listTitleSearchMessage} {intl.formatMessage(messages.listTitleStakePools, { - pools: filteredStakePoolsList.length, + pools: formattedNumber(filteredStakePoolsList.length), })} {tinyLoadingSpinner} diff --git a/source/renderer/app/components/status/DaedalusDiagnostics.js b/source/renderer/app/components/status/DaedalusDiagnostics.js index 66fa18d3b0..429dcf3f02 100644 --- a/source/renderer/app/components/status/DaedalusDiagnostics.js +++ b/source/renderer/app/components/status/DaedalusDiagnostics.js @@ -10,7 +10,6 @@ import { PopOver } from 'react-polymorph/lib/components/PopOver'; import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import SVGInline from 'react-svg-inline'; -import { BigNumber } from 'bignumber.js'; import { ALLOWED_TIME_DIFFERENCE } from '../../config/timingConfig'; import globalMessages from '../../i18n/global-messages'; import DialogCloseButton from '../widgets/DialogCloseButton'; @@ -18,6 +17,11 @@ import closeCrossThin from '../../assets/images/close-cross-thin.inline.svg'; import iconCopy from '../../assets/images/clipboard-ic.inline.svg'; import sandClockIcon from '../../assets/images/sand-clock-xs.inline.svg'; import LocalizableError from '../../i18n/LocalizableError'; +import { + formattedNumber, + formattedCpuModel, + formattedSize, +} from '../../utils/formatters'; import { CardanoNodeStates } from '../../../../common/types/cardano-node.types'; import styles from './DaedalusDiagnostics.scss'; import type { CardanoNodeState } from '../../../../common/types/cardano-node.types'; @@ -479,11 +483,16 @@ export default class DaedalusDiagnostics extends Component { const { platform, platformVersion, - cpu, + cpu: cpuInOriginalFormat, ram, - availableDiskSpace, + availableDiskSpace: availableDiskSpaceInOriginalFormat, } = systemInfo; + const cpu = formattedCpuModel(cpuInOriginalFormat); + const availableDiskSpace = formattedSize( + availableDiskSpaceInOriginalFormat + ); + const { daedalusVersion, daedalusBuildNumber, @@ -639,16 +648,14 @@ export default class DaedalusDiagnostics extends Component { {getRow('synced', isSynced)} {getRow( 'syncPercentage', - `${new BigNumber( - parseFloat(syncPercentage).toFixed(2) - ).toFormat(2)}%` + `${formattedNumber(syncPercentage, 2)}%` )} {getRow( 'lastNetworkBlock', {intl.formatMessage(messages.epoch)}:{' '} {networkTip && networkTip.epoch ? ( - networkTip.epoch + formattedNumber(networkTip.epoch) ) : ( { )} {intl.formatMessage(messages.slot)}:{' '} {networkTip && networkTip.slot ? ( - networkTip.slot + formattedNumber(networkTip.slot) ) : ( { {intl.formatMessage(messages.epoch)}:{' '} {localTip && localTip.epoch ? ( - localTip.epoch + formattedNumber(localTip.epoch) ) : ( { )} {intl.formatMessage(messages.slot)}:{' '} {localTip && localTip.slot ? ( - localTip.slot + formattedNumber(localTip.slot) ) : ( { ) : ( {isNTPServiceReachable - ? `${new BigNumber( - localTimeDifference || 0 - ).toFormat()} μs` + ? `${formattedNumber(localTimeDifference || 0)} μs` : intl.formatMessage(messages.serviceUnreachable)} )} diff --git a/source/renderer/app/components/widgets/NodeSyncStatusIcon.js b/source/renderer/app/components/widgets/NodeSyncStatusIcon.js index e8cade34ac..f40cb23e16 100644 --- a/source/renderer/app/components/widgets/NodeSyncStatusIcon.js +++ b/source/renderer/app/components/widgets/NodeSyncStatusIcon.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import SVGInline from 'react-svg-inline'; import { defineMessages, intlShape } from 'react-intl'; import classNames from 'classnames'; +import { formattedNumber } from '../../utils/formatters'; import spinnerIcon from '../../assets/images/top-bar/node-sync-spinner.inline.svg'; import syncedIcon from '../../assets/images/top-bar/node-sync-synced.inline.svg'; import styles from './NodeSyncStatusIcon.scss'; @@ -41,7 +42,7 @@ export default class NodeSyncStatusIcon extends Component {
{intl.formatMessage(messages.blocksSynced, { - percentage, + percentage: formattedNumber(percentage), })}
diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 8ee73f72a8..66f11f03c9 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -1896,13 +1896,13 @@ "description": "Status message \"Wallet restore in progress\" shown while wallet is being restored.", "end": { "column": 3, - "line": 17 + "line": 18 }, "file": "source/renderer/app/components/notifications/RestoreNotification.js", "id": "wallet.statusMessages.activeRestore", "start": { "column": 24, - "line": 11 + "line": 12 } } ], @@ -5633,13 +5633,13 @@ "description": "\"delegatingListTitle\" for the Stake Pools page.", "end": { "column": 3, - "line": 30 + "line": 31 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.delegatingListTitle", "start": { "column": 23, - "line": 26 + "line": 27 } }, { @@ -5647,13 +5647,13 @@ "description": "\"listTitle\" for the Stake Pools page.", "end": { "column": 3, - "line": 35 + "line": 36 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.listTitle", "start": { "column": 13, - "line": 31 + "line": 32 } }, { @@ -5661,13 +5661,13 @@ "description": "\"listTitleLoading\" for the Stake Pools page.", "end": { "column": 3, - "line": 40 + "line": 41 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.listTitleLoading", "start": { "column": 20, - "line": 36 + "line": 37 } }, { @@ -5675,13 +5675,13 @@ "description": "\"listTitleSearch\" for the Stake Pools page.", "end": { "column": 3, - "line": 45 + "line": 46 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.listTitleSearch", "start": { "column": 19, - "line": 41 + "line": 42 } }, { @@ -5689,13 +5689,13 @@ "description": "\"listTitleStakePools\" for the Stake Pools page.", "end": { "column": 3, - "line": 50 + "line": 51 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.listTitleStakePools", "start": { "column": 23, - "line": 46 + "line": 47 } }, { @@ -5703,13 +5703,13 @@ "description": "Loading stake pool message for the Delegation center body section.", "end": { "column": 3, - "line": 56 + "line": 57 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.loadingStakePoolsMessage", "start": { "column": 28, - "line": 51 + "line": 52 } }, { @@ -5717,13 +5717,13 @@ "description": "moderatedBy message for the Delegation center body section.", "end": { "column": 3, - "line": 61 + "line": 62 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.moderatedBy", "start": { "column": 15, - "line": 57 + "line": 58 } }, { @@ -5731,13 +5731,13 @@ "description": "unmoderated message for the Delegation center body section.", "end": { "column": 3, - "line": 66 + "line": 67 }, "file": "source/renderer/app/components/staking/stake-pools/StakePools.js", "id": "staking.stakePools.unmoderated", "start": { "column": 15, - "line": 62 + "line": 63 } } ], @@ -6699,13 +6699,13 @@ "description": "System info", "end": { "column": 3, - "line": 33 + "line": 37 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.system.info", "start": { "column": 14, - "line": 29 + "line": 33 } }, { @@ -6713,13 +6713,13 @@ "description": "Platform", "end": { "column": 3, - "line": 38 + "line": 42 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.platform", "start": { "column": 12, - "line": 34 + "line": 38 } }, { @@ -6727,13 +6727,13 @@ "description": "Platform version", "end": { "column": 3, - "line": 43 + "line": 47 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.platform.version", "start": { "column": 19, - "line": 39 + "line": 43 } }, { @@ -6741,13 +6741,13 @@ "description": "CPU", "end": { "column": 3, - "line": 48 + "line": 52 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cpu", "start": { "column": 7, - "line": 44 + "line": 48 } }, { @@ -6755,13 +6755,13 @@ "description": "RAM", "end": { "column": 3, - "line": 53 + "line": 57 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.ram", "start": { "column": 7, - "line": 49 + "line": 53 } }, { @@ -6769,13 +6769,13 @@ "description": "Available disk space", "end": { "column": 3, - "line": 58 + "line": 62 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.availableDiskSpace", "start": { "column": 22, - "line": 54 + "line": 58 } }, { @@ -6783,13 +6783,13 @@ "description": "Unknown amount of disk space", "end": { "column": 3, - "line": 63 + "line": 67 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.unknownDiskSpace", "start": { "column": 20, - "line": 59 + "line": 63 } }, { @@ -6797,13 +6797,13 @@ "description": "\"Support\" link URL while disk space is unknown", "end": { "column": 3, - "line": 68 + "line": 72 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.unknownDiskSpaceSupportUrl", "start": { "column": 30, - "line": 64 + "line": 68 } }, { @@ -6811,13 +6811,13 @@ "description": "CORE INFO", "end": { "column": 3, - "line": 73 + "line": 77 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.coreInfo", "start": { "column": 12, - "line": 69 + "line": 73 } }, { @@ -6825,13 +6825,13 @@ "description": "Daedalus version", "end": { "column": 3, - "line": 78 + "line": 82 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.daedalusVersion", "start": { "column": 19, - "line": 74 + "line": 78 } }, { @@ -6839,13 +6839,13 @@ "description": "Daedalus build number", "end": { "column": 3, - "line": 83 + "line": 87 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.daedalusBuildNumber", "start": { "column": 23, - "line": 79 + "line": 83 } }, { @@ -6853,13 +6853,13 @@ "description": "Daedalus main process ID", "end": { "column": 3, - "line": 88 + "line": 92 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.daedalusMainProcessID", "start": { "column": 25, - "line": 84 + "line": 88 } }, { @@ -6867,13 +6867,13 @@ "description": "Daedalus renderer process ID", "end": { "column": 3, - "line": 93 + "line": 97 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.daedalusProcessID", "start": { "column": 21, - "line": 89 + "line": 93 } }, { @@ -6881,13 +6881,13 @@ "description": "Daedalus 'Blank Screen Fix' active", "end": { "column": 3, - "line": 98 + "line": 102 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.blankScreenFix", "start": { "column": 18, - "line": 94 + "line": 98 } }, { @@ -6895,13 +6895,13 @@ "description": "Cardano node version", "end": { "column": 3, - "line": 103 + "line": 107 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeVersion", "start": { "column": 22, - "line": 99 + "line": 103 } }, { @@ -6909,13 +6909,13 @@ "description": "Cardano node process ID", "end": { "column": 3, - "line": 108 + "line": 112 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodePID", "start": { "column": 18, - "line": 104 + "line": 108 } }, { @@ -6923,13 +6923,13 @@ "description": "Cardano node port", "end": { "column": 3, - "line": 113 + "line": 117 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeApiPort", "start": { "column": 22, - "line": 109 + "line": 113 } }, { @@ -6937,13 +6937,13 @@ "description": "Cardano wallet process ID", "end": { "column": 3, - "line": 118 + "line": 122 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoWalletPID", "start": { "column": 20, - "line": 114 + "line": 118 } }, { @@ -6951,13 +6951,13 @@ "description": "Cardano wallet version", "end": { "column": 3, - "line": 123 + "line": 127 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoWalletVersion", "start": { "column": 24, - "line": 119 + "line": 123 } }, { @@ -6965,13 +6965,13 @@ "description": "Cardano wallet port", "end": { "column": 3, - "line": 128 + "line": 132 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoWalletApiPort", "start": { "column": 24, - "line": 124 + "line": 128 } }, { @@ -6979,13 +6979,13 @@ "description": "Cardano network", "end": { "column": 3, - "line": 133 + "line": 137 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNetwork", "start": { "column": 18, - "line": 129 + "line": 133 } }, { @@ -6993,13 +6993,13 @@ "description": "Daedalus state directory", "end": { "column": 3, - "line": 138 + "line": 142 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.stateDirectory", "start": { "column": 22, - "line": 134 + "line": 138 } }, { @@ -7007,13 +7007,13 @@ "description": "Open", "end": { "column": 3, - "line": 143 + "line": 147 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.stateDirectoryPathOpenBtn", "start": { "column": 29, - "line": 139 + "line": 143 } }, { @@ -7021,13 +7021,13 @@ "description": "CONNECTION ERROR", "end": { "column": 3, - "line": 148 + "line": 152 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.connectionError", "start": { "column": 19, - "line": 144 + "line": 148 } }, { @@ -7035,13 +7035,13 @@ "description": "DAEDALUS STATUS", "end": { "column": 3, - "line": 153 + "line": 157 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.daedalusStatus", "start": { "column": 18, - "line": 149 + "line": 153 } }, { @@ -7049,13 +7049,13 @@ "description": "Connected", "end": { "column": 3, - "line": 158 + "line": 162 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.connected", "start": { "column": 13, - "line": 154 + "line": 158 } }, { @@ -7063,13 +7063,13 @@ "description": "Synced", "end": { "column": 3, - "line": 163 + "line": 167 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.synced", "start": { "column": 10, - "line": 159 + "line": 163 } }, { @@ -7077,13 +7077,13 @@ "description": "Sync percentage", "end": { "column": 3, - "line": 168 + "line": 172 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.syncPercentage", "start": { "column": 18, - "line": 164 + "line": 168 } }, { @@ -7091,13 +7091,13 @@ "description": "Local time difference", "end": { "column": 3, - "line": 173 + "line": 177 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.localTimeDifference", "start": { "column": 23, - "line": 169 + "line": 173 } }, { @@ -7105,13 +7105,13 @@ "description": "System time correct", "end": { "column": 3, - "line": 178 + "line": 182 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.systemTimeCorrect", "start": { "column": 21, - "line": 174 + "line": 178 } }, { @@ -7119,13 +7119,13 @@ "description": "System time ignored", "end": { "column": 3, - "line": 183 + "line": 187 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.systemTimeIgnored", "start": { "column": 21, - "line": 179 + "line": 183 } }, { @@ -7133,13 +7133,13 @@ "description": "Checking system time", "end": { "column": 3, - "line": 188 + "line": 192 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.checkingNodeTime", "start": { "column": 20, - "line": 184 + "line": 188 } }, { @@ -7147,13 +7147,13 @@ "description": "CARDANO NODE STATUS", "end": { "column": 3, - "line": 193 + "line": 197 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeStatus", "start": { "column": 21, - "line": 189 + "line": 193 } }, { @@ -7161,13 +7161,13 @@ "description": "Restarting Cardano node...", "end": { "column": 3, - "line": 198 + "line": 202 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeStatusRestarting", "start": { "column": 31, - "line": 194 + "line": 198 } }, { @@ -7175,13 +7175,13 @@ "description": "Restart Cardano node", "end": { "column": 3, - "line": 203 + "line": 207 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeStatusRestart", "start": { "column": 28, - "line": 199 + "line": 203 } }, { @@ -7189,13 +7189,13 @@ "description": "Cardano node state", "end": { "column": 3, - "line": 208 + "line": 212 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeState", "start": { "column": 20, - "line": 204 + "line": 208 } }, { @@ -7203,13 +7203,13 @@ "description": "Updated", "end": { "column": 3, - "line": 213 + "line": 217 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeHasBeenUpdated", "start": { "column": 22, - "line": 209 + "line": 213 } }, { @@ -7217,13 +7217,13 @@ "description": "Crashed", "end": { "column": 3, - "line": 218 + "line": 222 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeHasCrashed", "start": { "column": 18, - "line": 214 + "line": 218 } }, { @@ -7231,13 +7231,13 @@ "description": "Errored", "end": { "column": 3, - "line": 223 + "line": 227 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeHasErrored", "start": { "column": 18, - "line": 219 + "line": 223 } }, { @@ -7245,13 +7245,13 @@ "description": "Stopped", "end": { "column": 3, - "line": 228 + "line": 232 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeHasStopped", "start": { "column": 18, - "line": 224 + "line": 228 } }, { @@ -7259,13 +7259,13 @@ "description": "Exiting", "end": { "column": 3, - "line": 233 + "line": 237 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsExiting", "start": { "column": 17, - "line": 229 + "line": 233 } }, { @@ -7273,13 +7273,13 @@ "description": "Running", "end": { "column": 3, - "line": 238 + "line": 242 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsRunning", "start": { "column": 17, - "line": 234 + "line": 238 } }, { @@ -7287,13 +7287,13 @@ "description": "Starting", "end": { "column": 3, - "line": 243 + "line": 247 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsStarting", "start": { "column": 18, - "line": 239 + "line": 243 } }, { @@ -7301,13 +7301,13 @@ "description": "Stopping", "end": { "column": 3, - "line": 248 + "line": 252 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsStopping", "start": { "column": 18, - "line": 244 + "line": 248 } }, { @@ -7315,13 +7315,13 @@ "description": "Unrecoverable", "end": { "column": 3, - "line": 253 + "line": 257 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsUnrecoverable", "start": { "column": 23, - "line": 249 + "line": 253 } }, { @@ -7329,13 +7329,13 @@ "description": "Updating", "end": { "column": 3, - "line": 258 + "line": 262 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.nodeIsUpdating", "start": { "column": 18, - "line": 254 + "line": 258 } }, { @@ -7343,13 +7343,13 @@ "description": "Cardano node responding", "end": { "column": 3, - "line": 263 + "line": 267 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeResponding", "start": { "column": 25, - "line": 259 + "line": 263 } }, { @@ -7357,13 +7357,13 @@ "description": "Cardano node subscribed", "end": { "column": 3, - "line": 268 + "line": 272 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeSubscribed", "start": { "column": 25, - "line": 264 + "line": 268 } }, { @@ -7371,13 +7371,13 @@ "description": "Cardano node time correct", "end": { "column": 3, - "line": 273 + "line": 277 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeTimeCorrect", "start": { "column": 26, - "line": 269 + "line": 273 } }, { @@ -7385,13 +7385,13 @@ "description": "Cardano node syncing", "end": { "column": 3, - "line": 278 + "line": 282 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeSyncing", "start": { "column": 22, - "line": 274 + "line": 278 } }, { @@ -7399,13 +7399,13 @@ "description": "Cardano node in sync", "end": { "column": 3, - "line": 283 + "line": 287 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.cardanoNodeInSync", "start": { "column": 21, - "line": 279 + "line": 283 } }, { @@ -7413,13 +7413,13 @@ "description": "Checking...", "end": { "column": 3, - "line": 288 + "line": 292 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.localTimeDifferenceChecking", "start": { "column": 31, - "line": 284 + "line": 288 } }, { @@ -7427,13 +7427,13 @@ "description": "Check time", "end": { "column": 3, - "line": 293 + "line": 297 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.localTimeDifferenceCheckTime", "start": { "column": 32, - "line": 289 + "line": 293 } }, { @@ -7441,13 +7441,13 @@ "description": "YES", "end": { "column": 3, - "line": 298 + "line": 302 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.statusOn", "start": { "column": 12, - "line": 294 + "line": 298 } }, { @@ -7455,13 +7455,13 @@ "description": "NO", "end": { "column": 3, - "line": 303 + "line": 307 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.statusOff", "start": { "column": 13, - "line": 299 + "line": 303 } }, { @@ -7469,13 +7469,13 @@ "description": "NTP service unreachable", "end": { "column": 3, - "line": 308 + "line": 312 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.serviceUnreachable", "start": { "column": 22, - "line": 304 + "line": 308 } }, { @@ -7483,13 +7483,13 @@ "description": "message", "end": { "column": 3, - "line": 313 + "line": 317 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.message", "start": { "column": 11, - "line": 309 + "line": 313 } }, { @@ -7497,13 +7497,13 @@ "description": "code", "end": { "column": 3, - "line": 318 + "line": 322 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.code", "start": { "column": 8, - "line": 314 + "line": 318 } }, { @@ -7511,13 +7511,13 @@ "description": "Last network block", "end": { "column": 3, - "line": 323 + "line": 327 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.lastNetworkBlock", "start": { "column": 20, - "line": 319 + "line": 323 } }, { @@ -7525,13 +7525,13 @@ "description": "Last synchronized block", "end": { "column": 3, - "line": 328 + "line": 332 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.lastSynchronizedBlock", "start": { "column": 25, - "line": 324 + "line": 328 } }, { @@ -7539,13 +7539,13 @@ "description": "epoch", "end": { "column": 3, - "line": 333 + "line": 337 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.epoch", "start": { "column": 9, - "line": 329 + "line": 333 } }, { @@ -7553,13 +7553,13 @@ "description": "slot", "end": { "column": 3, - "line": 338 + "line": 342 }, "file": "source/renderer/app/components/status/DaedalusDiagnostics.js", "id": "daedalus.diagnostics.dialog.slot", "start": { "column": 8, - "line": 334 + "line": 338 } } ], @@ -13790,13 +13790,13 @@ "description": "Label for the blocks synced info overlay on node sync status icon.", "end": { "column": 3, - "line": 16 + "line": 17 }, "file": "source/renderer/app/components/widgets/NodeSyncStatusIcon.js", "id": "cardano.node.sync.status.blocksSynced", "start": { "column": 16, - "line": 11 + "line": 12 } } ], diff --git a/source/renderer/app/utils/formatters.js b/source/renderer/app/utils/formatters.js index 6751a7ffd6..ac2291deb1 100644 --- a/source/renderer/app/utils/formatters.js +++ b/source/renderer/app/utils/formatters.js @@ -107,7 +107,7 @@ export const formattedBytesToSize = (bytes: number): string => { 10 ); if (i === 0) return `${bytes} ${sizes[i]})`; - return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`; + return `${formattedNumber(bytes / 1024 ** i, 1)} ${sizes[i]}`; }; export type FormattedDownloadData = { @@ -155,3 +155,33 @@ export const generateThousands = (value: number) => { return Math.round(value / 1000) * 1000; }; + +export const formattedNumber = (value: number | string, dp?: number): string => + new BigNumber(value).toFormat(dp); + +export const formattedCpuModel = (model: string): string => { + const atCharPosition = model.indexOf('@'); + const speedSection = model.substring(atCharPosition); + const speedNumbers = speedSection.match(/[\d,.]+/g); + const speedNumber = speedNumbers ? speedNumbers[0] : ''; + const formattedSpeedNumber = formattedNumber(speedNumber, 2); + const formattedSpeedSection = speedSection.replace( + /[\d,.]+/, + formattedSpeedNumber + ); + const formattedModel = `${model.substring( + 0, + atCharPosition + )}${formattedSpeedSection}`; + + return formattedModel; +}; + +export const formattedSize = (size: string): string => { + const sizeNumbers = size.match(/[\d,.]+/g); + const sizeNumber = sizeNumbers ? sizeNumbers[0] : ''; + const formattedSizeNumber = formattedNumber(sizeNumber); + const formattedResult = size.replace(/[\d,.]+/, formattedSizeNumber); + + return formattedResult; +}; From 9fa52c20c0bfdb9601debda6b781f1d0d433cdfe Mon Sep 17 00:00:00 2001 From: Yakov Karavelov Date: Tue, 2 Feb 2021 06:04:00 -0500 Subject: [PATCH 10/37] [DDW-507] Closing delegation wizard while transaction fees are being calculated throws error in the console (#2330) * [DDW-5047: Prevent stakepool join fee calculation after delegation modal is unmounted * [DDW-507]: Prevent stakepool join fee calculation after delegation modal is unmounted * [DDW-507]: Update changelog * [DDW-507] Improve code style Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + .../DelegationSetupWizardDialogContainer.js | 21 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 820fb465cd..be001198f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog ### Chores +- Fixed error thrown when closing delegation wizard while transaction fees are being calculated ([PR 2330](https://github.com/input-output-hk/daedalus/pull/2330)) - Fixed number format for syncing percentage and stake pools count ([PR 2313](https://github.com/input-output-hk/daedalus/pull/2313)) - Updated `cardano-wallet` to version `2021-01-28` and `cardano-node` to version `1.25.1` ([PR 2270](https://github.com/input-output-hk/daedalus/pull/2270)) - Updated `react-polymorph` package ([PR 2318](https://github.com/input-output-hk/daedalus/pull/2318)) diff --git a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js index da6dbb0dc5..549814101f 100644 --- a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js +++ b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js @@ -62,6 +62,19 @@ export default class DelegationSetupWizardDialogContainer extends Component< onClose: () => {}, }; + // We need to track the mounted state in order to avoid calling + // setState promise handling code after the component was already unmounted: + // Read more: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html + _isMounted = false; + + componentDidMount() { + this._isMounted = true; + } + + componentWillUnmount() { + this._isMounted = false; + } + handleIsWalletAcceptable = ( walletAmount?: BigNumber, walletReward?: BigNumber = 0 @@ -256,9 +269,13 @@ export default class DelegationSetupWizardDialogContainer extends Component< }); } - // Update state only if DelegationSetupWizardDialog is still active + // Update state only if DelegationSetupWizardDialog is still mounted and active // and fee calculation was successful - if (isOpen(DelegationSetupWizardDialog) && stakePoolJoinFee) { + if ( + this._isMounted && + isOpen(DelegationSetupWizardDialog) && + stakePoolJoinFee + ) { this.setState({ stakePoolJoinFee }); } } From 49f61d14f638ba29c72ba72cf0ba513fbe944774 Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Wed, 3 Feb 2021 11:14:26 +0100 Subject: [PATCH 11/37] [DDW-545] Removed steps count subtitle from the wallet restoration success screen (#2335) --- CHANGELOG.md | 4 ++++ .../wallet/wallet-restore/widgets/WalletRestoreDialog.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be001198f4..0a69147068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Changelog - Added SMASH server configuration options ([PR 2259](https://github.com/input-output-hk/daedalus/pull/2259)) +### Fixes + +- Removed steps counter from the "Success" wallet restoration dialog step ([PR 2335](https://github.com/input-output-hk/daedalus/pull/2335)) + ### Chores - Fixed error thrown when closing delegation wizard while transaction fees are being calculated ([PR 2330](https://github.com/input-output-hk/daedalus/pull/2330)) diff --git a/source/renderer/app/components/wallet/wallet-restore/widgets/WalletRestoreDialog.js b/source/renderer/app/components/wallet/wallet-restore/widgets/WalletRestoreDialog.js index 9449bfe399..e16fe2c590 100644 --- a/source/renderer/app/components/wallet/wallet-restore/widgets/WalletRestoreDialog.js +++ b/source/renderer/app/components/wallet/wallet-restore/widgets/WalletRestoreDialog.js @@ -69,7 +69,7 @@ export default class WalletRestoreDialog extends Component { Date: Wed, 3 Feb 2021 06:47:00 -0500 Subject: [PATCH 12/37] [DDW-551] Adjust sorting on rewards screen (#2333) * [DDW-551]: Adjust sorting on rewards screen * [DDW-551]: Update changelog * [DDW-551]: Fix type declarations --- CHANGELOG.md | 1 + .../staking/rewards/StakingRewards.js | 142 +++++++++++++----- .../StakingRewardsForIncentivizedTestnet.js | 97 +++++++++--- .../app/i18n/locales/defaultMessages.json | 76 +++++----- source/renderer/app/utils/sortComparators.js | 51 +++++++ 5 files changed, 276 insertions(+), 91 deletions(-) create mode 100644 source/renderer/app/utils/sortComparators.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a69147068..74ba48f7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Changelog ### Chores +- Adjusted sorting on rewards ([PR 2333](https://github.com/input-output-hk/daedalus/pull/2333)) - Fixed error thrown when closing delegation wizard while transaction fees are being calculated ([PR 2330](https://github.com/input-output-hk/daedalus/pull/2330)) - Fixed number format for syncing percentage and stake pools count ([PR 2313](https://github.com/input-output-hk/daedalus/pull/2313)) - Updated `cardano-wallet` to version `2021-01-28` and `cardano-node` to version `1.25.1` ([PR 2270](https://github.com/input-output-hk/daedalus/pull/2270)) diff --git a/source/renderer/app/components/staking/rewards/StakingRewards.js b/source/renderer/app/components/staking/rewards/StakingRewards.js index 9890ed7b46..b3e514bad1 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewards.js +++ b/source/renderer/app/components/staking/rewards/StakingRewards.js @@ -3,11 +3,15 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import SVGInline from 'react-svg-inline'; -import { get, map, orderBy } from 'lodash'; +import { get, map } from 'lodash'; import classNames from 'classnames'; -import { BigNumber } from 'bignumber.js'; import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; +import { + bigNumberComparator, + stringComparator, + dateComparator, +} from '../../../utils/sortComparators'; import BorderedBox from '../../widgets/BorderedBox'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import sortIcon from '../../../assets/images/ascending.inline.svg'; @@ -66,6 +70,21 @@ const messages = defineMessages({ }, }); +const REWARD_FIELDS = { + WALLET_NAME: 'wallet', + IS_RESTORING: 'isRestoring', + POOL: 'pool', + NAME: 'name', + TICKER: 'ticker', + REWARD: 'reward', + DATE: 'date', +}; + +const REWARD_ORDERS = { + ASCENDING: 'asc', + DESCENDING: 'desc', +}; + type Props = { rewards: Array, isLoading: boolean, @@ -87,46 +106,89 @@ export default class StakingRewards extends Component { isLoading: false, }; - constructor() { - super(); + constructor(props: Props) { + super(props); this.state = { - rewardsOrder: 'desc', - rewardsSortBy: 'date', + rewardsOrder: REWARD_ORDERS.DESCENDING, + rewardsSortBy: REWARD_FIELDS.DATE, }; } - render() { + getSortedRewards = (): Array => { + const { rewards } = this.props; const { rewardsOrder, rewardsSortBy } = this.state; - const { rewards, isLoading, onLearnMoreClick } = this.props; + return rewards.slice().sort((rewardA: Reward, rewardB: Reward) => { + const rewardCompareResult = bigNumberComparator( + rewardA.reward, + rewardB.reward, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + const walletNameCompareResult = stringComparator( + rewardA.wallet, + rewardB.wallet, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + const poolCompareResult = stringComparator( + rewardA.pool.name, + rewardB.pool.name, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + const dateCompareResult = dateComparator( + rewardA.date, + rewardB.date, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + if (rewardsSortBy === REWARD_FIELDS.REWARD) { + if (rewardCompareResult === 0) { + return walletNameCompareResult; + } + return rewardCompareResult; + } + if (rewardsSortBy === REWARD_FIELDS.WALLET_NAME) { + if (walletNameCompareResult === 0) { + return rewardCompareResult; + } + return walletNameCompareResult; + } + if (rewardsSortBy === REWARD_FIELDS.DATE) { + if (dateCompareResult === 0) { + return walletNameCompareResult; + } + return dateCompareResult; + } + if (rewardsSortBy === REWARD_FIELDS.POOL) { + if (poolCompareResult === 0) { + return walletNameCompareResult; + } + return poolCompareResult; + } + + return 0; + }); + }; + render() { + const { rewards, isLoading, onLearnMoreClick } = this.props; + const { rewardsOrder, rewardsSortBy } = this.state; const { intl } = this.context; const noRewards = !isLoading && ((rewards && !rewards.length) || !rewards); const showRewards = rewards && rewards.length > 0 && !isLoading; - - let sortedRewards; - if (showRewards) { - sortedRewards = orderBy( - rewards, - rewardsSortBy === 'pool' ? 'pool.name' : rewardsSortBy, - rewardsOrder - ); - } - + const sortedRewards = showRewards ? this.getSortedRewards() : []; const availableTableHeaders = [ { - name: 'date', + name: REWARD_FIELDS.DATE, title: intl.formatMessage(messages.tableHeaderDate), }, { - name: 'pool', + name: REWARD_FIELDS.POOL, title: intl.formatMessage(messages.tableHeaderPool), }, { - name: 'wallet', + name: REWARD_FIELDS.WALLET_NAME, title: intl.formatMessage(messages.tableHeaderWallet), }, { - name: 'reward', + name: REWARD_FIELDS.REWARD, title: intl.formatMessage(messages.tableHeaderReward), }, ]; @@ -179,12 +241,28 @@ export default class StakingRewards extends Component { {map(sortedRewards, (reward, key) => { - const rewardDate = get(reward, 'date', ''); - const rewardPoolTicker = get(reward, ['pool', 'ticker'], ''); - const rewardPoolName = get(reward, ['pool', 'name'], ''); - const rewardWallet = get(reward, 'wallet', ''); - const rewardAmount = get(reward, 'reward', ''); - const isRestoring = get(reward, 'isRestoring'); + const rewardDate = get(reward, REWARD_FIELDS.DATE, ''); + const rewardPoolTicker = get( + reward, + [REWARD_FIELDS.POOL, REWARD_FIELDS.TICKER], + '' + ); + const rewardPoolName = get( + reward, + [REWARD_FIELDS.POOL, REWARD_FIELDS.NAME], + '' + ); + const rewardWallet = get( + reward, + REWARD_FIELDS.WALLET_NAME, + '' + ); + const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); + const rewardAmount = get( + reward, + REWARD_FIELDS.REWARD + ).toFormat(DECIMAL_PLACES_IN_ADA); + return ( {rewardDate} @@ -197,13 +275,7 @@ export default class StakingRewards extends Component {

{rewardWallet} - - {isRestoring - ? '-' - : `${new BigNumber(rewardAmount).toFormat( - DECIMAL_PLACES_IN_ADA - )} ADA`} - + {isRestoring ? '-' : `${rewardAmount} ADA`} ); })} diff --git a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js index b4a0f84508..cb68edaa37 100644 --- a/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js +++ b/source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js @@ -3,7 +3,7 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; import SVGInline from 'react-svg-inline'; -import { get, map, orderBy } from 'lodash'; +import { get, map } from 'lodash'; import classNames from 'classnames'; import { PopOver } from 'react-polymorph/lib/components/PopOver'; import moment from 'moment'; @@ -11,6 +11,10 @@ import { Button } from 'react-polymorph/lib/components/Button'; import { ButtonSkin } from 'react-polymorph/lib/skins/simple/ButtonSkin'; import { DECIMAL_PLACES_IN_ADA } from '../../../config/numbersConfig'; import { StakingPageScrollContext } from '../layouts/StakingWithNavigation'; +import { + bigNumberComparator, + stringComparator, +} from '../../../utils/sortComparators'; import BorderedBox from '../../widgets/BorderedBox'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import sortIcon from '../../../assets/images/ascending.inline.svg'; @@ -75,6 +79,18 @@ const messages = defineMessages({ }, }); +const REWARD_FIELDS = { + WALLET_NAME: 'wallet', + IS_RESTORING: 'isRestoring', + SYNCING_PROGRESS: 'syncingProgress', + REWARD: 'reward', +}; + +const REWARD_ORDERS = { + ASCENDING: 'asc', + DESCENDING: 'desc', +}; + type Props = { rewards: Array, isLoading: boolean, @@ -101,11 +117,11 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< isExporting: false, }; - constructor() { - super(); + constructor(props: Props) { + super(props); this.state = { - rewardsOrder: 'desc', - rewardsSortBy: 'wallet', + rewardsOrder: REWARD_ORDERS.DESCENDING, + rewardsSortBy: REWARD_FIELDS.WALLET_NAME, }; } @@ -121,9 +137,9 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< ]; const date = `${moment().utc().format('YYYY-MM-DDTHHmmss.0SSS')}Z`; const exportedBody = sortedRewards.map((reward) => { - const rewardWallet = get(reward, 'wallet'); - const isRestoring = get(reward, 'isRestoring'); - const rewardAmount = get(reward, 'reward').toFormat( + const rewardWallet = get(reward, REWARD_FIELDS.WALLET_NAME); + const isRestoring = get(reward, REWARD_FIELDS.IS_RESTORING); + const rewardAmount = get(reward, REWARD_FIELDS.REWARD).toFormat( DECIMAL_PLACES_IN_ADA ); return [rewardWallet, isRestoring ? '-' : `${rewardAmount} ADA`, date]; @@ -136,6 +152,43 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< }); }; + getSortedRewards = (): Array => { + const { rewards } = this.props; + const { rewardsOrder, rewardsSortBy } = this.state; + return rewards + .slice() + .sort( + ( + rewardA: RewardForIncentivizedTestnet, + rewardB: RewardForIncentivizedTestnet + ) => { + const rewardCompareResult = bigNumberComparator( + rewardA.reward, + rewardB.reward, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + const walletNameCompareResult = stringComparator( + rewardA.wallet, + rewardB.wallet, + rewardsOrder === REWARD_ORDERS.ASCENDING + ); + if (rewardsSortBy === REWARD_FIELDS.REWARD) { + if (rewardCompareResult === 0) { + return walletNameCompareResult; + } + return rewardCompareResult; + } + if (rewardsSortBy === REWARD_FIELDS.WALLET_NAME) { + if (walletNameCompareResult === 0) { + return rewardCompareResult; + } + return walletNameCompareResult; + } + return 0; + } + ); + }; + render() { const { rewards, @@ -147,16 +200,14 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< const { intl } = this.context; const noRewards = !isLoading && ((rewards && !rewards.length) || !rewards); const showRewards = rewards && rewards.length > 0 && !isLoading; - const sortedRewards = showRewards - ? orderBy(rewards, rewardsSortBy, rewardsOrder) - : []; + const sortedRewards = showRewards ? this.getSortedRewards() : []; const availableTableHeaders = [ { - name: 'wallet', + name: REWARD_FIELDS.WALLET_NAME, title: intl.formatMessage(messages.tableHeaderWallet), }, { - name: 'reward', + name: REWARD_FIELDS.REWARD, title: intl.formatMessage(messages.tableHeaderReward), }, ]; @@ -238,12 +289,22 @@ export default class StakingRewardsForIncentivizedTestnet extends Component< {map(sortedRewards, (reward, key) => { - const rewardWallet = get(reward, 'wallet'); - const isRestoring = get(reward, 'isRestoring'); - const syncingProgress = get(reward, 'syncingProgress'); - const rewardAmount = get(reward, 'reward').toFormat( - DECIMAL_PLACES_IN_ADA + const rewardWallet = get( + reward, + REWARD_FIELDS.WALLET_NAME + ); + const isRestoring = get( + reward, + REWARD_FIELDS.IS_RESTORING + ); + const syncingProgress = get( + reward, + REWARD_FIELDS.SYNCING_PROGRESS ); + const rewardAmount = get( + reward, + REWARD_FIELDS.REWARD + ).toFormat(DECIMAL_PLACES_IN_ADA); return ( diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 66f11f03c9..73058c44ce 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -5357,13 +5357,13 @@ "description": "Title \"Earned delegation rewards\" label on the staking rewards page.", "end": { "column": 3, - "line": 24 + "line": 28 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.title", "start": { "column": 9, - "line": 19 + "line": 23 } }, { @@ -5371,13 +5371,13 @@ "description": "Label for the \"Export CSV\" button on the staking rewards page.", "end": { "column": 3, - "line": 30 + "line": 34 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.exportButtonLabel", "start": { "column": 21, - "line": 25 + "line": 29 } }, { @@ -5385,13 +5385,13 @@ "description": "\"No rewards\" rewards label on staking rewards page.", "end": { "column": 3, - "line": 35 + "line": 39 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.no.rewards", "start": { "column": 13, - "line": 31 + "line": 35 } }, { @@ -5399,13 +5399,13 @@ "description": "Table header \"Date\" label on staking rewards page", "end": { "column": 3, - "line": 40 + "line": 44 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.tableHeader.date", "start": { "column": 19, - "line": 36 + "line": 40 } }, { @@ -5413,13 +5413,13 @@ "description": "Table header \"Stake pool\" label on staking rewards page", "end": { "column": 3, - "line": 45 + "line": 49 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.tableHeader.pool", "start": { "column": 19, - "line": 41 + "line": 45 } }, { @@ -5427,13 +5427,13 @@ "description": "Table header \"Wallet\" label on staking rewards page", "end": { "column": 3, - "line": 50 + "line": 54 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.tableHeader.wallet", "start": { "column": 21, - "line": 46 + "line": 50 } }, { @@ -5441,13 +5441,13 @@ "description": "Table header \"Reward\" label on staking rewards page", "end": { "column": 3, - "line": 55 + "line": 59 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.tableHeader.reward", "start": { "column": 21, - "line": 51 + "line": 55 } }, { @@ -5455,13 +5455,13 @@ "description": "Label for \"Learn more\" button on staking rewards page", "end": { "column": 3, - "line": 60 + "line": 64 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.learnMore.ButtonLabel", "start": { "column": 24, - "line": 56 + "line": 60 } }, { @@ -5469,13 +5469,13 @@ "description": "Rewards description text on staking rewards page", "end": { "column": 3, - "line": 66 + "line": 70 }, "file": "source/renderer/app/components/staking/rewards/StakingRewards.js", "id": "staking.rewards.note", "start": { "column": 8, - "line": 61 + "line": 65 } } ], @@ -5488,13 +5488,13 @@ "description": "Title \"Earned delegation rewards\" label on the staking rewards page.", "end": { "column": 3, - "line": 27 + "line": 31 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.title", "start": { "column": 9, - "line": 22 + "line": 26 } }, { @@ -5502,13 +5502,13 @@ "description": "Filename prefix for the \"Export CSV\" on the staking rewards page.", "end": { "column": 3, - "line": 33 + "line": 37 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.csvFilenamePrefix", "start": { "column": 21, - "line": 28 + "line": 32 } }, { @@ -5516,13 +5516,13 @@ "description": "Label for the \"Export CSV\" button on the staking rewards page.", "end": { "column": 3, - "line": 39 + "line": 43 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.exportButtonLabel", "start": { "column": 21, - "line": 34 + "line": 38 } }, { @@ -5530,13 +5530,13 @@ "description": "\"No rewards\" rewards label on staking rewards page.", "end": { "column": 3, - "line": 44 + "line": 48 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.no.rewards", "start": { "column": 13, - "line": 40 + "line": 44 } }, { @@ -5544,13 +5544,13 @@ "description": "Table header \"Wallet\" label on staking rewards page", "end": { "column": 3, - "line": 49 + "line": 53 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.wallet", "start": { "column": 21, - "line": 45 + "line": 49 } }, { @@ -5558,13 +5558,13 @@ "description": "Table header \"Reward\" label on staking rewards page", "end": { "column": 3, - "line": 54 + "line": 58 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.reward", "start": { "column": 21, - "line": 50 + "line": 54 } }, { @@ -5572,13 +5572,13 @@ "description": "Table header \"Date\" label in exported csv file", "end": { "column": 3, - "line": 59 + "line": 63 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.tableHeader.date", "start": { "column": 19, - "line": 55 + "line": 59 } }, { @@ -5586,13 +5586,13 @@ "description": "Label for \"Learn more\" button on staking rewards page", "end": { "column": 3, - "line": 64 + "line": 68 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.learnMore.ButtonLabel", "start": { "column": 24, - "line": 60 + "line": 64 } }, { @@ -5600,13 +5600,13 @@ "description": "Rewards description text on staking rewards page", "end": { "column": 3, - "line": 70 + "line": 74 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.rewards.note", "start": { "column": 8, - "line": 65 + "line": 69 } }, { @@ -5614,13 +5614,13 @@ "description": "unknown stake pool label on staking rewards page.", "end": { "column": 3, - "line": 75 + "line": 79 }, "file": "source/renderer/app/components/staking/rewards/StakingRewardsForIncentivizedTestnet.js", "id": "staking.delegationCenter.syncingTooltipLabel", "start": { "column": 23, - "line": 71 + "line": 75 } } ], diff --git a/source/renderer/app/utils/sortComparators.js b/source/renderer/app/utils/sortComparators.js new file mode 100644 index 0000000000..cd0e19a440 --- /dev/null +++ b/source/renderer/app/utils/sortComparators.js @@ -0,0 +1,51 @@ +// @flow +import BigNumber from 'bignumber.js'; +import moment from 'moment'; + +export const bigNumberComparator = ( + numberA: BigNumber, + numberB: BigNumber, + isAscending: boolean = true +): number => { + if (numberA.isLessThan(numberB)) { + return isAscending ? -1 : 1; + } + + if (numberA.isGreaterThan(numberB)) { + return isAscending ? 1 : -1; + } + + return 0; +}; + +export const stringComparator = ( + stringA: string, + stringB: string, + isAscending: boolean = true +): number => { + if (stringA < stringB) { + return isAscending ? -1 : 1; + } + + if (stringA > stringB) { + return isAscending ? 1 : -1; + } + + return 0; +}; + +export const dateComparator = ( + dateA: string, + dateB: string, + isAscending: boolean = true +): number => { + if (moment(dateA).unix() < moment(dateB).unix()) { + return isAscending ? -1 : 1; + } + + if (moment(dateA).unix() > moment(dateB).unix()) { + return isAscending ? 1 : -1; + } + + return 0; +}; From fa71d3b20995de7233af135464fdb14a4e6fe8dd Mon Sep 17 00:00:00 2001 From: Yakov Karavelov Date: Wed, 3 Feb 2021 07:46:29 -0500 Subject: [PATCH 13/37] [DDW-548] Update recovery phrase input placeholder (#2334) * [DDW-548]: Update receovery phrase input placeholder * [DDW-548]: Update changelog * [DDW-548]: Update receovery phrase input placeholder * [DDW-548]: Update recovery phrase input placeholder on verification dialog Co-authored-by: Nikola Glumac --- CHANGELOG.md | 3 +- .../components/wallet/WalletRestoreDialog.js | 18 +- .../WalletRecoveryPhraseEntryDialog.js | 11 +- .../WalletRecoveryPhraseStep2Dialog.js | 16 +- .../wallet/wallet-restore/MnemonicsDialog.js | 4 +- .../app/i18n/locales/defaultMessages.json | 164 ++++++++++-------- source/renderer/app/i18n/locales/en-US.json | 6 +- source/renderer/app/i18n/locales/ja-JP.json | 6 +- 8 files changed, 138 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74ba48f7c2..fa6ce7e1a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ Changelog ### Chores -- Adjusted sorting on rewards ([PR 2333](https://github.com/input-output-hk/daedalus/pull/2333)) +- Updated recovery phrase entry ([PR 2334](https://github.com/input-output-hk/daedalus/pull/2334)) +- Adjusted sorting of table values on the "Rewards" screen ([PR 2333](https://github.com/input-output-hk/daedalus/pull/2333)) - Fixed error thrown when closing delegation wizard while transaction fees are being calculated ([PR 2330](https://github.com/input-output-hk/daedalus/pull/2330)) - Fixed number format for syncing percentage and stake pools count ([PR 2313](https://github.com/input-output-hk/daedalus/pull/2313)) - Updated `cardano-wallet` to version `2021-01-28` and `cardano-node` to version `1.25.1` ([PR 2270](https://github.com/input-output-hk/daedalus/pull/2270)) diff --git a/source/renderer/app/components/wallet/WalletRestoreDialog.js b/source/renderer/app/components/wallet/WalletRestoreDialog.js index 924edc2b5f..a9d7a8c90c 100644 --- a/source/renderer/app/components/wallet/WalletRestoreDialog.js +++ b/source/renderer/app/components/wallet/WalletRestoreDialog.js @@ -34,7 +34,6 @@ import { } from '../../config/walletsConfig'; import { LEGACY_WALLET_RECOVERY_PHRASE_WORD_COUNT, - PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT, WALLET_RECOVERY_PHRASE_WORD_COUNT, YOROI_WALLET_RECOVERY_PHRASE_WORD_COUNT, } from '../../config/cryptoConfig'; @@ -176,6 +175,12 @@ const messages = defineMessages({ description: 'Hint "Enter your 27-word paper wallet recovery phrase." for the recovery phrase input on the wallet restore dialog.', }, + shieldedRecoveryPhraseInputPlaceholder: { + id: 'wallet.restore.dialog.shielded.recovery.phrase.input.placeholder', + defaultMessage: '!!!Enter word #{wordNumber}', + description: + 'Placeholder "Enter word #" for the recovery phrase input on the wallet restore dialog.', + }, restorePaperWalletButtonLabel: { id: 'wallet.restore.dialog.paper.wallet.button.label', defaultMessage: '!!!Restore paper wallet', @@ -387,9 +392,7 @@ export default class WalletRestoreDialog extends Component { const label = this.isCertificate() ? this.context.intl.formatMessage(messages.restorePaperWalletButtonLabel) : this.context.intl.formatMessage(messages.importButtonLabel); - const buttonLabel = !isSubmitting ? label : ; - const actions = [ { label: buttonLabel, @@ -576,9 +579,12 @@ export default class WalletRestoreDialog extends Component { placeholder={ !this.isCertificate() ? intl.formatMessage(messages.recoveryPhraseInputHint) - : intl.formatMessage(messages.shieldedRecoveryPhraseInputHint, { - numberOfWords: PAPER_WALLET_RECOVERY_PHRASE_WORD_COUNT, - }) + : intl.formatMessage( + messages.shieldedRecoveryPhraseInputPlaceholder, + { + wordNumber: recoveryPhraseField.value.length + 1, + } + ) } options={suggestedMnemonics} requiredSelections={[RECOVERY_PHRASE_WORD_COUNT_OPTIONS[walletType]]} diff --git a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js index 42d6ccc313..3e145634e9 100644 --- a/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js +++ b/source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js @@ -42,6 +42,12 @@ const messages = defineMessages({ recoveryPhraseInputHint: { id: 'wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputHint', defaultMessage: '!!!Enter your {numberOfWords}-word recovery phrase', + description: 'Placeholder hint for the mnemonics autocomplete.', + }, + recoveryPhraseInputPlaceholder: { + id: + 'wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputPlaceholder', + defaultMessage: '!!!Enter word #{wordNumber}', description: 'Placeholder for the mnemonics autocomplete.', }, recoveryPhraseNoResults: { @@ -169,7 +175,6 @@ export default class WalletRecoveryPhraseEntryDialog extends Component { ]); const wordCount = WALLET_RECOVERY_PHRASE_WORD_COUNT; const enteredPhraseString = enteredPhrase.join(' '); - const buttonLabel = !isSubmitting ? ( intl.formatMessage(messages.buttonLabelConfirm) ) : ( @@ -212,9 +217,9 @@ export default class WalletRecoveryPhraseEntryDialog extends Component { {...recoveryPhraseField.bind()} label={intl.formatMessage(messages.recoveryPhraseInputLabel)} placeholder={intl.formatMessage( - messages.recoveryPhraseInputHint, + messages.recoveryPhraseInputPlaceholder, { - numberOfWords: wordCount, + wordNumber: enteredPhrase.length + 1, } )} options={suggestedMnemonics} diff --git a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.js b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.js index 214263d89a..10f3eeadeb 100644 --- a/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.js +++ b/source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.js @@ -41,11 +41,11 @@ export const messages = defineMessages({ defaultMessage: '!!!Verify', description: 'Label for the recoveryPhraseStep2Button on wallet settings.', }, - recoveryPhraseInputHint: { - id: 'wallet.settings.recoveryPhraseInputHint', - defaultMessage: '!!!Enter recovery phrase', + recoveryPhraseInputPlaceholder: { + id: 'wallet.settings.recoveryPhraseInputPlaceholder', + defaultMessage: '!!!Enter word #{wordNumber}', description: - 'Hint "Enter recovery phrase" for the recovery phrase input on the wallet restore dialog.', + 'Placeholder "Enter word #{wordNumber}" for the recovery phrase input on the verification dialog.', }, recoveryPhraseNoResults: { id: 'wallet.settings.recoveryPhraseInputNoResults', @@ -155,11 +155,15 @@ export default class WalletRecoveryPhraseStep2Dialog extends Component<

{intl.formatMessage(messages.recoveryPhraseStep2Description)}

- { Array.isArray(expectedWordCount) ? intl.formatMessage(messages.autocompleteMultiLengthPhrase) : intl.formatMessage(messages.autocompletePlaceholder, { - numberOfWords: expectedWordCount, + wordNumber: mnemonics.length + 1, }) } options={validWords} diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 73058c44ce..54634377f2 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -7719,7 +7719,7 @@ }, { "defaultMessage": "!!!Enter your {numberOfWords}-word recovery phrase", - "description": "Placeholder for the mnemonics autocomplete.", + "description": "Placeholder hint for the mnemonics autocomplete.", "end": { "column": 3, "line": 46 @@ -7731,18 +7731,32 @@ "line": 42 } }, + { + "defaultMessage": "!!!Enter word #{wordNumber}", + "description": "Placeholder for the mnemonics autocomplete.", + "end": { + "column": 3, + "line": 52 + }, + "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", + "id": "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputPlaceholder", + "start": { + "column": 34, + "line": 47 + } + }, { "defaultMessage": "!!!No results", "description": "\"No results\" message for the recovery phrase input search results.", "end": { "column": 3, - "line": 53 + "line": 59 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputNoResults", "start": { "column": 27, - "line": 47 + "line": 53 } }, { @@ -7750,13 +7764,13 @@ "description": "Error message shown when invalid recovery phrase was entered.", "end": { "column": 3, - "line": 60 + "line": 66 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInvalidMnemonics", "start": { "column": 34, - "line": 54 + "line": 60 } }, { @@ -7764,13 +7778,13 @@ "description": "Label for button \"Confirm\" on wallet backup dialog", "end": { "column": 3, - "line": 65 + "line": 71 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.recovery.phrase.show.entry.dialog.button.labelConfirm", "start": { "column": 22, - "line": 61 + "line": 67 } }, { @@ -7778,13 +7792,13 @@ "description": "Term on wallet creation to store recovery phrase offline", "end": { "column": 3, - "line": 72 + "line": 78 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.offline", "start": { "column": 15, - "line": 66 + "line": 72 } }, { @@ -7792,13 +7806,13 @@ "description": "Term and condition on wallet backup dialog describing that wallet can only be recovered with a security phrase", "end": { "column": 3, - "line": 80 + "line": 86 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery", "start": { "column": 16, - "line": 73 + "line": 79 } }, { @@ -7806,13 +7820,13 @@ "description": "Term and condition on wallet backup dialog describing that wallet can only be recovered with a security phrase", "end": { "column": 3, - "line": 87 + "line": 93 }, "file": "source/renderer/app/components/wallet/backup-recovery/WalletRecoveryPhraseEntryDialog.js", "id": "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.rewards", "start": { "column": 15, - "line": 81 + "line": 87 } } ], @@ -9517,16 +9531,16 @@ } }, { - "defaultMessage": "!!!Enter recovery phrase", - "description": "Hint \"Enter recovery phrase\" for the recovery phrase input on the wallet restore dialog.", + "defaultMessage": "!!!Enter word #{wordNumber}", + "description": "Placeholder \"Enter word #{wordNumber}\" for the recovery phrase input on the verification dialog.", "end": { "column": 3, "line": 49 }, "file": "source/renderer/app/components/wallet/settings/WalletRecoveryPhraseStep2Dialog.js", - "id": "wallet.settings.recoveryPhraseInputHint", + "id": "wallet.settings.recoveryPhraseInputPlaceholder", "start": { - "column": 27, + "column": 34, "line": 44 } }, @@ -11933,7 +11947,7 @@ { "descriptors": [ { - "defaultMessage": "!!!Enter your {numberOfWords}-word recovery phrase", + "defaultMessage": "!!!Enter word #{wordNumber}", "description": "Placeholder for the mnemonics autocomplete.", "end": { "column": 3, @@ -12962,13 +12976,13 @@ "description": "Label \"Restore wallet\" on the wallet restore dialog.", "end": { "column": 3, - "line": 49 + "line": 48 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.title.label", "start": { "column": 9, - "line": 45 + "line": 44 } }, { @@ -12976,13 +12990,13 @@ "description": "Label for the wallet name input on the wallet restore dialog.", "end": { "column": 3, - "line": 55 + "line": 54 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.wallet.name.input.label", "start": { "column": 24, - "line": 50 + "line": 49 } }, { @@ -12990,13 +13004,13 @@ "description": "Hint \"Name the wallet you are restoring\" for the wallet name input on the wallet restore dialog.", "end": { "column": 3, - "line": 61 + "line": 60 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.wallet.name.input.hint", "start": { "column": 23, - "line": 56 + "line": 55 } }, { @@ -13004,13 +13018,13 @@ "description": "Label for the recovery phrase type options on the wallet restore dialog.", "end": { "column": 3, - "line": 67 + "line": 66 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.type.options.label", "start": { "column": 27, - "line": 62 + "line": 61 } }, { @@ -13018,13 +13032,13 @@ "description": "Word for the recovery phrase type on the wallet restore dialog.", "end": { "column": 3, - "line": 73 + "line": 72 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.type.word", "start": { "column": 32, - "line": 68 + "line": 67 } }, { @@ -13032,13 +13046,13 @@ "description": "Label for the recovery phrase type 15-word option on the wallet restore dialog.", "end": { "column": 3, - "line": 79 + "line": 78 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.type.15word.option", "start": { "column": 34, - "line": 74 + "line": 73 } }, { @@ -13046,13 +13060,13 @@ "description": "Label for the recovery phrase type 12-word option on the wallet restore dialog.", "end": { "column": 3, - "line": 85 + "line": 84 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.type.12word.option", "start": { "column": 34, - "line": 80 + "line": 79 } }, { @@ -13060,13 +13074,13 @@ "description": "Label for the recovery phrase input on the wallet restore dialog.", "end": { "column": 3, - "line": 91 + "line": 90 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.input.label", "start": { "column": 28, - "line": 86 + "line": 85 } }, { @@ -13074,13 +13088,13 @@ "description": "Hint \"Enter recovery phrase\" for the recovery phrase input on the wallet restore dialog.", "end": { "column": 3, - "line": 97 + "line": 96 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.input.hint", "start": { "column": 27, - "line": 92 + "line": 91 } }, { @@ -13088,13 +13102,13 @@ "description": "Label \"new\" on the wallet restore dialog.", "end": { "column": 3, - "line": 102 + "line": 101 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.newLabel", "start": { "column": 12, - "line": 98 + "line": 97 } }, { @@ -13102,13 +13116,13 @@ "description": "\"No results\" message for the recovery phrase input search results.", "end": { "column": 3, - "line": 108 + "line": 107 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.recovery.phrase.input.noResults", "start": { "column": 27, - "line": 103 + "line": 102 } }, { @@ -13116,13 +13130,13 @@ "description": "Label for the \"Restore wallet\" button on the wallet restore dialog.", "end": { "column": 3, - "line": 114 + "line": 113 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.restore.wallet.button.label", "start": { "column": 21, - "line": 109 + "line": 108 } }, { @@ -13130,13 +13144,13 @@ "description": "Error message shown when invalid recovery phrase was entered.", "end": { "column": 3, - "line": 120 + "line": 119 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.form.errors.invalidRecoveryPhrase", "start": { "column": 25, - "line": 115 + "line": 114 } }, { @@ -13144,13 +13158,13 @@ "description": "Password creation label.", "end": { "column": 3, - "line": 125 + "line": 124 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.passwordSectionLabel", "start": { "column": 24, - "line": 121 + "line": 120 } }, { @@ -13158,13 +13172,13 @@ "description": "Password creation description.", "end": { "column": 3, - "line": 131 + "line": 130 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.passwordSectionDescription", "start": { "column": 30, - "line": 126 + "line": 125 } }, { @@ -13172,13 +13186,13 @@ "description": "Label for the \"Wallet password\" input in the wallet restore dialog.", "end": { "column": 3, - "line": 137 + "line": 136 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.spendingPasswordLabel", "start": { "column": 25, - "line": 132 + "line": 131 } }, { @@ -13186,13 +13200,13 @@ "description": "Label for the \"Repeat password\" input in the wallet restore dialog.", "end": { "column": 3, - "line": 143 + "line": 142 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.repeatPasswordLabel", "start": { "column": 23, - "line": 138 + "line": 137 } }, { @@ -13200,13 +13214,13 @@ "description": "Placeholder for the \"Password\" inputs in the wallet restore dialog.", "end": { "column": 3, - "line": 149 + "line": 148 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.passwordFieldPlaceholder", "start": { "column": 28, - "line": 144 + "line": 143 } }, { @@ -13214,13 +13228,13 @@ "description": "Tab title \"Daedalus wallet\" in the wallet restore dialog.", "end": { "column": 3, - "line": 154 + "line": 153 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.tab.title.recoveryPhrase", "start": { "column": 26, - "line": 150 + "line": 149 } }, { @@ -13228,13 +13242,13 @@ "description": "Tab title \"Daedalus paper wallet\" in the wallet restore dialog.", "end": { "column": 3, - "line": 160 + "line": 159 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.tab.title.certificate", "start": { "column": 23, - "line": 155 + "line": 154 } }, { @@ -13242,13 +13256,13 @@ "description": "Tab title \"Yoroi wallet\" in the wallet restore dialog.", "end": { "column": 3, - "line": 165 + "line": 164 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.tab.title.yoroi", "start": { "column": 17, - "line": 161 + "line": 160 } }, { @@ -13256,13 +13270,13 @@ "description": "Label for the shielded recovery phrase input on the wallet restore dialog.", "end": { "column": 3, - "line": 171 + "line": 170 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.shielded.recovery.phrase.input.label", "start": { "column": 36, - "line": 166 + "line": 165 } }, { @@ -13270,13 +13284,27 @@ "description": "Hint \"Enter your 27-word paper wallet recovery phrase.\" for the recovery phrase input on the wallet restore dialog.", "end": { "column": 3, - "line": 178 + "line": 177 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.shielded.recovery.phrase.input.hint", "start": { "column": 35, - "line": 172 + "line": 171 + } + }, + { + "defaultMessage": "!!!Enter word #{wordNumber}", + "description": "Placeholder \"Enter word #\" for the recovery phrase input on the wallet restore dialog.", + "end": { + "column": 3, + "line": 183 + }, + "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", + "id": "wallet.restore.dialog.shielded.recovery.phrase.input.placeholder", + "start": { + "column": 42, + "line": 178 } }, { @@ -13284,13 +13312,13 @@ "description": "Label for the \"Restore paper wallet\" button on the wallet restore dialog.", "end": { "column": 3, - "line": 184 + "line": 189 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.restore.dialog.paper.wallet.button.label", "start": { "column": 33, - "line": 179 + "line": 184 } }, { @@ -13298,13 +13326,13 @@ "description": "Tooltip for the password input in the wallet dialog.", "end": { "column": 3, - "line": 190 + "line": 195 }, "file": "source/renderer/app/components/wallet/WalletRestoreDialog.js", "id": "wallet.dialog.passwordTooltip", "start": { "column": 19, - "line": 185 + "line": 190 } } ], diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index c2c4c16278..4ed064cee6 100755 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -630,6 +630,7 @@ "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputHint": "Enter your {numberOfWords}-word recovery phrase", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputLabel": "Verify your recovery phrase", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputNoResults": "No results", + "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputPlaceholder": "Enter word #{wordNumber}", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInvalidMnemonics": "Invalid recovery phrase", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.offline": "I understand that the simplest way to keep my wallet recovery phrase secure is to never store it digitally or online. If I decide to use an online service, such as a password manager with an encrypted database, it is my responsibility to make sure that I use it correctly.", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "I understand that the only way to recover my wallet if my computer is lost, broken, stolen, or stops working is to use my wallet recovery phrase.", @@ -761,6 +762,7 @@ "wallet.restore.dialog.restore.wallet.button.label": "Restore wallet", "wallet.restore.dialog.shielded.recovery.phrase.input.hint": "Enter your {numberOfWords}-word paper wallet recovery phrase", "wallet.restore.dialog.shielded.recovery.phrase.input.label": "27-word paper wallet recovery phrase", + "wallet.restore.dialog.shielded.recovery.phrase.input.placeholder": "Enter word #{wordNumber}", "wallet.restore.dialog.spendingPasswordLabel": "Enter password", "wallet.restore.dialog.step.configuration.continueButtonLabel": "Continue", "wallet.restore.dialog.step.configuration.description1": "Name your restored wallet and set a spending password to keep your wallet secure.", @@ -774,7 +776,7 @@ "wallet.restore.dialog.step.mnemonics.autocomplete.invalidRecoveryPhrase": "Invalid recovery phrase", "wallet.restore.dialog.step.mnemonics.autocomplete.multiLengthPhrase.placeholder": "Enter your 12, 18 or 24-word recovery phrase", "wallet.restore.dialog.step.mnemonics.autocomplete.noResults": "No results", - "wallet.restore.dialog.step.mnemonics.autocomplete.placeholder": "Enter your {numberOfWords}-word recovery phrase", + "wallet.restore.dialog.step.mnemonics.autocomplete.placeholder": "Enter word #{wordNumber}", "wallet.restore.dialog.step.success.dialog.close": "Close", "wallet.restore.dialog.step.success.dialog.description.line1": "Your wallet has been successfully restored.", "wallet.restore.dialog.step.success.dialog.description.line2": "Restored wallets should have all the funds and transaction history of the original wallet. If your restored wallet does not have the funds and transaction history you were expecting, please check that you have the correct wallet recovery phrase for the wallet you were intending to restore.", @@ -877,8 +879,8 @@ "wallet.settings.password": "Password", "wallet.settings.passwordLastUpdated": "Last updated {lastUpdated}", "wallet.settings.passwordNotSet": "You still don't have password", - "wallet.settings.recoveryPhraseInputHint": "Enter recovery phrase", "wallet.settings.recoveryPhraseInputNoResults": "No results", + "wallet.settings.recoveryPhraseInputPlaceholder": "Enter word #{wordNumber}", "wallet.settings.recoveryPhraseStep1Button": "Continue", "wallet.settings.recoveryPhraseStep1Paragraph1": "To verify that you have the correct recovery phrase for this wallet, you can enter it on the following screen.", "wallet.settings.recoveryPhraseStep1Paragraph2": "Are you being watched? Please make sure that nobody can see your screen while you are entering your wallet recovery phrase.", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index 2fc93650ea..4157aa2fb7 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -630,6 +630,7 @@ "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputHint": "{numberOfWords}語のウォレット復元フレーズを入力してください", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputLabel": "復元フレーズを検証してください", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputNoResults": "該当するフレーズは見つかりませんでした", + "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInputPlaceholder": "{wordNumber}番目の単語を入力", "wallet.backup.recovery.phrase.entry.dialog.recoveryPhraseInvalidMnemonics": "無効な復元フレーズ", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.offline": "ウォレット復元フレーズを安全に保管する最もシンプルな方法はデジタルまたはオンラインに保存しないことであることを理解しました。暗号化データベース付きパスワードマネージャーなどオンラインサービスを使用する場合は、自己責任で適切に使用します。", "wallet.backup.recovery.phrase.entry.dialog.terms.and.condition.recovery": "コンピューターが喪失、故障、盗難、作動不能などとなった場合、ウォレットは復元フレーズを使用することによってのみ復元することができることを理解しました。", @@ -761,6 +762,7 @@ "wallet.restore.dialog.restore.wallet.button.label": "ウォレットを復元する", "wallet.restore.dialog.shielded.recovery.phrase.input.hint": "{numberOfWords}語のペーパーウォレット復元フレーズを入力してください。", "wallet.restore.dialog.shielded.recovery.phrase.input.label": "27語のペーパーウォレット復元フレーズ", + "wallet.restore.dialog.shielded.recovery.phrase.input.placeholder": "{wordNumber}番目の単語を入力", "wallet.restore.dialog.spendingPasswordLabel": "パスワードを入力してください", "wallet.restore.dialog.step.configuration.continueButtonLabel": "続ける", "wallet.restore.dialog.step.configuration.description1": "復元したウォレットに名前を付け、ウォレットを安全に保つために送信時パスワードを設定してください。", @@ -774,7 +776,7 @@ "wallet.restore.dialog.step.mnemonics.autocomplete.invalidRecoveryPhrase": "無効な復元フレーズ", "wallet.restore.dialog.step.mnemonics.autocomplete.multiLengthPhrase.placeholder": "12語、18語、または24語のウォレット復元フレーズを入力してください", "wallet.restore.dialog.step.mnemonics.autocomplete.noResults": "該当するフレーズは見つかりませんでした", - "wallet.restore.dialog.step.mnemonics.autocomplete.placeholder": "{numberOfWords}語のウォレット復元フレーズを入力してください", + "wallet.restore.dialog.step.mnemonics.autocomplete.placeholder": "{wordNumber}番目の単語を入力", "wallet.restore.dialog.step.success.dialog.close": "閉じる", "wallet.restore.dialog.step.success.dialog.description.line1": "ウォレットは無事に復元されました。", "wallet.restore.dialog.step.success.dialog.description.line2": "復元されたウォレットにはオリジナルウォレットの資金およびトランザクション履歴がすべて含まれているはずです。復元されたウォレットに期待していた資金やトランザクション履歴が入っていない場合には, 復元対象となるウォレットの正しい復元フレーズを使用しているか確認してください。", @@ -877,8 +879,8 @@ "wallet.settings.password": "パスワード", "wallet.settings.passwordLastUpdated": "最終更新 {lastUpdated}", "wallet.settings.passwordNotSet": "パスワードが設定されていません", - "wallet.settings.recoveryPhraseInputHint": "復元フレーズを入力してください", "wallet.settings.recoveryPhraseInputNoResults": "該当するフレーズは見つかりませんでした", + "wallet.settings.recoveryPhraseInputPlaceholder": "{wordNumber}番目の単語を入力", "wallet.settings.recoveryPhraseStep1Button": "続ける", "wallet.settings.recoveryPhraseStep1Paragraph1": "お持ちの復元フレーズが正しいかどうか確認するには、次の画面で復元フレーズを入力してください。", "wallet.settings.recoveryPhraseStep1Paragraph2": "誰かに見られていませんか。ウォレット復元フレーズを入力する際には、画面を誰にも見られないように注意してください。", From 3b45954a43682a0900032402e41f1e6ffbbf525a Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Wed, 3 Feb 2021 16:03:18 +0100 Subject: [PATCH 14/37] [DDW-528] Spacing issues with "i" icon on "Set password" dialog (#2337) * [DDW-528] Fix spacing issues with the info icon on the 'Set password' dialog * [DDW-528] Adds CHANGELOG entry --- CHANGELOG.md | 1 + .../wallet/settings/ChangeSpendingPasswordDialog.js | 2 +- .../settings/ChangeSpendingPasswordDialog.scss | 12 +++++++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6ce7e1a6..651d32e9d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Changelog ### Fixes +- Fixed misalignment of the "i" icon on the "Set password" dialog ([PR 2337](https://github.com/input-output-hk/daedalus/pull/2337)) - Removed steps counter from the "Success" wallet restoration dialog step ([PR 2335](https://github.com/input-output-hk/daedalus/pull/2335)) ### Chores diff --git a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.js b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.js index 6de7491388..0750aafc6d 100644 --- a/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.js +++ b/source/renderer/app/components/wallet/settings/ChangeSpendingPasswordDialog.js @@ -299,7 +299,7 @@ export default class ChangeSpendingPasswordDialog extends Component { )}
-
+
span { height: 12px; - left: 145px; + left: 158px; outline: none; position: absolute; top: 4px; @@ -35,6 +35,16 @@ &.jpLangTooltipIcon { > span { + left: 134px !important; + } + } + + .currentPassword + span { + left: 145px !important; + } + + &.jpLangTooltipIcon { + .currentPassword + span { left: 135px !important; } } From 2d00d076a340fdfa96752f0f8440d4610588e85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomislav=20Hora=C4=8Dek?= Date: Wed, 3 Feb 2021 16:18:11 +0100 Subject: [PATCH 15/37] [DDW-533] Add "Fix connection Issues" link to the hardware wallet pairing dialog (#2336) * [DDW-533] INIT connection issue link on the hw pairing dialog * [DDW-533] Apply latest designs and support links update * [DDW-533] Add Translations * [DDW-533] Updates CHANGELOG * [DDW-533] Update to fit latest design proposal Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + .../components/wallet/WalletConnectDialog.js | 46 ++++++++++++++- .../wallet/WalletConnectDialog.scss | 18 ++++++ .../dialogs/WalletConnectDialogContainer.js | 1 + .../app/i18n/locales/defaultMessages.json | 58 ++++++++++++++++--- source/renderer/app/i18n/locales/en-US.json | 3 + source/renderer/app/i18n/locales/ja-JP.json | 3 + 7 files changed, 121 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 651d32e9d4..1ac11ab7f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Changelog ### Chores +- Added link to connecting issues support article on the hardware wallet "Pairing" dialog ([PR 2336](https://github.com/input-output-hk/daedalus/pull/2336)) - Updated recovery phrase entry ([PR 2334](https://github.com/input-output-hk/daedalus/pull/2334)) - Adjusted sorting of table values on the "Rewards" screen ([PR 2333](https://github.com/input-output-hk/daedalus/pull/2333)) - Fixed error thrown when closing delegation wizard while transaction fees are being calculated ([PR 2330](https://github.com/input-output-hk/daedalus/pull/2330)) diff --git a/source/renderer/app/components/wallet/WalletConnectDialog.js b/source/renderer/app/components/wallet/WalletConnectDialog.js index fa90285d1d..7f7072211b 100644 --- a/source/renderer/app/components/wallet/WalletConnectDialog.js +++ b/source/renderer/app/components/wallet/WalletConnectDialog.js @@ -3,8 +3,15 @@ import React, { Component } from 'react'; import { observer } from 'mobx-react'; import classnames from 'classnames'; -import { defineMessages, intlShape, FormattedHTMLMessage } from 'react-intl'; +import { + defineMessages, + intlShape, + FormattedHTMLMessage, + FormattedMessage, +} from 'react-intl'; import SVGInline from 'react-svg-inline'; +import { Link } from 'react-polymorph/lib/components/Link'; +import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import { get } from 'lodash'; import ledgerIcon from '../../assets/images/hardware-wallet/ledger-cropped.inline.svg'; import ledgerXIcon from '../../assets/images/hardware-wallet/ledger-x-cropped.inline.svg'; @@ -52,6 +59,22 @@ const messages = defineMessages({ '!!!

Daedalus currently supports only Trezor Model T hardware wallet devices.

If you are pairing your device with Daedalus for the first time, please follow the instructions below.

If you have already paired your device with Daedalus, you don’t need to repeat this step. Just connect your device when you need to confirm a transaction.

', description: 'Follow instructions label', }, + connectingIssueSupportLabel: { + id: 'wallet.connect.dialog.connectingIssueSupportLabel', + defaultMessage: + '!!!If you are experiencing issues pairing your hardware wallet device, please {supportLink}', + description: 'Connecting issue support description', + }, + connectingIssueSupportLink: { + id: 'wallet.connect.dialog.connectingIssueSupportLink', + defaultMessage: '!!!read the instructions.', + description: 'Connecting issue support link', + }, + connectingIssueSupportLinkUrl: { + id: 'wallet.connect.dialog.connectingIssueSupportLinkUrl', + defaultMessage: 'https://support.ledger.com/hc/en-us/articles/115005165269', + description: 'Link to support article', + }, }); type Props = { @@ -133,6 +156,19 @@ export default class WalletConnectDialog extends Component { ? messages.instructions : messages.instructionsTrezorOnly; + const supportLink = ( + + onExternalLinkClick( + intl.formatMessage(messages.connectingIssueSupportLinkUrl) + ) + } + label={intl.formatMessage(messages.connectingIssueSupportLink)} + skin={LinkSkin} + /> + ); + return ( { isTransactionStatus={false} />
+
+

+ +

+
)}
diff --git a/source/renderer/app/components/wallet/WalletConnectDialog.scss b/source/renderer/app/components/wallet/WalletConnectDialog.scss index 17e528889f..7a2007e016 100644 --- a/source/renderer/app/components/wallet/WalletConnectDialog.scss +++ b/source/renderer/app/components/wallet/WalletConnectDialog.scss @@ -78,6 +78,24 @@ width: 100%; } + .hardwareWalletIssueArticleWrapper { + margin-top: 20px; + + p { + color: var(--theme-hardware-wallet-message-color); + font-family: var(--font-light); + font-size: 16px; + line-height: 22px; + } + + .externalLink { + font-family: var(--font-regular); + font-size: 16px; + margin-right: 4px; + word-break: break-word; + } + } + .error { @include error-message; margin-top: 27px; diff --git a/source/renderer/app/containers/wallet/dialogs/WalletConnectDialogContainer.js b/source/renderer/app/containers/wallet/dialogs/WalletConnectDialogContainer.js index 58dbc6ce51..5a9ba082e0 100644 --- a/source/renderer/app/containers/wallet/dialogs/WalletConnectDialogContainer.js +++ b/source/renderer/app/containers/wallet/dialogs/WalletConnectDialogContainer.js @@ -26,6 +26,7 @@ export default class WalletConnectDialogContainer extends Component { const { hardwareWallets, wallets, app } = stores; const { hwDeviceStatus, transportDevice } = hardwareWallets; const { createHardwareWalletRequest } = wallets; + return ( Daedalus supports Ledger Nano S, Ledger Nano X, and Trezor Model T hardware wallet devices.

If you are pairing your device with Daedalus for the first time, please follow the instructions below.

If you have already paired your device with Daedalus, you don’t need to repeat this step. Just connect your device when you need to confirm a transaction.

", "wallet.connect.dialog.instructionsTrezorOnly": "

Daedalus currently supports only Trezor Model T hardware wallet devices.

If you are pairing your device with Daedalus for the first time, please follow the instructions below.

If you have already paired your device with Daedalus, you don’t need to repeat this step. Just connect your device when you need to confirm a transaction.

", "wallet.connect.dialog.title": "Pair a hardware wallet device", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index 4157aa2fb7..cee7682762 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -649,6 +649,9 @@ "wallet.byron.notification.moveFundsDescription.line2.link.label": "ウォレットを新規作成する", "wallet.byron.notification.moveFundsTitle": "「{activeWalletName}」の資金を移してください", "wallet.connect.dialog.button.cancel": "キャンセル", + "wallet.connect.dialog.connectingIssueSupportLabel": "ハードウェアウォレットデバイスのペアリングで問題が生じた場合は、{supportLink}してください。", + "wallet.connect.dialog.connectingIssueSupportLink": "ガイドを参照", + "wallet.connect.dialog.connectingIssueSupportLinkUrl": "https://iohk.zendesk.com/hc/ja/articles/900004722083-How-to-use-Ledger-and-Trezor-HW-with-Daedalus", "wallet.connect.dialog.instructions": "

DaedalusはLedger Nano S、Ledger Nano X、Trezor Model Tハードウェアウォレットデバイスをサポートしています。

デバイスを初めてDaedalusにペアリングする場合は、以下の指示に従ってください。

Daedalusとペアリング済みであるデバイスの場合には、この手順を繰り返す必要はありません。トランザクションを確認する必要があるときに、デバイスを接続してください。

", "wallet.connect.dialog.instructionsTrezorOnly": "

Daedalusが現在サポートするハードウェアウォレットデバイスはTrezor Model Tのみです。

デバイスを初めてDaedalusにペアリングする場合は、以下の指示に従ってください。

Daedalusとペアリング済みであるデバイスの場合には、この手順を繰り返す必要はありません。トランザクションを確認する必要があるときに、デバイスを接続してください。

", "wallet.connect.dialog.title": "ハードウェアウォレットを接続する", From d285c78fe96b9cf2a095c8394aef5b3b6badd118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomislav=20Hora=C4=8Dek?= Date: Wed, 3 Feb 2021 18:10:14 +0100 Subject: [PATCH 16/37] [DDW-512] Implement dynamic TTL for HW transactions (#2331) * [DDW-512] Introduce dynamic TTL for HW transactions * [DDW-512] Code improvements --- CHANGELOG.md | 1 + source/renderer/app/api/api.js | 6 ++++-- source/renderer/app/api/network/types.js | 5 +++-- source/renderer/app/api/utils/patchAdaApi.js | 2 ++ source/renderer/app/config/txnsConfig.js | 1 + source/renderer/app/stores/HardwareWalletsStore.js | 14 +++++++++++--- .../stories/nodes/status/Diagnostics.stories.js | 6 +++--- .../stories/staking/DelegationCenter.stories.js | 1 + 8 files changed, 26 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac11ab7f9..bc7ec190d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Changelog ### Chores +- Implemented dynamic TTL calculation for hardware wallets transactions ([PR 2331](https://github.com/input-output-hk/daedalus/pull/2331)) - Added link to connecting issues support article on the hardware wallet "Pairing" dialog ([PR 2336](https://github.com/input-output-hk/daedalus/pull/2336)) - Updated recovery phrase entry ([PR 2334](https://github.com/input-output-hk/daedalus/pull/2334)) - Adjusted sorting of table values on the "Rewards" screen ([PR 2333](https://github.com/input-output-hk/daedalus/pull/2333)) diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index a5e08eb126..406c0108e5 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -2010,11 +2010,13 @@ export default class AdaApi { localTip: { epoch: get(nodeTip, 'epoch_number', 0), slot: get(nodeTip, 'slot_number', 0), + absoluteSlotNumber: get(nodeTip, 'absolute_slot_number', 0), }, networkTip: networkTip ? { - epoch: get(networkTip, 'epoch_number', null), - slot: get(networkTip, 'slot_number', null), + epoch: get(networkTip, 'epoch_number', 0), + slot: get(networkTip, 'slot_number', 0), + absoluteSlotNumber: get(networkTip, 'absolute_slot_number', 0), } : null, nextEpoch: nextEpoch diff --git a/source/renderer/app/api/network/types.js b/source/renderer/app/api/network/types.js index 6be5bb3355..513e09c473 100644 --- a/source/renderer/app/api/network/types.js +++ b/source/renderer/app/api/network/types.js @@ -1,7 +1,8 @@ // @flow export type TipInfo = { - epoch: ?number, - slot: ?number, + epoch: number, + slot: number, + absoluteSlotNumber: number, }; export type NextEpoch = { diff --git a/source/renderer/app/api/utils/patchAdaApi.js b/source/renderer/app/api/utils/patchAdaApi.js index 4460f6fa9b..7cb62e7098 100644 --- a/source/renderer/app/api/utils/patchAdaApi.js +++ b/source/renderer/app/api/utils/patchAdaApi.js @@ -49,10 +49,12 @@ export default (api: AdaApi) => { localTip: { epoch: get(node_tip, 'epoch_number', 0), slot: get(node_tip, 'slot_number', 0), + absoluteSlotNumber: get(node_tip, 'absolute_slot_number', 0), }, networkTip: { epoch: get(network_tip, 'epoch_number', null), slot: get(network_tip, 'slot_number', null), + absoluteSlotNumber: get(network_tip, 'absolute_slot_number', 0), }, nextEpoch: { // N+1 epoch diff --git a/source/renderer/app/config/txnsConfig.js b/source/renderer/app/config/txnsConfig.js index c4f3c73edc..e37b71ecc4 100644 --- a/source/renderer/app/config/txnsConfig.js +++ b/source/renderer/app/config/txnsConfig.js @@ -1 +1,2 @@ export const PENDING_TIME_LIMIT = 10 * 60 * 1000; // 10 minutes | unit: milliseconds +export const TIME_TO_LIVE = 2 * 60 * 60; // 2 hours | unit: seconds (7200) diff --git a/source/renderer/app/stores/HardwareWalletsStore.js b/source/renderer/app/stores/HardwareWalletsStore.js index 9363ef4f74..25dd1afe04 100644 --- a/source/renderer/app/stores/HardwareWalletsStore.js +++ b/source/renderer/app/stores/HardwareWalletsStore.js @@ -14,6 +14,7 @@ import { isTrezorEnabled, isLedgerEnabled, } from '../config/hardwareWalletsConfig'; +import { TIME_TO_LIVE } from '../config/txnsConfig'; import { getHardwareWalletTransportChannel, getExtendedPublicKeyChannel, @@ -1151,13 +1152,14 @@ export default class HardwareWalletsStore extends Store { } const { isMainnet } = this.environment; + const ttl = this._getTtl(); try { const signedTransaction = await signTransactionTrezorChannel.request({ inputs: inputsData, outputs: outputsData, fee: formattedAmountToLovelace(fee.toString()).toString(), - ttl: '150000000', + ttl: ttl.toString(), networkId: isMainnet ? HW_SHELLEY_CONFIG.NETWORK.MAINNET.networkId : HW_SHELLEY_CONFIG.NETWORK.TESTNET.networkId, @@ -1290,9 +1292,8 @@ export default class HardwareWalletsStore extends Store { }); const certificatesData = await Promise.all(_certificatesData); - const fee = formattedAmountToLovelace(flatFee.toString()); - const ttl = 150000000; + const ttl = this._getTtl(); const withdrawals = []; const metadataHashHex = null; const { isMainnet } = this.environment; @@ -1738,6 +1739,13 @@ export default class HardwareWalletsStore extends Store { return type; }; + _getTtl = (): number => { + const { networkTip } = this.stores.networkStatus; + const absoluteSlotNumber = get(networkTip, 'absoluteSlotNumber', 0); + const ttl = absoluteSlotNumber + TIME_TO_LIVE; + return ttl; + }; + _setHardwareWalletLocalData = async ({ walletId, data, diff --git a/storybook/stories/nodes/status/Diagnostics.stories.js b/storybook/stories/nodes/status/Diagnostics.stories.js index 4f2eaafbc6..9cf35e5778 100644 --- a/storybook/stories/nodes/status/Diagnostics.stories.js +++ b/storybook/stories/nodes/status/Diagnostics.stories.js @@ -67,8 +67,8 @@ storiesOf('Nodes|Status', module) 280719 )} nodeConnectionError={null} - localTip={{ epoch: 123, slot: 13400 }} - networkTip={{ epoch: 123, slot: 13400 }} + localTip={{ epoch: 123, slot: 13400, absoluteSlotNumber: 15000000 }} + networkTip={{ epoch: 123, slot: 13400, absoluteSlotNumber: 15000000 }} localBlockHeight={number('localBlockHeight', 280719)} networkBlockHeight={number('networkBlockHeight', 42539)} isCheckingSystemTime={boolean('isCheckingSystemTime', true)} @@ -109,7 +109,7 @@ storiesOf('Nodes|Status', module) 280719 )} nodeConnectionError={null} - localTip={{ epoch: 123, slot: 13400 }} + localTip={{ epoch: 123, slot: 13400, absoluteSlotNumber: 15000000 }} networkTip={null} localBlockHeight={number('localBlockHeight', 280719)} networkBlockHeight={number('networkBlockHeight', 42539)} diff --git a/storybook/stories/staking/DelegationCenter.stories.js b/storybook/stories/staking/DelegationCenter.stories.js index cda492dc2b..15974fba53 100644 --- a/storybook/stories/staking/DelegationCenter.stories.js +++ b/storybook/stories/staking/DelegationCenter.stories.js @@ -31,6 +31,7 @@ const walletSyncedStateRestoring = { const networkTip: TipInfo = { epoch: 1232, slot: 123, + absoluteSlotNumber: 15000000, }; const nextEpochDate = new Date(); From 65b1318ba11028c5e9ad3ed5b0aff4afced1e2bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomislav=20Hora=C4=8Dek?= Date: Thu, 4 Feb 2021 17:30:41 +0100 Subject: [PATCH 17/37] [DDW-559] Implement delegation deposit from coin selection (#2332) * [DDW-559] Introduce HW dynamic delegation deposit from coin selection * [DDW-559] CHANGELOG update * [DDW-559] Code improvements * [DDW-559] Change fee and deposit calculation logic to use BigNumber Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + source/renderer/app/api/api.js | 38 +++++++++---------- source/renderer/app/api/transactions/types.js | 2 +- source/renderer/app/config/stakingConfig.js | 2 - .../DelegationSetupWizardDialogContainer.js | 2 +- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc7ec190d8..09c2ed4aaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Changelog ### Chores +- Updated Hardware Wallets delegation deposit calculation ([PR 2332](https://github.com/input-output-hk/daedalus/pull/2332)) - Implemented dynamic TTL calculation for hardware wallets transactions ([PR 2331](https://github.com/input-output-hk/daedalus/pull/2331)) - Added link to connecting issues support article on the hardware wallet "Pairing" dialog ([PR 2336](https://github.com/input-output-hk/daedalus/pull/2336)) - Updated recovery phrase entry ([PR 2334](https://github.com/input-output-hk/daedalus/pull/2334)) diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 406c0108e5..f5cc29a869 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -100,7 +100,6 @@ import { LOVELACES_PER_ADA } from '../config/numbersConfig'; import { SMASH_SERVER_STATUSES, SMASH_SERVERS_LIST, - DELEGATION_DEPOSIT, MIN_REWARDS_REDEMPTION_RECEIVER_BALANCE, REWARDS_REDEMPTION_FEE_CALCULATION_AMOUNT, } from '../config/stakingConfig'; @@ -891,7 +890,6 @@ export default class AdaApi { parameters: filterLogData(request), }); const { walletId, payments, delegation } = request; - try { let data; if (delegation) { @@ -925,13 +923,15 @@ export default class AdaApi { const outputs = concat(response.outputs, response.change); // Calculate fee from inputs and outputs - let totalInputs = 0; - let totalOutputs = 0; const inputsData = []; const outputsData = []; const certificatesData = []; + let totalInputs = new BigNumber(0); + let totalOutputs = new BigNumber(0); + map(response.inputs, (input) => { - totalInputs += input.amount.quantity; + const inputAmount = new BigNumber(input.amount.quantity); + totalInputs = totalInputs.plus(inputAmount); const inputData = { address: input.address, amount: input.amount, @@ -941,8 +941,10 @@ export default class AdaApi { }; inputsData.push(inputData); }); + map(outputs, (output) => { - totalOutputs += output.amount.quantity; + const outputAmount = new BigNumber(output.amount.quantity); + totalOutputs = totalOutputs.plus(outputAmount); const outputData = { address: output.address, amount: output.amount, @@ -950,6 +952,7 @@ export default class AdaApi { }; outputsData.push(outputData); }); + if (response.certificates) { map(response.certificates, (certificate) => { const certificateData = { @@ -960,28 +963,21 @@ export default class AdaApi { certificatesData.push(certificateData); }); } - const fee = new BigNumber(totalInputs - totalOutputs).dividedBy( - LOVELACES_PER_ADA - ); - let transactionFee; - if (delegation && delegation.delegationAction) { - const delegationDeposit = new BigNumber(DELEGATION_DEPOSIT); - const isDepositIncluded = fee.gt(delegationDeposit); - transactionFee = isDepositIncluded ? fee.minus(delegationDeposit) : fee; - } else { - transactionFee = fee; - } + const deposits = map(response.deposits, (deposit) => deposit.quantity); + const totalDeposits = deposits.length + ? BigNumber.sum.apply(null, deposits) + : new BigNumber(0); + const feeWithDeposits = totalInputs.minus(totalOutputs); + const fee = feeWithDeposits.minus(totalDeposits); - // On first wallet delegation deposit is included in fee const extendedResponse = { inputs: inputsData, outputs: outputsData, certificates: certificatesData, - feeWithDelegationDeposit: fee, - fee: transactionFee, + feeWithDeposits: feeWithDeposits.dividedBy(LOVELACES_PER_ADA), + fee: fee.dividedBy(LOVELACES_PER_ADA), }; - logger.debug('AdaApi::selectCoins success', { extendedResponse }); return extendedResponse; } catch (error) { diff --git a/source/renderer/app/api/transactions/types.js b/source/renderer/app/api/transactions/types.js index a09d253b80..70ea7e1027 100644 --- a/source/renderer/app/api/transactions/types.js +++ b/source/renderer/app/api/transactions/types.js @@ -216,7 +216,7 @@ export type CoinSelectionsResponse = { inputs: Array, outputs: Array, certificates: CoinSelectionCertificates, - feeWithDelegationDeposit: BigNumber, + feeWithDeposits: BigNumber, fee: BigNumber, }; diff --git a/source/renderer/app/config/stakingConfig.js b/source/renderer/app/config/stakingConfig.js index 8eb4636455..88ca6b9bbb 100644 --- a/source/renderer/app/config/stakingConfig.js +++ b/source/renderer/app/config/stakingConfig.js @@ -104,8 +104,6 @@ export const REDEEM_ITN_REWARDS_STEPS: { RESULT: 'result', }; -export const DELEGATION_DEPOSIT = 2; // 2 ADA | unit: lovelace - export const DELEGATION_ACTIONS: { [key: string]: DelegationAction, } = { diff --git a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js index 549814101f..3ed23e297e 100644 --- a/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js +++ b/source/renderer/app/containers/staking/dialogs/DelegationSetupWizardDialogContainer.js @@ -260,7 +260,7 @@ export default class DelegationSetupWizardDialogContainer extends Component< poolId, delegationAction: DELEGATION_ACTIONS.JOIN, }); - stakePoolJoinFee = coinsSelection.feeWithDelegationDeposit; + stakePoolJoinFee = coinsSelection.feeWithDeposits; // Initiate Transaction (Delegation) hardwareWallets.initiateTransaction({ walletId: selectedWalletId }); } else { From b527992593058fe79b68b9dfd613f6140aaf4cc8 Mon Sep 17 00:00:00 2001 From: Yakov Karavelov Date: Fri, 5 Feb 2021 05:48:43 -0500 Subject: [PATCH 18/37] [DDW-522] Display fee and deposit on transaction details and delegation modal (#2339) * [DDW-522]: Display fee and deposit on transaction details and delegation confirmation modal * [DDW-522]: Update changelog * [DDW-522]: Hide deposit value when it is zero * [DDW-522]: Update hardware delegation deposit value shown up on delegation confirmation modal * [DDW-522]: Update hardware delegation deposit value shown up on delegation confirmation modal * [DDW-522]: Update delegation fee calculation * [DDW-522]: Update color variable of itn theme --- CHANGELOG.md | 1 + source/renderer/app/api/api.js | 24 ++- source/renderer/app/api/staking/types.js | 5 + .../DelegationSetupWizardDialog.js | 4 +- .../DelegationStepsConfirmationDialog.js | 72 ++++++--- .../DelegationStepsConfirmationDialog.scss | 25 ++- .../wallet/transactions/Transaction.js | 34 +++++ .../wallet/transactions/Transaction.scss | 12 ++ .../DelegationSetupWizardDialogContainer.js | 9 +- .../renderer/app/domains/WalletTransaction.js | 4 + .../app/i18n/locales/defaultMessages.json | 142 ++++++++++++------ source/renderer/app/i18n/locales/en-US.json | 4 + source/renderer/app/i18n/locales/ja-JP.json | 4 + source/renderer/app/stores/StakingStore.js | 8 +- .../renderer/app/themes/daedalus/cardano.js | 1 + .../renderer/app/themes/daedalus/dark-blue.js | 1 + .../app/themes/daedalus/dark-cardano.js | 1 + .../app/themes/daedalus/flight-candidate.js | 1 + .../themes/daedalus/incentivized-testnet.js | 1 + .../app/themes/daedalus/light-blue.js | 1 + .../app/themes/daedalus/shelley-testnet.js | 1 + source/renderer/app/themes/daedalus/white.js | 1 + source/renderer/app/themes/daedalus/yellow.js | 1 + .../renderer/app/themes/utils/createTheme.js | 1 + storybook/stories/_support/utils.js | 4 + .../_utils/WalletsTransactionsWrapper.js | 8 + 26 files changed, 287 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c2ed4aaf..d23fd110e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog ### Features +- Displayed fee and deposit info in transaction details and in the delegation wizard ([PR 2339](https://github.com/input-output-hk/daedalus/pull/2339)) - Added SMASH server configuration options ([PR 2259](https://github.com/input-output-hk/daedalus/pull/2259)) ### Fixes diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index f5cc29a869..77403a52b5 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -186,6 +186,7 @@ import type { GetNewsResponse } from './news/types'; import type { JoinStakePoolRequest, GetDelegationFeeRequest, + DelegationCalculateFeeResponse, AdaApiStakePools, AdaApiStakePool, QuitStakePoolRequest, @@ -390,9 +391,7 @@ export default class AdaApi { const transactions = response.map((tx) => _createTransactionFromServerData(tx) ); - return new Promise((resolve) => - resolve({ transactions, total: response.length }) - ); + return Promise.resolve({ transactions, total: response.length }); } catch (error) { logger.error('AdaApi::getTransactions error', { error }); throw new ApiError(error); @@ -2139,7 +2138,7 @@ export default class AdaApi { calculateDelegationFee = async ( request: GetDelegationFeeRequest - ): Promise => { + ): Promise => { logger.debug('AdaApi::calculateDelegationFee called', { parameters: filterLogData(request), }); @@ -2148,8 +2147,7 @@ export default class AdaApi { walletId: request.walletId, }); logger.debug('AdaApi::calculateDelegationFee success', { response }); - const delegationFee = _createDelegationFeeFromServerData(response); - return delegationFee; + return _createDelegationFeeFromServerData(response); } catch (error) { logger.error('AdaApi::calculateDelegationFee error', { error }); throw new ApiError(error); @@ -2305,6 +2303,8 @@ const _createTransactionFromServerData = action( const { id, amount, + fee, + deposit, inserted_at, // eslint-disable-line camelcase pending_since, // eslint-disable-line camelcase depth, @@ -2333,6 +2333,8 @@ const _createTransactionFromServerData = action( amount: new BigNumber( direction === 'outgoing' ? amount.quantity * -1 : amount.quantity ).dividedBy(LOVELACES_PER_ADA), + fee: new BigNumber(fee.quantity).dividedBy(LOVELACES_PER_ADA), + deposit: new BigNumber(deposit.quantity).dividedBy(LOVELACES_PER_ADA), date: utcStringToDate(date), description: '', addresses: { @@ -2369,8 +2371,14 @@ const _createMigrationFeeFromServerData = action( const _createDelegationFeeFromServerData = action( 'AdaApi::_createDelegationFeeFromServerData', (data: TransactionFee) => { - const amount = get(data, ['estimated_max', 'quantity'], 0); - return new BigNumber(amount).dividedBy(LOVELACES_PER_ADA); + const feeWithDeposit = new BigNumber( + get(data, ['estimated_max', 'quantity'], 0) + ).dividedBy(LOVELACES_PER_ADA); + const deposit = new BigNumber( + get(data, ['deposit', 'quantity'], 0) + ).dividedBy(LOVELACES_PER_ADA); + const fee = feeWithDeposit.minus(deposit); + return { fee, deposit }; } ); diff --git a/source/renderer/app/api/staking/types.js b/source/renderer/app/api/staking/types.js index 6f4048ea58..380d1d1858 100644 --- a/source/renderer/app/api/staking/types.js +++ b/source/renderer/app/api/staking/types.js @@ -94,6 +94,11 @@ export type GetDelegationFeeRequest = { walletId: string, }; +export type DelegationCalculateFeeResponse = { + fee: BigNumber, + deposit: BigNumber, +}; + export type QuitStakePoolRequest = { walletId: string, passphrase: string, diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js index dcece21bce..ec88cd5a3d 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationSetupWizardDialog.js @@ -1,7 +1,6 @@ // @flow import React, { Component } from 'react'; import { observer } from 'mobx-react'; -import { BigNumber } from 'bignumber.js'; import { get } from 'lodash'; import DelegationStepsSuccessDialog from './DelegationStepsSuccessDialog'; import DelegationStepsChooseWalletDialog from './DelegationStepsChooseWalletDialog'; @@ -13,6 +12,7 @@ import LocalizableError from '../../../i18n/LocalizableError'; import StakePool from '../../../domains/StakePool'; import Wallet from '../../../domains/Wallet'; +import type { DelegationCalculateFeeResponse } from '../../../api/staking/types'; import type { HwDeviceStatus } from '../../../domains/Wallet'; type Props = { @@ -35,7 +35,7 @@ type Props = { currentTheme: string, selectedWallet: ?Wallet, selectedPool: ?StakePool, - stakePoolJoinFee: ?BigNumber, + stakePoolJoinFee: ?DelegationCalculateFeeResponse, isSubmitting: boolean, error: ?LocalizableError, futureEpochStartTime: string, diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js index ba7b11a00c..c8b40c0ac2 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.js @@ -14,7 +14,6 @@ import { Stepper } from 'react-polymorph/lib/components/Stepper'; import { StepperSkin } from 'react-polymorph/lib/skins/simple/StepperSkin'; import { Input } from 'react-polymorph/lib/components/Input'; import { InputSkin } from 'react-polymorph/lib/skins/simple/InputSkin'; -import BigNumber from 'bignumber.js'; import commonStyles from './DelegationSteps.scss'; import styles from './DelegationStepsConfirmationDialog.scss'; import DialogCloseButton from '../../widgets/DialogCloseButton'; @@ -31,6 +30,7 @@ import StakePool from '../../../domains/StakePool'; import LoadingSpinner from '../../widgets/LoadingSpinner'; import HardwareWalletStatus from '../../hardware-wallet/HardwareWalletStatus'; +import type { DelegationCalculateFeeResponse } from '../../../api/staking/types'; import type { HwDeviceStatus } from '../../../domains/Wallet'; const messages = defineMessages({ @@ -65,6 +65,12 @@ const messages = defineMessages({ description: 'Fees label on the delegation setup "confirmation" step dialog.', }, + depositLabel: { + id: 'staking.delegationSetup.confirmation.step.dialog.depositLabel', + defaultMessage: '!!!Deposit', + description: + 'Deposit label on the delegation setup "confirmation" step dialog.', + }, spendingPasswordPlaceholder: { id: 'staking.delegationSetup.confirmation.step.dialog.spendingPasswordPlaceholder', @@ -92,7 +98,12 @@ const messages = defineMessages({ calculatingFees: { id: 'staking.delegationSetup.confirmation.step.dialog.calculatingFees', defaultMessage: '!!!Calculating fees', - description: '"Calculating fees" message in the "Undelegate" dialog.', + description: '"Calculating fees" message in the "confirmation" dialog.', + }, + calculatingDeposit: { + id: 'staking.delegationSetup.confirmation.step.dialog.calculatingDeposit', + defaultMessage: '!!!Calculating deposit', + description: '"Calculating deposit" message in the "confirmation" dialog.', }, }); @@ -102,7 +113,7 @@ type Props = { onBack: Function, onClose: Function, onConfirm: Function, - transactionFee: ?BigNumber, + transactionFee: ?DelegationCalculateFeeResponse, selectedWallet: ?Wallet, selectedPool: ?StakePool, stepsList: Array, @@ -276,24 +287,45 @@ export default class DelegationStepsConfirmationDialog extends Component

{selectedPoolId}

-
-

- {intl.formatMessage(messages.feesLabel)} -

-

- {!transactionFee ? ( - - {intl.formatMessage(messages.calculatingFees)} - - ) : ( - <> - {formattedWalletAmount(transactionFee, false)} - -  {intl.formatMessage(globalMessages.unitAda)} +

+
+

+ {intl.formatMessage(messages.feesLabel)} +

+

+ {!transactionFee ? ( + + {intl.formatMessage(messages.calculatingFees)} - - )} -

+ ) : ( + <> + + {formattedWalletAmount(transactionFee.fee, false)} + + +  {intl.formatMessage(globalMessages.unitAda)} + + + )} +

+
+ {transactionFee && !transactionFee.deposit.isZero() && ( + <> +
+

+ {intl.formatMessage(messages.depositLabel)} +

+

+ + {formattedWalletAmount(transactionFee.deposit, false)} + + +  {intl.formatMessage(globalMessages.unitAda)} + +

+
+ + )}
{isHardwareWallet ? ( diff --git a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.scss b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.scss index c2be657ce0..6d9d211c2c 100644 --- a/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.scss +++ b/source/renderer/app/components/staking/delegation-setup-wizard/DelegationStepsConfirmationDialog.scss @@ -15,14 +15,15 @@ } .stakePoolIdWrapper, - .feesWrapper { + .feesRow { font-family: var(--font-medium); font-weight: 500; line-height: 1.38; margin-bottom: 20px; .stakePoolIdLabel, - .feesLabel { + .feesLabel, + .depositLabel { color: var(--theme-delegation-steps-confirmation-fees-label-color); margin-bottom: 6px; } @@ -33,21 +34,35 @@ user-select: text; } - .calculatingFeesLabel { + .calculatingFeesLabel, + .calculatingDepositLabel { @include animated-ellipsis($duration: 1500, $width: 20px); --webkit-backface-visibility: hidden; } - .feesAmount { + .feesAmount, + .depositAmount { color: var(--theme-delegation-steps-confirmation-fees-amount-color); user-select: text; - .feesAmountLabel { + .feesAmountLabel, + .depositAmountLabel { font-family: var(--font-light); } } } + .feesRow { + align-items: center; + display: flex; + justify-content: space-between; + + .feesWrapper, + .depositWrapper { + flex: 1; + } + } + .hardwareWalletStatusWrapper { margin-top: 20px; max-width: 640px; diff --git a/source/renderer/app/components/wallet/transactions/Transaction.js b/source/renderer/app/components/wallet/transactions/Transaction.js index 72959e5a4a..8876159912 100644 --- a/source/renderer/app/components/wallet/transactions/Transaction.js +++ b/source/renderer/app/components/wallet/transactions/Transaction.js @@ -86,6 +86,16 @@ const messages = defineMessages({ defaultMessage: '!!!To addresses', description: 'To addresses', }, + transactionFee: { + id: 'wallet.transaction.transactionFee', + defaultMessage: '!!!Transaction fee', + description: 'Transaction fee', + }, + deposit: { + id: 'wallet.transaction.deposit', + defaultMessage: '!!!Deposit', + description: 'Deposit', + }, transactionAmount: { id: 'wallet.transaction.transactionAmount', defaultMessage: '!!!Transaction amount', @@ -486,6 +496,30 @@ export default class Transaction extends Component {
))} + {data.type === TransactionTypes.EXPEND && ( + <> +

{intl.formatMessage(messages.transactionFee)}

+
+
+ {formattedWalletAmount(data.fee, false)} + ADA +
+
+ + )} + + {!data.deposit.isZero() && ( + <> +

{intl.formatMessage(messages.deposit)}

+
+
+ {formattedWalletAmount(data.deposit, false)} + ADA +
+
+ + )} +

{intl.formatMessage(messages.transactionId)}

[{selectedPoolTicker}] stake pool for your {selectedWalletName} wallet.", "staking.delegationSetup.confirmation.step.dialog.feesLabel": "Fees", "staking.delegationSetup.confirmation.step.dialog.spendingPasswordLabel": "Spending password", @@ -948,6 +950,7 @@ "wallet.transaction.addresses.from": "From addresses", "wallet.transaction.addresses.to": "To addresses", "wallet.transaction.conversion.rate": "Conversion rate", + "wallet.transaction.deposit": "Deposit", "wallet.transaction.failed.cancelFailedTxnNote": "This transaction was submitted to the Cardano network, but it failed as it had expired. Transactions on the Cardano network have a ‘time to live’ attribute, which had passed before the network processed the transaction. You need to remove it to release the funds (UTXOs) used by this transaction for use in another transaction.", "wallet.transaction.failed.cancelFailedTxnSupportArticle": "Why should I cancel failed transactions?", "wallet.transaction.failed.removeTransactionButton": "Remove failed transaction", @@ -977,6 +980,7 @@ "wallet.transaction.state.failed": "Transaction failed", "wallet.transaction.state.pending": "Transaction pending", "wallet.transaction.transactionAmount": "Transaction amount", + "wallet.transaction.transactionFee": "Transaction fee", "wallet.transaction.transactionId": "Transaction ID", "wallet.transaction.type": "{currency} transaction", "wallet.transaction.type.card": "Card Payment", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index cee7682762..91af61dfbf 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -371,9 +371,11 @@ "staking.delegationSetup.chooseWallet.step.dialog.stepIndicatorLabel": "ステップ{currentStep}/{totalSteps}", "staking.delegationSetup.chooseWallet.step.dialog.syncingWallet": "同期", "staking.delegationSetup.chooseWallet.step.dialog.title": "ウォレットを委任する", + "staking.delegationSetup.confirmation.step.dialog.calculatingDeposit": "デポジットを計算しています", "staking.delegationSetup.confirmation.step.dialog.calculatingFees": "手数料を計算しています", "staking.delegationSetup.confirmation.step.dialog.cancelButtonLabel": "キャンセル", "staking.delegationSetup.confirmation.step.dialog.confirmButtonLabel": "確認", + "staking.delegationSetup.confirmation.step.dialog.depositLabel": "デポジット", "staking.delegationSetup.confirmation.step.dialog.description": "{selectedWalletName}ウォレットの委任先が「{selectedPoolTicker}」ステークプールであることを確認してください。", "staking.delegationSetup.confirmation.step.dialog.feesLabel": "手数料", "staking.delegationSetup.confirmation.step.dialog.spendingPasswordLabel": "送信時パスワード", @@ -948,6 +950,7 @@ "wallet.transaction.addresses.from": "送信元アドレス", "wallet.transaction.addresses.to": "送信先", "wallet.transaction.conversion.rate": "両替率", + "wallet.transaction.deposit": "デポジット", "wallet.transaction.failed.cancelFailedTxnNote": "トランザクションはCardanoネットワークに送信されましたが、期限切れのため失敗しました。Cardanoネットワークのトランザクションには「time to live(有効期限)」属性があり、ネットワークがトランザクションを処理する前にこれを経過しました。このトランザクションに使用されたファンド(UTXO)をリリースして別のトランザクションで使用可能にするために、このトランザクションを削除してください。", "wallet.transaction.failed.cancelFailedTxnSupportArticle": "このトランザクションをキャンセルする理由", "wallet.transaction.failed.removeTransactionButton": "失敗したトランザクションを削除する", @@ -977,6 +980,7 @@ "wallet.transaction.state.failed": "トランザクション失敗", "wallet.transaction.state.pending": "トランザクション処理中", "wallet.transaction.transactionAmount": "トランザクション額", + "wallet.transaction.transactionFee": "トランザクション手数料", "wallet.transaction.transactionId": "トランザクションID", "wallet.transaction.type": "{currency}トランザクション", "wallet.transaction.type.card": "カード支払い", diff --git a/source/renderer/app/stores/StakingStore.js b/source/renderer/app/stores/StakingStore.js index 6e289f790b..9be6b11744 100644 --- a/source/renderer/app/stores/StakingStore.js +++ b/source/renderer/app/stores/StakingStore.js @@ -26,6 +26,7 @@ import type { RewardForIncentivizedTestnet, JoinStakePoolRequest, GetDelegationFeeRequest, + DelegationCalculateFeeResponse, QuitStakePoolRequest, PoolMetadataSource, } from '../api/staking/types'; @@ -131,7 +132,8 @@ export default class StakingStore extends Store { @observable stakePoolsRequest: Request> = new Request( this.api.ada.getStakePools ); - @observable calculateDelegationFeeRequest: Request = new Request( + @observable + calculateDelegationFeeRequest: Request = new Request( this.api.ada.calculateDelegationFee ); // @REDEEM TODO: Proper type it when the API endpoint is implemented. @@ -400,7 +402,7 @@ export default class StakingStore extends Store { calculateDelegationFee = async ( delegationFeeRequest: GetDelegationFeeRequest - ): ?BigNumber => { + ): Promise => { const { walletId } = delegationFeeRequest; const wallet = this.stores.wallets.getWalletById(walletId); this._delegationFeeCalculationWalletId = walletId; @@ -416,7 +418,7 @@ export default class StakingStore extends Store { } try { - const delegationFee: BigNumber = await this.calculateDelegationFeeRequest.execute( + const delegationFee: DelegationCalculateFeeResponse = await this.calculateDelegationFeeRequest.execute( { ...delegationFeeRequest } ).promise; diff --git a/source/renderer/app/themes/daedalus/cardano.js b/source/renderer/app/themes/daedalus/cardano.js index 806854442f..5314a77163 100644 --- a/source/renderer/app/themes/daedalus/cardano.js +++ b/source/renderer/app/themes/daedalus/cardano.js @@ -923,6 +923,7 @@ export const CARDANO_THEME_OUTPUT = { '--theme-transactions-list-border-color': '#d2d3d3', '--theme-transactions-list-group-date-color': '#5e6066', '--theme-transactions-list-item-details-color': '#5e6066', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#007600', '--theme-transactions-state-pending-background-color': 'rgba(94, 96, 102, 0.5)', diff --git a/source/renderer/app/themes/daedalus/dark-blue.js b/source/renderer/app/themes/daedalus/dark-blue.js index 9601994f02..995df6e9a5 100644 --- a/source/renderer/app/themes/daedalus/dark-blue.js +++ b/source/renderer/app/themes/daedalus/dark-blue.js @@ -929,6 +929,7 @@ export const DARK_BLUE_THEME_OUTPUT = { '--theme-transactions-list-border-color': '#263345', '--theme-transactions-list-group-date-color': '#7a8691', '--theme-transactions-list-item-details-color': '#e9f4fe', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#274c2d', '--theme-transactions-state-pending-background-color': 'rgba(233, 244, 254, 0.3)', diff --git a/source/renderer/app/themes/daedalus/dark-cardano.js b/source/renderer/app/themes/daedalus/dark-cardano.js index 82d5af0342..e9200ae9cf 100644 --- a/source/renderer/app/themes/daedalus/dark-cardano.js +++ b/source/renderer/app/themes/daedalus/dark-cardano.js @@ -909,6 +909,7 @@ export const DARK_CARDANO_THEME_OUTPUT = { '--theme-transactions-list-border-color': '1e1f31', '--theme-transactions-list-group-date-color': '#ffffff', '--theme-transactions-list-item-details-color': '#ffffff', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#2cbb69', '--theme-transactions-state-pending-background-color': 'rgba(255, 255, 255, 0.5)', diff --git a/source/renderer/app/themes/daedalus/flight-candidate.js b/source/renderer/app/themes/daedalus/flight-candidate.js index 43862dd107..d9df86346e 100644 --- a/source/renderer/app/themes/daedalus/flight-candidate.js +++ b/source/renderer/app/themes/daedalus/flight-candidate.js @@ -909,6 +909,7 @@ export const FLIGHT_CANDIDATE_THEME_OUTPUT = { '--theme-transactions-list-border-color': '1e1f31', '--theme-transactions-list-group-date-color': '#ffffff', '--theme-transactions-list-item-details-color': '#ffffff', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#2cbb69', '--theme-transactions-state-pending-background-color': 'rgba(255, 255, 255, 0.5)', diff --git a/source/renderer/app/themes/daedalus/incentivized-testnet.js b/source/renderer/app/themes/daedalus/incentivized-testnet.js index 1fcf9a6ec6..1a20096cf5 100644 --- a/source/renderer/app/themes/daedalus/incentivized-testnet.js +++ b/source/renderer/app/themes/daedalus/incentivized-testnet.js @@ -923,6 +923,7 @@ export const INCENTIVIZED_TESTNET_THEME_OUTPUT = { '--theme-transactions-list-border-color': '1e1f31', '--theme-transactions-list-group-date-color': '#ffffff', '--theme-transactions-list-item-details-color': '#ffffff', + '--theme-transactions-list-item-highlight-color': '#eb4a22', '--theme-transactions-search-background-color': '#121326', '--theme-transactions-state-ok-background-color': '#2cbb69', '--theme-transactions-state-pending-background-color': diff --git a/source/renderer/app/themes/daedalus/light-blue.js b/source/renderer/app/themes/daedalus/light-blue.js index 03ab6921ae..9fdd48e2d0 100644 --- a/source/renderer/app/themes/daedalus/light-blue.js +++ b/source/renderer/app/themes/daedalus/light-blue.js @@ -921,6 +921,7 @@ export const LIGHT_BLUE_THEME_OUTPUT = { '--theme-transactions-list-border-color': '#c6cdd6', '--theme-transactions-list-group-date-color': '#5e6066', '--theme-transactions-list-item-details-color': '#5e6066', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#007600', '--theme-transactions-state-pending-background-color': 'rgba(94, 96, 102, 0.5)', diff --git a/source/renderer/app/themes/daedalus/shelley-testnet.js b/source/renderer/app/themes/daedalus/shelley-testnet.js index addcc842f4..0da5770e8f 100644 --- a/source/renderer/app/themes/daedalus/shelley-testnet.js +++ b/source/renderer/app/themes/daedalus/shelley-testnet.js @@ -908,6 +908,7 @@ export const SHELLEY_TESTNET_THEME_OUTPUT = { '--theme-transactions-list-border-color': '1e1f31', '--theme-transactions-list-group-date-color': '#ffffff', '--theme-transactions-list-item-details-color': '#ffffff', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#2cbb69', '--theme-transactions-state-pending-background-color': 'rgba(255, 255, 255, 0.5)', diff --git a/source/renderer/app/themes/daedalus/white.js b/source/renderer/app/themes/daedalus/white.js index 9de36ad520..0b67c985af 100644 --- a/source/renderer/app/themes/daedalus/white.js +++ b/source/renderer/app/themes/daedalus/white.js @@ -914,6 +914,7 @@ export const WHITE_THEME_OUTPUT = { '--theme-transactions-list-border-color': 'transparent', '--theme-transactions-list-group-date-color': '#2d2d2d', '--theme-transactions-list-item-details-color': '#2d2d2d', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': 'rgba(0, 118, 0, 1);', '--theme-transactions-state-pending-background-color': 'rgba(45, 45, 45, 0.5)', diff --git a/source/renderer/app/themes/daedalus/yellow.js b/source/renderer/app/themes/daedalus/yellow.js index 55a00ebbca..48e9da5912 100644 --- a/source/renderer/app/themes/daedalus/yellow.js +++ b/source/renderer/app/themes/daedalus/yellow.js @@ -913,6 +913,7 @@ export const YELLOW_THEME_OUTPUT = { '--theme-transactions-list-border-color': '#e1dac6', '--theme-transactions-list-group-date-color': '#2d2d2d', '--theme-transactions-list-item-details-color': '#2d2d2d', + '--theme-transactions-list-item-highlight-color': '#ea4c5b', '--theme-transactions-state-ok-background-color': '#007600', '--theme-transactions-state-pending-background-color': 'rgba(45, 45, 45, 0.5)', diff --git a/source/renderer/app/themes/utils/createTheme.js b/source/renderer/app/themes/utils/createTheme.js index 4d92e5660b..1b4c7457c3 100644 --- a/source/renderer/app/themes/utils/createTheme.js +++ b/source/renderer/app/themes/utils/createTheme.js @@ -1154,6 +1154,7 @@ export const createDaedalusComponentsTheme = ( '--theme-transactions-list-border-color': `${border}`, '--theme-transactions-list-group-date-color': `${text.primary}`, '--theme-transactions-list-item-details-color': `${text.primary}`, + '--theme-transactions-list-item-highlight-color': `${error.regular}`, '--theme-transactions-state-ok-background-color': '#007600', '--theme-transactions-state-pending-background-color': `${background.primary.dark}`, '--theme-transactions-state-pending-warning-background-color': '#ec5d6b', diff --git a/storybook/stories/_support/utils.js b/storybook/stories/_support/utils.js index d4a934f3ed..f4178d71cb 100644 --- a/storybook/stories/_support/utils.js +++ b/storybook/stories/_support/utils.js @@ -75,6 +75,8 @@ export const generateTransaction = ( type: TransactionType = TransactionTypes.INCOME, date: Date = faker.date.past(), amount: BigNumber = new BigNumber(faker.finance.amount()), + fee: BigNumber = new BigNumber(faker.finance.amount()), + deposit: BigNumber = new BigNumber(faker.finance.amount()), state: TransactionState = TransactionStates.OK, hasUnresolvedIncomeAddresses: boolean = false, noIncomeAddresses: boolean = false, @@ -85,6 +87,8 @@ export const generateTransaction = ( title: '', type, amount, + fee, + deposit, date, state, depth: { diff --git a/storybook/stories/wallets/_utils/WalletsTransactionsWrapper.js b/storybook/stories/wallets/_utils/WalletsTransactionsWrapper.js index 2dbe297f77..3a7798b257 100644 --- a/storybook/stories/wallets/_utils/WalletsTransactionsWrapper.js +++ b/storybook/stories/wallets/_utils/WalletsTransactionsWrapper.js @@ -117,6 +117,8 @@ export default class WalletsTransactionsWrapper extends Component< TransactionTypes.INCOME, new Date(), new BigNumber(1), + new BigNumber(1), + new BigNumber(1), TransactionStates.OK, false, true @@ -125,6 +127,8 @@ export default class WalletsTransactionsWrapper extends Component< TransactionTypes.INCOME, new Date(), new BigNumber(1), + new BigNumber(1), + new BigNumber(1), TransactionStates.OK, false, true @@ -135,6 +139,8 @@ export default class WalletsTransactionsWrapper extends Component< TransactionTypes.INCOME, new Date(), new BigNumber(1), + new BigNumber(1), + new BigNumber(1), TransactionStates.OK, false, false, @@ -144,6 +150,8 @@ export default class WalletsTransactionsWrapper extends Component< TransactionTypes.INCOME, new Date(), new BigNumber(1), + new BigNumber(1), + new BigNumber(1), TransactionStates.OK, false, false, From 2c437df82824a4ad2dc3e634b9d98ea0654fbb88 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Fri, 5 Feb 2021 13:38:44 +0100 Subject: [PATCH 19/37] [DDW-521] Display Transaction Metadata (#2338) * [DDW-521] Prepare json-bigint integration * [DDW-521] Fix json-bigint dependency and lockfile * [DDW-521] Render transaction metadata * [DDW-521] Fix flow issues * [DDW-521] Add changelog entry * [DDW-521] Finish api integration with json stringify rendering * [DDW-521] improve handling of metadata maps * [DDW-521] add more metadata map examples * [DDW-521] Fix small linting issue * [DDW-521] Add 0x prefix for metadata byte strings * [DDW-521] Fix tx metadata toggling recalculation * [DDW-521] Fix distance of tx metadata section * [DDW-521] Translations and improvements Co-authored-by: Nikola Glumac --- CHANGELOG.md | 1 + package.json | 1 + source/renderer/app/api/api.js | 2 + source/renderer/app/api/transactions/types.js | 2 + source/renderer/app/api/utils/request.js | 5 +- .../wallet/transactions/Transaction.js | 62 ++++++++ .../wallet/transactions/Transaction.scss | 15 ++ .../transactions/WalletTransactionsList.js | 19 +++ .../metadata/MetadataValueView.js | 76 +++++++++ .../metadata/TransactionMetadataView.js | 57 +++++++ .../metadata/TransactionMetadataView.scss | 65 ++++++++ .../VirtualTransactionList.js | 1 - .../renderer/app/domains/WalletTransaction.js | 3 + .../app/i18n/locales/defaultMessages.json | 146 +++++++++++------- source/renderer/app/i18n/locales/en-US.json | 3 + source/renderer/app/i18n/locales/ja-JP.json | 3 + .../renderer/app/types/TransactionMetadata.js | 14 ++ source/renderer/app/utils/transaction.js | 14 +- storybook/stories/_support/utils.js | 51 +++++- storybook/stories/wallets/index.js | 1 + .../TransactionMetadata.stories.js | 10 ++ .../transactions/TransactionsList.stories.js | 2 - yarn.lock | 12 +- 23 files changed, 495 insertions(+), 70 deletions(-) create mode 100644 source/renderer/app/components/wallet/transactions/metadata/MetadataValueView.js create mode 100644 source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.js create mode 100644 source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss create mode 100644 source/renderer/app/types/TransactionMetadata.js create mode 100644 storybook/stories/wallets/transactions/TransactionMetadata.stories.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d23fd110e0..4652013be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changelog ### Features +- Implemented transaction metadata display ([PR 2338](https://github.com/input-output-hk/daedalus/pull/2338)) - Displayed fee and deposit info in transaction details and in the delegation wizard ([PR 2339](https://github.com/input-output-hk/daedalus/pull/2339)) - Added SMASH server configuration options ([PR 2259](https://github.com/input-output-hk/daedalus/pull/2259)) diff --git a/package.json b/package.json index b1357cf1b2..4c6180e0bf 100644 --- a/package.json +++ b/package.json @@ -201,6 +201,7 @@ "history": "4.10.1", "humanize-duration": "3.23.1", "inquirer": "7.3.3", + "json-bigint": "1.0.0", "lodash": "4.17.20", "lodash-es": "4.17.15", "mime-types": "2.1.27", diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 77403a52b5..6235535832 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -2313,6 +2313,7 @@ const _createTransactionFromServerData = action( outputs, withdrawals, status, + metadata, } = data; const state = _conditionToTxState(status); const stateInfo = @@ -2343,6 +2344,7 @@ const _createTransactionFromServerData = action( withdrawals: withdrawals.map(({ stake_address: address }) => address), }, state, + metadata, }); } ); diff --git a/source/renderer/app/api/transactions/types.js b/source/renderer/app/api/transactions/types.js index 70ea7e1027..f89889e458 100644 --- a/source/renderer/app/api/transactions/types.js +++ b/source/renderer/app/api/transactions/types.js @@ -3,6 +3,7 @@ import BigNumber from 'bignumber.js'; import { WalletTransaction } from '../../domains/WalletTransaction'; import { WalletUnits } from '../../domains/Wallet'; import type { DelegationAction } from '../../types/stakingTypes'; +import type { TransactionMetadata } from '../../types/TransactionMetadata'; export type TransactionAmount = { quantity: number, @@ -50,6 +51,7 @@ export type Transaction = { outputs: Array, withdrawals: Array, status: TransactionState, + metadata?: TransactionMetadata, }; export type Transactions = Array; diff --git a/source/renderer/app/api/utils/request.js b/source/renderer/app/api/utils/request.js index 19ad6d251e..27098c14c8 100644 --- a/source/renderer/app/api/utils/request.js +++ b/source/renderer/app/api/utils/request.js @@ -1,5 +1,6 @@ // @flow import { includes, omit, size } from 'lodash'; +import JSONBigInt from 'json-bigint'; import querystring from 'querystring'; import { getContentLength } from '.'; @@ -106,10 +107,10 @@ function typedRequest( "data": ${data} }`; } - resolve(JSON.parse(body)); + resolve(JSONBigInt.parse(body)); } else if (body) { // Error response with a body - const parsedBody = JSON.parse(body); + const parsedBody = JSONBigInt.parse(body); if (parsedBody.code && parsedBody.message) { reject(parsedBody); } else { diff --git a/source/renderer/app/components/wallet/transactions/Transaction.js b/source/renderer/app/components/wallet/transactions/Transaction.js index 8876159912..271addc092 100644 --- a/source/renderer/app/components/wallet/transactions/Transaction.js +++ b/source/renderer/app/components/wallet/transactions/Transaction.js @@ -8,6 +8,7 @@ import classNames from 'classnames'; import { Link } from 'react-polymorph/lib/components/Link'; import { LinkSkin } from 'react-polymorph/lib/skins/simple/LinkSkin'; import CancelTransactionButton from './CancelTransactionButton'; +import { TransactionMetadataView } from './metadata/TransactionMetadataView'; import styles from './Transaction.scss'; import TransactionTypeIcon from './TransactionTypeIcon'; import adaSymbol from '../../../assets/images/ada-symbol.inline.svg'; @@ -46,6 +47,22 @@ const messages = defineMessages({ defaultMessage: '!!!Transaction ID', description: 'Transaction ID.', }, + metadataLabel: { + id: 'wallet.transaction.metadataLabel', + defaultMessage: '!!!Transaction metadata', + description: 'Transaction metadata label', + }, + metadataDisclaimer: { + id: 'wallet.transaction.metadataDisclaimer', + defaultMessage: + '!!!Transaction metadata is not moderated and may contain inappropriate content.', + description: 'Transaction metadata disclaimer', + }, + metadataConfirmationLabel: { + id: 'wallet.transaction.metadataConfirmationLabel', + defaultMessage: '!!!Show unmoderated content', + description: 'Transaction metadata confirmation toggle', + }, conversionRate: { id: 'wallet.transaction.conversion.rate', defaultMessage: '!!!Conversion rate', @@ -171,9 +188,11 @@ type Props = { isExpanded: boolean, isRestoreActive: boolean, isLastInList: boolean, + isShowingMetadata: boolean, formattedWalletAmount: Function, onDetailsToggled: ?Function, onOpenExternalLink: Function, + onShowMetadata: () => void, getUrlByType: Function, currentTimeFormat: string, walletId: string, @@ -182,6 +201,7 @@ type Props = { type State = { showConfirmationDialog: boolean, + showUnmoderatedMetadata: boolean, }; export default class Transaction extends Component { @@ -191,8 +211,20 @@ export default class Transaction extends Component { state = { showConfirmationDialog: false, + showUnmoderatedMetadata: false, }; + componentDidUpdate(prevProps: Props, prevState: State) { + // Tell parent components that meta data was toggled + if ( + !prevState.showUnmoderatedMetadata && + this.state.showUnmoderatedMetadata && + this.props.onShowMetadata + ) { + this.props.onShowMetadata(); + } + } + toggleDetails() { const { onDetailsToggled } = this.props; if (onDetailsToggled) onDetailsToggled(); @@ -310,6 +342,7 @@ export default class Transaction extends Component { const { data, isLastInList, + isShowingMetadata, state, formattedWalletAmount, onOpenExternalLink, @@ -532,6 +565,35 @@ export default class Transaction extends Component { />
{this.renderCancelPendingTxnContent()} + + {data.metadata != null && ( +
+

{intl.formatMessage(messages.metadataLabel)}

+ {data.metadata && + (this.state.showUnmoderatedMetadata || + isShowingMetadata) ? ( + + ) : ( + <> +

+ {intl.formatMessage(messages.metadataDisclaimer)} +

+ { + e.preventDefault(); + this.setState({ showUnmoderatedMetadata: true }); + }} + /> + + )} +
+ )}
diff --git a/source/renderer/app/components/wallet/transactions/Transaction.scss b/source/renderer/app/components/wallet/transactions/Transaction.scss index 6af36a6e2e..00fed49804 100644 --- a/source/renderer/app/components/wallet/transactions/Transaction.scss +++ b/source/renderer/app/components/wallet/transactions/Transaction.scss @@ -256,3 +256,18 @@ } } } + +.metadataDisclaimer { + font-family: var(--font-light); + font-size: 16px; + line-height: 22px; +} + +.metadata { + margin-top: 20px; + + pre { + white-space: pre-wrap; + word-break: break-all; + } +} diff --git a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js index 747fcf71b2..154c3d4b4f 100644 --- a/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js +++ b/source/renderer/app/components/wallet/transactions/WalletTransactionsList.js @@ -83,6 +83,7 @@ export default class WalletTransactionsList extends Component { }; expandedTransactionIds: Map = new Map(); + transactionsShowingMetadata: Map = new Map(); virtualList: ?VirtualTransactionList; simpleList: ?SimpleTransactionList; loadingSpinner: ?LoadingSpinner; @@ -136,6 +137,9 @@ export default class WalletTransactionsList extends Component { isTxExpanded = (tx: WalletTransaction) => this.expandedTransactionIds.has(tx.id); + isTxShowingMetadata = (tx: WalletTransaction) => + this.transactionsShowingMetadata.has(tx.id); + toggleTransactionExpandedState = (tx: WalletTransaction) => { const isExpanded = this.isTxExpanded(tx); if (isExpanded) { @@ -150,6 +154,19 @@ export default class WalletTransactionsList extends Component { } }; + /** + * Update the height of the transaction when metadata is shown + * @param tx + */ + onShowMetadata = (tx: WalletTransaction) => { + this.transactionsShowingMetadata.set(tx.id, tx); + if (this.virtualList) { + this.virtualList.updateTxRowHeight(tx, true, true); + } else if (this.simpleList) { + this.simpleList.forceUpdate(); + } + }; + onShowMoreTransactions = (walletId: string) => { if (this.props.onShowMoreTransactions) { this.props.onShowMoreTransactions(walletId); @@ -186,10 +203,12 @@ export default class WalletTransactionsList extends Component { deletePendingTransaction={deletePendingTransaction} formattedWalletAmount={formattedWalletAmount} isExpanded={this.isTxExpanded(tx)} + isShowingMetadata={this.isTxShowingMetadata(tx)} isLastInList={isLastInGroup} isRestoreActive={isRestoreActive} onDetailsToggled={() => this.toggleTransactionExpandedState(tx)} onOpenExternalLink={onOpenExternalLink} + onShowMetadata={() => this.onShowMetadata(tx)} getUrlByType={getUrlByType} state={tx.state} walletId={walletId} diff --git a/source/renderer/app/components/wallet/transactions/metadata/MetadataValueView.js b/source/renderer/app/components/wallet/transactions/metadata/MetadataValueView.js new file mode 100644 index 0000000000..cafa248597 --- /dev/null +++ b/source/renderer/app/components/wallet/transactions/metadata/MetadataValueView.js @@ -0,0 +1,76 @@ +// @flow +import React from 'react'; +import type { + MetadataBytes, + MetadataInteger, + MetadataList, + MetadataMap, + MetadataString, + MetadataValue, +} from '../../../../types/TransactionMetadata'; +import styles from './TransactionMetadataView.scss'; + +/** + * NOTE: These components are currently not used because we simply + * JSON.stringify the metadata for transactions. This can be + * used as the basis for a more sophisticated implementation + * later on: + */ + +function IntegerView({ value }: { value: MetadataInteger }) { + return

{value.int}

; +} + +function StringView({ value }: { value: MetadataString }) { + return

{value.string}

; +} + +function BytesView({ value }: { value: MetadataBytes }) { + return

{value.bytes}

; +} + +function ListView({ value }: { value: MetadataList }) { + return ( +
    + {value.list.map((v, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  1. + +
  2. + ))} +
+ ); +} + +function MapView({ value }: { value: MetadataMap }) { + return ( +
    + {value.map.map((v, index) => ( + // eslint-disable-next-line react/no-array-index-key +
  1. + : +
  2. + ))} +
+ ); +} + +function MetadataValueView(props: { value: MetadataValue }) { + const { value } = props; + if (value.int) { + return ; + } + if (value.string) { + return ; + } + if (value.bytes) { + return ; + } + if (value.list) { + return ; + } + if (value.map) { + return ; + } + return null; +} diff --git a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.js b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.js new file mode 100644 index 0000000000..85ca2ed830 --- /dev/null +++ b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.js @@ -0,0 +1,57 @@ +// @flow +import React from 'react'; +import JSONBigInt from 'json-bigint'; +import type { + MetadataMapValue, + MetadataValue, + TransactionMetadata, +} from '../../../../types/TransactionMetadata'; +import styles from './TransactionMetadataView.scss'; + +function flattenMetadata(data: MetadataValue) { + if (data.int) { + return data.int; + } + if (data.string) { + return data.string; + } + if (data.bytes) { + return `0x${data.bytes}`; + } + if (data.list) { + return data.list.map((v: MetadataValue) => flattenMetadata(v)); + } + if (data.map) { + return data.map.map((v: MetadataMapValue) => { + if (v.k.list || v.k.map) { + return { + key: flattenMetadata(v.k), + value: flattenMetadata(v.v), + }; + } + if (v.k.int) { + return { [v.k.int]: flattenMetadata(v.v) }; + } + if (v.k.string) { + return { [v.k.string]: flattenMetadata(v.v) }; + } + if (v.k.bytes) { + return { [v.k.bytes]: flattenMetadata(v.v) }; + } + return null; + }); + } + return null; +} + +export function TransactionMetadataView(props: { data: TransactionMetadata }) { + return ( +
+ {Object.keys(props.data).map((key: string) => ( +
+          {JSONBigInt.stringify(flattenMetadata(props.data[key]), null, 2)}
+        
+ ))} +
+ ); +} diff --git a/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss new file mode 100644 index 0000000000..499195c6c3 --- /dev/null +++ b/source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView.scss @@ -0,0 +1,65 @@ +.root { + font-family: var(--font-light); + & > * { + margin-bottom: 5px; + &:last-child { + margin-bottom: 0; + } + } +} + +/** + * NOTE: These styles are currently not used because we simply + * JSON.stringify the metadata for transactions. This can be + * used as the basis for a more sophisticated implementation + * later on: + */ + +//.list { +// &::before { +// content: '['; +// display: inline; +// } +// &::after { +// content: ']'; +// display: inline; +// } +// li::after { +// content: ','; +// display: inline; +// } +// li:last-child::after { +// content: ''; +// } +// li { +// padding-left: 10px; +// } +// li > * { +// display: inline-block; +// } +//} +// +//.map { +// vertical-align: top; +// &::before { +// content: '{'; +// display: inline; +// } +// &::after { +// content: '}'; +// display: inline; +// } +// li::after { +// content: ','; +// display: inline; +// } +// li:last-child::after { +// content: ''; +// } +// li { +// padding-left: 10px; +// } +// li > * { +// display: inline-block; +// } +//} diff --git a/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.js b/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.js index 76f9f1794c..33982a19ed 100644 --- a/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.js +++ b/source/renderer/app/components/wallet/transactions/render-strategies/VirtualTransactionList.js @@ -128,7 +128,6 @@ export class VirtualTransactionList extends Component { ? this.estimateHeightOfTxExpandedRow(row, tx) : this.estimateHeightOfTxContractedRow(row); this.recomputeVirtualRowHeights(); - // In case transaction has just been manually expanded we need to schedule // another row height calculation if the transaction still isn't fully // expanded in the moment of the initial execution of this method diff --git a/source/renderer/app/domains/WalletTransaction.js b/source/renderer/app/domains/WalletTransaction.js index b8e3de786a..20c7ffa090 100644 --- a/source/renderer/app/domains/WalletTransaction.js +++ b/source/renderer/app/domains/WalletTransaction.js @@ -8,6 +8,7 @@ import type { TransactionState, TransactionWithdrawalType, } from '../api/transactions/types'; +import type { TransactionMetadata } from '../types/TransactionMetadata'; export const TransactionStates: EnumMap = { PENDING: 'pending', @@ -43,6 +44,7 @@ export class WalletTransaction { @observable depth: TransactionDepth; @observable slotNumber: ?number; @observable epochNumber: ?number; + @observable metadata: ?TransactionMetadata; constructor(data: { id: string, @@ -58,6 +60,7 @@ export class WalletTransaction { depth: TransactionDepth, slotNumber: ?number, epochNumber: ?number, + metadata: ?TransactionMetadata, }) { Object.assign(this, data); } diff --git a/source/renderer/app/i18n/locales/defaultMessages.json b/source/renderer/app/i18n/locales/defaultMessages.json index 294598c1d3..0e330458f1 100644 --- a/source/renderer/app/i18n/locales/defaultMessages.json +++ b/source/renderer/app/i18n/locales/defaultMessages.json @@ -10472,13 +10472,13 @@ "description": "Transaction type shown for credit card payments.", "end": { "column": 3, - "line": 32 + "line": 33 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type.card", "start": { "column": 8, - "line": 28 + "line": 29 } }, { @@ -10486,13 +10486,13 @@ "description": "Transaction type shown for {currency} transactions.", "end": { "column": 3, - "line": 37 + "line": 38 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type", "start": { "column": 8, - "line": 33 + "line": 34 } }, { @@ -10500,13 +10500,13 @@ "description": "Transaction type shown for money exchanges between currencies.", "end": { "column": 3, - "line": 43 + "line": 44 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.type.exchange", "start": { "column": 12, - "line": 38 + "line": 39 } }, { @@ -10514,13 +10514,55 @@ "description": "Transaction ID.", "end": { "column": 3, - "line": 48 + "line": 49 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.transactionId", "start": { "column": 17, - "line": 44 + "line": 45 + } + }, + { + "defaultMessage": "!!!Transaction metadata", + "description": "Transaction metadata label", + "end": { + "column": 3, + "line": 54 + }, + "file": "source/renderer/app/components/wallet/transactions/Transaction.js", + "id": "wallet.transaction.metadataLabel", + "start": { + "column": 17, + "line": 50 + } + }, + { + "defaultMessage": "!!!Transaction metadata is not moderated and may contain inappropriate content.", + "description": "Transaction metadata disclaimer", + "end": { + "column": 3, + "line": 60 + }, + "file": "source/renderer/app/components/wallet/transactions/Transaction.js", + "id": "wallet.transaction.metadataDisclaimer", + "start": { + "column": 22, + "line": 55 + } + }, + { + "defaultMessage": "!!!Show unmoderated content", + "description": "Transaction metadata confirmation toggle", + "end": { + "column": 3, + "line": 65 + }, + "file": "source/renderer/app/components/wallet/transactions/Transaction.js", + "id": "wallet.transaction.metadataConfirmationLabel", + "start": { + "column": 29, + "line": 61 } }, { @@ -10528,13 +10570,13 @@ "description": "Conversion rate.", "end": { "column": 3, - "line": 53 + "line": 70 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.conversion.rate", "start": { "column": 18, - "line": 49 + "line": 66 } }, { @@ -10542,13 +10584,13 @@ "description": "Label \"{currency} sent\" for the transaction.", "end": { "column": 3, - "line": 58 + "line": 75 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.sent", "start": { "column": 8, - "line": 54 + "line": 71 } }, { @@ -10556,13 +10598,13 @@ "description": "Label \"{currency} received\" for the transaction.", "end": { "column": 3, - "line": 63 + "line": 80 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.received", "start": { "column": 12, - "line": 59 + "line": 76 } }, { @@ -10570,13 +10612,13 @@ "description": "From address", "end": { "column": 3, - "line": 68 + "line": 85 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.address.from", "start": { "column": 15, - "line": 64 + "line": 81 } }, { @@ -10584,13 +10626,13 @@ "description": "From addresses", "end": { "column": 3, - "line": 73 + "line": 90 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.addresses.from", "start": { "column": 17, - "line": 69 + "line": 86 } }, { @@ -10598,13 +10640,13 @@ "description": "From rewards", "end": { "column": 3, - "line": 78 + "line": 95 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.rewards.from", "start": { "column": 15, - "line": 74 + "line": 91 } }, { @@ -10612,13 +10654,13 @@ "description": "To address", "end": { "column": 3, - "line": 83 + "line": 100 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.address.to", "start": { "column": 13, - "line": 79 + "line": 96 } }, { @@ -10626,13 +10668,13 @@ "description": "To addresses", "end": { "column": 3, - "line": 88 + "line": 105 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.addresses.to", "start": { "column": 15, - "line": 84 + "line": 101 } }, { @@ -10640,13 +10682,13 @@ "description": "Transaction fee", "end": { "column": 3, - "line": 93 + "line": 110 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.transactionFee", "start": { "column": 18, - "line": 89 + "line": 106 } }, { @@ -10654,13 +10696,13 @@ "description": "Deposit", "end": { "column": 3, - "line": 98 + "line": 115 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.deposit", "start": { "column": 11, - "line": 94 + "line": 111 } }, { @@ -10668,13 +10710,13 @@ "description": "Transaction amount.", "end": { "column": 3, - "line": 103 + "line": 120 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.transactionAmount", "start": { "column": 21, - "line": 99 + "line": 116 } }, { @@ -10682,13 +10724,13 @@ "description": "Note to cancel a transaction that has been pending too long", "end": { "column": 3, - "line": 109 + "line": 126 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.pending.cancelPendingTxnNote", "start": { "column": 24, - "line": 104 + "line": 121 } }, { @@ -10696,13 +10738,13 @@ "description": "Link to support article for canceling a pending transaction", "end": { "column": 3, - "line": 114 + "line": 131 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.pending.cancelPendingTxnSupportArticle", "start": { "column": 34, - "line": 110 + "line": 127 } }, { @@ -10710,13 +10752,13 @@ "description": "Url to support article for canceling a pending transaction", "end": { "column": 3, - "line": 120 + "line": 137 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.pending.supportArticleUrl", "start": { "column": 21, - "line": 115 + "line": 132 } }, { @@ -10724,13 +10766,13 @@ "description": "Input Addresses label.", "end": { "column": 3, - "line": 125 + "line": 142 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.noInputAddressesLabel", "start": { "column": 25, - "line": 121 + "line": 138 } }, { @@ -10738,13 +10780,13 @@ "description": "Unresolved Input Addresses link label.", "end": { "column": 3, - "line": 130 + "line": 147 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.unresolvedInputAddressesLinkLabel", "start": { "column": 37, - "line": 126 + "line": 143 } }, { @@ -10752,13 +10794,13 @@ "description": "Unresolved Input Addresses additional label.", "end": { "column": 3, - "line": 135 + "line": 152 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.unresolvedInputAddressesAdditionalLabel", "start": { "column": 43, - "line": 131 + "line": 148 } }, { @@ -10766,13 +10808,13 @@ "description": "Note to cancel a transaction that has been failed", "end": { "column": 3, - "line": 141 + "line": 158 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.failed.cancelFailedTxnNote", "start": { "column": 23, - "line": 136 + "line": 153 } }, { @@ -10780,13 +10822,13 @@ "description": "Link to support article for removing a failed transaction", "end": { "column": 3, - "line": 146 + "line": 163 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.failed.cancelFailedTxnSupportArticle", "start": { "column": 33, - "line": 142 + "line": 159 } }, { @@ -10794,13 +10836,13 @@ "description": "Transaction state \"confirmed\"", "end": { "column": 3, - "line": 154 + "line": 171 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.state.confirmed", "start": { "column": 26, - "line": 150 + "line": 167 } }, { @@ -10808,13 +10850,13 @@ "description": "Transaction state \"pending\"", "end": { "column": 3, - "line": 159 + "line": 176 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.state.pending", "start": { "column": 31, - "line": 155 + "line": 172 } }, { @@ -10822,13 +10864,13 @@ "description": "Transaction state \"failed\"", "end": { "column": 3, - "line": 164 + "line": 181 }, "file": "source/renderer/app/components/wallet/transactions/Transaction.js", "id": "wallet.transaction.state.failed", "start": { "column": 30, - "line": 160 + "line": 177 } } ], diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index 44a6a381ee..87dade58d8 100755 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -968,6 +968,9 @@ "wallet.transaction.filter.resultInfo": "{filtered} out of {total} transactions match your filter.", "wallet.transaction.filter.selectTimeRange": "Select time range", "wallet.transaction.filter.thisYear": "This year", + "wallet.transaction.metadataConfirmationLabel": "Show unmoderated content", + "wallet.transaction.metadataDisclaimer": "Transaction metadata is not moderated and may contain inappropriate content.", + "wallet.transaction.metadataLabel": "Transaction metadata", "wallet.transaction.noInputAddressesLabel": "No addresses", "wallet.transaction.pending.cancelPendingTxnNote": "This transaction has been pending for a long time. To release the funds used by this transaction, you can try canceling it.", "wallet.transaction.pending.cancelPendingTxnSupportArticle": "Why should I cancel this transaction?", diff --git a/source/renderer/app/i18n/locales/ja-JP.json b/source/renderer/app/i18n/locales/ja-JP.json index 91af61dfbf..b7ba274412 100755 --- a/source/renderer/app/i18n/locales/ja-JP.json +++ b/source/renderer/app/i18n/locales/ja-JP.json @@ -968,6 +968,9 @@ "wallet.transaction.filter.resultInfo": "フィルター条件と一致するのは{total}トランザクション中{filtered}件です。", "wallet.transaction.filter.selectTimeRange": "期間を選択してください", "wallet.transaction.filter.thisYear": "今年", + "wallet.transaction.metadataConfirmationLabel": "調整されていないコンテンツを表示する", + "wallet.transaction.metadataDisclaimer": "トランザクションメタデータは承認制ではなく、不適切な内容を含む場合があります。", + "wallet.transaction.metadataLabel": "トランザクションメタデータ", "wallet.transaction.noInputAddressesLabel": "アドレスなし", "wallet.transaction.pending.cancelPendingTxnNote": "このトランザクションは長時間処理中となっています。このトランザクションに使用されている資金を解放するために、トランザクションのキャンセルを試みることができます。", "wallet.transaction.pending.cancelPendingTxnSupportArticle": "このトランザクションをキャンセルする理由", diff --git a/source/renderer/app/types/TransactionMetadata.js b/source/renderer/app/types/TransactionMetadata.js new file mode 100644 index 0000000000..ee5455754d --- /dev/null +++ b/source/renderer/app/types/TransactionMetadata.js @@ -0,0 +1,14 @@ +// @flow +export type MetadataInteger = {| int: number |}; +export type MetadataString = {| string: string |}; +export type MetadataBytes = {| bytes: string |}; +export type MetadataList = {| list: MetadataValue[] |}; +export type MetadataMapValue = {| k: MetadataValue, v: MetadataValue |}; +export type MetadataMap = {| map: MetadataMapValue[] |}; +export type MetadataValue = + | MetadataInteger + | MetadataString + | MetadataBytes + | MetadataList + | MetadataMap; +export type TransactionMetadata = { [string]: MetadataValue }; diff --git a/source/renderer/app/utils/transaction.js b/source/renderer/app/utils/transaction.js index bcea6930e9..a18534449e 100644 --- a/source/renderer/app/utils/transaction.js +++ b/source/renderer/app/utils/transaction.js @@ -1,19 +1,19 @@ // @flow -import React from 'react'; -import moment from 'moment'; import BigNumber from 'bignumber.js'; +import moment from 'moment'; +import React from 'react'; +import type { CoinSelectionsResponse } from '../api/transactions/types'; import { - WalletTransaction, TransactionTypes, + WalletTransaction, } from '../domains/WalletTransaction'; -import { formattedWalletAmount } from './formatters'; -import { DateRangeTypes } from '../stores/TransactionsStore'; -import type { TransactionFilterOptionsType } from '../stores/TransactionsStore'; import type { ByronEncodeSignedTransactionRequest, ByronSignedTransactionWitnesses, } from '../stores/HardwareWalletsStore'; -import type { CoinSelectionsResponse } from '../api/transactions/types'; +import type { TransactionFilterOptionsType } from '../stores/TransactionsStore'; +import { DateRangeTypes } from '../stores/TransactionsStore'; +import { formattedWalletAmount } from './formatters'; const cbor = require('cbor'); const bs58 = require('bs58'); diff --git a/storybook/stories/_support/utils.js b/storybook/stories/_support/utils.js index f4178d71cb..8d2f7fc027 100644 --- a/storybook/stories/_support/utils.js +++ b/storybook/stories/_support/utils.js @@ -1,6 +1,7 @@ // @flow import hash from 'hash.js'; import faker from 'faker'; +import JSONBigInt from 'json-bigint'; import moment from 'moment'; import { random, get } from 'lodash'; import BigNumber from 'bignumber.js'; @@ -24,6 +25,52 @@ import type { TransactionState, } from '../../../source/renderer/app/api/transactions/types'; import type { SyncStateStatus } from '../../../source/renderer/app/api/wallets/types'; +import type { TransactionMetadata } from '../../../source/renderer/app/types/TransactionMetadata'; + +export const EXAMPLE_METADATA = JSONBigInt.parse(`{ + "0": { + "string": "some string" + }, + "1": { + "int": 99999999999999999999999 + }, + "2": { + "bytes": "2512a00e9653fe49a44a5886202e24d77eeb998f" + }, + "3": { + "list": [ + { "int": 14 }, + { "int": 42 }, + { "string": "1337" }, + { "list": [ + { "string": "nested list" } + ]} + ] + }, + "4": { + "map": [ + { + "k": { "int": "5" }, + "v": { "bytes": "2512a00e9653fe49a44a5886202e24d77eeb998f" } + }, + { + "k": { "map": [ + { + "k": { "int": 14 }, + "v": { "int": 42 } + } + ]}, + "v": { "string": "nested" } + }, + { + "k": { "string": "key" }, + "v": { "list": [ + { "string": "nested list" } + ] } + } + ] + } + }`); export const generateHash = () => { const now = new Date().valueOf().toString(); @@ -80,7 +127,8 @@ export const generateTransaction = ( state: TransactionState = TransactionStates.OK, hasUnresolvedIncomeAddresses: boolean = false, noIncomeAddresses: boolean = false, - noWithdrawals: boolean = true + noWithdrawals: boolean = true, + metadata?: TransactionMetadata = EXAMPLE_METADATA ) => new WalletTransaction({ id: faker.random.uuid(), @@ -117,6 +165,7 @@ export const generateTransaction = ( faker.random.alphaNumeric(Math.round(Math.random() * 10) + 100), ], }, + metadata, }); export const generateRandomTransaction = (index: number) => diff --git a/storybook/stories/wallets/index.js b/storybook/stories/wallets/index.js index 27347e811b..c92a98a28d 100644 --- a/storybook/stories/wallets/index.js +++ b/storybook/stories/wallets/index.js @@ -5,6 +5,7 @@ import './summary/WalletSummary.stories'; import './send/WalletSend.stories'; import './receive/WalletReceive.stories'; import './transactions/WalletTransactions.stories'; +import './transactions/TransactionMetadata.stories'; import './settings/WalletSettings.stories'; import './addWallet/AddWallet.stories'; import './import/WalletImportFile.stories'; diff --git a/storybook/stories/wallets/transactions/TransactionMetadata.stories.js b/storybook/stories/wallets/transactions/TransactionMetadata.stories.js new file mode 100644 index 0000000000..f84dca2c6d --- /dev/null +++ b/storybook/stories/wallets/transactions/TransactionMetadata.stories.js @@ -0,0 +1,10 @@ +// @flow +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { TransactionMetadataView } from '../../../../source/renderer/app/components/wallet/transactions/metadata/TransactionMetadataView'; +import { EXAMPLE_METADATA } from '../../_support/utils'; + +storiesOf('Wallets|Transactions', module) + // ====== Stories ====== + + .add('Metadata', () => ); diff --git a/storybook/stories/wallets/transactions/TransactionsList.stories.js b/storybook/stories/wallets/transactions/TransactionsList.stories.js index e81b500d04..db4416ff6f 100644 --- a/storybook/stories/wallets/transactions/TransactionsList.stories.js +++ b/storybook/stories/wallets/transactions/TransactionsList.stories.js @@ -3,7 +3,6 @@ import React from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { withKnobs, select } from '@storybook/addon-knobs'; - // Assets and helpers import { generateWallet } from '../../_support/utils'; import { formattedWalletAmount } from '../../../../source/renderer/app/utils/formatters'; @@ -17,7 +16,6 @@ import { } from '../../../../source/renderer/app/config/profileConfig'; import { WalletTransaction } from '../../../../source/renderer/app/domains/WalletTransaction'; import type { TransactionFilterOptionsType } from '../../../../source/renderer/app/stores/TransactionsStore'; - // Screens import WalletTransactions from '../../../../source/renderer/app/components/wallet/transactions/WalletTransactions'; diff --git a/yarn.lock b/yarn.lock index 2032be38fe..7e2e4d2c41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3326,11 +3326,7 @@ bigi@^1.1.0, bigi@^1.4.0, bigi@^1.4.1: version "1.4.2" resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825" -bignumber.js@9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" - -bignumber.js@^9.0.0: +bignumber.js@9.0.1, bignumber.js@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" @@ -9115,6 +9111,12 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" +json-bigint@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + dependencies: + bignumber.js "^9.0.0" + json-buffer@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" From 00525e5a9c21d004362edbb40a2c112aa98e340f Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Fri, 5 Feb 2021 17:17:50 +0100 Subject: [PATCH 20/37] [DDW-562] Fix the count of packed cardano-wallet logs (#2341) * [DDW-562] Fix cardano-wallet logs packing * [DDW-562] Updates CHANGELOG --- CHANGELOG.md | 1 + source/main/config.js | 2 ++ source/main/ipc/get-logs.js | 12 +++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4652013be9..bc45cf7b67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Changelog ### Fixes +- Fixed logging issue with too few `cardano-wallet` logs being packed into logs zip archive ([PR 2341](https://github.com/input-output-hk/daedalus/pull/2341)) - Fixed misalignment of the "i" icon on the "Set password" dialog ([PR 2337](https://github.com/input-output-hk/daedalus/pull/2337)) - Removed steps counter from the "Success" wallet restoration dialog step ([PR 2335](https://github.com/input-output-hk/daedalus/pull/2335)) diff --git a/source/main/config.js b/source/main/config.js index 9579d9d96a..e5204776e4 100644 --- a/source/main/config.js +++ b/source/main/config.js @@ -145,8 +145,10 @@ export const ALLOWED_LOGS = [ 'node.log', ]; export const ALLOWED_NODE_LOGS = new RegExp(/(node.log-)(\d{14}$)/); +export const ALLOWED_WALLET_LOGS = new RegExp(/(cardano-wallet.log-)(\d{14}$)/); export const ALLOWED_LAUNCHER_LOGS = new RegExp(/(launcher-)(\d{14}$)/); export const MAX_NODE_LOGS_ALLOWED = 3; +export const MAX_WALLET_LOGS_ALLOWED = 3; export const MAX_LAUNCHER_LOGS_ALLOWED = 3; // CardanoNode config diff --git a/source/main/ipc/get-logs.js b/source/main/ipc/get-logs.js index f52f6c1628..fd69be8969 100644 --- a/source/main/ipc/get-logs.js +++ b/source/main/ipc/get-logs.js @@ -4,10 +4,12 @@ import fs from 'fs'; import path from 'path'; import { pubLogsFolderPath, - MAX_NODE_LOGS_ALLOWED, ALLOWED_LOGS, ALLOWED_NODE_LOGS, + ALLOWED_WALLET_LOGS, ALLOWED_LAUNCHER_LOGS, + MAX_NODE_LOGS_ALLOWED, + MAX_WALLET_LOGS_ALLOWED, MAX_LAUNCHER_LOGS_ALLOWED, } from '../config'; import { MainIpcChannel } from './lib/MainIpcChannel'; @@ -50,6 +52,10 @@ const isFileAllowed = (fileName: string) => const isFileNodeLog = (fileName: string, nodeLogsIncluded: number) => ALLOWED_NODE_LOGS.test(fileName) && nodeLogsIncluded < MAX_NODE_LOGS_ALLOWED; +const isFileWalletLog = (fileName: string, walletLogsIncluded: number) => + ALLOWED_WALLET_LOGS.test(fileName) && + walletLogsIncluded < MAX_WALLET_LOGS_ALLOWED; + const isFileLauncherLog = (fileName: string, nodeLogsIncluded: number) => ALLOWED_LAUNCHER_LOGS.test(fileName) && nodeLogsIncluded < MAX_LAUNCHER_LOGS_ALLOWED; @@ -62,6 +68,7 @@ export default () => { const files = fs.readdirSync(pubLogsFolderPath).sort().reverse(); let nodeLogsIncluded = 0; + let walletLogsIncluded = 0; let launcherLogsIncluded = 0; for (let i = 0; i < files.length; i++) { const currentFile = path.join(pubLogsFolderPath, files[i]); @@ -72,6 +79,9 @@ export default () => { } else if (isFileNodeLog(fileName, nodeLogsIncluded)) { logFiles.push(fileName); nodeLogsIncluded++; + } else if (isFileWalletLog(fileName, walletLogsIncluded)) { + logFiles.push(fileName); + walletLogsIncluded++; } else if (isFileLauncherLog(fileName, launcherLogsIncluded)) { logFiles.push(fileName); launcherLogsIncluded++; From ac4c73cfa1331cdab78c164364f9fc81aa0379f5 Mon Sep 17 00:00:00 2001 From: Nikola Glumac Date: Fri, 5 Feb 2021 19:50:51 +0100 Subject: [PATCH 21/37] [DDW-539, DDW-541] Fund3 voting registration (#2315) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create PinCode widget * Add containers for Catalyst voting * Add components for Catalyst voting info, add and create routes * Create voting config file and add routes to Sidebar * Add voting icon * Create voting store and add routes to sidebar store and config * Add voting themes styles * Add voting actions for VotingStore * Create PinCode validations * Create voting types file * Add english translations * Add chain-wallet-lib dependency for encryption and key generation * Use chain-wallet-libs plugin to generate a random key. Encrypt derived private key into a QR code. Create a transaction in Cardano Wallet Backend API and send the public key as metadata. Listen to transaction status until it is confirmed by the blockchain. * Display error message when a transaction fails * [VIT-395] Runs translation manager * [VIT-395] Fix builds * [VIT-395] Catalyst newsfeed, state dir and app name * [VIT-395] Fix theme * Add to filter log data votingKey from createVotingRegistrationTransaction * Add Catalyst voting steps Add voting assets Implement voting info view Refactor themes for implement alphabetical order * Fix lint and flow errors * Fix lint and flow errors in createVotingRegistrationTransaction method * Add comment for voting funds in ada * Add error messages to pin code using tooltip * [VIT-395] Updates cardano-wallet and fixes the smashURL handling issue * Refactor voting registration container to dialog Add new confirmation step Add getWalletKey request for stake_key Add createWalletSignature request for get signature key Update metadata for voting registration transaction * nuke mapping references in a new file * use testnet temporarily * cardano-wallet to latest master * Add wallet-js package from npm * Add request for get transaction detail by id * Parse Stake key using wallet-js method Parse signature response to hex and send transaction Handle JSON.parse errors in request util * Refactor formattedBytesToB16 helper * fix yarn.lock * Create new requestBinary for octet-stream responses Remove unused parsed methods * Refactor applying flow comments * fix darwin build * Fix pin code auto focus and update minimum amount of ADA for voting * Refactor applying flow comments * Updates Daedalus Catalyst icon * temporarily reduce wait time to 60 seconds * Add autofocus to repeat pin code * Revert "use testnet temporarily" This reverts commit 38e3d372564a0b3af2cc8a879152b5880af25878. * Add Japanese translations * Add missing translations Update fill color to svg icons Add learn more link to step sign * Improve the id translation for learn more label and URL * Change minimum amount to vote from 8000 to 100 * Remove transform from PIN code to string * Manage Sign metadata errors * Show transaction error message Set to false closeOnOverlayClick in Voting Dialog * Improve focus pin code and manage global value in array * Update svg icons and theme colors * Increase threshold to 7950 ADA and timeout to 3600 seconds * cardano-wallet: udpate to latest stable release * Revert "Increase threshold to 7950 ADA and timeout to 3600 seconds" This reverts commit 3962d3b17dc32885268debe339f10768d6139a90. * Remove interval for check transaction status Add observable boolean for set if transaction is approved Show error message if transaction status is not in_ledger Hide ITN rewards icon only for Catalyst Hide the delegation center menu for Catalyst * production limits of 7950 ada for voting threshold and 1 hour for tx confirmation * [DDW-541] Updates CHANGELOG * [DDW-541] Code cleanup * [DDW-541] Code cleanup * [DDW-541] Code cleanup * [DDW-541] Code cleanup * [DDW-541] Code cleanup * [DDW-541] Code improvements * [DDW-541] Code improvements * [DDW-541] Code improvements * [DDW-541] Fix bignumber errors * [DDW-541] Rename all components to Voting Registration namespace * [DDW-541] Voting namespace fixing, ordering of theming variables * [DDW-541] Code improvements * [DDW-541] Type improvements * [DDW-541] Code improvements * [DDW-541] Code cleanup * [DDW-541] Code improvements * add payment address to voter registration * fix lint errors * [DDW-541] Code improvements * [DDW-541] Adds HW check, Adds Byron check, Improves styling * [DDW-541] Introduce transaction confirmations * [DDW-541] Introduce Voting registration transaction confirmations check * [DDW-541] Code improvements * [DDW-541] Correctly handle octet-stream error responses * [DDW-541] Refactor steps handling * [DDW-541] Change the Dialog container approach * [DDW-541] Remove unecessary INTL messages * [DDW-541] Remove unecessary INTL component * [DDW-541] Dialog change - Step 2 * [DDW-541] Dialog change - Step 3 * [DDW-541] Dialog change - Step 4 * [DDW-541] Dialog change - Step 5 * [DDW-541] Remove lint errors * [DDW-541] Adjustment * [DDW-541] Expand ProgressBar component for light bg mode * [DDW-541] Storybook stories for all steps * [DDW-541] Voting info * [DDW-541] Flow and Lint issues * [DDW-541] Updates UI * [DDW-541] Adds back button * [DDW-541] Adds loading sceens for Staking/Voting/ITN-redemption * [DDW-541] Text copy update * [DDW-541] UI improvements * [DDW-541] Progress bar animated background * [DDW-541] Updates text copy and adds Japanese translations * [DDW-541] PDF generator * [DDW-541] PDF formatting adjustments * [DDW-541] Translation manager * [DDW-541] Adds Theming support * [DDW-541] Network name - centered and uppercase * [DDW-541] Animation glitches - alternative * [DDW-541] Fix PIN code entry, revert progress bar animation * [DDW-541] Increase poller interval to 5 sec * [DDW-541] Improve confirmation state * [DDW-541] JP translation * [DDW-541] Code improvements * [DDW-541] Updates cardano-wallet * [DDW-541] Adds border to mobile apps QR codes on dark themes * [DDW-541] Fix loading states * [DDW-541] Fixes wallet syncing state on voting dialog step 1 Co-authored-by: Juan Christian Cieri Co-authored-by: Michael Bishop Co-authored-by: Samuel Leathers Co-authored-by: Tomislav Horaček Co-authored-by: Danilo Prates --- CHANGELOG.md | 3 +- nix/sources.json | 6 +- package.json | 3 +- source/common/ipc/api.js | 8 + .../common/types/voting-pdf-request.types.js | 14 + source/common/utils/logging.js | 3 + source/main/ipc/generateVotingPDFChannel.js | 142 ++ source/main/ipc/index.js | 2 + source/main/windows/main.js | 2 +- source/renderer/app/Routes.js | 5 + source/renderer/app/actions/index.js | 9 +- source/renderer/app/actions/voting-actions.js | 18 + source/renderer/app/api/api.js | 205 ++- .../requests/createExternalTransaction.js | 4 +- .../transactions/requests/getTransaction.js | 15 + source/renderer/app/api/transactions/types.js | 5 + source/renderer/app/api/utils/request.js | 91 +- .../voting/requests/createWalletSignature.js | 19 + source/renderer/app/api/voting/types.js | 30 + .../images/sidebar/voting-ic.inline.svg | 3 + .../voting/confirm-step-message-ic.inline.svg | 22 + .../images/voting/download-app-ic.inline.svg | 5 + .../download-app-store-icon-ic.inline.svg | 3 + .../download-play-store-icon-ic.inline.svg | 3 + .../images/voting/open-app-ic.inline.svg | 11 + .../assets/images/voting/wait-ic.inline.svg | 5 + .../components/appUpdate/AppUpdateOverlay.js | 46 +- .../appUpdate/AppUpdateOverlay.scss | 19 +- .../components/sidebar/SidebarCategory.scss | 1 + .../components/staking/StakingUnavailable.js | 33 + .../staking/StakingUnavailable.scss | 21 + .../DelegationCenterNoWallets.js | 7 +- .../DelegationCenterNoWallets.scss | 7 +- .../UndelegateConfirmationResultDialog.scss | 2 + .../RedemptionUnavailableDialog.js | 25 +- .../RedemptionUnavailableDialog.scss | 19 + .../app/components/voting/VotingInfo.js | 201 +++ .../app/components/voting/VotingInfo.scss | 169 +++ .../app/components/voting/VotingNoWallets.js | 64 + .../components/voting/VotingNoWallets.scss | 39 + .../voting/VotingRegistrationDialogWizard.js | 151 ++ .../components/voting/VotingUnavailable.js | 33 + .../components/voting/VotingUnavailable.scss | 21 + .../app/components/voting/_votingConfig.scss | 16 + .../VotingRegistrationSteps.scss | 12 + .../VotingRegistrationStepsChooseWallet.js | 216 +++ .../VotingRegistrationStepsChooseWallet.scss | 66 + .../VotingRegistrationStepsConfirm.js | 191 +++ .../VotingRegistrationStepsConfirm.scss | 58 + .../VotingRegistrationStepsEnterPinCode.js | 194 +++ .../VotingRegistrationStepsEnterPinCode.scss | 26 + .../VotingRegistrationStepsQrCode.js | 180 +++ .../VotingRegistrationStepsQrCode.scss | 46 + .../VotingRegistrationStepsRegister.js | 229 +++ .../VotingRegistrationStepsRegister.scss | 61 + .../widgets/ConfirmationDialog.js | 88 ++ .../widgets/ConfirmationDialog.scss | 12 + .../widgets/VotingRegistrationDialog.js | 102 ++ .../widgets/VotingRegistrationDialog.scss | 44 + .../widgets/ConfirmationDialog.scss | 1 + .../renderer/app/components/widgets/Dialog.js | 4 +- .../components/widgets/ProgressBarLarge.js | 51 +- .../components/widgets/ProgressBarLarge.scss | 72 +- .../app/components/widgets/forms/PinCode.js | 149 ++ .../app/components/widgets/forms/PinCode.scss | 33 + source/renderer/app/config/sidebarConfig.js | 7 + source/renderer/app/config/votingConfig.js | 9 + source/renderer/app/containers/Root.js | 6 +- .../notifications/NotificationsContainer.js | 11 + .../app/containers/staking/Staking.js | 25 +- .../Step1ConfigurationContainer.js | 17 +- .../voting/VotingRegistrationPage.js | 76 + .../VotingRegistrationDialogContainer.js | 274 ++++ .../renderer/app/domains/WalletTransaction.js | 6 +- source/renderer/app/i18n/global-messages.js | 7 + .../app/i18n/locales/defaultMessages.json | 1259 +++++++++++++++-- source/renderer/app/i18n/locales/en-US.json | 79 +- source/renderer/app/i18n/locales/ja-JP.json | 83 +- .../app/ipc/generateVotingPDFChannel.js | 13 + source/renderer/app/routes-config.js | 3 + source/renderer/app/stores/SidebarStore.js | 14 +- .../renderer/app/stores/TransactionsStore.js | 4 + source/renderer/app/stores/VotingStore.js | 324 +++++ source/renderer/app/stores/index.js | 15 +- .../renderer/app/themes/daedalus/cardano.js | 25 + .../renderer/app/themes/daedalus/dark-blue.js | 25 + .../app/themes/daedalus/dark-cardano.js | 26 + .../app/themes/daedalus/flight-candidate.js | 24 + .../themes/daedalus/incentivized-testnet.js | 24 + .../app/themes/daedalus/light-blue.js | 25 + .../app/themes/daedalus/shelley-testnet.js | 24 + source/renderer/app/themes/daedalus/white.js | 24 + source/renderer/app/themes/daedalus/yellow.js | 24 + .../renderer/app/themes/utils/createTheme.js | 25 + .../renderer/app/types/notificationTypes.js | 1 + source/renderer/app/utils/formatters.js | 25 +- source/renderer/app/utils/validations.js | 10 + .../renderer/app/utils/votingPDFGenerator.js | 112 ++ source/renderer/app/utils/walletUtils.js | 1 + storybook/stories/_support/utils.js | 5 +- storybook/stories/index.js | 3 + storybook/stories/voting/Voting.stories.js | 116 ++ yarn.lock | 4 + yarn2nix.nix | 2 +- 104 files changed, 5822 insertions(+), 280 deletions(-) create mode 100644 source/common/types/voting-pdf-request.types.js create mode 100644 source/main/ipc/generateVotingPDFChannel.js create mode 100644 source/renderer/app/actions/voting-actions.js create mode 100644 source/renderer/app/api/transactions/requests/getTransaction.js create mode 100644 source/renderer/app/api/voting/requests/createWalletSignature.js create mode 100644 source/renderer/app/api/voting/types.js create mode 100644 source/renderer/app/assets/images/sidebar/voting-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/confirm-step-message-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/download-app-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/download-app-store-icon-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/download-play-store-icon-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/open-app-ic.inline.svg create mode 100644 source/renderer/app/assets/images/voting/wait-ic.inline.svg create mode 100644 source/renderer/app/components/staking/StakingUnavailable.js create mode 100644 source/renderer/app/components/staking/StakingUnavailable.scss create mode 100644 source/renderer/app/components/voting/VotingInfo.js create mode 100644 source/renderer/app/components/voting/VotingInfo.scss create mode 100644 source/renderer/app/components/voting/VotingNoWallets.js create mode 100644 source/renderer/app/components/voting/VotingNoWallets.scss create mode 100644 source/renderer/app/components/voting/VotingRegistrationDialogWizard.js create mode 100644 source/renderer/app/components/voting/VotingUnavailable.js create mode 100644 source/renderer/app/components/voting/VotingUnavailable.scss create mode 100644 source/renderer/app/components/voting/_votingConfig.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationSteps.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsChooseWallet.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsConfirm.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsEnterPinCode.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsQrCode.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/VotingRegistrationStepsRegister.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/ConfirmationDialog.scss create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.js create mode 100644 source/renderer/app/components/voting/voting-registration-wizard-steps/widgets/VotingRegistrationDialog.scss create mode 100644 source/renderer/app/components/widgets/forms/PinCode.js create mode 100644 source/renderer/app/components/widgets/forms/PinCode.scss create mode 100644 source/renderer/app/config/votingConfig.js create mode 100644 source/renderer/app/containers/voting/VotingRegistrationPage.js create mode 100644 source/renderer/app/containers/voting/dialogs/VotingRegistrationDialogContainer.js create mode 100644 source/renderer/app/ipc/generateVotingPDFChannel.js create mode 100644 source/renderer/app/stores/VotingStore.js create mode 100644 source/renderer/app/utils/votingPDFGenerator.js create mode 100644 source/renderer/app/utils/walletUtils.js create mode 100644 storybook/stories/voting/Voting.stories.js diff --git a/CHANGELOG.md b/CHANGELOG.md index bc45cf7b67..e77b0739cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ Changelog ========= -## vNext +## 3.3.0 ### Features +- Implemented Voting Centar ([PR 2315](https://github.com/input-output-hk/daedalus/pull/2315)) - Implemented transaction metadata display ([PR 2338](https://github.com/input-output-hk/daedalus/pull/2338)) - Displayed fee and deposit info in transaction details and in the delegation wizard ([PR 2339](https://github.com/input-output-hk/daedalus/pull/2339)) - Added SMASH server configuration options ([PR 2259](https://github.com/input-output-hk/daedalus/pull/2259)) diff --git a/nix/sources.json b/nix/sources.json index ad3eb85189..b5ef6f9ed1 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -29,10 +29,10 @@ "homepage": null, "owner": "input-output-hk", "repo": "cardano-wallet", - "rev": "e82e58a9032627e2ae56e52d39cf303898b50f88", - "sha256": "0xszv7k531p7jsragvhy5248ihni3alajzd5csjalpv28vd4j0sk", + "rev": "2428be4709293733f75fc842d63a5a1944614dd7", + "sha256": "0qyrlsn9aci3di5iw2j3sxcqrjfp284b0hswc2bh53b557g2mzj4", "type": "tarball", - "url": "https://github.com/input-output-hk/cardano-wallet/archive/e82e58a9032627e2ae56e52d39cf303898b50f88.tar.gz", + "url": "https://github.com/input-output-hk/cardano-wallet/archive/2428be4709293733f75fc842d63a5a1944614dd7.tar.gz", "url_template": "https://github.com///archive/.tar.gz", "version": "v2021-01-28" }, diff --git a/package.json b/package.json index 4c6180e0bf..323f37b719 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "daedalus", "productName": "Daedalus", - "version": "3.2.0", + "version": "3.3.0", "description": "Cryptocurrency Wallet", "main": "./dist/main/index.js", "scripts": { @@ -173,6 +173,7 @@ }, "dependencies": { "@cardano-foundation/ledgerjs-hw-app-cardano": "2.1.0", + "@iohk-jormungandr/wallet-js": "0.5.0-pre7", "aes-js": "3.1.2", "bignumber.js": "9.0.1", "bip39": "2.3.0", diff --git a/source/common/ipc/api.js b/source/common/ipc/api.js index 98b9f2378c..e1401eb494 100644 --- a/source/common/ipc/api.js +++ b/source/common/ipc/api.js @@ -11,6 +11,7 @@ import type { SaveFileDialogResponseParams, } from '../types/file-dialog.types'; import type { GenerateAddressPDFParams } from '../types/address-pdf-request.types'; +import type { GenerateVotingPDFParams } from '../types/voting-pdf-request.types'; import type { GenerateCsvParams } from '../types/csv-request.types'; import type { GenerateQRCodeParams } from '../types/save-qrCode.types'; import type { @@ -204,6 +205,13 @@ export const GENERATE_ADDRESS_PDF_CHANNEL = 'GENERATE_ADDRESS_PDF_CHANNEL'; export type GenerateAddressPDFRendererRequest = GenerateAddressPDFParams; export type GenerateAddressPDFMainResponse = void; +/** + * Channel to generate and save a share voting PDF + */ +export const GENERATE_VOTING_PDF_CHANNEL = 'GENERATE_VOTING_PDF_CHANNEL'; +export type GenerateVotingPDFRendererRequest = GenerateVotingPDFParams; +export type GenerateVotingPDFMainResponse = void; + /** * Channel to generate and save a csv file */ diff --git a/source/common/types/voting-pdf-request.types.js b/source/common/types/voting-pdf-request.types.js new file mode 100644 index 0000000000..ffe4f953b7 --- /dev/null +++ b/source/common/types/voting-pdf-request.types.js @@ -0,0 +1,14 @@ +// @flow +export type GenerateVotingPDFParams = { + title: string, + currentLocale: string, + creationDate: string, + qrCode: string, + walletNameLabel: string, + walletName: string, + isMainnet: boolean, + networkLabel: string, + networkName: string, + filePath: string, + author: string, +}; diff --git a/source/common/utils/logging.js b/source/common/utils/logging.js index c04d628f28..d9d61addd7 100644 --- a/source/common/utils/logging.js +++ b/source/common/utils/logging.js @@ -29,6 +29,9 @@ export const filterLogData = (data: Object): Object => { 'recoveryPhrase', 'passphrase', 'password', + 'votingKey', + 'stakeKey', + 'signature', 'accountPublicKey', 'extendedPublicKey', 'publicKeyHex', diff --git a/source/main/ipc/generateVotingPDFChannel.js b/source/main/ipc/generateVotingPDFChannel.js new file mode 100644 index 0000000000..62b9040011 --- /dev/null +++ b/source/main/ipc/generateVotingPDFChannel.js @@ -0,0 +1,142 @@ +// @flow +import fs from 'fs'; +import path from 'path'; +import PDFDocument from 'pdfkit'; +import qr from 'qr-image'; +import { MainIpcChannel } from './lib/MainIpcChannel'; +import { GENERATE_VOTING_PDF_CHANNEL } from '../../common/ipc/api'; +import type { + GenerateVotingPDFRendererRequest, + GenerateVotingPDFMainResponse, +} from '../../common/ipc/api'; +import fontRegularEn from '../../common/assets/pdf/NotoSans-Regular.ttf'; +import fontMediumEn from '../../common/assets/pdf/NotoSans-Medium.ttf'; +import fontUnicode from '../../common/assets/pdf/arial-unicode.ttf'; + +export const generateVotingPDFChannel: // IpcChannel +MainIpcChannel< + GenerateVotingPDFRendererRequest, + GenerateVotingPDFMainResponse +> = new MainIpcChannel(GENERATE_VOTING_PDF_CHANNEL); + +export const handleVotingPDFRequests = () => { + generateVotingPDFChannel.onReceive( + (request: GenerateVotingPDFRendererRequest) => + new Promise((resolve, reject) => { + // Prepare params + const { + title, + currentLocale, + creationDate, + qrCode, + walletNameLabel, + walletName, + isMainnet, + networkLabel, + networkName, + filePath, + author, + } = request; + + const readAssetSync = (p) => fs.readFileSync(path.join(__dirname, p)); + let fontRegular; + let fontMedium; + + if (currentLocale === 'ja-JP') { + fontRegular = fontUnicode; + fontMedium = fontUnicode; + } else { + fontRegular = fontRegularEn; + fontMedium = fontMediumEn; + } + + // Generate QR image for wallet voting + const qrCodeImage = qr.imageSync(qrCode, { + type: 'png', + size: 10, + ec_level: 'L', + margin: 0, + }); + + try { + const fontBufferMedium = readAssetSync(fontMedium); + const fontBufferRegular = readAssetSync(fontRegular); + + const textColor = '#5e6066'; + const textColorRed = '#ea4c5b'; + const width = 640; + const height = 450; + const doc = new PDFDocument({ + size: [width, height], + margins: { + bottom: 20, + left: 30, + right: 30, + top: 20, + }, + info: { + Title: title, + Author: author, + }, + }).fillColor(textColor); + + // Title + doc.font(fontBufferMedium).fontSize(18).text(title.toUpperCase(), { + align: 'center', + characterSpacing: 2, + }); + + // Creation date + doc + .font(fontBufferRegular) + .fontSize(12) + .text(creationDate.toUpperCase(), { + align: 'center', + characterSpacing: 0.6, + }); + + doc.moveDown(); + + // QR Code + doc.image(qrCodeImage, { + fit: [width - 60, 192], + align: 'center', + }); + + doc.moveDown(); + + // Wallet name + doc.font(fontBufferMedium).fontSize(14).text(walletNameLabel, { + align: 'center', + characterSpacing: 0.6, + }); + doc.font(fontBufferRegular).text(walletName, { + align: 'center', + characterSpacing: 0.6, + }); + + doc.moveDown(); + + // Footer + if (!isMainnet) { + doc + .fontSize(12) + .font(fontBufferMedium) + .fillColor(textColorRed) + .text(`${networkLabel} ${networkName}`, { + align: 'center', + }); + } + + // Write file to disk + const writeStream = fs.createWriteStream(filePath); + doc.pipe(writeStream); + doc.end(); + writeStream.on('close', resolve); + writeStream.on('error', reject); + } catch (error) { + reject(error); + } + }) + ); +}; diff --git a/source/main/ipc/index.js b/source/main/ipc/index.js index e73376a6ef..f638aaca28 100644 --- a/source/main/ipc/index.js +++ b/source/main/ipc/index.js @@ -14,6 +14,7 @@ import { handleBugReportRequests } from './bugReportRequestChannel'; import { handleFileMetaRequests } from './generateFileMetaChannel'; import { handlePaperWalletRequests } from './generatePaperWalletChannel'; import { handleAddressPDFRequests } from './generateAddressPDFChannel'; +import { handleVotingPDFRequests } from './generateVotingPDFChannel'; import { saveQRCodeImageRequests } from './saveQRCodeImageChannel'; import { handleRewardsCsvRequests } from './generateCsvChannel'; import { handleFileDialogRequests } from './show-file-dialog-channels'; @@ -33,6 +34,7 @@ export default (window: BrowserWindow) => { handleFileMetaRequests(); handlePaperWalletRequests(); handleAddressPDFRequests(); + handleVotingPDFRequests(); saveQRCodeImageRequests(); handleRewardsCsvRequests(); handleFileDialogRequests(window); diff --git a/source/main/windows/main.js b/source/main/windows/main.js index 24a13fe40b..676abb21e9 100644 --- a/source/main/windows/main.js +++ b/source/main/windows/main.js @@ -130,7 +130,7 @@ export const createMainWindow = (locale: string) => { }); window.webContents.on('did-finish-load', () => { - if (isTest) { + if (isTest || isDev) { window.showInactive(); // show without focusing the window } else { window.show(); // show also focuses the window diff --git a/source/renderer/app/Routes.js b/source/renderer/app/Routes.js index 641c856bda..96047822b3 100644 --- a/source/renderer/app/Routes.js +++ b/source/renderer/app/Routes.js @@ -31,6 +31,7 @@ import WalletReceivePage from './containers/wallet/WalletReceivePage'; import WalletTransactionsPage from './containers/wallet/WalletTransactionsPage'; import WalletSettingsPage from './containers/wallet/WalletSettingsPage'; import WalletUtxoPage from './containers/wallet/WalletUtxoPage'; +import VotingRegistrationPage from './containers/voting/VotingRegistrationPage'; export const Routes = withRouter(() => ( @@ -144,6 +145,10 @@ export const Routes = withRouter(() => ( component={RedeemItnRewardsContainer} /> + diff --git a/source/renderer/app/actions/index.js b/source/renderer/app/actions/index.js index dc3fc7ce2c..a2d6649434 100644 --- a/source/renderer/app/actions/index.js +++ b/source/renderer/app/actions/index.js @@ -1,16 +1,17 @@ // @flow import AddressesActions from './addresses-actions'; import AppActions from './app-actions'; +import AppUpdateActions from './app-update-actions'; import DialogsActions from './dialogs-actions'; import HardwareWalletsActions from './hardware-wallets-actions'; import NetworkStatusActions from './network-status-actions'; -import AppUpdateActions from './app-update-actions'; import NotificationsActions from './notifications-actions'; import ProfileActions from './profile-actions'; import RouterActions from './router-actions'; import SidebarActions from './sidebar-actions'; import StakingActions from './staking-actions'; import TransactionsActions from './transactions-actions'; +import VotingActions from './voting-actions'; import WalletsActions from './wallets-actions'; import WalletsLocalAction from './wallets-local-actions'; import WalletBackupActions from './wallet-backup-actions'; @@ -21,16 +22,17 @@ import WindowActions from './window-actions'; export type ActionsMap = { addresses: AddressesActions, app: AppActions, + appUpdate: AppUpdateActions, dialogs: DialogsActions, hardwareWallets: HardwareWalletsActions, networkStatus: NetworkStatusActions, - appUpdate: AppUpdateActions, notifications: NotificationsActions, profile: ProfileActions, router: RouterActions, sidebar: SidebarActions, staking: StakingActions, transactions: TransactionsActions, + voting: VotingActions, wallets: WalletsActions, walletsLocal: WalletsLocalAction, walletBackup: WalletBackupActions, @@ -42,16 +44,17 @@ export type ActionsMap = { const actionsMap: ActionsMap = { addresses: new AddressesActions(), app: new AppActions(), + appUpdate: new AppUpdateActions(), dialogs: new DialogsActions(), hardwareWallets: new HardwareWalletsActions(), networkStatus: new NetworkStatusActions(), - appUpdate: new AppUpdateActions(), notifications: new NotificationsActions(), profile: new ProfileActions(), router: new RouterActions(), sidebar: new SidebarActions(), staking: new StakingActions(), transactions: new TransactionsActions(), + voting: new VotingActions(), wallets: new WalletsActions(), walletsLocal: new WalletsLocalAction(), walletBackup: new WalletBackupActions(), diff --git a/source/renderer/app/actions/voting-actions.js b/source/renderer/app/actions/voting-actions.js new file mode 100644 index 0000000000..09d5cb5147 --- /dev/null +++ b/source/renderer/app/actions/voting-actions.js @@ -0,0 +1,18 @@ +// @flow +import Action from './lib/Action'; + +export default class VotingActions { + selectWallet: Action = new Action(); + sendTransaction: Action<{ + amount: number, + passphrase: string, + }> = new Action(); + generateQrCode: Action = new Action(); + saveAsPDF: Action = new Action(); + saveAsPDFSuccess: Action = new Action(); + nextRegistrationStep: Action = new Action(); + previousRegistrationStep: Action = new Action(); + resetRegistration: Action = new Action(); + showConfirmationDialog: Action = new Action(); + closeConfirmationDialog: Action = new Action(); +} diff --git a/source/renderer/app/api/api.js b/source/renderer/app/api/api.js index 6235535832..66c40875ba 100644 --- a/source/renderer/app/api/api.js +++ b/source/renderer/app/api/api.js @@ -31,6 +31,7 @@ import { getNetworkParameters } from './network/requests/getNetworkParameters'; // Transactions requests import { getTransactionFee } from './transactions/requests/getTransactionFee'; import { getByronWalletTransactionFee } from './transactions/requests/getByronWalletTransactionFee'; +import { getTransaction } from './transactions/requests/getTransaction'; import { getTransactionHistory } from './transactions/requests/getTransactionHistory'; import { getLegacyWalletTransactionHistory } from './transactions/requests/getLegacyWalletTransactionHistory'; import { getWithdrawalHistory } from './transactions/requests/getWithdrawalHistory'; @@ -41,6 +42,9 @@ import { selectCoins } from './transactions/requests/selectCoins'; import { createExternalTransaction } from './transactions/requests/createExternalTransaction'; import { getPublicKey } from './transactions/requests/getPublicKey'; +// Voting requests +import { createWalletSignature } from './voting/requests/createWalletSignature'; + // Wallets requests import { updateSpendingPassword } from './wallets/requests/updateSpendingPassword'; import { updateByronSpendingPassword } from './wallets/requests/updateByronSpendingPassword'; @@ -137,6 +141,7 @@ import type { GetTransactionFeeRequest, CreateTransactionRequest, DeleteTransactionRequest, + GetTransactionRequest, GetTransactionsRequest, GetTransactionsResponse, CoinSelectionsPaymentRequestType, @@ -198,6 +203,13 @@ import type { CheckSmashServerHealthApiResponse, PoolMetadataSource, } from './staking/types'; + +// Voting Types +import type { + CreateVotingRegistrationRequest, + CreateWalletSignatureRequest, +} from './voting/types'; + import type { StakePoolProps } from '../domains/StakePool'; import type { FaultInjectionIpcRequest } from '../../../common/types/cardano-node.types'; @@ -312,7 +324,7 @@ export default class AdaApi { }); try { const { walletId, role, index } = request; - const walletPublicKey = await getWalletPublicKey(this.config, { + const walletPublicKey: string = await getWalletPublicKey(this.config, { walletId, role, index, @@ -321,10 +333,7 @@ export default class AdaApi { return walletPublicKey; } catch (error) { logger.error('AdaApi::getWalletPublicKey error', { error }); - // @TODO: Uncomment this when api is ready - // throw new ApiError(error); - // @TODO: Delete this when api is ready - return '8edd9c9b73873ce8826cbe3e2e08534d35f1ba64cc94c063c0525865aa28e35527be51bb72ee9983d173f5617493bc6804a6750b359538c79cd5b43ccbbd48e5'; + throw new ApiError(error); } }; @@ -356,6 +365,26 @@ export default class AdaApi { } }; + getTransaction = async ( + request: GetTransactionRequest + ): Promise => { + logger.debug('AdaApi::getTransaction called', { parameters: request }); + const { walletId, transactionId } = request; + + try { + const response = await getTransaction( + this.config, + walletId, + transactionId + ); + logger.debug('AdaApi::getTransaction success', { response }); + return _createTransactionFromServerData(response); + } catch (error) { + logger.error('AdaApi::getTransaction error', { error }); + throw new ApiError(error); + } + }; + getTransactions = async ( request: GetTransactionsRequest ): Promise => { @@ -2181,6 +2210,169 @@ export default class AdaApi { } }; + createWalletSignature = async ( + request: CreateWalletSignatureRequest + ): Promise => { + logger.debug('AdaApi::createWalletSignature called', { + parameters: filterLogData(request), + }); + const { + walletId, + role, + index, + passphrase, + votingKey, + stakeKey, + addressHex, + } = request; + + try { + const data = { + passphrase, + metadata: { + [61284]: { + map: [ + { + k: { + int: 1, + }, + v: { + bytes: votingKey, + }, + }, + { + k: { + int: 2, + }, + v: { + bytes: stakeKey, + }, + }, + { + k: { + int: 3, + }, + v: { + bytes: addressHex, + }, + }, + ], + }, + }, + }; + const response = await createWalletSignature(this.config, { + walletId, + role, + index, + data, + }); + logger.debug('AdaApi::createWalletSignature success', { response }); + return response; + } catch (error) { + logger.error('AdaApi::createWalletSignature error', { error }); + throw new ApiError(error); + } + }; + + createVotingRegistrationTransaction = async ( + request: CreateVotingRegistrationRequest + ): Promise => { + logger.debug('AdaApi::createVotingRegistrationTransaction called', { + parameters: filterLogData(request), + }); + const { + walletId, + address, + addressHex, + amount, + passphrase, + votingKey, + stakeKey, + signature, + } = request; + + try { + const data = { + payments: [ + { + address, + amount: { + quantity: amount, + unit: WalletUnits.LOVELACE, + }, + }, + ], + passphrase, + metadata: { + [61284]: { + map: [ + { + k: { + int: 1, + }, + v: { + bytes: votingKey, + }, + }, + { + k: { + int: 2, + }, + v: { + bytes: stakeKey, + }, + }, + { + k: { + int: 3, + }, + v: { + bytes: addressHex, + }, + }, + ], + }, + [61285]: { + map: [ + { + k: { + int: 1, + }, + v: { + bytes: signature, + }, + }, + ], + }, + }, + }; + const response: Transaction = await createTransaction(this.config, { + walletId, + data: { ...data }, + }); + + logger.debug('AdaApi::createVotingRegistrationTransaction success', { + transaction: response, + }); + + return _createTransactionFromServerData(response); + } catch (error) { + logger.error('AdaApi::createVotingRegistrationTransaction error', { + error, + }); + throw new ApiError(error) + .set('wrongEncryptionPassphrase') + .where('code', 'bad_request') + .inc('message', 'passphrase is too short') + .set('transactionIsTooBig', true, { + linkLabel: 'tooBigTransactionErrorLinkLabel', + linkURL: 'tooBigTransactionErrorLinkURL', + }) + .where('code', 'transaction_is_too_big') + .result(); + } + }; + setCardanoNodeFault = async (fault: FaultInjectionIpcRequest) => { await cardanoFaultInjectionChannel.send(fault); }; @@ -2321,9 +2513,10 @@ const _createTransactionFromServerData = action( const date = get(stateInfo, 'time'); const slotNumber = get(stateInfo, ['block', 'slot_number'], null); const epochNumber = get(stateInfo, ['block', 'epoch_number'], null); + const confirmations = get(depth, 'quantity', 0); return new WalletTransaction({ id, - depth, + confirmations, slotNumber, epochNumber, title: direction === 'outgoing' ? 'Ada sent' : 'Ada received', diff --git a/source/renderer/app/api/transactions/requests/createExternalTransaction.js b/source/renderer/app/api/transactions/requests/createExternalTransaction.js index fe75c92d53..b689b52f17 100644 --- a/source/renderer/app/api/transactions/requests/createExternalTransaction.js +++ b/source/renderer/app/api/transactions/requests/createExternalTransaction.js @@ -13,10 +13,10 @@ export const createExternalTransaction = ( request( { method: 'POST', - path: `/v2/proxy/transactions`, + path: '/v2/proxy/transactions', ...config, }, {}, signedTransactionBlob, - { isOctetStream: true } + { isOctetStreamRequest: true } ); diff --git a/source/renderer/app/api/transactions/requests/getTransaction.js b/source/renderer/app/api/transactions/requests/getTransaction.js new file mode 100644 index 0000000000..3b69d9e5b9 --- /dev/null +++ b/source/renderer/app/api/transactions/requests/getTransaction.js @@ -0,0 +1,15 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { Transaction } from '../types'; +import { request } from '../../utils/request'; + +export const getTransaction = ( + config: RequestConfig, + walletId: string, + transactionId: string +): Promise => + request({ + method: 'GET', + path: `/v2/wallets/${walletId}/transactions/${transactionId}`, + ...config, + }); diff --git a/source/renderer/app/api/transactions/types.js b/source/renderer/app/api/transactions/types.js index f89889e458..d161ae0f8e 100644 --- a/source/renderer/app/api/transactions/types.js +++ b/source/renderer/app/api/transactions/types.js @@ -102,6 +102,11 @@ export type GetTransactionsRequest = { // cachedTransactions: Array, }; +export type GetTransactionRequest = { + walletId: string, + transactionId: string, +}; + export type GetTransactionFeeRequest = { walletId: string, address: string, diff --git a/source/renderer/app/api/utils/request.js b/source/renderer/app/api/utils/request.js index 27098c14c8..bee0883f2f 100644 --- a/source/renderer/app/api/utils/request.js +++ b/source/renderer/app/api/utils/request.js @@ -28,12 +28,16 @@ function typedRequest( rawBodyParams?: any, requestOptions?: { returnMeta?: boolean, - isOctetStream?: boolean, + isOctetStreamRequest?: boolean, + isOctetStreamResponse?: boolean, } ): Promise { return new Promise((resolve, reject) => { const options: RequestOptions = Object.assign({}, httpOptions); - // const { returnMeta } = Object.assign({}, requestOptions); + const { isOctetStreamRequest, isOctetStreamResponse } = Object.assign( + {}, + requestOptions + ); let hasRequestBody = false; let requestBody = ''; @@ -42,26 +46,27 @@ function typedRequest( } // Handle raw body params - if ( - requestOptions && - requestOptions.isOctetStream && - rawBodyParams && - typeof rawBodyParams === 'string' - ) { + if (rawBodyParams) { hasRequestBody = true; - requestBody = rawBodyParams; - options.headers = { - 'Content-Length': requestBody.length / 2, - 'Content-Type': 'application/octet-stream', - Accept: 'application/json; charset=utf-8', - }; - } else if (rawBodyParams) { - hasRequestBody = true; - requestBody = JSON.stringify(rawBodyParams); + if (isOctetStreamRequest) { + requestBody = rawBodyParams; + options.headers = { + 'Content-Length': requestBody.length / 2, + 'Content-Type': 'application/octet-stream', + }; + } else { + requestBody = JSON.stringify(rawBodyParams); + options.headers = { + 'Content-Length': getContentLength(requestBody), + 'Content-Type': 'application/json; charset=utf-8', + }; + } + options.headers = { - 'Content-Length': getContentLength(requestBody), - 'Content-Type': 'application/json; charset=utf-8', - Accept: 'application/json; charset=utf-8', + ...options.headers, + Accept: isOctetStreamResponse + ? 'application/octet-stream' + : 'application/json; charset=utf-8', }; } @@ -71,17 +76,23 @@ function typedRequest( : global.https.request(options); if (hasRequestBody) { - if (requestOptions && requestOptions.isOctetStream) { + if (isOctetStreamRequest) { httpsRequest.write(requestBody, 'hex'); } else { httpsRequest.write(requestBody); } } + httpsRequest.on('response', (response) => { let body = ''; - // Cardano-sl returns chunked requests, so we need to concat them + let stream; + // cardano-wallet returns chunked requests, so we need to concat them response.on('data', (chunk) => { - body += chunk; + if (isOctetStreamResponse) { + stream = chunk; + } else { + body += chunk; + } }); // Reject errors response.on('error', (error) => reject(error)); @@ -95,19 +106,27 @@ function typedRequest( includes(ALLOWED_ERROR_EXCEPTION_PATHS, options.path)); if (isSuccessResponse) { - const data = - statusCode === 404 - ? 'null' - : `"statusCode: ${statusCode} -- statusMessage: ${statusMessage}"`; - // When deleting a wallet, the API does not return any data in body - // even if it was successful - if (!body) { - body = `{ - "status": ${statusCode}, - "data": ${data} - }`; + if (isOctetStreamResponse) { + resolve(stream); + } else { + const data = + statusCode === 404 + ? 'null' + : `"statusCode: ${statusCode} -- statusMessage: ${statusMessage}"`; + // When deleting a wallet, the API does not return any data in body + // even if it was successful + if (!body) { + body = `{ + "status": ${statusCode}, + "data": ${data} + }`; + } + resolve(JSONBigInt.parse(body)); } - resolve(JSONBigInt.parse(body)); + } else if (stream) { + // Error response with a stream + const parsedStream = JSONBigInt.parse(stream.toString()); + reject(parsedStream); } else if (body) { // Error response with a body const parsedBody = JSONBigInt.parse(body); @@ -117,7 +136,7 @@ function typedRequest( reject(new Error('Unknown API response')); } } else { - // Error response without a body + // Error response without a stream or body reject(new Error('Unknown API response')); } } catch (error) { diff --git a/source/renderer/app/api/voting/requests/createWalletSignature.js b/source/renderer/app/api/voting/requests/createWalletSignature.js new file mode 100644 index 0000000000..fb1b854594 --- /dev/null +++ b/source/renderer/app/api/voting/requests/createWalletSignature.js @@ -0,0 +1,19 @@ +// @flow +import type { RequestConfig } from '../../common/types'; +import type { SignatureParams } from '../types'; +import { request } from '../../utils/request'; + +export const createWalletSignature = ( + config: RequestConfig, + { walletId, role, index, data }: SignatureParams +): Promise => + request( + { + method: 'POST', + path: `/v2/wallets/${walletId}/signatures/${role}/${index}`, + ...config, + }, + {}, + data, + { isOctetStreamResponse: true } + ); diff --git a/source/renderer/app/api/voting/types.js b/source/renderer/app/api/voting/types.js new file mode 100644 index 0000000000..e599251c73 --- /dev/null +++ b/source/renderer/app/api/voting/types.js @@ -0,0 +1,30 @@ +// @flow +export type CreateVotingRegistrationRequest = { + walletId: string, + address: string, + addressHex: string, + amount: number, + passphrase: string, + votingKey: string, + stakeKey: string, + signature: string, +}; + +export type CreateWalletSignatureRequest = { + walletId: string, + role: string, + index: string, + passphrase: string, + votingKey: string, + stakeKey: string, + addressHex: string, +}; + +export type SignatureParams = { + walletId: string, + role: string, + index: string, + data: { + passphrase: string, + }, +}; diff --git a/source/renderer/app/assets/images/sidebar/voting-ic.inline.svg b/source/renderer/app/assets/images/sidebar/voting-ic.inline.svg new file mode 100644 index 0000000000..398c5fd3f6 --- /dev/null +++ b/source/renderer/app/assets/images/sidebar/voting-ic.inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/renderer/app/assets/images/voting/confirm-step-message-ic.inline.svg b/source/renderer/app/assets/images/voting/confirm-step-message-ic.inline.svg new file mode 100644 index 0000000000..40f0c1d11a --- /dev/null +++ b/source/renderer/app/assets/images/voting/confirm-step-message-ic.inline.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/voting/download-app-ic.inline.svg b/source/renderer/app/assets/images/voting/download-app-ic.inline.svg new file mode 100644 index 0000000000..edaa882958 --- /dev/null +++ b/source/renderer/app/assets/images/voting/download-app-ic.inline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/renderer/app/assets/images/voting/download-app-store-icon-ic.inline.svg b/source/renderer/app/assets/images/voting/download-app-store-icon-ic.inline.svg new file mode 100644 index 0000000000..318ed8dcb1 --- /dev/null +++ b/source/renderer/app/assets/images/voting/download-app-store-icon-ic.inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/renderer/app/assets/images/voting/download-play-store-icon-ic.inline.svg b/source/renderer/app/assets/images/voting/download-play-store-icon-ic.inline.svg new file mode 100644 index 0000000000..8be9b2ff2e --- /dev/null +++ b/source/renderer/app/assets/images/voting/download-play-store-icon-ic.inline.svg @@ -0,0 +1,3 @@ + + + diff --git a/source/renderer/app/assets/images/voting/open-app-ic.inline.svg b/source/renderer/app/assets/images/voting/open-app-ic.inline.svg new file mode 100644 index 0000000000..b334a7c28b --- /dev/null +++ b/source/renderer/app/assets/images/voting/open-app-ic.inline.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/source/renderer/app/assets/images/voting/wait-ic.inline.svg b/source/renderer/app/assets/images/voting/wait-ic.inline.svg new file mode 100644 index 0000000000..f91772cf38 --- /dev/null +++ b/source/renderer/app/assets/images/voting/wait-ic.inline.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/source/renderer/app/components/appUpdate/AppUpdateOverlay.js b/source/renderer/app/components/appUpdate/AppUpdateOverlay.js index 3918d57517..89c90dd81b 100644 --- a/source/renderer/app/components/appUpdate/AppUpdateOverlay.js +++ b/source/renderer/app/components/appUpdate/AppUpdateOverlay.js @@ -158,24 +158,19 @@ export default class AppUpdateOverlay extends Component { downloadProgress, } = this.props; return ( -
-
-

- {intl.formatMessage(messages.downloadProgressLabel)} -

-

- - {intl.formatMessage(messages.downloadTimeLeft, { - downloadTimeLeft, - })} - {' '} - {intl.formatMessage(messages.downloadProgressData, { - totalDownloaded, - totalDownloadSize, - })} -

-
- +
+
); }; @@ -234,14 +229,13 @@ export default class AppUpdateOverlay extends Component { /> )} {isLinux && isWaitingToQuitDaedalus ? ( - <> -
-

- {intl.formatMessage(messages.installingUpdateLabel)} -

-
- - +
+ +
) : ( <> +
+ +
+
+
+ +
+ +
+
+
+
+
+